openfree commited on
Commit
491aa2d
·
verified ·
1 Parent(s): c225467

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -203
app.py CHANGED
@@ -211,38 +211,45 @@ class CharacterProfile:
211
 
212
  # --- Core logic classes ---
213
  class ScreenplayTracker:
214
- """Unified screenplay tracker"""
215
- def __init__(self):
216
- self.screenplay_bible = ScreenplayBible()
217
- self.scenes: List[SceneBreakdown] = []
218
- self.characters: Dict[str, CharacterProfile] = {}
219
- self.page_count = 0
220
- self.act_pages = {"1": 0, "2A": 0, "2B": 0, "3": 0}
221
- self.dialogue_action_ratio = 0.0
222
-
223
- def add_scene(self, scene: SceneBreakdown):
224
- """Add scene to tracker"""
225
- self.scenes.append(scene)
226
- self.page_count += scene.page_count
227
-
228
- def add_character(self, character: CharacterProfile):
229
- """Add character to tracker"""
230
- self.characters[character.name] = character
231
-
232
- def update_bible(self, key: str, value: Any):
233
- """Update screenplay bible"""
234
- if hasattr(self.screenplay_bible, key):
235
- setattr(self.screenplay_bible, key, value)
236
-
237
- def get_act_page_target(self, act: str, total_pages: int) -> int:
238
- """Get target pages for each act"""
239
- if act == "1":
240
- return int(total_pages * 0.25)
241
- elif act in ["2A", "2B"]:
242
- return int(total_pages * 0.25)
243
- elif act == "3":
244
- return int(total_pages * 0.25)
245
- return 0
 
 
 
 
 
 
 
246
 
247
  class ScreenplayDatabase:
248
  """Database management for screenplay sessions"""
@@ -1622,183 +1629,189 @@ You consider all perspectives: producers, actors, audience.
1622
  You provide feedback that's critical yet encouraging."""
1623
  }
1624
  }
1625
-
1626
- return base_prompts.get(language, base_prompts["English"])
1627
 
1628
- # --- Main process ---
1629
- def process_screenplay_stream(self, query: str, screenplay_type: str, genre: str,
1630
- language: str, session_id: Optional[str] = None
1631
- ) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
1632
- """Main screenplay generation process"""
1633
- try:
1634
- resume_from_stage = 0
1635
- if session_id:
1636
- self.current_session_id = session_id
1637
- session = ScreenplayDatabase.get_session(session_id)
1638
- if session:
1639
- query = session['user_query']
1640
- screenplay_type = session['screenplay_type']
1641
- genre = session['genre']
1642
- language = session['language']
1643
- resume_from_stage = session['current_stage'] + 1
1644
- else:
1645
- self.current_session_id = ScreenplayDatabase.create_session(
1646
- query, screenplay_type, genre, language
1647
- )
1648
- logger.info(f"Created new screenplay session: {self.current_session_id}")
1649
-
1650
- stages = []
1651
- if resume_from_stage > 0:
1652
- # Get existing stages from database
1653
- db_stages = ScreenplayDatabase.get_stages(self.current_session_id)
1654
- stages = [{
1655
- "name": s['stage_name'],
1656
- "status": s['status'],
1657
- "content": s.get('content', ''),
1658
- "page_count": s.get('page_count', 0)
1659
- } for s in db_stages]
1660
-
1661
- for stage_idx in range(resume_from_stage, len(SCREENPLAY_STAGES)):
1662
- role, stage_name = SCREENPLAY_STAGES[stage_idx]
1663
-
1664
- if stage_idx >= len(stages):
1665
- stages.append({
1666
- "name": stage_name,
1667
- "status": "active",
1668
- "content": "",
1669
- "page_count": 0
1670
- })
1671
- else:
1672
- stages[stage_idx]["status"] = "active"
1673
-
1674
- yield f"🔄 Processing {stage_name}...", stages, self.current_session_id
1675
-
1676
- prompt = self.get_stage_prompt(stage_idx, role, query, screenplay_type,
1677
- genre, language, stages)
1678
- stage_content = ""
1679
-
1680
- for chunk in self.call_llm_streaming([{"role": "user", "content": prompt}],
1681
- role, language):
1682
- stage_content += chunk
1683
- stages[stage_idx]["content"] = stage_content
1684
- if role == "screenwriter":
1685
- stages[stage_idx]["page_count"] = len(stage_content.split('\n')) / 55
1686
- yield f"🔄 {stage_name} in progress...", stages, self.current_session_id
1687
-
1688
- # Process content based on role
1689
- if role == "producer":
1690
- self._process_producer_content(stage_content)
1691
- elif role == "story_developer":
1692
- self._process_story_content(stage_content)
1693
- elif role == "character_designer":
1694
- self._process_character_content(stage_content)
1695
- elif role == "scene_planner":
1696
- self._process_scene_content(stage_content)
1697
-
1698
- stages[stage_idx]["status"] = "complete"
1699
- ScreenplayDatabase.save_stage(
1700
- self.current_session_id, stage_idx, stage_name, role,
1701
- stage_content, "complete"
1702
- )
1703
-
1704
- yield f"✅ {stage_name} completed", stages, self.current_session_id
1705
 
