Spaces:
Sleeping
Sleeping
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 |
+
)
|