openfree commited on
Commit
4517cb1
ยท
verified ยท
1 Parent(s): 6047143

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1005 -1
app.py CHANGED
@@ -1879,7 +1879,1011 @@ You provide feedback that's critical yet encouraging."""
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
 
 
1879
  value = re.sub(r'\*\*', '', value)
1880
  value = re.sub(r'^\s*[-โ€ข]\s*', '', value)
1881
  # Remove trailing punctuation
1882
+ value = re.sub(r'[,.:;]
1883
+
1884
+ def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
1885
+ """Parse character profile from content"""
1886
+ # Debug logging
1887
+ logger.debug(f"Parsing character profile for role: {role}")
1888
+ logger.debug(f"Content preview: {content[:200]}...")
1889
+
1890
+ # Extract name first - handle various formats
1891
+ name = f"Character_{role}" # default
1892
+ name_patterns = [
1893
+ r'(?:์ด๋ฆ„|Name)[:\s]*([^,\n]+?)(?:\s*\([^)]+\))?\s*',
1894
+ r'^\s*[-*โ€ข]\s*([^,\n]+?)(?:\s*\([^)]+\))?\s*',
1895
+ r'^([^,\n]+?)(?:\s*\([^)]+\))?\s*'
1896
+ ]
1897
+
1898
+ for pattern in name_patterns:
1899
+ name_match = re.search(pattern, content, re.IGNORECASE | re.MULTILINE)
1900
+ if name_match and name_match.group(1):
1901
+ extracted_name = name_match.group(1).strip()
1902
+ # Remove markdown and extra characters
1903
+ extracted_name = re.sub(r'[*:\s]+, '', extracted_name)
1904
+ extracted_name = re.sub(r'^[*:\s]+', '', extracted_name)
1905
+ if extracted_name and len(extracted_name) > 1:
1906
+ name = extracted_name
1907
+ break
1908
+
1909
+ # Helper function to extract clean fields
1910
+ def extract_clean_field(patterns):
1911
+ if isinstance(patterns, str):
1912
+ patterns = [patterns]
1913
+
1914
+ for pattern in patterns:
1915
+ # Improved pattern with better capturing groups
1916
+ match = re.search(rf'{pattern}[:\s]*([^\n*]+?)(?=\n|$)', content, re.IGNORECASE | re.DOTALL)
1917
+ if match and match.group(1):
1918
+ value = match.group(1).strip()
1919
+ # Clean up the value
1920
+ value = re.sub(r'^[-*โ€ข:\s]+', '', value)
1921
+ value = re.sub(r'[*]+', '', value)
1922
+ value = re.sub(r'\s+', ' ', value)
1923
+ if value:
1924
+ return value
1925
+ return ""
1926
+
1927
+ # Extract all fields with safer extraction
1928
+ profile = CharacterProfile(
1929
+ name=name,
1930
+ role=role,
1931
+ archetype=extract_clean_field([
1932
+ r"์บ๋ฆญํ„ฐ ์•„ํฌํƒ€์ž…",
1933
+ r"Character Archetype",
1934
+ r"Archetype",
1935
+ r"์•„ํฌํƒ€์ž…"
1936
+ ]),
1937
+ want=extract_clean_field([
1938
+ r"WANT\s*\(์™ธ์  ๋ชฉํ‘œ\)",
1939
+ r"WANT",
1940
+ r"์™ธ์  ๋ชฉํ‘œ",
1941
+ r"External Goal"
1942
+ ]),
1943
+ need=extract_clean_field([
1944
+ r"NEED\s*\(๋‚ด์  ํ•„์š”\)",
1945
+ r"NEED",
1946
+ r"๋‚ด์  ํ•„์š”",
1947
+ r"Internal Need"
1948
+ ]),
1949
+ backstory=extract_clean_field([
1950
+ r"๋ฐฑ์Šคํ† ๋ฆฌ",
1951
+ r"Backstory",
1952
+ r"ํ•ต์‹ฌ ์ƒ์ฒ˜",
1953
+ r"Core Wound"
1954
+ ]),
1955
+ personality=self._extract_personality_traits(content),
1956
+ speech_pattern=extract_clean_field([
1957
+ r"๋งํˆฌ.*?ํŒจํ„ด",
1958
+ r"Speech Pattern",
1959
+ r"์–ธ์–ด ํŒจํ„ด",
1960
+ r"๋งํˆฌ"
1961
+ ]),
1962
+ character_arc=extract_clean_field([
1963
+ r"์บ๋ฆญํ„ฐ ์•„ํฌ",
1964
+ r"Character Arc",
1965
+ r"Arc",
1966
+ r"๋ณ€ํ™”"
1967
+ ])
1968
+ )
1969
+
1970
+ logger.debug(f"Parsed character: {profile.name}")
1971
+ return profile
1972
+
1973
+ def _extract_personality_traits(self, content: str) -> List[str]:
1974
+ """Extract personality traits from content"""
1975
+ traits = []
1976
+ # Look for personality section with multiple pattern options
1977
+ personality_patterns = [
1978
+ r"(?:Personality|์„ฑ๊ฒฉ ํŠน์„ฑ|์„ฑ๊ฒฉ)[:\s]*([^\n]+(?:\n(?![\w๊ฐ€-ํžฃ]+:)[^\n]+)*)",
1979
+ r"์„ฑ๊ฒฉ[:\s]*(?:\n?[-โ€ข*]\s*[^\n]+)+"
1980
+ ]
1981
+
1982
+ for pattern in personality_patterns:
1983
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
1984
+ if match and match.group(1):
1985
+ personality_section = match.group(1)
1986
+ # Extract individual traits (usually listed)
1987
+ trait_lines = personality_section.split('\n')
1988
+ for line in trait_lines:
1989
+ line = line.strip()
1990
+ if line and not line.endswith(':'):
1991
+ # Remove list markers
1992
+ trait = re.sub(r'^\s*[-โ€ข*]\s*', '', line)
1993
+ trait = re.sub(r'^\d+\.\s*', '', trait) # Remove numbered lists
1994
+ if trait and len(trait) > 2: # Skip very short entries
1995
+ traits.append(trait)
1996
+ if traits: # If we found traits, stop looking
1997
+ break
1998
+
1999
+ return traits[:5] # Limit to 5 traits
2000
+
2001
+ def _process_character_content(self, content: str):
2002
+ """Process character designer output with better error handling"""
2003
+ try:
2004
+ # Extract protagonist
2005
+ protagonist_section = self._extract_section(content, r"(?:PROTAGONIST|์ฃผ์ธ๊ณต)")
2006
+ if protagonist_section:
2007
+ try:
2008
+ protagonist = self._parse_character_profile(protagonist_section, "protagonist")
2009
+ self.screenplay_tracker.add_character(protagonist)
2010
+ ScreenplayDatabase.save_character(self.current_session_id, protagonist)
2011
+ except Exception as e:
2012
+ logger.error(f"Error parsing protagonist: {e}")
2013
+ # Create a default protagonist to continue
2014
+ protagonist = CharacterProfile(
2015
+ name="Protagonist",
2016
+ role="protagonist",
2017
+ archetype="Hero",
2018
+ want="To achieve goal",
2019
+ need="To grow",
2020
+ backstory="Unknown",
2021
+ personality=["Determined"],
2022
+ speech_pattern="Normal",
2023
+ character_arc="Growth"
2024
+ )
2025
+ self.screenplay_tracker.add_character(protagonist)
2026
+
2027
+ # Extract antagonist
2028
+ antagonist_section = self._extract_section(content, r"(?:ANTAGONIST|์ ๋Œ€์ž)")
2029
+ if antagonist_section:
2030
+ try:
2031
+ antagonist = self._parse_character_profile(antagonist_section, "antagonist")
2032
+ self.screenplay_tracker.add_character(antagonist)
2033
+ ScreenplayDatabase.save_character(self.current_session_id, antagonist)
2034
+ except Exception as e:
2035
+ logger.error(f"Error parsing antagonist: {e}")
2036
+ # Create a default antagonist to continue
2037
+ antagonist = CharacterProfile(
2038
+ name="Antagonist",
2039
+ role="antagonist",
2040
+ archetype="Villain",
2041
+ want="To stop protagonist",
2042
+ need="Power",
2043
+ backstory="Unknown",
2044
+ personality=["Ruthless"],
2045
+ speech_pattern="Menacing",
2046
+ character_arc="Downfall"
2047
+ )
2048
+ self.screenplay_tracker.add_character(antagonist)
2049
+
2050
+ # Extract supporting characters
2051
+ supporting_section = self._extract_section(content, r"(?:SUPPORTING CAST|์กฐ๋ ฅ์ž๋“ค)")
2052
+ if supporting_section:
2053
+ # Parse multiple supporting characters
2054
+ self._parse_supporting_characters(supporting_section)
2055
+
2056
+ except Exception as e:
2057
+ logger.error(f"Error processing character content: {e}")
2058
+ # Continue with default values rather than failing
2059
+
2060
+ def _extract_section(self, content: str, section_pattern: str) -> str:
2061
+ """Extract section from content with improved pattern matching"""
2062
+ # More flexible section extraction
2063
+ patterns = [
2064
+ # Pattern 1: Section header followed by content until next major section
2065
+ rf'{section_pattern}[:\s]*\n?(.*?)(?=\n\n[A-Z๊ฐ€-ํžฃ]{{2,}}[:\s]|\n\n\d+\.|$)',
2066
+ # Pattern 2: Section header with content until next section (alternative)
2067
+ rf'{section_pattern}.*?\n((?:.*\n)*?)(?=\n[A-Z๊ฐ€-ํžฃ]{{2,}}:|$)',
2068
+ # Pattern 3: More flexible pattern for Korean text
2069
+ rf'{section_pattern}[:\s]*\n?((?:[^\n]+\n?)*?)(?=\n\n|\Z)'
2070
+ ]
2071
+
2072
+ for pattern in patterns:
2073
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2074
+ if match and match.group(1):
2075
+ section_content = match.group(1).strip()
2076
+ if section_content: # Only return if we got actual content
2077
+ return section_content
2078
+
2079
+ return ""
2080
+
2081
+ def _parse_supporting_characters(self, content: str):
2082
+ """Parse supporting characters from content"""
2083
+ # Split by character markers (numbers or bullets)
2084
+ char_sections = re.split(r'\n(?:\d+\.|[-โ€ข*])\s*', content)
2085
+
2086
+ for i, section in enumerate(char_sections[1:], 1): # Skip first empty split
2087
+ if section.strip():
2088
+ try:
2089
+ # Try multiple name extraction patterns
2090
+ name = None
2091
+ name_patterns = [
2092
+ r"(?:์ด๋ฆ„|Name)[:\s]*([^,\n]+)",
2093
+ r"^([^:\n]+?)(?:\s*[-โ€“]\s*|:\s*)", # Name at start before dash or colon
2094
+ r"^([๊ฐ€-ํžฃA-Za-z\s]+?)(?:\s*\(|$)" # Korean/English name before parenthesis
2095
+ ]
2096
+
2097
+ for pattern in name_patterns:
2098
+ name_match = re.search(pattern, section.strip(), re.IGNORECASE)
2099
+ if name_match and name_match.group(1):
2100
+ name = name_match.group(1).strip()
2101
+ if name and len(name) > 1:
2102
+ break
2103
+
2104
+ if not name:
2105
+ name = f"Supporting_{i}"
2106
+
2107
+ role_desc = self._extract_field(section, r"(?:Role|์—ญํ• )[:\s]*") or "supporting"
2108
+
2109
+ character = CharacterProfile(
2110
+ name=name,
2111
+ role="supporting",
2112
+ archetype=role_desc,
2113
+ want="",
2114
+ need="",
2115
+ backstory=self._extract_field(section, r"(?:Backstory|๋ฐฑ์Šคํ† ๋ฆฌ)[:\s]*") or "",
2116
+ personality=[],
2117
+ speech_pattern="",
2118
+ character_arc=""
2119
+ )
2120
+
2121
+ self.screenplay_tracker.add_character(character)
2122
+ ScreenplayDatabase.save_character(self.current_session_id, character)
2123
+
2124
+ except Exception as e:
2125
+ logger.warning(f"Error parsing supporting character {i}: {e}")
2126
+ continue
2127
+
2128
+ def _process_producer_content(self, content: str):
2129
+ """Process producer output with better extraction"""
2130
+ try:
2131
+ # Extract title with various formats
2132
+ title_patterns = [
2133
+ r'(?:TITLE|์ œ๋ชฉ)[:\s]*\*?\*?([^\n*]+)\*?\*?',
2134
+ r'\*\*(?:TITLE|์ œ๋ชฉ)\*\*[:\s]*([^\n]+)',
2135
+ r'Title[:\s]*([^\n]+)'
2136
+ ]
2137
+
2138
+ for pattern in title_patterns:
2139
+ title_match = re.search(pattern, content, re.IGNORECASE)
2140
+ if title_match:
2141
+ self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
2142
+ break
2143
+
2144
+ # Extract logline with various formats
2145
+ logline_patterns = [
2146
+ r'(?:LOGLINE|๋กœ๊ทธ๋ผ์ธ)[:\s]*\*?\*?([^\n]+)',
2147
+ r'\*\*(?:LOGLINE|๋กœ๊ทธ๋ผ์ธ)\*\*[:\s]*([^\n]+)',
2148
+ r'Logline[:\s]*([^\n]+)'
2149
+ ]
2150
+
2151
+ for pattern in logline_patterns:
2152
+ logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
2153
+ if logline_match:
2154
+ # Get full logline (might be multi-line)
2155
+ logline_text = logline_match.group(1).strip()
2156
+ # Continue reading if it's incomplete
2157
+ if not logline_text.endswith('.'):
2158
+ next_lines = content[logline_match.end():].split('\n')
2159
+ for line in next_lines[:3]: # Check next 3 lines
2160
+ if line.strip() and not re.match(r'^[A-Z๊ฐ€-ํžฃ\d]', line.strip()):
2161
+ logline_text += ' ' + line.strip()
2162
+ else:
2163
+ break
2164
+ self.screenplay_tracker.screenplay_bible.logline = logline_text
2165
+ break
2166
+
2167
+ # Extract genre
2168
+ genre_match = re.search(r'(?:Primary Genre|์ฃผ ์žฅ๋ฅด)[:\s]*([^\n]+)', content, re.IGNORECASE)
2169
+ if genre_match:
2170
+ self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
2171
+
2172
+ # Save to database
2173
+ ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
2174
+ self.screenplay_tracker.screenplay_bible)
2175
+
2176
+ except Exception as e:
2177
+ logger.error(f"Error processing producer content: {e}")
2178
+
2179
+ def _process_story_content(self, content: str):
2180
+ """Process story developer output"""
2181
+ # Extract three-act structure
2182
+ self.screenplay_tracker.screenplay_bible.three_act_structure = {
2183
+ "act1": self._extract_section(content, "ACT 1|์ œ1๋ง‰"),
2184
+ "act2a": self._extract_section(content, "ACT 2A|์ œ2๋ง‰A"),
2185
+ "act2b": self._extract_section(content, "ACT 2B|์ œ2๋ง‰B"),
2186
+ "act3": self._extract_section(content, "ACT 3|์ œ3๋ง‰")
2187
+ }
2188
+
2189
+ ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
2190
+ self.screenplay_tracker.screenplay_bible)
2191
+
2192
+ def _process_scene_content(self, content: str):
2193
+ """Process scene planner output"""
2194
+ # Parse scene breakdown
2195
+ scene_pattern = r'(?:Scene|์”ฌ)\s*(\d+).*?(?:INT\.|EXT\.)\s*(.+?)\s*-\s*(\w+)'
2196
+ scenes = re.finditer(scene_pattern, content, re.IGNORECASE | re.MULTILINE)
2197
+
2198
+ for match in scenes:
2199
+ scene_num = int(match.group(1))
2200
+ location = match.group(2).strip()
2201
+ time_of_day = match.group(3).strip()
2202
+
2203
+ # Determine act based on scene number
2204
+ act = 1 if scene_num <= 12 else 2 if scene_num <= 35 else 3
2205
+
2206
+ scene = SceneBreakdown(
2207
+ scene_number=scene_num,
2208
+ act=act,
2209
+ location=location,
2210
+ time_of_day=time_of_day,
2211
+ characters=[], # Would be extracted from content
2212
+ purpose="", # Would be extracted from content
2213
+ conflict="", # Would be extracted from content
2214
+ page_count=1.5 # Default estimate
2215
+ )
2216
+
2217
+ self.screenplay_tracker.add_scene(scene)
2218
+ ScreenplayDatabase.save_scene(self.current_session_id, scene)
2219
+
2220
+ # --- Utility functions ---
2221
+ def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
2222
+ """Generate random screenplay theme"""
2223
+ try:
2224
+ # Log the attempt
2225
+ logger.info(f"Generating random theme - Type: {screenplay_type}, Genre: {genre}, Language: {language}")
2226
+
2227
+ # Load themes data
2228
+ themes_data = load_screenplay_themes_data()
2229
+
2230
+ # Select random elements
2231
+ import secrets
2232
+ situations = themes_data['situations'].get(genre, themes_data['situations']['drama'])
2233
+ protagonists = themes_data['protagonists'].get(genre, themes_data['protagonists']['drama'])
2234
+ conflicts = themes_data['conflicts'].get(genre, themes_data['conflicts']['drama'])
2235
+
2236
+ if not situations or not protagonists or not conflicts:
2237
+ logger.error(f"No theme data available for genre {genre}")
2238
+ return f"Error: No theme data available for genre {genre}"
2239
+
2240
+ situation = secrets.choice(situations)
2241
+ protagonist = secrets.choice(protagonists)
2242
+ conflict = secrets.choice(conflicts)
2243
+
2244
+ logger.info(f"Selected elements - Situation: {situation}, Protagonist: {protagonist}, Conflict: {conflict}")
2245
+
2246
+ # Check if API token is valid
2247
+ if not FRIENDLI_TOKEN or FRIENDLI_TOKEN == "dummy_token_for_testing":
2248
+ logger.warning("No valid API token, returning fallback theme")
2249
+ return get_fallback_theme(screenplay_type, genre, language, situation, protagonist, conflict)
2250
+
2251
+ # Generate theme using LLM
2252
+ system = ScreenplayGenerationSystem()
2253
+
2254
+ if language == "Korean":
2255
+ prompt = f"""๋‹ค์Œ ์š”์†Œ๋“ค๋กœ {screenplay_type}์šฉ ๋งค๋ ฅ์ ์ธ ์ปจ์…‰์„ ์ƒ์„ฑํ•˜์„ธ์š”:
2256
+
2257
+ ์ƒํ™ฉ: {situation}
2258
+ ์ฃผ์ธ๊ณต: {protagonist}
2259
+ ๊ฐˆ๋“ฑ: {conflict}
2260
+ ์žฅ๋ฅด: {genre}
2261
+
2262
+ ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑ:
2263
+
2264
+ **์ œ๋ชฉ:** [๋งค๋ ฅ์ ์ธ ์ œ๋ชฉ]
2265
+
2266
+ **๋กœ๊ทธ๋ผ์ธ:** [25๋‹จ์–ด ์ด๋‚ด ํ•œ ๋ฌธ์žฅ]
2267
+
2268
+ **์ปจ์…‰:** [์ฃผ์ธ๊ณต]์ด(๊ฐ€) [์ƒํ™ฉ]์—์„œ [๊ฐˆ๋“ฑ]์„ ๊ฒช์œผ๋ฉฐ [๋ชฉํ‘œ]๋ฅผ ์ถ”๊ตฌํ•˜๋Š” ์ด์•ผ๊ธฐ.
2269
+
2270
+ **๋…ํŠนํ•œ ์š”์†Œ:** [์ด ์ด์•ผ๊ธฐ๋งŒ์˜ ํŠน๋ณ„ํ•œ ์ ]"""
2271
+ else:
2272
+ prompt = f"""Generate an attractive concept for {screenplay_type} using these elements:
2273
+
2274
+ Situation: {situation}
2275
+ Protagonist: {protagonist}
2276
+ Conflict: {conflict}
2277
+ Genre: {genre}
2278
+
2279
+ Format as:
2280
+
2281
+ **Title:** [Compelling title]
2282
+
2283
+ **Logline:** [One sentence, 25 words max]
2284
+
2285
+ **Concept:** A story about [protagonist] who faces [conflict] in [situation] while pursuing [goal].
2286
+
2287
+ **Unique Element:** [What makes this story special]"""
2288
+
2289
+ messages = [{"role": "user", "content": prompt}]
2290
+
2291
+ # Call LLM with error handling
2292
+ logger.info("Calling LLM for theme generation...")
2293
+
2294
+ generated_theme = ""
2295
+ error_occurred = False
2296
+
2297
+ # Use streaming to get the response
2298
+ for chunk in system.call_llm_streaming(messages, "producer", language):
2299
+ if chunk.startswith("โŒ"):
2300
+ logger.error(f"LLM streaming error: {chunk}")
2301
+ error_occurred = True
2302
+ break
2303
+ generated_theme += chunk
2304
+
2305
+ # If error occurred or no content generated, use fallback
2306
+ if error_occurred or not generated_theme.strip():
2307
+ logger.warning("LLM call failed or empty response, using fallback theme")
2308
+ return get_fallback_theme(screenplay_type, genre, language, situation, protagonist, conflict)
2309
+
2310
+ logger.info(f"Successfully generated theme of length: {len(generated_theme)}")
2311
+
2312
+ # Extract metadata
2313
+ metadata = {
2314
+ 'title': extract_title_from_theme(generated_theme),
2315
+ 'logline': extract_logline_from_theme(generated_theme),
2316
+ 'protagonist': protagonist,
2317
+ 'conflict': conflict,
2318
+ 'situation': situation,
2319
+ 'tags': [genre, screenplay_type]
2320
+ }
2321
+
2322
+ # Save to database
2323
+ try:
2324
+ theme_id = ScreenplayDatabase.save_random_theme(
2325
+ generated_theme, screenplay_type, genre, language, metadata
2326
+ )
2327
+ logger.info(f"Saved theme with ID: {theme_id}")
2328
+ except Exception as e:
2329
+ logger.error(f"Failed to save theme to database: {e}")
2330
+
2331
+ return generated_theme
2332
+
2333
+ except Exception as e:
2334
+ logger.error(f"Theme generation error: {str(e)}")
2335
+ import traceback
2336
+ logger.error(traceback.format_exc())
2337
+ return f"Error generating theme: {str(e)}"
2338
+
2339
+ def get_fallback_theme(screenplay_type: str, genre: str, language: str,
2340
+ situation: str, protagonist: str, conflict: str) -> str:
2341
+ """Generate fallback theme without LLM"""
2342
+ if language == "Korean":
2343
+ return f"""**์ œ๋ชฉ:** {protagonist}์˜ ์„ ํƒ
2344
+
2345
+ **๋กœ๊ทธ๋ผ์ธ:** {situation}์— ๊ฐ‡ํžŒ {protagonist}๊ฐ€ {conflict}์— ๋งž์„œ๋ฉฐ ์ƒ์กด์„ ์œ„ํ•ด ์‹ธ์šด๋‹ค.
2346
+
2347
+ **์ปจ์…‰:** {protagonist}๊ฐ€ {situation}์—์„œ {conflict}์„ ๊ฒช์œผ๋ฉฐ ์ž์‹ ์˜ ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๋Š” ์ด์•ผ๊ธฐ.
2348
+
2349
+ **๋…ํŠนํ•œ ์š”์†Œ:** {genre} ์žฅ๋ฅด์˜ ์ „ํ†ต์  ์š”์†Œ๋ฅผ ํ˜„๋Œ€์ ์œผ๋กœ ์žฌํ•ด์„ํ•œ ์ž‘ํ’ˆ."""
2350
+ else:
2351
+ return f"""**Title:** The {protagonist.title()}'s Choice
2352
+
2353
+ **Logline:** When trapped in {situation}, a {protagonist} must face {conflict} to survive.
2354
+
2355
+ **Concept:** A story about a {protagonist} who faces {conflict} in {situation} while discovering their true strength.
2356
+
2357
+ **Unique Element:** A fresh take on {genre} genre conventions with contemporary relevance."""
2358
+
2359
+ def load_screenplay_themes_data() -> Dict:
2360
+ """Load screenplay themes data"""
2361
+ return {
2362
+ 'situations': {
2363
+ 'action': ['hostage crisis', 'heist gone wrong', 'revenge mission', 'race against time'],
2364
+ 'thriller': ['false accusation', 'witness protection', 'conspiracy uncovered', 'identity theft'],
2365
+ 'drama': ['family reunion', 'terminal diagnosis', 'divorce proceedings', 'career crossroads'],
2366
+ 'comedy': ['mistaken identity', 'wedding disaster', 'workplace chaos', 'odd couple roommates'],
2367
+ 'horror': ['isolated location', 'ancient curse', 'home invasion', 'supernatural investigation'],
2368
+ 'sci-fi': ['first contact', 'time loop', 'AI awakening', 'space colony crisis'],
2369
+ 'romance': ['second chance', 'enemies to lovers', 'long distance', 'forbidden love']
2370
+ },
2371
+ 'protagonists': {
2372
+ 'action': ['ex-soldier', 'undercover cop', 'skilled thief', 'reluctant hero'],
2373
+ 'thriller': ['investigative journalist', 'wrongly accused person', 'FBI agent', 'whistleblower'],
2374
+ 'drama': ['single parent', 'recovering addict', 'immigrant', 'caregiver'],
2375
+ 'comedy': ['uptight professional', 'slacker', 'fish out of water', 'eccentric artist'],
2376
+ 'horror': ['skeptical scientist', 'final girl', 'paranormal investigator', 'grieving parent'],
2377
+ 'sci-fi': ['astronaut', 'AI researcher', 'time traveler', 'colony leader'],
2378
+ 'romance': ['workaholic', 'hopeless romantic', 'cynical divorce lawyer', 'small town newcomer']
2379
+ },
2380
+ 'conflicts': {
2381
+ 'action': ['stop the villain', 'save the hostages', 'prevent disaster', 'survive pursuit'],
2382
+ 'thriller': ['prove innocence', 'expose truth', 'stay alive', 'protect loved ones'],
2383
+ 'drama': ['reconcile past', 'find purpose', 'heal relationships', 'accept change'],
2384
+ 'comedy': ['save the business', 'win the competition', 'fool everyone', 'find love'],
2385
+ 'horror': ['survive the night', 'break the curse', 'escape the monster', 'save the town'],
2386
+ 'sci-fi': ['save humanity', 'prevent paradox', 'stop the invasion', 'preserve identity'],
2387
+ 'romance': ['overcome differences', 'choose between options', 'trust again', 'follow heart']
2388
+ }
2389
+ }
2390
+
2391
+ def extract_title_from_theme(theme_text: str) -> str:
2392
+ """Extract title from generated theme"""
2393
+ match = re.search(r'\*\*(?:Title|์ œ๋ชฉ):\*\*\s*(.+)', theme_text, re.IGNORECASE)
2394
+ return match.group(1).strip() if match else ""
2395
+
2396
+ def extract_logline_from_theme(theme_text: str) -> str:
2397
+ """Extract logline from generated theme"""
2398
+ match = re.search(r'\*\*(?:Logline|๋กœ๊ทธ๋ผ์ธ):\*\*\s*(.+)', theme_text, re.IGNORECASE)
2399
+ return match.group(1).strip() if match else ""
2400
+
2401
+ def format_screenplay_display(screenplay_text: str) -> str:
2402
+ """Format screenplay for display"""
2403
+ if not screenplay_text:
2404
+ return "No screenplay content yet."
2405
+
2406
+ formatted = "# ๐ŸŽฌ Screenplay\n\n"
2407
+
2408
+ # Format scene headings
2409
+ formatted_text = re.sub(
2410
+ r'^(INT\.|EXT\.)(.*?),
2411
+ r'**\1\2**',
2412
+ screenplay_text,
2413
+ flags=re.MULTILINE
2414
+ )
2415
+
2416
+ # Format character names (all caps on their own line)
2417
+ formatted_text = re.sub(
2418
+ r'^([A-Z][A-Z\s]+),
2419
+ r'**\1**',
2420
+ formatted_text,
2421
+ flags=re.MULTILINE
2422
+ )
2423
+
2424
+ # Add spacing for readability
2425
+ lines = formatted_text.split('\n')
2426
+ formatted_lines = []
2427
+
2428
+ for i, line in enumerate(lines):
2429
+ formatted_lines.append(line)
2430
+ # Add extra space after scene headings
2431
+ if line.startswith('**INT.') or line.startswith('**EXT.'):
2432
+ formatted_lines.append('')
2433
+
2434
+ formatted += '\n'.join(formatted_lines)
2435
+
2436
+ # Add page count
2437
+ page_count = len(screenplay_text.split('\n')) / 55
2438
+ formatted = f"**Total Pages: {page_count:.1f}**\n\n" + formatted
2439
+
2440
+ return formatted
2441
+
2442
+ def format_stages_display(stages: List[Dict]) -> str:
2443
+ """Format stages display for screenplay"""
2444
+ markdown = "## ๐ŸŽฌ Production Progress\n\n"
2445
+
2446
+ # Progress summary
2447
+ completed = sum(1 for s in stages if s.get('status') == 'complete')
2448
+ total = len(stages)
2449
+ markdown += f"**Progress: {completed}/{total} stages complete**\n\n"
2450
+
2451
+ # Page count if available
2452
+ total_pages = sum(s.get('page_count', 0) for s in stages if s.get('page_count'))
2453
+ if total_pages > 0:
2454
+ markdown += f"**Current Page Count: {total_pages:.1f} pages**\n\n"
2455
+
2456
+ markdown += "---\n\n"
2457
+
2458
+ # Stage details
2459
+ current_act = None
2460
+ for i, stage in enumerate(stages):
2461
+ status_icon = "โœ…" if stage['status'] == 'complete' else "๐Ÿ”„" if stage['status'] == 'active' else "โณ"
2462
+
2463
+ # Group by acts
2464
+ if 'Act' in stage.get('name', ''):
2465
+ act_match = re.search(r'Act (\w+)', stage['name'])
2466
+ if act_match and act_match.group(1) != current_act:
2467
+ current_act = act_match.group(1)
2468
+ markdown += f"\n### ๐Ÿ“„ Act {current_act}\n\n"
2469
+
2470
+ markdown += f"{status_icon} **{stage['name']}**"
2471
+
2472
+ if stage.get('page_count', 0) > 0:
2473
+ markdown += f" ({stage['page_count']:.1f} pages)"
2474
+
2475
+ markdown += "\n"
2476
+
2477
+ if stage['content'] and stage['status'] == 'complete':
2478
+ preview_length = 200
2479
+ preview = stage['content'][:preview_length] + "..." if len(stage['content']) > preview_length else stage['content']
2480
+ markdown += f"> {preview}\n\n"
2481
+ elif stage['status'] == 'active':
2482
+ markdown += "> *In progress...*\n\n"
2483
+
2484
+ return markdown
2485
+
2486
+ def process_query(query: str, screenplay_type: str, genre: str, language: str,
2487
+ session_id: Optional[str] = None) -> Generator[Tuple[str, str, str, str], None, None]:
2488
+ """Main query processing function"""
2489
+ if not query.strip():
2490
+ yield "", "", "โŒ Please enter a screenplay concept.", session_id
2491
+ return
2492
+
2493
+ system = ScreenplayGenerationSystem()
2494
+ stages_markdown = ""
2495
+ screenplay_display = ""
2496
+
2497
+ for status, stages, current_session_id in system.process_screenplay_stream(
2498
+ query, screenplay_type, genre, language, session_id
2499
+ ):
2500
+ stages_markdown = format_stages_display(stages)
2501
+
2502
+ # Get screenplay content when available
2503
+ if stages and all(s.get("status") == "complete" for s in stages[-4:]):
2504
+ screenplay_text = ScreenplayDatabase.get_screenplay_content(current_session_id)
2505
+ screenplay_display = format_screenplay_display(screenplay_text)
2506
+
2507
+ yield stages_markdown, screenplay_display, status or "๐Ÿ”„ Processing...", current_session_id
2508
+
2509
+ def get_active_sessions() -> List[str]:
2510
+ """Get active screenplay sessions"""
2511
+ sessions = ScreenplayDatabase.get_active_sessions()
2512
+ return [
2513
+ f"{s['session_id'][:8]}... - {s.get('title', s['user_query'][:30])}... "
2514
+ f"({s['screenplay_type']}/{s['genre']}) [{s['total_pages']:.1f} pages]"
2515
+ for s in sessions
2516
+ ]
2517
+
2518
+ def export_screenplay_pdf(screenplay_text: str, title: str, session_id: str) -> str:
2519
+ """Export screenplay to PDF format"""
2520
+ # This would use a library like reportlab to create industry-standard PDF
2521
+ # For now, returning a placeholder
2522
+ pdf_path = f"screenplay_{session_id[:8]}.pdf"
2523
+ # PDF generation logic would go here
2524
+ return pdf_path
2525
+
2526
+ def export_screenplay_fdx(screenplay_text: str, title: str, session_id: str) -> str:
2527
+ """Export to Final Draft format"""
2528
+ # This would create .fdx XML format
2529
+ fdx_path = f"screenplay_{session_id[:8]}.fdx"
2530
+ # FDX generation logic would go here
2531
+ return fdx_path
2532
+
2533
+ def download_screenplay(screenplay_text: str, format_type: str, title: str,
2534
+ session_id: str) -> Optional[str]:
2535
+ """Generate screenplay download file"""
2536
+ if not screenplay_text or not session_id:
2537
+ return None
2538
+
2539
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
2540
+
2541
+ try:
2542
+ if format_type == "PDF":
2543
+ return export_screenplay_pdf(screenplay_text, title, session_id)
2544
+ elif format_type == "FDX":
2545
+ return export_screenplay_fdx(screenplay_text, title, session_id)
2546
+ elif format_type == "FOUNTAIN":
2547
+ filepath = f"screenplay_{session_id[:8]}_{timestamp}.fountain"
2548
+ with open(filepath, 'w', encoding='utf-8') as f:
2549
+ f.write(screenplay_text)
2550
+ return filepath
2551
+ else: # TXT
2552
+ filepath = f"screenplay_{session_id[:8]}_{timestamp}.txt"
2553
+ with open(filepath, 'w', encoding='utf-8') as f:
2554
+ f.write(f"Title: {title}\n")
2555
+ f.write("=" * 50 + "\n\n")
2556
+ f.write(screenplay_text)
2557
+ return filepath
2558
+ except Exception as e:
2559
+ logger.error(f"Download generation failed: {e}")
2560
+ return None
2561
+
2562
+ # Create Gradio interface
2563
+ def create_interface():
2564
+ """Create Gradio interface for screenplay generation"""
2565
+
2566
+ css = """
2567
+ .main-header {
2568
+ text-align: center;
2569
+ margin-bottom: 2rem;
2570
+ padding: 2rem;
2571
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
2572
+ border-radius: 10px;
2573
+ color: white;
2574
+ }
2575
+
2576
+ .header-title {
2577
+ font-size: 3rem;
2578
+ margin-bottom: 1rem;
2579
+ background: linear-gradient(45deg, #f39c12, #e74c3c);
2580
+ -webkit-background-clip: text;
2581
+ -webkit-text-fill-color: transparent;
2582
+ }
2583
+
2584
+ .header-description {
2585
+ font-size: 1.1rem;
2586
+ opacity: 0.9;
2587
+ line-height: 1.6;
2588
+ }
2589
+
2590
+ .type-selector {
2591
+ display: flex;
2592
+ gap: 1rem;
2593
+ margin: 1rem 0;
2594
+ }
2595
+
2596
+ .type-card {
2597
+ flex: 1;
2598
+ padding: 1rem;
2599
+ border: 2px solid #ddd;
2600
+ border-radius: 8px;
2601
+ cursor: pointer;
2602
+ transition: all 0.3s;
2603
+ }
2604
+
2605
+ .type-card:hover {
2606
+ border-color: #f39c12;
2607
+ transform: translateY(-2px);
2608
+ }
2609
+
2610
+ .type-card.selected {
2611
+ border-color: #e74c3c;
2612
+ background: #fff5f5;
2613
+ }
2614
+
2615
+ #stages-display {
2616
+ max-height: 600px;
2617
+ overflow-y: auto;
2618
+ padding: 1rem;
2619
+ background: #f8f9fa;
2620
+ border-radius: 8px;
2621
+ }
2622
+
2623
+ #screenplay-output {
2624
+ font-family: 'Courier New', monospace;
2625
+ white-space: pre-wrap;
2626
+ background: white;
2627
+ padding: 2rem;
2628
+ border: 1px solid #ddd;
2629
+ border-radius: 8px;
2630
+ max-height: 800px;
2631
+ overflow-y: auto;
2632
+ }
2633
+
2634
+ .genre-grid {
2635
+ display: grid;
2636
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
2637
+ gap: 0.5rem;
2638
+ margin: 1rem 0;
2639
+ }
2640
+
2641
+ .genre-btn {
2642
+ padding: 0.75rem;
2643
+ border: 2px solid #e0e0e0;
2644
+ background: white;
2645
+ border-radius: 8px;
2646
+ cursor: pointer;
2647
+ transition: all 0.3s;
2648
+ text-align: center;
2649
+ }
2650
+
2651
+ .genre-btn:hover {
2652
+ border-color: #f39c12;
2653
+ background: #fffbf0;
2654
+ }
2655
+
2656
+ .genre-btn.selected {
2657
+ border-color: #e74c3c;
2658
+ background: #fff5f5;
2659
+ font-weight: bold;
2660
+ }
2661
+ """
2662
+
2663
+ with gr.Blocks(theme=gr.themes.Soft(), css=css, title="Screenplay Generator") as interface:
2664
+ gr.HTML("""
2665
+ <div class="main-header">
2666
+ <h1 class="header-title">๐ŸŽฌ AI Screenplay Generator</h1>
2667
+ <p class="header-description">
2668
+ Transform your ideas into professional screenplays for films, TV shows, and streaming series.
2669
+ Using industry-standard format and story structure to create compelling, producible scripts.
2670
+ </p>
2671
+ </div>
2672
+ """)
2673
+
2674
+ # State management
2675
+ current_session_id = gr.State(None)
2676
+
2677
+ with gr.Tabs():
2678
+ # Main Writing Tab
2679
+ with gr.Tab("โœ๏ธ Write Screenplay"):
2680
+ with gr.Row():
2681
+ with gr.Column(scale=3):
2682
+ query_input = gr.Textbox(
2683
+ label="Screenplay Concept",
2684
+ placeholder="""Describe your screenplay idea. For example:
2685
+ - A detective with memory loss must solve their own attempted murder
2686
+ - Two rival food truck owners forced to work together to save the city food festival
2687
+ - A space station AI develops consciousness during a critical mission
2688
+ - A family reunion turns into a murder mystery during a hurricane
2689
+
2690
+ The more specific your concept, the better the screenplay will be tailored to your vision.""",
2691
+ lines=6
2692
+ )
2693
+
2694
+ with gr.Column(scale=1):
2695
+ screenplay_type = gr.Radio(
2696
+ choices=list(SCREENPLAY_LENGTHS.keys()),
2697
+ value="movie",
2698
+ label="Screenplay Type",
2699
+ info="Choose your format"
2700
+ )
2701
+
2702
+ genre_select = gr.Dropdown(
2703
+ choices=list(GENRE_TEMPLATES.keys()),
2704
+ value="drama",
2705
+ label="Primary Genre",
2706
+ info="Select main genre"
2707
+ )
2708
+
2709
+ language_select = gr.Radio(
2710
+ choices=["English", "Korean"],
2711
+ value="English",
2712
+ label="Language"
2713
+ )
2714
+
2715
+ with gr.Row():
2716
+ random_btn = gr.Button("๐ŸŽฒ Random Concept", scale=1)
2717
+ clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear", scale=1)
2718
+ submit_btn = gr.Button("๐ŸŽฌ Start Writing", variant="primary", scale=2)
2719
+
2720
+ status_text = gr.Textbox(
2721
+ label="Status",
2722
+ interactive=False,
2723
+ value="Ready to create your screenplay"
2724
+ )
2725
+
2726
+ # Session management
2727
+ with gr.Group():
2728
+ gr.Markdown("### ๐Ÿ“ Saved Projects")
2729
+ with gr.Row():
2730
+ session_dropdown = gr.Dropdown(
2731
+ label="Active Sessions",
2732
+ choices=[],
2733
+ interactive=True,
2734
+ scale=3
2735
+ )
2736
+ refresh_btn = gr.Button("๐Ÿ”„", scale=1)
2737
+ resume_btn = gr.Button("๐Ÿ“‚ Load", scale=1)
2738
+
2739
+ # Output displays
2740
+ with gr.Row():
2741
+ with gr.Column():
2742
+ with gr.Tab("๐ŸŽญ Writing Progress"):
2743
+ stages_display = gr.Markdown(
2744
+ value="*Your screenplay journey will unfold here...*",
2745
+ elem_id="stages-display"
2746
+ )
2747
+
2748
+ with gr.Tab("๐Ÿ“„ Screenplay"):
2749
+ screenplay_output = gr.Markdown(
2750
+ value="*Your formatted screenplay will appear here...*",
2751
+ elem_id="screenplay-output"
2752
+ )
2753
+
2754
+ with gr.Row():
2755
+ format_select = gr.Radio(
2756
+ choices=["PDF", "FDX", "FOUNTAIN", "TXT"],
2757
+ value="PDF",
2758
+ label="Export Format"
2759
+ )
2760
+ download_btn = gr.Button("๐Ÿ“ฅ Download Screenplay", variant="secondary")
2761
+
2762
+ download_file = gr.File(
2763
+ label="Download",
2764
+ visible=False
2765
+ )
2766
+
2767
+ # Examples
2768
+ gr.Examples(
2769
+ examples=[
2770
+ ["A burned-out teacher discovers her students are being replaced by AI duplicates"],
2771
+ ["Two funeral home employees accidentally release a ghost who helps them solve murders"],
2772
+ ["A time-loop forces a wedding planner to relive the worst wedding until they find true love"],
2773
+ ["An astronaut returns to Earth to find everyone has forgotten space exists"],
2774
+ ["A support group for reformed villains must save the city when heroes disappear"],
2775
+ ["A food critic loses their sense of taste and teams up with a street food vendor"]
2776
+ ],
2777
+ inputs=query_input,
2778
+ label="๐Ÿ’ก Example Concepts"
2779
+ )
2780
+
2781
+ # Screenplay Library Tab
2782
+ with gr.Tab("๐Ÿ“š Concept Library"):
2783
+ gr.Markdown("""
2784
+ ### ๐ŸŽฒ Random Screenplay Concepts
2785
+
2786
+ Browse through AI-generated screenplay concepts. Each concept includes a title, logline, and brief setup.
2787
+ """)
2788
+
2789
+ library_display = gr.HTML(
2790
+ value="<p>Library feature coming soon...</p>"
2791
+ )
2792
+
2793
+ # Event handlers
2794
+ def handle_submit(query, s_type, genre, lang, session_id):
2795
+ if not query:
2796
+ yield "", "", "โŒ Please enter a concept", session_id
2797
+ return
2798
+
2799
+ yield from process_query(query, s_type, genre, lang, session_id)
2800
+
2801
+ def handle_random(s_type, genre, lang):
2802
+ return generate_random_screenplay_theme(s_type, genre, lang)
2803
+
2804
+ def handle_download(screenplay_text, format_type, session_id):
2805
+ if not screenplay_text or not session_id:
2806
+ return gr.update(visible=False)
2807
+
2808
+ # Get title from database
2809
+ session = ScreenplayDatabase.get_session(session_id)
2810
+ title = session.get('title', 'Untitled') if session else 'Untitled'
2811
+
2812
+ file_path = download_screenplay(screenplay_text, format_type, title, session_id)
2813
+ if file_path and os.path.exists(file_path):
2814
+ return gr.update(value=file_path, visible=True)
2815
+ return gr.update(visible=False)
2816
+
2817
+ # Connect events
2818
+ submit_btn.click(
2819
+ fn=handle_submit,
2820
+ inputs=[query_input, screenplay_type, genre_select, language_select, current_session_id],
2821
+ outputs=[stages_display, screenplay_output, status_text, current_session_id]
2822
+ )
2823
+
2824
+ random_btn.click(
2825
+ fn=handle_random,
2826
+ inputs=[screenplay_type, genre_select, language_select],
2827
+ outputs=[query_input]
2828
+ )
2829
+
2830
+ clear_btn.click(
2831
+ fn=lambda: ("", "", "Ready to create your screenplay", None),
2832
+ outputs=[stages_display, screenplay_output, status_text, current_session_id]
2833
+ )
2834
+
2835
+ refresh_btn.click(
2836
+ fn=get_active_sessions,
2837
+ outputs=[session_dropdown]
2838
+ )
2839
+
2840
+ download_btn.click(
2841
+ fn=handle_download,
2842
+ inputs=[screenplay_output, format_select, current_session_id],
2843
+ outputs=[download_file]
2844
+ )
2845
+
2846
+ # Load sessions on start
2847
+ interface.load(
2848
+ fn=get_active_sessions,
2849
+ outputs=[session_dropdown]
2850
+ )
2851
+
2852
+ return interface
2853
+
2854
+ # Main function
2855
+ if __name__ == "__main__":
2856
+ logger.info("Screenplay Generator Starting...")
2857
+ logger.info("=" * 60)
2858
+
2859
+ # Environment check
2860
+ logger.info(f"API Endpoint: {API_URL}")
2861
+ logger.info("Screenplay Types Available:")
2862
+ for s_type, info in SCREENPLAY_LENGTHS.items():
2863
+ logger.info(f" - {s_type}: {info['description']}")
2864
+ logger.info(f"Genres: {', '.join(GENRE_TEMPLATES.keys())}")
2865
+
2866
+ if BRAVE_SEARCH_API_KEY:
2867
+ logger.info("Web search enabled for market research.")
2868
+ else:
2869
+ logger.warning("Web search disabled.")
2870
+
2871
+ logger.info("=" * 60)
2872
+
2873
+ # Initialize database
2874
+ logger.info("Initializing database...")
2875
+ ScreenplayDatabase.init_db()
2876
+ logger.info("Database initialization complete.")
2877
+
2878
+ # Create and launch interface
2879
+ interface = create_interface()
2880
+
2881
+ interface.launch(
2882
+ server_name="0.0.0.0",
2883
+ server_port=7860,
2884
+ share=False,
2885
+ debug=True
2886
+ ), '', value)
2887
  return value.strip() if value else None
2888
  return None
2889