1706
- # Final processing
1707
- final_screenplay = ScreenplayDatabase.get_screenplay_content(self.current_session_id)
1708
- title = self.screenplay_tracker.screenplay_bible.title
1709
- logline = self.screenplay_tracker.screenplay_bible.logline
1710
-
1711
- ScreenplayDatabase.update_final_screenplay(
1712
- self.current_session_id, final_screenplay, title, logline
1713
- )
1714
-
1715
- yield f"✅ Screenplay completed! {title}", stages, self.current_session_id
 
 
1716
 
1717
- except Exception as e:
1718
- logger.error(f"Screenplay generation error: {e}", exc_info=True)
1719
- yield f"❌ Error occurred: {e}", stages if 'stages' in locals() else [], self.current_session_id
1720
-
1721
- def get_stage_prompt(self, stage_idx: int, role: str, query: str,
1722
- screenplay_type: str, genre: str, language: str,
1723
- stages: List[Dict]) -> str:
1724
- """Generate stage-specific prompt"""
1725
- if stage_idx == 0: # Producer
1726
- return self.create_producer_prompt(query, screenplay_type, genre, language)
1727
-
1728
- if stage_idx == 1: # Story Developer
1729
- return self.create_story_developer_prompt(
1730
- stages[0]["content"], query, screenplay_type, genre, language
1731
- )
1732
-
1733
- if stage_idx == 2: # Character Designer
1734
- return self.create_character_designer_prompt(
1735
- stages[0]["content"], stages[1]["content"], genre, language
1736
- )
1737
-
1738
- if stage_idx == 3: # Structure Critic
1739
- return self.create_critic_structure_prompt(
1740
- stages[1]["content"], stages[2]["content"], screenplay_type, genre, language
1741
- )
1742
-
1743
- if stage_idx == 4: # Scene Planner
1744
- return self.create_scene_planner_prompt(
1745
- stages[1]["content"], stages[2]["content"], screenplay_type, genre, language
1746
- )
1747
-
1748
- # Screenwriter acts
1749
- if role == "screenwriter":
1750
- act_mapping = {5: "Act 1", 7: "Act 2A", 9: "Act 2B", 11: "Act 3"}
1751
- if stage_idx in act_mapping:
1752
- act = act_mapping[stage_idx]
1753
- previous_acts = self._get_previous_acts(stages, stage_idx)
1754
- return self.create_screenwriter_prompt(
1755
- act, stages[4]["content"], stages[2]["content"],
1756
- previous_acts, screenplay_type, genre, language
1757
- )
1758
-
1759
- # Script doctor reviews
1760
- if role == "script_doctor":
1761
- act_mapping = {6: "Act 1", 8: "Act 2A", 10: "Act 2B"}
1762
- if stage_idx in act_mapping:
1763
- act = act_mapping[stage_idx]
1764
- act_content = stages[stage_idx-1]["content"]
1765
- return self.create_script_doctor_prompt(act_content, act, genre, language)
1766
-
1767
- # Final reviewer
1768
- if role == "final_reviewer":
1769
- complete_screenplay = ScreenplayDatabase.get_screenplay_content(self.current_session_id)
1770
- return self.create_final_reviewer_prompt(
1771
- complete_screenplay, screenplay_type, genre, language
1772
- )
1773
-
1774
- return ""
1775
 
