openfree commited on
Commit
6be5586
·
verified ·
1 Parent(s): 2549695

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +221 -233
app.py CHANGED
@@ -536,18 +536,6 @@ class ScreenplayDatabase:
536
 
537
  return theme_id
538
 
539
- @staticmethod
540
- def get_stages(session_id: str) -> List[Dict]:
541
- """Get all stages for a session"""
542
- with ScreenplayDatabase.get_db() as conn:
543
- rows = conn.cursor().execute(
544
- '''SELECT * FROM screenplay_stages
545
- WHERE session_id = ?
546
- ORDER BY stage_number''',
547
- (session_id,)
548
- ).fetchall()
549
- return [dict(row) for row in rows]
550
-
551
  class WebSearchIntegration:
552
  """Web search functionality for screenplay research"""
553
  def __init__(self):
@@ -1481,8 +1469,6 @@ Provide specific solutions for each issue."""
1481
  raise Exception(f"LLM Call Failed: {full_content}")
1482
  return full_content
1483
 
1484
-
1485
-
1486
  def call_llm_streaming(self, messages: List[Dict[str, str]], role: str,
1487
  language: str) -> Generator[str, None, None]:
1488
  try:
@@ -1632,8 +1618,6 @@ Provide specific solutions for each issue."""
1632
  logger.error(traceback.format_exc())
1633
  yield f"❌ Unexpected error: {str(e)}"
1634
 
1635
-
1636
-
1637
  def get_system_prompts(self, language: str) -> Dict[str, str]:
1638
  """Role-specific system prompts"""
1639
 
@@ -1724,9 +1708,7 @@ You provide feedback that's critical yet encouraging."""
1724
 
1725
  return base_prompts.get(language, base_prompts["English"])
1726
 
1727
-
1728
-
1729
- # --- Main process ---
1730
  def process_screenplay_stream(self, query: str, screenplay_type: str, genre: str,
1731
  language: str, session_id: Optional[str] = None
1732
  ) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
@@ -1886,6 +1868,20 @@ You provide feedback that's critical yet encouraging."""
1886
 
1887
  return "\n\n---\n\n".join(previous) if previous else ""
1888
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1889
 
1890
  def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
1891
  """Parse character profile from content"""
@@ -1903,10 +1899,10 @@ You provide feedback that's critical yet encouraging."""
1903
 
1904
  for pattern in name_patterns:
1905
  name_match = re.search(pattern, content, re.IGNORECASE | re.MULTILINE)
1906
- if name_match:
1907
  extracted_name = name_match.group(1).strip()
1908
  # Remove markdown and extra characters
1909
- extracted_name = re.sub(r'[*:\s]+$', '', extracted_name)
1910
  extracted_name = re.sub(r'^[*:\s]+', '', extracted_name)
1911
  if extracted_name and len(extracted_name) > 1:
1912
  name = extracted_name
@@ -1918,8 +1914,9 @@ You provide feedback that's critical yet encouraging."""
1918
  patterns = [patterns]
1919
 
1920
  for pattern in patterns:
 
1921
  match = re.search(rf'{pattern}[:\s]*([^\n*]+?)(?=\n|$)', content, re.IGNORECASE | re.DOTALL)
1922
- if match:
1923
  value = match.group(1).strip()
1924
  # Clean up the value
1925
  value = re.sub(r'^[-*•:\s]+', '', value)
@@ -1929,7 +1926,7 @@ You provide feedback that's critical yet encouraging."""
1929
  return value
1930
  return ""
1931
 
