Spaces:
Running
Running
Update app-backup.py
Browse files- app-backup.py +327 -380
app-backup.py
CHANGED
@@ -197,7 +197,6 @@ class SceneBreakdown:
|
|
197 |
class CharacterProfile:
|
198 |
"""Detailed character profile"""
|
199 |
name: str
|
200 |
-
age: int
|
201 |
role: str # protagonist, antagonist, supporting, etc.
|
202 |
archetype: str
|
203 |
want: str # External goal
|
@@ -537,18 +536,6 @@ class ScreenplayDatabase:
|
|
537 |
|
538 |
return theme_id
|
539 |
|
540 |
-
@staticmethod
|
541 |
-
def get_stages(session_id: str) -> List[Dict]:
|
542 |
-
"""Get all stages for a session"""
|
543 |
-
with ScreenplayDatabase.get_db() as conn:
|
544 |
-
rows = conn.cursor().execute(
|
545 |
-
'''SELECT * FROM screenplay_stages
|
546 |
-
WHERE session_id = ?
|
547 |
-
ORDER BY stage_number''',
|
548 |
-
(session_id,)
|
549 |
-
).fetchall()
|
550 |
-
return [dict(row) for row in rows]
|
551 |
-
|
552 |
class WebSearchIntegration:
|
553 |
"""Web search functionality for screenplay research"""
|
554 |
def __init__(self):
|
@@ -878,7 +865,7 @@ Create specific, emotionally resonant story."""
|
|
878 |
**ํ์ ์บ๋ฆญํฐ ํ๋กํ:**
|
879 |
|
880 |
1. **์ฃผ์ธ๊ณต (PROTAGONIST)**
|
881 |
-
-
|
882 |
- ์ง์
/์ญํ :
|
883 |
- ์บ๋ฆญํฐ ์ํฌํ์
:
|
884 |
- WANT (์ธ์ ๋ชฉํ):
|
@@ -891,7 +878,7 @@ Create specific, emotionally resonant story."""
|
|
891 |
- ์บ๋ฆญํฐ ์ํฌ (AโB):
|
892 |
|
893 |
2. **์ ๋์ (ANTAGONIST)**
|
894 |
-
-
|
895 |
- ์ง์
/์ญํ :
|
896 |
- ์
์ญ ์ํฌํ์
:
|
897 |
- ๋ชฉํ & ๋๊ธฐ:
|
@@ -916,7 +903,7 @@ Create specific, emotionally resonant story."""
|
|
916 |
|
917 |
5. **์บ์คํ
์ ์**
|
918 |
- ๊ฐ ์ฃผ์ ์บ๋ฆญํฐ๋ณ ์ด์์ ์ธ ๋ฐฐ์ฐ ํ์
|
919 |
-
-
|
920 |
|
921 |
6. **๋ํ ์ํ**
|
922 |
- ๊ฐ ์ฃผ์ ์บ๋ฆญํฐ์ ์๊ทธ๋์ฒ ๋์ฌ 2-3๊ฐ
|
@@ -935,7 +922,7 @@ Create specific, emotionally resonant story."""
|
|
935 |
**Required Character Profiles:**
|
936 |
|
937 |
1. **PROTAGONIST**
|
938 |
-
- Name
|
939 |
- Occupation/Role:
|
940 |
- Character Archetype:
|
941 |
- WANT (External Goal):
|
@@ -948,7 +935,7 @@ Create specific, emotionally resonant story."""
|
|
948 |
- Character Arc (AโB):
|
949 |
|
950 |
2. **ANTAGONIST**
|
951 |
-
- Name
|
952 |
- Occupation/Role:
|
953 |
- Villain Archetype:
|
954 |
- Goal & Motivation:
|
@@ -973,7 +960,7 @@ Create specific, emotionally resonant story."""
|
|
973 |
|
974 |
5. **CASTING SUGGESTIONS**
|
975 |
- Ideal actor type for each major character
|
976 |
-
-
|
977 |
|
978 |
6. **DIALOGUE SAMPLES**
|
979 |
- 2-3 signature lines per major character
|
@@ -1104,7 +1091,7 @@ Plan each scene to advance story and develop character."""
|
|
1104 |
- ๊ฐ์ ์ ํ๋์ผ๋ก ํํ
|
1105 |
|
1106 |
3. **์บ๋ฆญํฐ ์๊ฐ**
|
1107 |
-
์ฒซ ๋ฑ์ฅ์:
|
1108 |
|
1109 |
4. **๋ํ**
|
1110 |
์บ๋ฆญํฐ๋ช
|
@@ -1151,7 +1138,7 @@ Plan each scene to advance story and develop character."""
|
|
1151 |
- Emotions through actions
|
1152 |
|
1153 |
3. **Character Intros**
|
1154 |
-
First appearance: NAME
|
1155 |
|
1156 |
4. **Dialogue**
|
1157 |
CHARACTER NAME
|
@@ -1482,8 +1469,6 @@ Provide specific solutions for each issue."""
|
|
1482 |
raise Exception(f"LLM Call Failed: {full_content}")
|
1483 |
return full_content
|
1484 |
|
1485 |
-
|
1486 |
-
|
1487 |
def call_llm_streaming(self, messages: List[Dict[str, str]], role: str,
|
1488 |
language: str) -> Generator[str, None, None]:
|
1489 |
try:
|
@@ -1604,7 +1589,6 @@ Provide specific solutions for each issue."""
|
|
1604 |
yield buffer
|
1605 |
buffer = ""
|
1606 |
time.sleep(0.01)
|
1607 |
-
|
1608 |
except Exception as e:
|
1609 |
logger.error(f"Error processing line {line_count}: {str(e)}")
|
1610 |
logger.debug(f"Problematic line: {line_str[:100] if 'line_str' in locals() else 'N/A'}")
|
@@ -1634,8 +1618,6 @@ Provide specific solutions for each issue."""
|
|
1634 |
logger.error(traceback.format_exc())
|
1635 |
yield f"โ Unexpected error: {str(e)}"
|
1636 |
|
1637 |
-
|
1638 |
-
|
1639 |
def get_system_prompts(self, language: str) -> Dict[str, str]:
|
1640 |
"""Role-specific system prompts"""
|
1641 |
|
@@ -1726,9 +1708,7 @@ You provide feedback that's critical yet encouraging."""
|
|
1726 |
|
1727 |
return base_prompts.get(language, base_prompts["English"])
|
1728 |
|
1729 |
-
|
1730 |
-
|
1731 |
-
# --- Main process ---
|
1732 |
def process_screenplay_stream(self, query: str, screenplay_type: str, genre: str,
|
1733 |
language: str, session_id: Optional[str] = None
|
1734 |
) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
|
@@ -1889,279 +1869,296 @@ You provide feedback that's critical yet encouraging."""
|
|
1889 |
return "\n\n---\n\n".join(previous) if previous else ""
|
1890 |
|
1891 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1892 |
def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
|
1893 |
"""Parse character profile from content"""
|
1894 |
-
# Debug logging
|
1895 |
logger.debug(f"Parsing character profile for role: {role}")
|
1896 |
logger.debug(f"Content preview: {content[:200]}...")
|
1897 |
-
|
1898 |
-
#
|
1899 |
-
name = f"Character_{role}" #
|
1900 |
name_patterns = [
|
1901 |
-
r'(?:์ด๋ฆ|Name)[:\s]*([
|
1902 |
-
r'^\s*[-*โข]\s*([
|
1903 |
-
r'^([
|
1904 |
]
|
1905 |
-
|
1906 |
-
|
1907 |
-
|
1908 |
-
|
1909 |
-
|
1910 |
-
|
1911 |
-
|
1912 |
-
|
1913 |
-
|
1914 |
-
|
1915 |
-
|
1916 |
-
|
1917 |
-
|
1918 |
-
|
1919 |
-
|
1920 |
-
|
1921 |
-
|
1922 |
-
r',\s*(\d+)\s*[,\s]',
|
1923 |
-
r'\((\d+)\)',
|
1924 |
-
r'Age[:\s]*(\d+)',
|
1925 |
-
r'๋์ด[:\s]*(\d+)'
|
1926 |
-
]
|
1927 |
-
|
1928 |
-
for pattern in age_patterns:
|
1929 |
-
age_match = re.search(pattern, content, re.IGNORECASE)
|
1930 |
-
if age_match:
|
1931 |
-
try:
|
1932 |
-
extracted_age = int(age_match.group(1))
|
1933 |
-
if 10 <= extracted_age <= 100: # Reasonable age range
|
1934 |
-
age = extracted_age
|
1935 |
-
logger.debug(f"Extracted age: {age}")
|
1936 |
-
break
|
1937 |
-
except ValueError:
|
1938 |
-
continue
|
1939 |
-
|
1940 |
-
# Helper function to extract clean fields
|
1941 |
-
def extract_clean_field(patterns):
|
1942 |
-
if isinstance(patterns, str):
|
1943 |
-
patterns = [patterns]
|
1944 |
-
|
1945 |
-
for pattern in patterns:
|
1946 |
-
match = re.search(rf'{pattern}[:\s]*([^\n*]+?)(?=\n|$)', content, re.IGNORECASE | re.DOTALL)
|
1947 |
-
if match:
|
1948 |
-
value = match.group(1).strip()
|
1949 |
-
# Clean up the value
|
1950 |
-
value = re.sub(r'^[-*โข:\s]+', '', value)
|
1951 |
-
value = re.sub(r'[*]+', '', value)
|
1952 |
-
value = re.sub(r'\s+', ' ', value)
|
1953 |
-
if value:
|
1954 |
-
return value
|
1955 |
return ""
|
1956 |
-
|
1957 |
-
#
|
1958 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1959 |
name=name,
|
1960 |
-
age=age,
|
1961 |
role=role,
|
1962 |
-
archetype=extract_clean_field(
|
1963 |
-
r"์บ๋ฆญํฐ ์ํฌํ์
",
|
1964 |
-
|
1965 |
-
|
1966 |
-
r"
|
1967 |
-
|
1968 |
-
|
1969 |
-
r"
|
1970 |
-
|
1971 |
-
|
1972 |
-
r"
|
1973 |
-
|
1974 |
-
|
1975 |
-
|
1976 |
-
r"
|
1977 |
-
|
1978 |
-
|
1979 |
-
|
1980 |
-
|
1981 |
-
r"๋ฐฑ์คํ ๋ฆฌ",
|
1982 |
-
r"Backstory",
|
1983 |
-
r"ํต์ฌ ์์ฒ",
|
1984 |
-
r"Core Wound"
|
1985 |
-
]),
|
1986 |
-
personality=self._extract_personality_traits(content),
|
1987 |
-
speech_pattern=extract_clean_field([
|
1988 |
-
r"๋งํฌ.*?ํจํด",
|
1989 |
-
r"Speech Pattern",
|
1990 |
-
r"์ธ์ด ํจํด",
|
1991 |
-
r"๋งํฌ"
|
1992 |
-
]),
|
1993 |
-
character_arc=extract_clean_field([
|
1994 |
-
r"์บ๋ฆญํฐ ์ํฌ",
|
1995 |
-
r"Character Arc",
|
1996 |
-
r"Arc",
|
1997 |
-
r"๋ณํ"
|
1998 |
-
])
|
1999 |
)
|
2000 |
-
|
2001 |
-
|
2002 |
-
return profile
|
2003 |
-
|
2004 |
-
def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
|
2005 |
-
"""Extract field value from content with improved parsing"""
|
2006 |
-
# Use word boundary to prevent partial matches
|
2007 |
-
pattern = rf'\b{field_pattern}\b[:\s]*([^\n]+?)(?=\n|$)'
|
2008 |
-
match = re.search(pattern, content, re.IGNORECASE)
|
2009 |
-
if match:
|
2010 |
-
value = match.group(1).strip()
|
2011 |
-
# Remove markdown formatting
|
2012 |
-
value = re.sub(r'\*+', '', value)
|
2013 |
-
# Remove list markers
|
2014 |
-
value = re.sub(r'^\s*[-โข*]\s*', '', value)
|
2015 |
-
# Remove trailing punctuation
|
2016 |
-
value = re.sub(r'[,.:;]$', '', value)
|
2017 |
-
return value.strip()
|
2018 |
-
return None
|
2019 |
|
2020 |
def _extract_personality_traits(self, content: str) -> List[str]:
|
2021 |
-
|
2022 |
-
|
2023 |
-
|
2024 |
-
|
2025 |
-
|
2026 |
-
|
2027 |
-
|
2028 |
-
|
2029 |
-
|
2030 |
-
|
2031 |
-
|
2032 |
-
|
2033 |
-
|
2034 |
-
|
2035 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2036 |
|
2037 |
def _process_character_content(self, content: str):
|
2038 |
-
|
2039 |
-
|
2040 |
-
|
2041 |
-
|
2042 |
-
|
2043 |
-
|
2044 |
-
|
2045 |
-
|
2046 |
-
|
2047 |
-
|
2048 |
-
|
2049 |
-
|
2050 |
-
|
2051 |
-
|
2052 |
-
|
2053 |
-
|
2054 |
-
|
2055 |
-
|
2056 |
-
|
2057 |
-
|
2058 |
-
|
2059 |
-
|
2060 |
-
|
2061 |
-
|
2062 |
-
|
2063 |
-
|
2064 |
-
|
2065 |
-
|
2066 |
-
|
2067 |
-
|
2068 |
-
|
2069 |
-
|
2070 |
-
|
2071 |
-
|
2072 |
-
|
2073 |
-
|
2074 |
-
|
2075 |
-
|
2076 |
-
|
2077 |
-
|
2078 |
-
|
2079 |
-
|
2080 |
-
|
2081 |
-
|
2082 |
-
|
2083 |
-
|
2084 |
-
|
2085 |
-
|
2086 |
-
|
2087 |
-
|
2088 |
-
|
2089 |
-
|
2090 |
-
|
2091 |
-
|
2092 |
-
|
2093 |
-
|
|
|
2094 |
|
2095 |
def _extract_section(self, content: str, section_pattern: str) -> str:
|
2096 |
-
|
2097 |
-
|
2098 |
-
|
2099 |
-
|
2100 |
-
|
2101 |
-
|
2102 |
-
|
2103 |
-
|
2104 |
-
|
2105 |
-
|
2106 |
-
|
2107 |
-
|
2108 |
-
|
2109 |
-
|
2110 |
-
|
2111 |
-
|
2112 |
-
|
2113 |
-
|
2114 |
-
|
2115 |
-
title_patterns = [
|
2116 |
-
r'(?:TITLE|์ ๋ชฉ)[:\s]*\*?\*?([^\n*]+)\*?\*?',
|
2117 |
-
r'\*\*(?:TITLE|์ ๋ชฉ)\*\*[:\s]*([^\n]+)',
|
2118 |
-
r'Title[:\s]*([^\n]+)'
|
2119 |
-
]
|
2120 |
-
|
2121 |
-
for pattern in title_patterns:
|
2122 |
-
title_match = re.search(pattern, content, re.IGNORECASE)
|
2123 |
-
if title_match:
|
2124 |
-
self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
|
2125 |
-
break
|
2126 |
-
|
2127 |
-
# Extract logline with various formats
|
2128 |
-
logline_patterns = [
|
2129 |
-
r'(?:LOGLINE|๋ก๊ทธ๋ผ์ธ)[:\s]*\*?\*?([^\n]+)',
|
2130 |
-
r'\*\*(?:LOGLINE|๋ก๊ทธ๋ผ์ธ)\*\*[:\s]*([^\n]+)',
|
2131 |
-
r'Logline[:\s]*([^\n]+)'
|
2132 |
-
]
|
2133 |
-
|
2134 |
-
for pattern in logline_patterns:
|
2135 |
-
logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2136 |
-
if logline_match:
|
2137 |
-
# Get full logline (might be multi-line)
|
2138 |
-
logline_text = logline_match.group(1).strip()
|
2139 |
-
# Continue reading if it's incomplete
|
2140 |
-
if not logline_text.endswith('.'):
|
2141 |
-
next_lines = content[logline_match.end():].split('\n')
|
2142 |
-
for line in next_lines[:3]: # Check next 3 lines
|
2143 |
-
if line.strip() and not re.match(r'^[A-Z๊ฐ-ํฃ\d]', line.strip()):
|
2144 |
-
logline_text += ' ' + line.strip()
|
2145 |
-
else:
|
2146 |
-
break
|
2147 |
-
self.screenplay_tracker.screenplay_bible.logline = logline_text
|
2148 |
-
break
|
2149 |
-
|
2150 |
-
# Extract genre
|
2151 |
-
genre_match = re.search(r'(?:Primary Genre|์ฃผ ์ฅ๋ฅด)[:\s]*([^\n]+)', content, re.IGNORECASE)
|
2152 |
-
if genre_match:
|
2153 |
-
self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
|
2154 |
-
|
2155 |
-
# Save to database
|
2156 |
-
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2157 |
-
self.screenplay_tracker.screenplay_bible)
|
2158 |
-
|
2159 |
-
except Exception as e:
|
2160 |
-
logger.error(f"Error processing producer content: {e}")
|
2161 |
-
|
2162 |
-
|
2163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2165 |
|
2166 |
def _process_story_content(self, content: str):
|
2167 |
"""Process story developer output"""
|
@@ -2176,22 +2173,6 @@ You provide feedback that's critical yet encouraging."""
|
|
2176 |
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2177 |
self.screenplay_tracker.screenplay_bible)
|
2178 |
|
2179 |
-
def _process_character_content(self, content: str):
|
2180 |
-
"""Process character designer output"""
|
2181 |
-
# Extract protagonist
|
2182 |
-
protagonist_section = self._extract_section(content, "PROTAGONIST|์ฃผ์ธ๊ณต")
|
2183 |
-
if protagonist_section:
|
2184 |
-
protagonist = self._parse_character_profile(protagonist_section, "protagonist")
|
2185 |
-
self.screenplay_tracker.add_character(protagonist)
|
2186 |
-
ScreenplayDatabase.save_character(self.current_session_id, protagonist)
|
2187 |
-
|
2188 |
-
# Extract antagonist
|
2189 |
-
antagonist_section = self._extract_section(content, "ANTAGONIST|์ ๋์")
|
2190 |
-
if antagonist_section:
|
2191 |
-
antagonist = self._parse_character_profile(antagonist_section, "antagonist")
|
2192 |
-
self.screenplay_tracker.add_character(antagonist)
|
2193 |
-
ScreenplayDatabase.save_character(self.current_session_id, antagonist)
|
2194 |
-
|
2195 |
def _process_scene_content(self, content: str):
|
2196 |
"""Process scene planner output"""
|
2197 |
# Parse scene breakdown
|
@@ -2220,46 +2201,6 @@ You provide feedback that's critical yet encouraging."""
|
|
2220 |
self.screenplay_tracker.add_scene(scene)
|
2221 |
ScreenplayDatabase.save_scene(self.current_session_id, scene)
|
2222 |
|
2223 |
-
def _extract_section(self, content: str, section_pattern: str) -> str:
|
2224 |
-
"""Extract section from content"""
|
2225 |
-
pattern = rf'(?:{section_pattern})[:\s]*(.+?)(?=\n(?:[A-Z]{{2,}}|[๊ฐ-ํฃ]{{2,}}):|\Z)'
|
2226 |
-
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2227 |
-
return match.group(1).strip() if match else ""
|
2228 |
-
|
2229 |
-
def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
|
2230 |
-
"""Parse character profile from content"""
|
2231 |
-
# Extract character details using regex or string parsing
|
2232 |
-
name = self._extract_field(content, "Name|์ด๋ฆ") or f"Character_{role}"
|
2233 |
-
age = int(self._extract_field(content, "Age|๋์ด") or "30")
|
2234 |
-
|
2235 |
-
return CharacterProfile(
|
2236 |
-
name=name,
|
2237 |
-
age=age,
|
2238 |
-
role=role,
|
2239 |
-
archetype=self._extract_field(content, "Archetype|์ํฌํ์
") or "",
|
2240 |
-
want=self._extract_field(content, "WANT|์ธ์ ๋ชฉํ") or "",
|
2241 |
-
need=self._extract_field(content, "NEED|๋ด์ ํ์") or "",
|
2242 |
-
backstory=self._extract_field(content, "Backstory|๋ฐฑ์คํ ๋ฆฌ") or "",
|
2243 |
-
personality=[], # Would be parsed from content
|
2244 |
-
speech_pattern=self._extract_field(content, "Speech|๋งํฌ") or "",
|
2245 |
-
character_arc=self._extract_field(content, "Arc|์ํฌ") or ""
|
2246 |
-
)
|
2247 |
-
|
2248 |
-
def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
|
2249 |
-
"""Extract field value from content with improved parsing"""
|
2250 |
-
# More flexible pattern that handles various formats
|
2251 |
-
pattern = rf'{field_pattern}(.+?)(?=\n[A-Z๊ฐ-ํฃ]|$)'
|
2252 |
-
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2253 |
-
if match:
|
2254 |
-
value = match.group(1).strip()
|
2255 |
-
# Remove markdown formatting if present
|
2256 |
-
value = re.sub(r'\*\*', '', value)
|
2257 |
-
value = re.sub(r'^\s*[-โข]\s*', '', value)
|
2258 |
-
# Remove trailing punctuation
|
2259 |
-
value = re.sub(r'[,.:;]$', '', value)
|
2260 |
-
return value.strip()
|
2261 |
-
return None
|
2262 |
-
|
2263 |
# --- Utility functions ---
|
2264 |
def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
|
2265 |
"""Generate random screenplay theme"""
|
@@ -2441,46 +2382,52 @@ def extract_logline_from_theme(theme_text: str) -> str:
|
|
2441 |
match = re.search(r'\*\*(?:Logline|๋ก๊ทธ๋ผ์ธ):\*\*\s*(.+)', theme_text, re.IGNORECASE)
|
2442 |
return match.group(1).strip() if match else ""
|
2443 |
|
|
|
|
|
2444 |
def format_screenplay_display(screenplay_text: str) -> str:
|
2445 |
-
|
2446 |
-
|
2447 |
-
|
2448 |
-
|
2449 |
-
|
2450 |
-
|
2451 |
-
|
2452 |
-
|
2453 |
-
|
2454 |
-
|
2455 |
-
|
2456 |
-
|
2457 |
-
|
2458 |
-
|
2459 |
-
|
2460 |
-
|
2461 |
-
|
2462 |
-
|
2463 |
-
|
2464 |
-
|
2465 |
-
|
2466 |
-
|
2467 |
-
|
2468 |
-
|
2469 |
-
|
2470 |
-
|
2471 |
-
|
2472 |
-
|
2473 |
-
|
2474 |
-
|
2475 |
-
|
2476 |
-
|
2477 |
-
|
2478 |
-
|
2479 |
-
|
2480 |
-
|
2481 |
-
|
2482 |
-
|
2483 |
-
|
|
|
|
|
|
|
|
|
2484 |
|
2485 |
def format_stages_display(stages: List[Dict]) -> str:
|
2486 |
"""Format stages display for screenplay"""
|
@@ -2926,4 +2873,4 @@ if __name__ == "__main__":
|
|
2926 |
server_port=7860,
|
2927 |
share=False,
|
2928 |
debug=True
|
2929 |
-
)
|
|
|
197 |
class CharacterProfile:
|
198 |
"""Detailed character profile"""
|
199 |
name: str
|
|
|
200 |
role: str # protagonist, antagonist, supporting, etc.
|
201 |
archetype: str
|
202 |
want: str # External goal
|
|
|
536 |
|
537 |
return theme_id
|
538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
539 |
class WebSearchIntegration:
|
540 |
"""Web search functionality for screenplay research"""
|
541 |
def __init__(self):
|
|
|
865 |
**ํ์ ์บ๋ฆญํฐ ํ๋กํ:**
|
866 |
|
867 |
1. **์ฃผ์ธ๊ณต (PROTAGONIST)**
|
868 |
+
- ์ด๋ฆ:
|
869 |
- ์ง์
/์ญํ :
|
870 |
- ์บ๋ฆญํฐ ์ํฌํ์
:
|
871 |
- WANT (์ธ์ ๋ชฉํ):
|
|
|
878 |
- ์บ๋ฆญํฐ ์ํฌ (AโB):
|
879 |
|
880 |
2. **์ ๋์ (ANTAGONIST)**
|
881 |
+
- ์ด๋ฆ:
|
882 |
- ์ง์
/์ญํ :
|
883 |
- ์
์ญ ์ํฌํ์
:
|
884 |
- ๋ชฉํ & ๋๊ธฐ:
|
|
|
903 |
|
904 |
5. **์บ์คํ
์ ์**
|
905 |
- ๊ฐ ์ฃผ์ ์บ๋ฆญํฐ๋ณ ์ด์์ ์ธ ๋ฐฐ์ฐ ํ์
|
906 |
+
- ์ธ๋ชจ, ์ฐ๊ธฐ ์คํ์ผ
|
907 |
|
908 |
6. **๋ํ ์ํ**
|
909 |
- ๊ฐ ์ฃผ์ ์บ๋ฆญํฐ์ ์๊ทธ๋์ฒ ๋์ฌ 2-3๊ฐ
|
|
|
922 |
**Required Character Profiles:**
|
923 |
|
924 |
1. **PROTAGONIST**
|
925 |
+
- Name:
|
926 |
- Occupation/Role:
|
927 |
- Character Archetype:
|
928 |
- WANT (External Goal):
|
|
|
935 |
- Character Arc (AโB):
|
936 |
|
937 |
2. **ANTAGONIST**
|
938 |
+
- Name:
|
939 |
- Occupation/Role:
|
940 |
- Villain Archetype:
|
941 |
- Goal & Motivation:
|
|
|
960 |
|
961 |
5. **CASTING SUGGESTIONS**
|
962 |
- Ideal actor type for each major character
|
963 |
+
- Appearance, acting style
|
964 |
|
965 |
6. **DIALOGUE SAMPLES**
|
966 |
- 2-3 signature lines per major character
|
|
|
1091 |
- ๊ฐ์ ์ ํ๋์ผ๋ก ํํ
|
1092 |
|
1093 |
3. **์บ๋ฆญํฐ ์๊ฐ**
|
1094 |
+
์ฒซ ๋ฑ์ฅ์: ์ด๋ฆ๊ณผ ๊ฐ๋จํ ๋ฌ์ฌ
|
1095 |
|
1096 |
4. **๋ํ**
|
1097 |
์บ๋ฆญํฐ๋ช
|
|
|
1138 |
- Emotions through actions
|
1139 |
|
1140 |
3. **Character Intros**
|
1141 |
+
First appearance: NAME with brief description
|
1142 |
|
1143 |
4. **Dialogue**
|
1144 |
CHARACTER NAME
|
|
|
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:
|
|
|
1589 |
yield buffer
|
1590 |
buffer = ""
|
1591 |
time.sleep(0.01)
|
|
|
1592 |
except Exception as e:
|
1593 |
logger.error(f"Error processing line {line_count}: {str(e)}")
|
1594 |
logger.debug(f"Problematic line: {line_str[:100] if 'line_str' in locals() else 'N/A'}")
|
|
|
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]:
|
|
|
1869 |
return "\n\n---\n\n".join(previous) if previous else ""
|
1870 |
|
1871 |
|
1872 |
+
def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
|
1873 |
+
"""Extract field value from content with improved parsing"""
|
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):
|
1877 |
+
value = match.group(1).strip()
|
1878 |
+
value = re.sub(r'\*\*', '', value) # **bold** ์ ๊ฑฐ
|
1879 |
+
value = re.sub(r'^\s*[-โข]\s*', '', value) # ๊ธ๋จธ๋ฆฌํ ์ ๊ฑฐ
|
1880 |
+
value = re.sub(r'[,.:;]+$', '', value) # ํ ๋ ๊ตฌ๋์ ์ ๊ฑฐ
|
1881 |
+
return value.strip() if value else None
|
1882 |
+
return None
|
1883 |
+
|
1884 |
+
|
1885 |
+
|
1886 |
+
|
1887 |
def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
|
1888 |
"""Parse character profile from content"""
|
|
|
1889 |
logger.debug(f"Parsing character profile for role: {role}")
|
1890 |
logger.debug(f"Content preview: {content[:200]}...")
|
1891 |
+
|
1892 |
+
# 1) ์ด๋ฆ ์ถ์ถ โ ํจํด 3์ข
|
1893 |
+
name = f"Character_{role}" # fallback
|
1894 |
name_patterns = [
|
1895 |
+
r'(?:์ด๋ฆ|Name)[:\s]*([^\n,(]+)', # "์ด๋ฆ: ํ๊ธธ๋"
|
1896 |
+
r'^\s*[-*โข]\s*([^\n,(]+)', # "- ํ๊ธธ๋"
|
1897 |
+
r'^([^\n,(]+)' # ๋ฌธ๋จ ์ฒซ ๋จ์ด
|
1898 |
]
|
1899 |
+
for pat in name_patterns:
|
1900 |
+
m = re.search(pat, content, re.IGNORECASE | re.MULTILINE)
|
1901 |
+
if m and m.group(1).strip():
|
1902 |
+
name = re.sub(r'[\*:\s]+', '', m.group(1).strip()) # ๋ถํ์ ๊ธฐํธ ์ ๊ฑฐ
|
1903 |
+
break
|
1904 |
+
|
1905 |
+
# 2) ํ๋ ์ถ์ถ์ฉ ํฌํผ
|
1906 |
+
def extract_clean_field(pats):
|
1907 |
+
pats = [pats] if isinstance(pats, str) else pats
|
1908 |
+
for p in pats:
|
1909 |
+
m = re.search(rf'{p}[:\s]*([^\n*]+?)(?=\n|$)', content,
|
1910 |
+
re.IGNORECASE | re.DOTALL)
|
1911 |
+
if m and m.group(1).strip():
|
1912 |
+
v = m.group(1).strip()
|
1913 |
+
v = re.sub(r'^[-*โข:\s]+', '', v) # ๋ฆฌ์คํธยท๊ธฐํธ ์ ๊ฑฐ
|
1914 |
+
v = v.replace('*', '').strip()
|
1915 |
+
return v
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1916 |
return ""
|
1917 |
+
|
1918 |
+
# 3) Personality(์ฌ๋ฌ ์ค) ๋ฐ๋ก ํ์ฑ
|
1919 |
+
def extract_traits():
|
1920 |
+
section = re.search(r'(?:Personality|์ฑ๊ฒฉ[^\n]*)\n((?:[-*โข].+\n?)+)',
|
1921 |
+
content, re.IGNORECASE)
|
1922 |
+
if not section:
|
1923 |
+
return []
|
1924 |
+
traits = [
|
1925 |
+
re.sub(r'^[-*โข]\s*', '', line.strip())
|
1926 |
+
for line in section.group(1).splitlines() if line.strip()
|
1927 |
+
]
|
1928 |
+
return traits[:5]
|
1929 |
+
|
1930 |
+
# 4) CharacterProfile ์์ฑ
|
1931 |
+
return CharacterProfile(
|
1932 |
name=name,
|
|
|
1933 |
role=role,
|
1934 |
+
archetype=extract_clean_field(
|
1935 |
+
[r"์บ๋ฆญํฐ ์ํฌํ์
", r"Character Archetype", r"Archetype", r"์ํฌํ์
"]
|
1936 |
+
),
|
1937 |
+
want=extract_clean_field(
|
1938 |
+
[r"WANT\s*\(์ธ์ ๋ชฉํ\)", r"WANT", r"์ธ์ ๋ชฉํ", r"External Goal"]
|
1939 |
+
),
|
1940 |
+
need=extract_clean_field(
|
1941 |
+
[r"NEED\s*\(๋ด์ ํ์\)", r"NEED", r"๋ด์ ํ์", r"Internal Need"]
|
1942 |
+
),
|
1943 |
+
backstory=extract_clean_field(
|
1944 |
+
[r"๋ฐฑ์คํ ๋ฆฌ", r"Backstory", r"ํต์ฌ ์์ฒ", r"Core Wound"]
|
1945 |
+
),
|
1946 |
+
personality=extract_traits(),
|
1947 |
+
speech_pattern=extract_clean_field(
|
1948 |
+
[r"๋งํฌ.*?ํจํด", r"Speech Pattern", r"์ธ์ด ํจํด", r"๋งํฌ"]
|
1949 |
+
),
|
1950 |
+
character_arc=extract_clean_field(
|
1951 |
+
[r"์บ๋ฆญํฐ ์ํฌ", r"Character Arc", r"Arc", r"๋ณํ"]
|
1952 |
+
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1953 |
)
|
1954 |
+
|
1955 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1956 |
|
1957 |
def _extract_personality_traits(self, content: str) -> List[str]:
|
1958 |
+
"""Extract personality traits from content"""
|
1959 |
+
traits = []
|
1960 |
+
# Look for personality section with multiple pattern options
|
1961 |
+
personality_patterns = [
|
1962 |
+
r"(?:Personality|์ฑ๊ฒฉ ํน์ฑ|์ฑ๊ฒฉ)[:\s]*([^\n]+(?:\n(?![\w๊ฐ-ํฃ]+:)[^\n]+)*)",
|
1963 |
+
r"์ฑ๊ฒฉ[:\s]*(?:\n?[-โข*]\s*[^\n]+)+"
|
1964 |
+
]
|
1965 |
+
|
1966 |
+
for pattern in personality_patterns:
|
1967 |
+
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
1968 |
+
if match and match.group(1):
|
1969 |
+
personality_section = match.group(1)
|
1970 |
+
# Extract individual traits (usually listed)
|
1971 |
+
trait_lines = personality_section.split('\n')
|
1972 |
+
for line in trait_lines:
|
1973 |
+
line = line.strip()
|
1974 |
+
if line and not line.endswith(':'):
|
1975 |
+
# Remove list markers
|
1976 |
+
trait = re.sub(r'^\s*[-โข*]\s*', '', line)
|
1977 |
+
trait = re.sub(r'^\d+\.\s*', '', trait) # Remove numbered lists
|
1978 |
+
if trait and len(trait) > 2: # Skip very short entries
|
1979 |
+
traits.append(trait)
|
1980 |
+
if traits: # If we found traits, stop looking
|
1981 |
+
break
|
1982 |
+
|
1983 |
+
return traits[:5] # Limit to 5 traits
|
1984 |
|
1985 |
def _process_character_content(self, content: str):
|
1986 |
+
"""Process character designer output with better error handling"""
|
1987 |
+
try:
|
1988 |
+
# Extract protagonist
|
1989 |
+
protagonist_section = self._extract_section(content, r"(?:PROTAGONIST|์ฃผ์ธ๊ณต)")
|
1990 |
+
if protagonist_section:
|
1991 |
+
try:
|
1992 |
+
protagonist = self._parse_character_profile(protagonist_section, "protagonist")
|
1993 |
+
self.screenplay_tracker.add_character(protagonist)
|
1994 |
+
ScreenplayDatabase.save_character(self.current_session_id, protagonist)
|
1995 |
+
except Exception as e:
|
1996 |
+
logger.error(f"Error parsing protagonist: {e}")
|
1997 |
+
# Create a default protagonist to continue
|
1998 |
+
protagonist = CharacterProfile(
|
1999 |
+
name="Protagonist",
|
2000 |
+
role="protagonist",
|
2001 |
+
archetype="Hero",
|
2002 |
+
want="To achieve goal",
|
2003 |
+
need="To grow",
|
2004 |
+
backstory="Unknown",
|
2005 |
+
personality=["Determined"],
|
2006 |
+
speech_pattern="Normal",
|
2007 |
+
character_arc="Growth"
|
2008 |
+
)
|
2009 |
+
self.screenplay_tracker.add_character(protagonist)
|
2010 |
+
|
2011 |
+
# Extract antagonist
|
2012 |
+
antagonist_section = self._extract_section(content, r"(?:ANTAGONIST|์ ๋์)")
|
2013 |
+
if antagonist_section:
|
2014 |
+
try:
|
2015 |
+
antagonist = self._parse_character_profile(antagonist_section, "antagonist")
|
2016 |
+
self.screenplay_tracker.add_character(antagonist)
|
2017 |
+
ScreenplayDatabase.save_character(self.current_session_id, antagonist)
|
2018 |
+
except Exception as e:
|
2019 |
+
logger.error(f"Error parsing antagonist: {e}")
|
2020 |
+
# Create a default antagonist to continue
|
2021 |
+
antagonist = CharacterProfile(
|
2022 |
+
name="Antagonist",
|
2023 |
+
role="antagonist",
|
2024 |
+
archetype="Villain",
|
2025 |
+
want="To stop protagonist",
|
2026 |
+
need="Power",
|
2027 |
+
backstory="Unknown",
|
2028 |
+
personality=["Ruthless"],
|
2029 |
+
speech_pattern="Menacing",
|
2030 |
+
character_arc="Downfall"
|
2031 |
+
)
|
2032 |
+
self.screenplay_tracker.add_character(antagonist)
|
2033 |
+
|
2034 |
+
# Extract supporting characters
|
2035 |
+
supporting_section = self._extract_section(content, r"(?:SUPPORTING CAST|์กฐ๋ ฅ์๋ค)")
|
2036 |
+
if supporting_section:
|
2037 |
+
# Parse multiple supporting characters
|
2038 |
+
self._parse_supporting_characters(supporting_section)
|
2039 |
+
|
2040 |
+
except Exception as e:
|
2041 |
+
logger.error(f"Error processing character content: {e}")
|
2042 |
+
# Continue with default values rather than failing
|
2043 |
|
2044 |
def _extract_section(self, content: str, section_pattern: str) -> str:
|
2045 |
+
"""Extract section from content with improved pattern matching"""
|
2046 |
+
# More flexible section extraction
|
2047 |
+
patterns = [
|
2048 |
+
# Pattern 1: Section header followed by content until next major section
|
2049 |
+
rf'{section_pattern}[:\s]*\n?(.*?)(?=\n\n[A-Z๊ฐ-ํฃ]{{2,}}[:\s]|\n\n\d+\.|$)',
|
2050 |
+
# Pattern 2: Section header with content until next section (alternative)
|
2051 |
+
rf'{section_pattern}.*?\n((?:.*\n)*?)(?=\n[A-Z๊ฐ-ํฃ]{{2,}}:|$)',
|
2052 |
+
# Pattern 3: More flexible pattern for Korean text
|
2053 |
+
rf'{section_pattern}[:\s]*\n?((?:[^\n]+\n?)*?)(?=\n\n|\Z)'
|
2054 |
+
]
|
2055 |
+
|
2056 |
+
for pattern in patterns:
|
2057 |
+
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2058 |
+
if match and match.group(1):
|
2059 |
+
section_content = match.group(1).strip()
|
2060 |
+
if section_content: # Only return if we got actual content
|
2061 |
+
return section_content
|
2062 |
+
|
2063 |
+
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2064 |
|
2065 |
+
def _parse_supporting_characters(self, content: str):
|
2066 |
+
"""Parse supporting characters from content"""
|
2067 |
+
# Split by character markers (numbers or bullets)
|
2068 |
+
char_sections = re.split(r'\n(?:\d+\.|[-โข*])\s*', content)
|
2069 |
+
|
2070 |
+
for i, section in enumerate(char_sections[1:], 1): # Skip first empty split
|
2071 |
+
if section.strip():
|
2072 |
+
try:
|
2073 |
+
# Try multiple name extraction patterns
|
2074 |
+
name = None
|
2075 |
+
name_patterns = [
|
2076 |
+
r"(?:์ด๋ฆ|Name)[:\s]*([^,\n]+)",
|
2077 |
+
r"^([^:\n]+?)(?:\s*[-โ]\s*|:\s*)", # Name at start before dash or colon
|
2078 |
+
r"^([๊ฐ-ํฃA-Za-z\s]+?)(?:\s*\(|$)" # Korean/English name before parenthesis
|
2079 |
+
]
|
2080 |
+
|
2081 |
+
for pattern in name_patterns:
|
2082 |
+
name_match = re.search(pattern, section.strip(), re.IGNORECASE)
|
2083 |
+
if name_match and name_match.group(1):
|
2084 |
+
name = name_match.group(1).strip()
|
2085 |
+
if name and len(name) > 1:
|
2086 |
+
break
|
2087 |
+
|
2088 |
+
if not name:
|
2089 |
+
name = f"Supporting_{i}"
|
2090 |
+
|
2091 |
+
role_desc = self._extract_field(section, r"(?:Role|์ญํ )[:\s]*") or "supporting"
|
2092 |
+
|
2093 |
+
character = CharacterProfile(
|
2094 |
+
name=name,
|
2095 |
+
role="supporting",
|
2096 |
+
archetype=role_desc,
|
2097 |
+
want="",
|
2098 |
+
need="",
|
2099 |
+
backstory=self._extract_field(section, r"(?:Backstory|๋ฐฑ์คํ ๋ฆฌ)[:\s]*") or "",
|
2100 |
+
personality=[],
|
2101 |
+
speech_pattern="",
|
2102 |
+
character_arc=""
|
2103 |
+
)
|
2104 |
+
|
2105 |
+
self.screenplay_tracker.add_character(character)
|
2106 |
+
ScreenplayDatabase.save_character(self.current_session_id, character)
|
2107 |
+
|
2108 |
+
except Exception as e:
|
2109 |
+
logger.warning(f"Error parsing supporting character {i}: {e}")
|
2110 |
+
continue
|
2111 |
|
2112 |
+
def _process_producer_content(self, content: str):
|
2113 |
+
"""Process producer output with better extraction"""
|
2114 |
+
try:
|
2115 |
+
# Extract title with various formats
|
2116 |
+
title_patterns = [
|
2117 |
+
r'(?:TITLE|์ ๋ชฉ)[:\s]*\*?\*?([^\n*]+)\*?\*?',
|
2118 |
+
r'\*\*(?:TITLE|์ ๋ชฉ)\*\*[:\s]*([^\n]+)',
|
2119 |
+
r'Title[:\s]*([^\n]+)'
|
2120 |
+
]
|
2121 |
+
|
2122 |
+
for pattern in title_patterns:
|
2123 |
+
title_match = re.search(pattern, content, re.IGNORECASE)
|
2124 |
+
if title_match:
|
2125 |
+
self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
|
2126 |
+
break
|
2127 |
+
|
2128 |
+
# Extract logline with various formats
|
2129 |
+
logline_patterns = [
|
2130 |
+
r'(?:LOGLINE|๋ก๊ทธ๋ผ์ธ)[:\s]*\*?\*?([^\n]+)',
|
2131 |
+
r'\*\*(?:LOGLINE|๋ก๊ทธ๋ผ์ธ)\*\*[:\s]*([^\n]+)',
|
2132 |
+
r'Logline[:\s]*([^\n]+)'
|
2133 |
+
]
|
2134 |
+
|
2135 |
+
for pattern in logline_patterns:
|
2136 |
+
logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2137 |
+
if logline_match:
|
2138 |
+
# Get full logline (might be multi-line)
|
2139 |
+
logline_text = logline_match.group(1).strip()
|
2140 |
+
# Continue reading if it's incomplete
|
2141 |
+
if not logline_text.endswith('.'):
|
2142 |
+
next_lines = content[logline_match.end():].split('\n')
|
2143 |
+
for line in next_lines[:3]: # Check next 3 lines
|
2144 |
+
if line.strip() and not re.match(r'^[A-Z๊ฐ-ํฃ\d]', line.strip()):
|
2145 |
+
logline_text += ' ' + line.strip()
|
2146 |
+
else:
|
2147 |
+
break
|
2148 |
+
self.screenplay_tracker.screenplay_bible.logline = logline_text
|
2149 |
+
break
|
2150 |
+
|
2151 |
+
# Extract genre
|
2152 |
+
genre_match = re.search(r'(?:Primary Genre|์ฃผ ์ฅ๋ฅด)[:\s]*([^\n]+)', content, re.IGNORECASE)
|
2153 |
+
if genre_match:
|
2154 |
+
self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
|
2155 |
+
|
2156 |
+
# Save to database
|
2157 |
+
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2158 |
+
self.screenplay_tracker.screenplay_bible)
|
2159 |
+
|
2160 |
+
except Exception as e:
|
2161 |
+
logger.error(f"Error processing producer content: {e}")
|
2162 |
|
2163 |
def _process_story_content(self, content: str):
|
2164 |
"""Process story developer output"""
|
|
|
2173 |
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2174 |
self.screenplay_tracker.screenplay_bible)
|
2175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2176 |
def _process_scene_content(self, content: str):
|
2177 |
"""Process scene planner output"""
|
2178 |
# Parse scene breakdown
|
|
|
2201 |
self.screenplay_tracker.add_scene(scene)
|
2202 |
ScreenplayDatabase.save_scene(self.current_session_id, scene)
|
2203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2204 |
# --- Utility functions ---
|
2205 |
def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
|
2206 |
"""Generate random screenplay theme"""
|
|
|
2382 |
match = re.search(r'\*\*(?:Logline|๋ก๊ทธ๋ผ์ธ):\*\*\s*(.+)', theme_text, re.IGNORECASE)
|
2383 |
return match.group(1).strip() if match else ""
|
2384 |
|
2385 |
+
import re
|
2386 |
+
|
2387 |
def format_screenplay_display(screenplay_text: str) -> str:
|
2388 |
+
"""Convert raw screenplay text to a nicely formatted Markdown preview."""
|
2389 |
+
|
2390 |
+
if not screenplay_text:
|
2391 |
+
return "No screenplay content yet."
|
2392 |
+
|
2393 |
+
# 1) ์ ๋ชฉ ์์ญ
|
2394 |
+
formatted = "# ๐ฌ Screenplay\n\n"
|
2395 |
+
|
2396 |
+
# 2) ์ฌ ํค๋ฉ(INT./EXT. ๋ผ์ธ) ๋ณผ๋ ์ฒ๋ฆฌ
|
2397 |
+
# - ^ : ํ์ ์์
|
2398 |
+
# - .* : ํ ์ ์ฒด
|
2399 |
+
# - re.MULTILINE : ๊ฐ ์ค๋ง๋ค ^ $๊ฐ ๋์
|
2400 |
+
formatted_text = re.sub(
|
2401 |
+
r'^(INT\.|EXT\.).*$', # ์บก์ฒ: INT. ๋๋ EXT.์ผ๋ก ์์ํ๋ ํ ์ค
|
2402 |
+
r'**\g<0>**', # ์ ์ฒด ํ์ ๊ตต๊ฒ
|
2403 |
+
screenplay_text,
|
2404 |
+
flags=re.MULTILINE
|
2405 |
+
)
|
2406 |
+
|
2407 |
+
# 3) ๋๋ฌธ์ ์ ์(์ธ๋ฌผ ์ด๋ฆ) ๋ณผ๋ ์ฒ๋ฆฌ
|
2408 |
+
# - [A-Z][A-Z\s]+$ : ALL-CAPS ๊ธ์์ ๊ณต๋ฐฑ๋ง์ผ๋ก ์ด๋ค์ง ํ
|
2409 |
+
formatted_text = re.sub(
|
2410 |
+
r'^([A-Z][A-Z\s]+)$',
|
2411 |
+
r'**\g<0>**',
|
2412 |
+
formatted_text,
|
2413 |
+
flags=re.MULTILINE
|
2414 |
+
)
|
2415 |
+
|
2416 |
+
# 4) ๊ฐ๋
์ฑ์ ์ํด INT./EXT. ๋ค์ ๋น ์ค ์ฝ์
|
2417 |
+
lines = formatted_text.splitlines()
|
2418 |
+
pretty_lines = []
|
2419 |
+
for line in lines:
|
2420 |
+
pretty_lines.append(line)
|
2421 |
+
if line.startswith("**INT.") or line.startswith("**EXT."):
|
2422 |
+
pretty_lines.append("") # ๋น ์ค ์ถ๊ฐ
|
2423 |
+
|
2424 |
+
formatted += "\n".join(pretty_lines)
|
2425 |
+
|
2426 |
+
# 5) ํ์ด์ง ์(์คํฌ๋ฆฝํธ ๊ท์น: 1 ํ์ด์ง โ 55 ๋ผ์ธ) ๊ณ์ฐ
|
2427 |
+
page_count = len(screenplay_text.splitlines()) / 55
|
2428 |
+
formatted = f"**Total Pages: {page_count:.1f}**\n\n" + formatted
|
2429 |
+
return formatted
|
2430 |
+
|
2431 |
|
2432 |
def format_stages_display(stages: List[Dict]) -> str:
|
2433 |
"""Format stages display for screenplay"""
|
|
|
2873 |
server_port=7860,
|
2874 |
share=False,
|
2875 |
debug=True
|
2876 |
+
)
|