1776
- def _get_previous_acts(self, stages: List[Dict], current_idx: int) -> str:
1777
- """Get previous acts content"""
1778
- previous = []
1779
- act_indices = {5: [], 7: [5], 9: [5, 7], 11: [5, 7, 9]}
1780
-
1781
- if current_idx in act_indices:
1782
- for idx in act_indices[current_idx]:
1783
- if idx < len(stages) and stages[idx]["content"]:
1784
- previous.append(stages[idx]["content"])
1785
-
1786
- return "\n\n---\n\n".join(previous) if previous else ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1787
 
1788
  def _process_producer_content(self, content: str):
1789
- """Process producer output"""
1790
- # Extract title and logline
1791
- title_match = re.search(r'(?:TITLE|제목):\s*(.+)', content)
1792
- logline_match = re.search(r'(?:LOGLINE|로그라인):\s*(.+)', content)
1793
-
1794
- if title_match:
1795
- self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
1796
- if logline_match:
1797
- self.screenplay_tracker.screenplay_bible.logline = logline_match.group(1).strip()
1798
-
1799
- # Save to database
1800
- ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
1801
- self.screenplay_tracker.screenplay_bible)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1802
 
1803
  def _process_story_content(self, content: str):
1804
  """Process story developer output"""
 
211
 
212
  # --- Core logic classes ---
213
  class ScreenplayTracker:
214
+ """Unified screenplay tracker"""
215
+ def __init__(self):
216
+ self.screenplay_bible = ScreenplayBible()
217
+ self.scenes: List[SceneBreakdown] = []
218
+ self.characters: Dict[str, CharacterProfile] = {}
219
+ self.page_count = 0
220
+ self.act_pages = {"1": 0, "2A": 0, "2B": 0, "3": 0}
221
+ self.dialogue_action_ratio = 0.0
222
+
223
+ def add_scene(self, scene: SceneBreakdown):
224
+ """Add scene to tracker"""
225
+ self.scenes.append(scene)
226
+ self.page_count += scene.page_count
227
+
228
+ def add_character(self, character: CharacterProfile):
229
+ """Add character to tracker"""
230
+ self.characters[character.name] = character
231
+ # Update bible with main characters
232
+ if character.role == "protagonist":
233
+ self.screenplay_bible.protagonist = asdict(character)
234
+ elif character.role == "antagonist":
235
+ self.screenplay_bible.antagonist = asdict(character)
236
+ elif character.role == "supporting":
237
+ self.screenplay_bible.supporting_cast[character.name] = asdict(character)
238
+
239
+ def update_bible(self, key: str, value: Any):
240
+ """Update screenplay bible"""
241
+ if hasattr(self.screenplay_bible, key):
242
+ setattr(self.screenplay_bible, key, value)
243
+
244
+ def get_act_page_target(self, act: str, total_pages: int) -> int:
245
+ """Get target pages for each act"""
246
+ if act == "1":
247
+ return int(total_pages * 0.25)
248
+ elif act in ["2A", "2B"]:
249
+ return int(total_pages * 0.25)
250
+ elif act == "3":
251
+ return int(total_pages * 0.25)
252
+ return 0
253
 
254
  class ScreenplayDatabase:
255
  """Database management for screenplay sessions"""
 
1629
  You provide feedback that's critical yet encouraging."""
1630
  }
1631
  }
 
 
1632
 