1932
- # Extract all fields
1933
  profile = CharacterProfile(
1934
  name=name,
1935
  role=role,
@@ -1975,166 +1972,211 @@ You provide feedback that's critical yet encouraging."""
1975
  logger.debug(f"Parsed character: {profile.name}")
1976
  return profile
1977
 
1978
- def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
1979
- """Extract field value from content with improved parsing"""
1980
- # Use word boundary to prevent partial matches
1981
- pattern = rf'\b{field_pattern}\b[:\s]*([^\n]+?)(?=\n|$)'
1982
- match = re.search(pattern, content, re.IGNORECASE)
1983
- if match:
1984
- value = match.group(1).strip()
1985
- # Remove markdown formatting
1986
- value = re.sub(r'\*+', '', value)
1987
- # Remove list markers
1988
- value = re.sub(r'^\s*[-•*]\s*', '', value)
1989
- # Remove trailing punctuation
1990
- value = re.sub(r'[,.:;]$', '', value)
1991
- return value.strip()
1992
- return None
1993
-
1994
  def _extract_personality_traits(self, content: str) -> List[str]:
1995
- """Extract personality traits from content"""
1996
- traits = []
1997
- # Look for personality section
1998
- personality_section = self._extract_field(content, r"(?:Personality|성격)[:\s]*")
1999
- if personality_section:
2000
- # Extract individual traits (usually listed)
2001
- trait_lines = personality_section.split('\n')
2002
- for line in trait_lines:
2003
- line = line.strip()
2004
- if line and not line.endswith(':'):
2005
- # Remove list markers
2006
- trait = re.sub(r'^\s*[-•*]\s*', '', line)
2007
- if trait:
2008
- traits.append(trait)
2009
- return traits[:5] # Limit to 5 traits
 
 
 
 
 
 
 
 
 
 
 
2010
 
2011
  def _process_character_content(self, content: str):
2012
- """Process character designer output with better error handling"""
2013
- try:
2014
- # Extract protagonist
2015
- protagonist_section = self._extract_section(content, r"(?:PROTAGONIST|주인공)")
2016
- if protagonist_section:
2017
- protagonist = self._parse_character_profile(protagonist_section, "protagonist")
2018
- self.screenplay_tracker.add_character(protagonist)
2019
- ScreenplayDatabase.save_character(self.current_session_id, protagonist)
2020
-
2021
- # Extract antagonist
2022
- antagonist_section = self._extract_section(content, r"(?:ANTAGONIST|적대자)")
2023
- if antagonist_section:
2024
- antagonist = self._parse_character_profile(antagonist_section, "antagonist")
2025
- self.screenplay_tracker.add_character(antagonist)
2026
- ScreenplayDatabase.save_character(self.current_session_id, antagonist)
2027
-
2028
- # Extract supporting characters
2029
- supporting_section = self._extract_section(content, r"(?:SUPPORTING CAST|조력자들)")
2030
- if supporting_section:
2031
- # Parse multiple supporting characters
2032
- self._parse_supporting_characters(supporting_section)
2033
-
2034
- except Exception as e:
2035
- logger.error(f"Error processing character content: {e}")
2036
- # Continue with default values rather than failing
2037
-
2038
- def _parse_supporting_characters(self, content: str):
2039
- """Parse supporting characters from content"""
2040
- # Split by character markers (numbers or bullets)
2041
- char_sections = re.split(r'\n(?:\d+\.|[-•*])\s*', content)
2042
-
2043
- for i, section in enumerate(char_sections[1:], 1): # Skip first empty split
2044
- if section.strip():
2045
- try:
2046
- name = self._extract_field(section, r"(?:Name|이름)[:\s]*") or f"Supporting_{i}"
2047
- role = self._extract_field(section, r"(?:Role|역할)[:\s]*") or "supporting"
2048
-
2049
- character = CharacterProfile(
2050
- name=name,
2051
- role="supporting",
2052
- archetype=role,
2053
- want="",
2054
- need="",
2055
- backstory=self._extract_field(section, r"(?:Backstory|백스토리)[:\s]*") or "",
2056
- personality=[],
2057
- speech_pattern="",
2058
- character_arc=""
2059
- )
2060
-
2061
- self.screenplay_tracker.add_character(character)
2062
- ScreenplayDatabase.save_character(self.current_session_id, character)
2063
-
2064
- except Exception as e:
2065
- logger.warning(f"Error parsing supporting character {i}: {e}")
2066
- continue
 
 
2067
 
2068
  def _extract_section(self, content: str, section_pattern: str) -> str:
2069
- """Extract section from content with improved pattern matching"""
2070
- # More flexible section extraction
2071
- pattern = rf'{section_pattern}[:\s]*\n?(.*?)(?=\n\n[A-Z가-힣]{{2,}}[:\s]|\n\n\d+\.|$)'
2072
- match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2073
- if match:
2074
- return match.group(1).strip()
2075
-
2076
- # Try alternative pattern
2077
- pattern2 = rf'{section_pattern}.*?\n((?:.*\n)*?)(?=\n[A-Z가-힣]{{2,}}:|$)'
2078
- match2 = re.search(pattern2, content, re.IGNORECASE | re.DOTALL)
2079
- if match2:
2080
- return match2.group(1).strip()
2081
-
2082
- return ""
2083
-
2084
- def _process_producer_content(self, content: str):
2085
- """Process producer output with better extraction"""
2086
- try:
2087
- # Extract title with various formats
2088
- title_patterns = [
2089
- r'(?:TITLE|제목)[:\s]*\*?\*?([^\n*]+)\*?\*?',
2090
- r'\*\*(?:TITLE|제목)\*\*[:\s]*([^\n]+)',
2091
- r'Title[:\s]*([^\n]+)'
2092
- ]
2093
-
2094
- for pattern in title_patterns:
2095
- title_match = re.search(pattern, content, re.IGNORECASE)
2096
- if title_match:
2097
- self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
2098
- break
2099
-
2100
- # Extract logline with various formats
2101
- logline_patterns = [
2102
- r'(?:LOGLINE|로그라인)[:\s]*\*?\*?([^\n]+)',
2103
- r'\*\*(?:LOGLINE|로그라인)\*\*[:\s]*([^\n]+)',
2104
- r'Logline[:\s]*([^\n]+)'
2105
- ]
2106
-
2107
- for pattern in logline_patterns:
2108
- logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2109
- if logline_match:
2110
- # Get full logline (might be multi-line)
2111
- logline_text = logline_match.group(1).strip()
2112
- # Continue reading if it's incomplete
2113
- if not logline_text.endswith('.'):
2114
- next_lines = content[logline_match.end():].split('\n')
2115
- for line in next_lines[:3]: # Check next 3 lines
2116
- if line.strip() and not re.match(r'^[A-Z가-힣\d]', line.strip()):
2117
- logline_text += ' ' + line.strip()
2118
- else:
2119
- break
2120
- self.screenplay_tracker.screenplay_bible.logline = logline_text
2121
- break
2122
-
2123
- # Extract genre
2124
- genre_match = re.search(r'(?:Primary Genre|주 장르)[:\s]*([^\n]+)', content, re.IGNORECASE)
2125
- if genre_match:
2126
- self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
2127
-
2128
- # Save to database
2129
- ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
2130
- self.screenplay_tracker.screenplay_bible)
2131
-
2132
- except Exception as e:
2133
- logger.error(f"Error processing producer content: {e}")
2134
-
2135
-
2136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2138
 