1633
+ def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
1634
+ """Parse character profile from content"""
1635
+ # Extract character details using regex or string parsing
1636
+ name = self._extract_field(content, r"(?:Name|이름)[:\s]*") or f"Character_{role}"
1637
+
1638
+ # Extract age with better parsing
1639
+ age_str = self._extract_field(content, r"(?:Age|나이)[:\s]*") or "30"
1640
+ # Extract just the number from age string
1641
+ age_match = re.search(r'\d+', age_str)
1642
+ age = int(age_match.group()) if age_match else 30
1643
+
1644
+ return CharacterProfile(
1645
+ name=name,
1646
+ age=age,
1647
+ role=role,
1648
+ archetype=self._extract_field(content, r"(?:Archetype|아크타입)[:\s]*") or "",
1649
+ want=self._extract_field(content, r"(?:WANT|외적 목표)[:\s]*") or "",
1650
+ need=self._extract_field(content, r"(?:NEED|내적 필요)[:\s]*") or "",
1651
+ backstory=self._extract_field(content, r"(?:Backstory|백스토리)[:\s]*") or "",
1652
+ personality=self._extract_personality_traits(content),
1653
+ speech_pattern=self._extract_field(content, r"(?:Speech Pattern|말투)[:\s]*") or "",
1654
+ character_arc=self._extract_field(content, r"(?:Arc|아크|Character Arc)[:\s]*") or ""
1655
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1656
 
1657
+ def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
1658
+ """Extract field value from content with improved parsing"""
1659
+ # More flexible pattern that handles various formats
1660
+ pattern = rf'{field_pattern}(.+?)(?=\n[A-Z가-힣]|$)'
1661
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
1662
+ if match:
1663
+ value = match.group(1).strip()
1664
+ # Remove markdown formatting if present
1665
+ value = re.sub(r'\*\*', '', value)
1666
+ value = re.sub(r'^\s*[-•]\s*', '', value)
1667
+ return value.strip()
1668
+ return None
1669
 
1670
+ def _extract_personality_traits(self, content: str) -> List[str]:
1671
+ """Extract personality traits from content"""
1672
+ traits = []
1673
+ # Look for personality section
1674
+ personality_section = self._extract_field(content, r"(?:Personality|성격)[:\s]*")
1675
+ if personality_section:
1676
+ # Extract individual traits (usually listed)
1677
+ trait_lines = personality_section.split('\n')
1678
+ for line in trait_lines:
1679
+ line = line.strip()
1680
+ if line and not line.endswith(':'):
1681
+ # Remove list markers
1682
+ trait = re.sub(r'^\s*[-•*]\s*', '', line)
1683
+ if trait:
1684
+ traits.append(trait)
1685
+ return traits[:5] # Limit to 5 traits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1686
 
1687
+ def _process_character_content(self, content: str):
1688
+ """Process character designer output with better error handling"""
1689
+ try:
1690
+ # Extract protagonist
1691
+ protagonist_section = self._extract_section(content, r"(?:PROTAGONIST|주인공)")
1692
+ if protagonist_section:
1693
+ protagonist = self._parse_character_profile(protagonist_section, "protagonist")
1694
+ self.screenplay_tracker.add_character(protagonist)
1695
+ ScreenplayDatabase.save_character(self.current_session_id, protagonist)
1696
+
1697
+ # Extract antagonist
1698
+ antagonist_section = self._extract_section(content, r"(?:ANTAGONIST|적대자)")
1699
+ if antagonist_section:
1700
+ antagonist = self._parse_character_profile(antagonist_section, "antagonist")
1701
+ self.screenplay_tracker.add_character(antagonist)
1702
+ ScreenplayDatabase.save_character(self.current_session_id, antagonist)
1703
+
1704
+ # Extract supporting characters
1705
+ supporting_section = self._extract_section(content, r"(?:SUPPORTING CAST|조력자들)")
1706
+ if supporting_section:
1707
+ # Parse multiple supporting characters
1708
+ self._parse_supporting_characters(supporting_section)
1709
+
1710
+ except Exception as e:
1711
+ logger.error(f"Error processing character content: {e}")
1712
+ # Continue with default values rather than failing
1713
+
1714
+ def _parse_supporting_characters(self, content: str):
1715
+ """Parse supporting characters from content"""
1716
+ # Split by character markers (numbers or bullets)
1717
+ char_sections = re.split(r'\n(?:\d+\.|[-•*])\s*', content)
1718
+
1719
+ for i, section in enumerate(char_sections[1:], 1): # Skip first empty split
1720
+ if section.strip():
1721
+ try:
1722
+ name = self._extract_field(section, r"(?:Name|이름)[:\s]*") or f"Supporting_{i}"
1723
+ role = self._extract_field(section, r"(?:Role|역할)[:\s]*") or "supporting"
1724
+
1725
+ character = CharacterProfile(
1726
+ name=name,
1727
+ age=30, # Default age for supporting characters
1728
+ role="supporting",
1729
+ archetype=role,
1730
+ want="",
1731
+ need="",
1732
+ backstory=self._extract_field(section, r"(?:Backstory|백스토리)[:\s]*") or "",
1733
+ personality=[],
1734
+ speech_pattern="",
1735
+ character_arc=""
1736
+ )
1737
+
1738
+ self.screenplay_tracker.add_character(character)
1739
+ ScreenplayDatabase.save_character(self.current_session_id, character)
1740
+
1741
+ except Exception as e:
1742
+ logger.warning(f"Error parsing supporting character {i}: {e}")
1743
+ continue
1744
+
1745
+ def _extract_section(self, content: str, section_pattern: str) -> str:
1746
+ """Extract section from content with improved pattern matching"""
1747
+ # More flexible section extraction
1748
+ pattern = rf'{section_pattern}[:\s]*\n?(.*?)(?=\n\n[A-Z가-힣]{{2,}}[:\s]|\n\n\d+\.|$)'
1749
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
1750
+ if match:
1751
+ return match.group(1).strip()
1752
+
1753
+ # Try alternative pattern
1754
+ pattern2 = rf'{section_pattern}.*?\n((?:.*\n)*?)(?=\n[A-Z가-힣]{{2,}}:|$)'
1755
+ match2 = re.search(pattern2, content, re.IGNORECASE | re.DOTALL)
1756
+ if match2:
1757
+ return match2.group(1).strip()
1758
+
1759
+ return ""
1760
 
1761
  def _process_producer_content(self, content: str):
1762
+ """Process producer output with better extraction"""
1763
+ try:
1764
+ # Extract title with various formats
1765
+ title_patterns = [
1766
+ r'(?:TITLE|제목)[:\s]*\*?\*?([^\n*]+)\*?\*?',
1767
+ r'\*\*(?:TITLE|제목)\*\*[:\s]*([^\n]+)',
1768
+ r'Title[:\s]*([^\n]+)'
1769
+ ]
1770
+
1771
+ for pattern in title_patterns:
1772
+ title_match = re.search(pattern, content, re.IGNORECASE)
1773
+ if title_match:
1774
+ self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
1775
+ break
1776
+
1777
+ # Extract logline with various formats
1778
+ logline_patterns = [
1779
+ r'(?:LOGLINE|로그라인)[:\s]*\*?\*?([^\n]+)',
1780
+ r'\*\*(?:LOGLINE|로그라인)\*\*[:\s]*([^\n]+)',
1781
+ r'Logline[:\s]*([^\n]+)'
1782
+ ]
1783
+
1784
+ for pattern in logline_patterns:
1785
+ logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
1786
+ if logline_match:
1787
+ # Get full logline (might be multi-line)
1788
+ logline_text = logline_match.group(1).strip()
1789
+ # Continue reading if it's incomplete
1790
+ if not logline_text.endswith('.'):
1791
+ next_lines = content[logline_match.end():].split('\n')
1792
+ for line in next_lines[:3]: # Check next 3 lines
1793
+ if line.strip() and not re.match(r'^[A-Z가-힣\d]', line.strip()):
1794
+ logline_text += ' ' + line.strip()
1795
+ else:
1796
+ break
1797
+ self.screenplay_tracker.screenplay_bible.logline = logline_text
1798
+ break
1799
+
1800
+ # Extract genre
1801
+ genre_match = re.search(r'(?:Primary Genre|주 장르)[:\s]*([^\n]+)', content, re.IGNORECASE)
1802
+ if genre_match:
1803
+ self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
1804
+
1805
+ # Save to database
1806
+ ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
1807
+ self.screenplay_tracker.screenplay_bible)
1808
+
1809
+ except Exception as e:
1810
+ logger.error(f"Error processing producer content: {e}")
1811
+
1812
+
1813
+
1814
+
1815
 
1816
  def _process_story_content(self, content: str):
1817
  """Process story developer output"""