2139
  def _process_story_content(self, content: str):
2140
  """Process story developer output"""
@@ -2149,22 +2191,6 @@ You provide feedback that's critical yet encouraging."""
2149
  ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
2150
  self.screenplay_tracker.screenplay_bible)
2151
 
2152
- def _process_character_content(self, content: str):
2153
- """Process character designer output"""
2154
- # Extract protagonist
2155
- protagonist_section = self._extract_section(content, "PROTAGONIST|주인공")
2156
- if protagonist_section:
2157
- protagonist = self._parse_character_profile(protagonist_section, "protagonist")
2158
- self.screenplay_tracker.add_character(protagonist)
2159
- ScreenplayDatabase.save_character(self.current_session_id, protagonist)
2160
-
2161
- # Extract antagonist
2162
- antagonist_section = self._extract_section(content, "ANTAGONIST|적대자")
2163
- if antagonist_section:
2164
- antagonist = self._parse_character_profile(antagonist_section, "antagonist")
2165
- self.screenplay_tracker.add_character(antagonist)
2166
- ScreenplayDatabase.save_character(self.current_session_id, antagonist)
2167
-
2168
  def _process_scene_content(self, content: str):
2169
  """Process scene planner output"""
2170
  # Parse scene breakdown
@@ -2193,44 +2219,6 @@ You provide feedback that's critical yet encouraging."""
2193
  self.screenplay_tracker.add_scene(scene)
2194
  ScreenplayDatabase.save_scene(self.current_session_id, scene)
2195
 
2196
- def _extract_section(self, content: str, section_pattern: str) -> str:
2197
- """Extract section from content"""
2198
- pattern = rf'(?:{section_pattern})[:\s]*(.+?)(?=\n(?:[A-Z]{{2,}}|[가-힣]{{2,}}):|\Z)'
2199
- match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2200
- return match.group(1).strip() if match else ""
2201
-
2202
- def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
2203
- """Parse character profile from content"""
2204
- # Extract character details using regex or string parsing
2205
- name = self._extract_field(content, "Name|이름") or f"Character_{role}"
2206
-
2207
- return CharacterProfile(
2208
- name=name,
2209
- role=role,
2210
- archetype=self._extract_field(content, "Archetype|아크타입") or "",
2211
- want=self._extract_field(content, "WANT|외적 목표") or "",
2212
- need=self._extract_field(content, "NEED|내적 필요") or "",
2213
- backstory=self._extract_field(content, "Backstory|백스토리") or "",
2214
- personality=[], # Would be parsed from content
2215
- speech_pattern=self._extract_field(content, "Speech|말투") or "",
2216
- character_arc=self._extract_field(content, "Arc|아크") or ""
2217
- )
2218
-
2219
- def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
2220
- """Extract field value from content with improved parsing"""
2221
- # More flexible pattern that handles various formats
2222
- pattern = rf'{field_pattern}(.+?)(?=\n[A-Z가-힣]|$)'
2223
- match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2224
- if match:
2225
- value = match.group(1).strip()
2226
- # Remove markdown formatting if present
2227
- value = re.sub(r'\*\*', '', value)
2228
- value = re.sub(r'^\s*[-•]\s*', '', value)
2229
- # Remove trailing punctuation
2230
- value = re.sub(r'[,.:;]$', '', value)
2231
- return value.strip()
2232
- return None
2233
-
2234
  # --- Utility functions ---
2235
  def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
2236
  """Generate random screenplay theme"""
@@ -2421,7 +2409,7 @@ def format_screenplay_display(screenplay_text: str) -> str:
2421
 
2422
  # Format scene headings
2423
  formatted_text = re.sub(
2424
- r'^(INT\.|EXT\.)(.*?)$',
2425
  r'**\1\2**',
2426
  screenplay_text,
2427
  flags=re.MULTILINE
@@ -2429,7 +2417,7 @@ def format_screenplay_display(screenplay_text: str) -> str:
2429
 
2430
  # Format character names (all caps on their own line)
2431
  formatted_text = re.sub(
2432
- r'^([A-Z][A-Z\s]+)$',
2433
  r'**\1**',
2434
  formatted_text,
2435
  flags=re.MULTILINE
@@ -2897,4 +2885,4 @@ if __name__ == "__main__":
2897
  server_port=7860,
2898
  share=False,
2899
  debug=True
2900
- )
 
536
 
537
  return theme_id
538
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  class WebSearchIntegration:
540
  """Web search functionality for screenplay research"""
541
  def __init__(self):
 
1469
  raise Exception(f"LLM Call Failed: {full_content}")
1470
  return full_content
1471
 
 
 
1472
  def call_llm_streaming(self, messages: List[Dict[str, str]], role: str,
1473
  language: str) -> Generator[str, None, None]:
1474
  try:
 
1618
  logger.error(traceback.format_exc())
1619
  yield f"❌ Unexpected error: {str(e)}"
1620
 
 
 
1621
  def get_system_prompts(self, language: str) -> Dict[str, str]:
1622
  """Role-specific system prompts"""
1623
 
 
1708
 
1709
  return base_prompts.get(language, base_prompts["English"])
1710
 
1711
+ # --- Main process ---
 
 
1712
  def process_screenplay_stream(self, query: str, screenplay_type: str, genre: str,
1713
  language: str, session_id: Optional[str] = None
1714
  ) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
 
1868
 
1869
  return "\n\n---\n\n".join(previous) if previous else ""
1870
 
1871
+ def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
1872
+ """Extract field value from content with improved parsing"""
1873
+ # More flexible pattern that handles various formats
1874
+ pattern = rf'{field_pattern}[:\s]*([^\n]+?)(?=\n[A-Z가-힣]|$)'
1875
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
1876
+ if match and match.group(1): # Check if group(1) exists and is not None
1877
+ value = match.group(1).strip()
1878
+ # Remove markdown formatting if present
1879
+ value = re.sub(r'\*\*', '', value)
1880
+ value = re.sub(r'^\s*[-•]\s*', '', value)
1881
+ # Remove trailing punctuation
1882
+ value = re.sub(r'[,.:;], '', value)
1883
+ return value.strip() if value else None
1884
+ return None
1885
 
1886
  def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
1887
  """Parse character profile from content"""
 
1899
 
1900
  for pattern in name_patterns:
1901
  name_match = re.search(pattern, content, re.IGNORECASE | re.MULTILINE)
1902
+ if name_match and name_match.group(1):
1903
  extracted_name = name_match.group(1).strip()
1904
  # Remove markdown and extra characters
1905
+ extracted_name = re.sub(r'[*:\s]+, '', extracted_name)
1906
  extracted_name = re.sub(r'^[*:\s]+', '', extracted_name)
1907
  if extracted_name and len(extracted_name) > 1:
1908
  name = extracted_name
 
1914
  patterns = [patterns]
1915
 
1916
  for pattern in patterns:
1917
+ # Improved pattern with better capturing groups
1918
  match = re.search(rf'{pattern}[:\s]*([^\n*]+?)(?=\n|$)', content, re.IGNORECASE | re.DOTALL)
1919
+ if match and match.group(1):
1920
  value = match.group(1).strip()
1921
  # Clean up the value
1922
  value = re.sub(r'^[-*•:\s]+', '', value)
 
1926
  return value
1927
  return ""
1928
 
1929
+ # Extract all fields with safer extraction
1930
  profile = CharacterProfile(
1931
  name=name,
1932
  role=role,
 
1972
  logger.debug(f"Parsed character: {profile.name}")
1973
  return profile
1974
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1975
  def _extract_personality_traits(self, content: str) -> List[str]:
1976
+ """Extract personality traits from content"""
1977
+ traits = []
1978
+ # Look for personality section with multiple pattern options
1979
+ personality_patterns = [
1980
+ r"(?:Personality|성격 특성|성격)[:\s]*([^\n]+(?:\n(?![\w가-힣]+:)[^\n]+)*)",
1981
+ r"성격[:\s]*(?:\n?[-•*]\s*[^\n]+)+"
1982
+ ]
1983
+
1984
+ for pattern in personality_patterns:
1985
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
1986
+ if match and match.group(1):
1987
+ personality_section = match.group(1)
1988
+ # Extract individual traits (usually listed)
1989
+ trait_lines = personality_section.split('\n')
1990
+ for line in trait_lines:
1991
+ line = line.strip()
1992
+ if line and not line.endswith(':'):
1993
+ # Remove list markers
1994
+ trait = re.sub(r'^\s*[-•*]\s*', '', line)
1995
+ trait = re.sub(r'^\d+\.\s*', '', trait) # Remove numbered lists
1996
+ if trait and len(trait) > 2: # Skip very short entries
1997
+ traits.append(trait)
1998
+ if traits: # If we found traits, stop looking
1999
+ break
2000
+
2001
+ return traits[:5] # Limit to 5 traits
2002
 
2003
  def _process_character_content(self, content: str):
2004
+ """Process character designer output with better error handling"""
2005
+ try:
2006
+ # Extract protagonist
2007
+ protagonist_section = self._extract_section(content, r"(?:PROTAGONIST|주인공)")
2008
+ if protagonist_section:
2009
+ try:
2010
+ protagonist = self._parse_character_profile(protagonist_section, "protagonist")
2011
+ self.screenplay_tracker.add_character(protagonist)
2012
+ ScreenplayDatabase.save_character(self.current_session_id, protagonist)
2013
+ except Exception as e:
2014
+ logger.error(f"Error parsing protagonist: {e}")
2015
+ # Create a default protagonist to continue
2016
+ protagonist = CharacterProfile(
2017
+ name="Protagonist",
2018
+ role="protagonist",
2019
+ archetype="Hero",
2020
+ want="To achieve goal",
2021
+ need="To grow",
2022
+ backstory="Unknown",
2023
+ personality=["Determined"],
2024
+ speech_pattern="Normal",
2025
+ character_arc="Growth"
2026
+ )
2027
+ self.screenplay_tracker.add_character(protagonist)
2028
+
2029
+ # Extract antagonist
2030
+ antagonist_section = self._extract_section(content, r"(?:ANTAGONIST|적대자)")
2031
+ if antagonist_section:
2032
+ try:
2033
+ antagonist = self._parse_character_profile(antagonist_section, "antagonist")
2034
+ self.screenplay_tracker.add_character(antagonist)
2035
+ ScreenplayDatabase.save_character(self.current_session_id, antagonist)
2036
+ except Exception as e:
2037
+ logger.error(f"Error parsing antagonist: {e}")
2038
+ # Create a default antagonist to continue
2039
+ antagonist = CharacterProfile(
2040
+ name="Antagonist",
2041
+ role="antagonist",
2042
+ archetype="Villain",
2043
+ want="To stop protagonist",
2044
+ need="Power",
2045
+ backstory="Unknown",
2046
+ personality=["Ruthless"],
2047
+ speech_pattern="Menacing",
2048
+ character_arc="Downfall"
2049
+ )
2050
+ self.screenplay_tracker.add_character(antagonist)
2051
+
2052
+ # Extract supporting characters
2053
+ supporting_section = self._extract_section(content, r"(?:SUPPORTING CAST|조력자들)")
2054
+ if supporting_section:
2055
+ # Parse multiple supporting characters
2056
+ self._parse_supporting_characters(supporting_section)
2057
+
2058
+ except Exception as e:
2059
+ logger.error(f"Error processing character content: {e}")
2060
+ # Continue with default values rather than failing
2061
 
2062
  def _extract_section(self, content: str, section_pattern: str) -> str:
2063
+ """Extract section from content with improved pattern matching"""
2064
+ # More flexible section extraction
2065
+ patterns = [
2066
+ # Pattern 1: Section header followed by content until next major section
2067
+ rf'{section_pattern}[:\s]*\n?(.*?)(?=\n\n[A-Z가-힣]{{2,}}[:\s]|\n\n\d+\.|$)',
2068
+ # Pattern 2: Section header with content until next section (alternative)
2069
+ rf'{section_pattern}.*?\n((?:.*\n)*?)(?=\n[A-Z가-힣]{{2,}}:|$)',
2070
+ # Pattern 3: More flexible pattern for Korean text
2071
+ rf'{section_pattern}[:\s]*\n?((?:[^\n]+\n?)*?)(?=\n\n|\Z)'
2072
+ ]
2073
+
2074
+ for pattern in patterns:
2075
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2076
+ if match and match.group(1):
2077
+ section_content = match.group(1).strip()
2078
+ if section_content: # Only return if we got actual content
2079
+ return section_content
2080
+
2081
+ return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2082
 
2083
+ def _parse_supporting_characters(self, content: str):
2084
+ """Parse supporting characters from content"""
2085
+ # Split by character markers (numbers or bullets)
2086
+ char_sections = re.split(r'\n(?:\d+\.|[-•*])\s*', content)
2087
+
2088
+ for i, section in enumerate(char_sections[1:], 1): # Skip first empty split
2089
+ if section.strip():
2090
+ try:
2091
+ # Try multiple name extraction patterns
2092
+ name = None
2093
+ name_patterns = [
2094
+ r"(?:이름|Name)[:\s]*([^,\n]+)",
2095
+ r"^([^:\n]+?)(?:\s*[-–]\s*|:\s*)", # Name at start before dash or colon
2096
+ r"^([가-힣A-Za-z\s]+?)(?:\s*\(|$)" # Korean/English name before parenthesis
2097
+ ]
2098
+
2099
+ for pattern in name_patterns:
2100
+ name_match = re.search(pattern, section.strip(), re.IGNORECASE)
2101
+ if name_match and name_match.group(1):
2102
+ name = name_match.group(1).strip()
2103
+ if name and len(name) > 1:
2104
+ break
2105
+
2106
+ if not name:
2107
+ name = f"Supporting_{i}"
2108
+
2109
+ role_desc = self._extract_field(section, r"(?:Role|역할)[:\s]*") or "supporting"
2110
+
2111
+ character = CharacterProfile(
2112
+ name=name,
2113
+ role="supporting",
2114
+ archetype=role_desc,
2115
+ want="",
2116
+ need="",
2117
+ backstory=self._extract_field(section, r"(?:Backstory|백스토리)[:\s]*") or "",
2118
+ personality=[],
2119
+ speech_pattern="",
2120
+ character_arc=""
2121
+ )
2122
+
2123
+ self.screenplay_tracker.add_character(character)
2124
+ ScreenplayDatabase.save_character(self.current_session_id, character)
2125
+
2126
+ except Exception as e:
2127
+ logger.warning(f"Error parsing supporting character {i}: {e}")
2128
+ continue
2129
 
2130
+ def _process_producer_content(self, content: str):
2131
+ """Process producer output with better extraction"""
2132
+ try:
2133
+ # Extract title with various formats
2134
+ title_patterns = [
2135
+ r'(?:TITLE|제목)[:\s]*\*?\*?([^\n*]+)\*?\*?',
2136
+ r'\*\*(?:TITLE|제목)\*\*[:\s]*([^\n]+)',
2137
+ r'Title[:\s]*([^\n]+)'
2138
+ ]
2139
+
2140
+ for pattern in title_patterns:
2141
+ title_match = re.search(pattern, content, re.IGNORECASE)
2142
+ if title_match:
2143
+ self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
2144
+ break
2145
+
2146
+ # Extract logline with various formats
2147
+ logline_patterns = [
2148
+ r'(?:LOGLINE|로그라인)[:\s]*\*?\*?([^\n]+)',
2149
+ r'\*\*(?:LOGLINE|로그라인)\*\*[:\s]*([^\n]+)',
2150
+ r'Logline[:\s]*([^\n]+)'
2151
+ ]
2152
+
2153
+ for pattern in logline_patterns:
2154
+ logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2155
+ if logline_match:
2156
+ # Get full logline (might be multi-line)
2157
+ logline_text = logline_match.group(1).strip()
2158
+ # Continue reading if it's incomplete
2159
+ if not logline_text.endswith('.'):
2160
+ next_lines = content[logline_match.end():].split('\n')
2161
+ for line in next_lines[:3]: # Check next 3 lines
2162
+ if line.strip() and not re.match(r'^[A-Z가-힣\d]', line.strip()):
2163
+ logline_text += ' ' + line.strip()
2164
+ else:
2165
+ break
2166
+ self.screenplay_tracker.screenplay_bible.logline = logline_text
2167
+ break
2168
+
2169
+ # Extract genre
2170
+ genre_match = re.search(r'(?:Primary Genre|주 장르)[:\s]*([^\n]+)', content, re.IGNORECASE)
2171
+ if genre_match:
2172
+ self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
2173
+
2174
+ # Save to database
2175
+ ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
2176
+ self.screenplay_tracker.screenplay_bible)
2177
+
2178
+ except Exception as e:
2179
+ logger.error(f"Error processing producer content: {e}")
2180
 
2181
  def _process_story_content(self, content: str):
2182
  """Process story developer output"""
 
2191
  ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
2192
  self.screenplay_tracker.screenplay_bible)
2193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2194
  def _process_scene_content(self, content: str):
2195
  """Process scene planner output"""
2196
  # Parse scene breakdown
 
2219
  self.screenplay_tracker.add_scene(scene)
2220
  ScreenplayDatabase.save_scene(self.current_session_id, scene)
2221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2222
  # --- Utility functions ---
2223
  def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
2224
  """Generate random screenplay theme"""
 
2409
 
2410
  # Format scene headings
2411
  formatted_text = re.sub(
2412
+ r'^(INT\.|EXT\.)(.*?),
2413
  r'**\1\2**',
2414
  screenplay_text,
2415
  flags=re.MULTILINE
 
2417
 
2418
  # Format character names (all caps on their own line)
2419
  formatted_text = re.sub(
2420
+ r'^([A-Z][A-Z\s]+),
2421
  r'**\1**',
2422
  formatted_text,
2423
  flags=re.MULTILINE
 
2885
  server_port=7860,
2886
  share=False,
2887
  debug=True
2888
+ )