openfree commited on
Commit
7e3f44f
ยท
verified ยท
1 Parent(s): e737c15

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +495 -823
app.py CHANGED
@@ -16,25 +16,11 @@ from contextlib import contextmanager
16
  from dataclasses import dataclass, field, asdict
17
  from collections import defaultdict
18
  import random
19
- from huggingface_hub import HfApi, upload_file, hf_hub_download
20
 
21
  # --- Logging setup ---
22
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
  logger = logging.getLogger(__name__)
24
 
25
- # --- Document export imports ---
26
- try:
27
- from docx import Document
28
- from docx.shared import Inches, Pt, RGBColor, Mm
29
- from docx.enum.text import WD_ALIGN_PARAGRAPH
30
- from docx.enum.style import WD_STYLE_TYPE
31
- from docx.oxml.ns import qn
32
- from docx.oxml import OxmlElement
33
- DOCX_AVAILABLE = True
34
- except ImportError:
35
- DOCX_AVAILABLE = False
36
- logger.warning("python-docx not installed. DOCX export will be disabled.")
37
-
38
  # --- Environment variables and constants ---
39
  FIREWORKS_API_KEY = os.getenv("FIREWORKS_API_KEY", "")
40
  BRAVE_SEARCH_API_KEY = os.getenv("BRAVE_SEARCH_API_KEY", "")
@@ -42,7 +28,7 @@ API_URL = "https://api.fireworks.ai/inference/v1/chat/completions"
42
  MODEL_ID = "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
43
  DB_PATH = "screenplay_sessions_v2.db"
44
 
45
- # Enhanced screenplay length settings for longer content
46
  SCREENPLAY_LENGTHS = {
47
  "movie": {"pages": 120, "description": "Feature Film (110-130 pages)", "min_pages": 110},
48
  "tv_drama": {"pages": 60, "description": "TV Drama Episode (55-65 pages)", "min_pages": 55},
@@ -61,7 +47,7 @@ if not BRAVE_SEARCH_API_KEY:
61
  # --- Global variables ---
62
  db_lock = threading.Lock()
63
 
64
- # Enhanced genre templates with more detailed structure
65
  GENRE_TEMPLATES = {
66
  "action": {
67
  "pacing": "fast",
@@ -69,8 +55,8 @@ GENRE_TEMPLATES = {
69
  "dialogue_ratio": 0.3,
70
  "key_elements": ["set pieces", "physical conflict", "urgency", "stakes escalation", "hero moments"],
71
  "structure_beats": ["explosive opening", "pursuit/chase", "confrontation", "climactic battle", "hero's triumph"],
72
- "scene_density": 1.2, # More scenes per page
73
- "action_description_ratio": 0.6 # More action lines
74
  },
75
  "thriller": {
76
  "pacing": "fast",
@@ -128,102 +114,86 @@ GENRE_TEMPLATES = {
128
  }
129
  }
130
 
131
- # Enhanced screenplay stages with more detail phases
132
  SCREENPLAY_STAGES = [
133
  ("producer", "๐ŸŽฌ Producer: Concept Development & Market Analysis"),
134
- ("story_developer", "๐Ÿ“– Story Developer: Extended Synopsis & Detailed Three-Act Structure"),
135
- ("character_designer", "๐Ÿ‘ฅ Character Designer: Deep Character Profiles & Relationships"),
136
- ("world_builder", "๐ŸŒ World Builder: Setting, Atmosphere & Visual Design"),
137
- ("critic_structure", "๐Ÿ” Structure Critic: Comprehensive Story & Character Review"),
138
- ("scene_planner", "๐ŸŽฏ Scene Planner: Detailed Scene-by-Scene Breakdown"),
139
 
140
- # Act 1 - 4 stages: Draft -> Polish -> Review -> Final
141
- ("screenwriter", "โœ๏ธ Screenwriter: Act 1 - Setup (25%) - Initial Draft"),
142
- ("screenwriter", "โœ๏ธ Screenwriter: Act 1 - Polish & Expand"),
143
- ("script_doctor", "๐Ÿ”ง Script Doctor: Act 1 Comprehensive Review"),
144
- ("screenwriter", "โœ๏ธ Screenwriter: Act 1 - Final Extended Version"),
145
 
146
  # Act 2A - 4 stages
147
- ("screenwriter", "โœ๏ธ Screenwriter: Act 2A - Rising Action (25%) - Initial Draft"),
148
- ("screenwriter", "โœ๏ธ Screenwriter: Act 2A - Polish & Expand"),
149
- ("script_doctor", "๐Ÿ”ง Script Doctor: Act 2A Comprehensive Review"),
150
- ("screenwriter", "โœ๏ธ Screenwriter: Act 2A - Final Extended Version"),
151
 
152
  # Act 2B - 4 stages
153
- ("screenwriter", "โœ๏ธ Screenwriter: Act 2B - Complications (25%) - Initial Draft"),
154
- ("screenwriter", "โœ๏ธ Screenwriter: Act 2B - Polish & Expand"),
155
- ("script_doctor", "๐Ÿ”ง Script Doctor: Act 2B Comprehensive Review"),
156
- ("screenwriter", "โœ๏ธ Screenwriter: Act 2B - Final Extended Version"),
157
 
158
  # Act 3 - 4 stages
159
- ("screenwriter", "โœ๏ธ Screenwriter: Act 3 - Resolution (25%) - Initial Draft"),
160
- ("screenwriter", "โœ๏ธ Screenwriter: Act 3 - Polish & Expand"),
161
- ("script_doctor", "๐Ÿ”ง Script Doctor: Act 3 Comprehensive Review"),
162
- ("screenwriter", "โœ๏ธ Screenwriter: Act 3 - Final Extended Version"),
163
 
164
- ("dialogue_specialist", "๐Ÿ’ฌ Dialogue Specialist: Dialogue Enhancement & Subtext"),
165
- ("final_reviewer", "๐ŸŽญ Final Review: Complete Professional Screenplay Analysis"),
166
  ]
167
 
168
- # Enhanced Save the Cat Beat Sheet with more detail
169
  SAVE_THE_CAT_BEATS = {
170
- 1: {"name": "Opening Image", "percentage": "0-1%", "description": "Visual that represents the struggle & tone"},
171
- 2: {"name": "Setup", "percentage": "1-10%", "description": "Introduce hero, stakes, and goals"},
172
- 3: {"name": "Theme Stated", "percentage": "5%", "description": "What the story is really about"},
173
- 4: {"name": "Catalyst", "percentage": "10%", "description": "The moment that begins the journey"},
174
- 5: {"name": "Debate", "percentage": "10-20%", "description": "Should I stay or should I go?"},
175
- 6: {"name": "Break into Two", "percentage": "20%", "description": "Hero enters the new world"},
176
- 7: {"name": "B Story", "percentage": "22%", "description": "Love story or relationship subplot"},
177
- 8: {"name": "Fun and Games", "percentage": "20-50%", "description": "The promise of the premise"},
178
- 9: {"name": "Midpoint", "percentage": "50%", "description": "Stakes are raised, time clock appears"},
179
- 10: {"name": "Bad Guys Close In", "percentage": "50-75%", "description": "Problems intensify"},
180
- 11: {"name": "All Is Lost", "percentage": "75%", "description": "The lowest point"},
181
- 12: {"name": "Dark Night of the Soul", "percentage": "75-80%", "description": "Hero hits rock bottom"},
182
- 13: {"name": "Break into Three", "percentage": "80%", "description": "Solution discovered"},
183
- 14: {"name": "Finale", "percentage": "80-99%", "description": "Hero conquers the problem"},
184
- 15: {"name": "Final Image", "percentage": "99-100%", "description": "Opposite of opening image"}
185
  }
186
 
187
  # --- Data classes ---
188
  @dataclass
189
  class ScreenplayBible:
190
- """Enhanced screenplay bible for maintaining consistency"""
191
  title: str = ""
192
  logline: str = ""
193
  genre: str = ""
194
  subgenre: str = ""
195
  tone: str = ""
196
  themes: List[str] = field(default_factory=list)
197
-
198
- # Characters
199
  protagonist: Dict[str, Any] = field(default_factory=dict)
200
  antagonist: Dict[str, Any] = field(default_factory=dict)
201
  supporting_cast: Dict[str, Dict[str, Any]] = field(default_factory=dict)
202
- character_arcs: Dict[str, str] = field(default_factory=dict)
203
-
204
- # Structure
205
  three_act_structure: Dict[str, str] = field(default_factory=dict)
206
  save_the_cat_beats: Dict[int, str] = field(default_factory=dict)
207
-
208
- # World
209
  time_period: str = ""
210
  primary_locations: List[Dict[str, str]] = field(default_factory=list)
211
  world_rules: List[str] = field(default_factory=list)
212
- atmosphere: str = ""
213
-
214
- # Visual style
215
  visual_style: str = ""
216
  key_imagery: List[str] = field(default_factory=list)
217
- color_palette: str = ""
218
-
219
- # Dialogue style
220
- dialogue_style: str = ""
221
- vernacular: str = ""
222
- period_appropriate: bool = True
223
 
224
  @dataclass
225
  class SceneBreakdown:
226
- """Enhanced scene information"""
227
  scene_number: int
228
  act: int
229
  location: str
@@ -231,16 +201,13 @@ class SceneBreakdown:
231
  characters: List[str]
232
  purpose: str
233
  conflict: str
234
- emotional_arc: str
235
- visual_notes: str
236
- dialogue_focus: str
237
  page_count: float
238
  beat: str = ""
239
  transition: str = "CUT TO:"
240
 
241
  @dataclass
242
  class CharacterProfile:
243
- """Enhanced character profile"""
244
  name: str
245
  role: str
246
  archetype: str
@@ -252,36 +219,22 @@ class CharacterProfile:
252
  character_arc: str
253
  relationships: Dict[str, str] = field(default_factory=dict)
254
  first_appearance: str = ""
255
- motivation: str = ""
256
- internal_conflict: str = ""
257
- external_conflict: str = ""
258
- transformation: str = ""
259
- key_dialogue_samples: List[str] = field(default_factory=list)
260
 
261
  # --- Core logic classes ---
262
  class ScreenplayTracker:
263
- """Enhanced screenplay tracker with more detailed tracking"""
264
  def __init__(self):
265
  self.screenplay_bible = ScreenplayBible()
266
  self.scenes: List[SceneBreakdown] = []
267
  self.characters: Dict[str, CharacterProfile] = {}
268
  self.page_count = 0
269
  self.act_pages = {"1": 0, "2A": 0, "2B": 0, "3": 0}
270
- self.dialogue_action_ratio = 0.0
271
- self.scene_count = 0
272
- self.location_list: Set[str] = set()
273
- self.time_of_day_distribution = defaultdict(int)
274
 
275
  def add_scene(self, scene: SceneBreakdown):
276
- """Add scene to tracker"""
277
  self.scenes.append(scene)
278
  self.page_count += scene.page_count
279
- self.scene_count += 1
280
- self.location_list.add(scene.location)
281
- self.time_of_day_distribution[scene.time_of_day] += 1
282
 
283
  def add_character(self, character: CharacterProfile):
284
- """Add character to tracker"""
285
  self.characters[character.name] = character
286
  if character.role == "protagonist":
287
  self.screenplay_bible.protagonist = asdict(character)
@@ -291,62 +244,41 @@ class ScreenplayTracker:
291
  self.screenplay_bible.supporting_cast[character.name] = asdict(character)
292
 
293
  def update_bible(self, key: str, value: Any):
294
- """Update screenplay bible"""
295
  if hasattr(self.screenplay_bible, key):
296
  setattr(self.screenplay_bible, key, value)
297
-
298
- def get_act_page_target(self, act: str, total_pages: int) -> int:
299
- """Get target pages for each act"""
300
- if act == "1":
301
- return int(total_pages * 0.25)
302
- elif act in ["2A", "2B"]:
303
- return int(total_pages * 0.25)
304
- elif act == "3":
305
- return int(total_pages * 0.25)
306
- return 0
307
 
308
  class ScreenplayDatabase:
309
- """Enhanced database management for screenplay sessions"""
310
  @staticmethod
311
  def init_db():
312
  with sqlite3.connect(DB_PATH) as conn:
313
  conn.execute("PRAGMA journal_mode=WAL")
314
  cursor = conn.cursor()
315
 
316
- # Enhanced screenplay sessions table
317
  cursor.execute('''
318
  CREATE TABLE IF NOT EXISTS screenplay_sessions (
319
  session_id TEXT PRIMARY KEY,
320
  user_query TEXT NOT NULL,
321
  screenplay_type TEXT NOT NULL,
322
  genre TEXT NOT NULL,
323
- subgenre TEXT,
324
  target_pages INTEGER,
325
- min_pages INTEGER,
326
  language TEXT NOT NULL,
327
  title TEXT,
328
  logline TEXT,
329
  synopsis TEXT,
330
- extended_synopsis TEXT,
331
  three_act_structure TEXT,
332
  character_profiles TEXT,
333
  scene_breakdown TEXT,
334
  screenplay_bible TEXT,
335
- world_building TEXT,
336
- dialogue_notes TEXT,
337
  final_screenplay TEXT,
338
- pdf_path TEXT,
339
  created_at TEXT DEFAULT (datetime('now')),
340
  updated_at TEXT DEFAULT (datetime('now')),
341
  status TEXT DEFAULT 'active',
342
  current_stage INTEGER DEFAULT 0,
343
- total_pages REAL DEFAULT 0,
344
- actual_pages REAL DEFAULT 0,
345
- quality_score REAL DEFAULT 0
346
  )
347
  ''')
348
 
349
- # Enhanced stages table
350
  cursor.execute('''
351
  CREATE TABLE IF NOT EXISTS screenplay_stages (
352
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -355,55 +287,24 @@ class ScreenplayDatabase:
355
  stage_name TEXT NOT NULL,
356
  role TEXT NOT NULL,
357
  content TEXT,
358
- enhanced_content TEXT,
359
  page_count REAL DEFAULT 0,
360
- word_count INTEGER DEFAULT 0,
361
- dialogue_count INTEGER DEFAULT 0,
362
- action_count INTEGER DEFAULT 0,
363
  status TEXT DEFAULT 'pending',
364
- quality_notes TEXT,
365
  created_at TEXT DEFAULT (datetime('now')),
366
- updated_at TEXT DEFAULT (datetime('now')),
367
  FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id),
368
  UNIQUE(session_id, stage_number)
369
  )
370
  ''')
371
 
372
- # Enhanced scenes table
373
- cursor.execute('''
374
- CREATE TABLE IF NOT EXISTS scenes (
375
- id INTEGER PRIMARY KEY AUTOINCREMENT,
376
- session_id TEXT NOT NULL,
377
- act_number INTEGER NOT NULL,
378
- scene_number INTEGER NOT NULL,
379
- location TEXT NOT NULL,
380
- time_of_day TEXT NOT NULL,
381
- characters TEXT,
382
- purpose TEXT,
383
- emotional_arc TEXT,
384
- visual_notes TEXT,
385
- dialogue_focus TEXT,
386
- conflict_level INTEGER,
387
- content TEXT,
388
- page_count REAL,
389
- created_at TEXT DEFAULT (datetime('now')),
390
- FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id)
391
- )
392
- ''')
393
-
394
- # Enhanced characters table
395
  cursor.execute('''
396
- CREATE TABLE IF NOT EXISTS characters (
397
- id INTEGER PRIMARY KEY AUTOINCREMENT,
398
- session_id TEXT NOT NULL,
399
- character_name TEXT NOT NULL,
400
- character_data TEXT,
401
- dialogue_samples TEXT,
402
- arc_progression TEXT,
403
- relationship_map TEXT,
404
- created_at TEXT DEFAULT (datetime('now')),
405
- FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id),
406
- UNIQUE(session_id, character_name)
407
  )
408
  ''')
409
 
@@ -424,14 +325,13 @@ class ScreenplayDatabase:
424
  def create_session(user_query: str, screenplay_type: str, genre: str, language: str) -> str:
425
  session_id = hashlib.md5(f"{user_query}{screenplay_type}{datetime.now()}".encode()).hexdigest()
426
  target_pages = SCREENPLAY_LENGTHS[screenplay_type]["pages"]
427
- min_pages = SCREENPLAY_LENGTHS[screenplay_type]["min_pages"]
428
 
429
  with ScreenplayDatabase.get_db() as conn:
430
  conn.cursor().execute(
431
  '''INSERT INTO screenplay_sessions
432
- (session_id, user_query, screenplay_type, genre, target_pages, min_pages, language)
433
- VALUES (?, ?, ?, ?, ?, ?, ?)''',
434
- (session_id, user_query, screenplay_type, genre, target_pages, min_pages, language)
435
  )
436
  conn.commit()
437
  return session_id
@@ -440,8 +340,6 @@ class ScreenplayDatabase:
440
  def save_stage(session_id: str, stage_number: int, stage_name: str,
441
  role: str, content: str, status: str = 'complete'):
442
  page_count = 0
443
- word_count = len(content.split()) if content else 0
444
-
445
  if role == "screenwriter" and content:
446
  page_count = ScreenplayGenerationSystem.calculate_screenplay_pages(content)
447
 
@@ -449,32 +347,16 @@ class ScreenplayDatabase:
449
  cursor = conn.cursor()
450
  cursor.execute('''
451
  INSERT INTO screenplay_stages
452
- (session_id, stage_number, stage_name, role, content, page_count, word_count, status)
453
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
454
  ON CONFLICT(session_id, stage_number)
455
- DO UPDATE SET content=?, page_count=?, word_count=?, status=?, updated_at=datetime('now')
456
- ''', (session_id, stage_number, stage_name, role, content, page_count, word_count, status,
457
- content, page_count, word_count, status))
458
-
459
- if role == "screenwriter" and "Final" in stage_name:
460
- cursor.execute('''
461
- UPDATE screenplay_sessions
462
- SET actual_pages = (
463
- SELECT SUM(page_count)
464
- FROM screenplay_stages
465
- WHERE session_id = ?
466
- AND role = 'screenwriter'
467
- AND stage_name LIKE '%Final%'
468
- ),
469
- updated_at = datetime('now')
470
- WHERE session_id = ?
471
- ''', (session_id, session_id))
472
-
473
  conn.commit()
474
 
475
  @staticmethod
476
  def get_screenplay_content(session_id: str) -> str:
477
- """Get complete screenplay content - only final versions"""
478
  with ScreenplayDatabase.get_db() as conn:
479
  rows = conn.cursor().execute('''
480
  SELECT content FROM screenplay_stages
@@ -495,65 +377,44 @@ class ScreenplayDatabase:
495
  return '\n\n'.join(row['content'] for row in rows if row['content'])
496
  return ""
497
 
498
- class WebSearchIntegration:
499
- """Web search functionality for screenplay research"""
500
- def __init__(self):
501
- self.brave_api_key = BRAVE_SEARCH_API_KEY
502
- self.search_url = "https://api.search.brave.com/res/v1/web/search"
503
- self.enabled = bool(self.brave_api_key)
504
-
505
- def search(self, query: str, count: int = 3, language: str = "en") -> List[Dict]:
506
- if not self.enabled:
507
- return []
508
- headers = {
509
- "Accept": "application/json",
510
- "X-Subscription-Token": self.brave_api_key
511
- }
512
- params = {
513
- "q": query,
514
- "count": count,
515
- "search_lang": "ko" if language == "Korean" else "en",
516
- "text_decorations": False,
517
- "safesearch": "moderate"
518
- }
519
- try:
520
- response = requests.get(self.search_url, headers=headers, params=params, timeout=10)
521
- response.raise_for_status()
522
- results = response.json().get("web", {}).get("results", [])
523
- return results
524
- except requests.exceptions.RequestException as e:
525
- logger.error(f"Web search API error: {e}")
526
- return []
527
 
528
- def extract_relevant_info(self, results: List[Dict], max_chars: int = 2000) -> str:
529
- if not results:
530
- return ""
531
- extracted = []
532
- total_chars = 0
533
- for i, result in enumerate(results[:5], 1):
534
- title = result.get("title", "")
535
- description = result.get("description", "")
536
- info = f"[{i}] {title}: {description}"
537
- if total_chars + len(info) < max_chars:
538
- extracted.append(info)
539
- total_chars += len(info)
540
- else:
541
- break
542
- return "\n".join(extracted)
543
 
544
  class ScreenplayGenerationSystem:
545
- """Enhanced professional screenplay generation system"""
546
  def __init__(self):
547
  self.api_key = FIREWORKS_API_KEY
548
  self.api_url = API_URL
549
  self.model_id = MODEL_ID
550
  self.screenplay_tracker = ScreenplayTracker()
551
- self.web_search = WebSearchIntegration()
552
  self.current_session_id = None
553
  ScreenplayDatabase.init_db()
554
 
555
  def create_headers(self):
556
- """Create headers for API request"""
557
  if not self.api_key or self.api_key == "dummy_token_for_testing":
558
  raise ValueError("Valid FIREWORKS_API_KEY is required")
559
 
@@ -563,407 +424,109 @@ class ScreenplayGenerationSystem:
563
  "Authorization": f"Bearer {self.api_key}"
564
  }
565
 
566
- # Enhanced prompt generation functions with more detail
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  def create_screenwriter_prompt(self, act: str, scene_breakdown: str,
568
  characters: str, previous_acts: str,
569
  screenplay_type: str, genre: str, language: str,
570
  is_polish: bool = False, is_final: bool = False) -> str:
571
- """Enhanced screenwriter prompt with multiple phases"""
572
 
573
  act_pages = int(SCREENPLAY_LENGTHS[screenplay_type]['pages'] * 0.25)
574
- min_lines = int(act_pages * 60) # Increased from 55 to 60 lines per page
575
- genre_template = GENRE_TEMPLATES.get(genre, GENRE_TEMPLATES["drama"])
576
 
577
- # Different requirements for different phases
578
  if is_final:
579
- min_lines = int(min_lines * 1.3) # 30% more for final version
580
- instruction_focus = "final polished version with rich details"
581
  elif is_polish:
582
- min_lines = int(min_lines * 1.15) # 15% more for polish
583
- instruction_focus = "enhanced version with added depth"
584
  else:
585
- instruction_focus = "complete initial draft"
586
 
587
  lang_prompts = {
588
- "Korean": f"""๋‹น์‹ ์€ ํ• ๋ฆฌ์šฐ๋“œ A๊ธ‰ ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. {act}์˜ {instruction_focus}์„ ์ž‘์„ฑํ•˜์„ธ์š”.
589
-
590
- **๋ชฉํ‘œ ๋ถ„๋Ÿ‰:** {act_pages}ํŽ˜์ด์ง€ (์ตœ์†Œ {min_lines}์ค„ ํ•„์ˆ˜)
591
- **์ ˆ๋Œ€ ๊ทœ์น™: {min_lines}์ค„ ๋ฏธ๋งŒ์€ ๋ถˆํ•ฉ๊ฒฉ์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ ์ดˆ๊ณผ ๋‹ฌ์„ฑํ•˜์„ธ์š”.**
592
-
593
- **์”ฌ ๋ธŒ๋ ˆ์ดํฌ๋‹ค์šด:**
594
- {scene_breakdown}
595
-
596
- **์บ๋ฆญํ„ฐ ์ •๋ณด:**
597
- {characters}
598
-
599
- **์ด์ „ ๋‚ด์šฉ:**
600
- {previous_acts if previous_acts else "์ฒซ ๋ง‰์ž…๋‹ˆ๋‹ค."}
601
-
602
- **ํ•„์ˆ˜ ์ž‘์„ฑ ์š”๊ตฌ์‚ฌํ•ญ:**
603
-
604
- 1. **๋ถ„๋Ÿ‰ ๋‹ฌ์„ฑ ์ „๋žต**
605
- - ๊ฐ ์”ฌ: ์ตœ์†Œ 4-6ํŽ˜์ด์ง€ (240-360์ค„)
606
- - ๋Œ€ํ™” ์‹œํ€€์Šค: ๊ฐ 15-25์ค„ (์บ๋ฆญํ„ฐ๊ฐ„ ์ถฉ๋ถ„ํ•œ ๊ตํ™˜)
607
- - ์•ก์…˜ ๋ฌ˜์‚ฌ: ๊ฐ ๋น„ํŠธ๋‹น 5-8์ค„ (์‹œ๋„ค๋งˆํ‹ฑํ•œ ๋””ํ…Œ์ผ)
608
- - ์บ๋ฆญํ„ฐ ๋ฐ˜์‘: ํ‘œ์ •, ์ œ์Šค์ฒ˜, ์นจ๋ฌต์˜ ์ˆœ๊ฐ„ ํฌํ•จ
609
- - ํ™˜๊ฒฝ ๋ฌ˜์‚ฌ: ๊ฐ ์”ฌ ์˜คํ”„๋‹ 10-15์ค„
610
-
611
- 2. **๋Œ€ํ™” ์ž‘์„ฑ ์‹ฌํ™”**
612
- - ์„œ๋ธŒํ…์ŠคํŠธ 3์ธต ๊ตฌ์กฐ (ํ‘œ๋ฉด/์˜๋„/์ง„์งœ ๊ฐ์ •)
613
- - ์บ๋ฆญํ„ฐ๋ณ„ ๊ณ ์œ  ์–ดํœ˜์™€ ๋ง๋ฒ„๋ฆ‡
614
- - ๊ฐˆ๋“ฑ์˜ ์ ์ง„์  ๊ณ ์กฐ (5๋‹จ๊ณ„ ์—์Šค์ปฌ๋ ˆ์ด์…˜)
615
- - ๋Œ€ํ™” ์‚ฌ์ด ํ–‰๋™๊ณผ ๋ฐ˜์‘ ์‚ฝ์ž…
616
- - ํŒŒ์›Œ ๋‹ค์ด๋‚˜๋ฏน์Šค ํ‘œํ˜„
617
-
618
- 3. **์•ก์…˜ ๋ผ์ธ ์‹œ๋„ค๋งˆํ‹ฑ ๋ฌ˜์‚ฌ**
619
- - ์นด๋ฉ”๋ผ ์•ต๊ธ€ ์•”์‹œ (ํด๋กœ์ฆˆ์—…, ์™€์ด๋“œ์ƒท ๋“ฑ)
620
- - ์กฐ๋ช…๊ณผ ๊ทธ๋ฆผ์ž ํ™œ์šฉ
621
- - ์†Œ๋ฆฌ์™€ ์Œํ–ฅ ํšจ๊ณผ ๋ฌ˜์‚ฌ
622
- - ๋‚ ์”จ์™€ ์‹œ๊ฐ„๋Œ€ ๋ณ€ํ™”
623
- - ์†Œํ’ˆ๊ณผ ์˜์ƒ์˜ ์ƒ์ง•์  ํ™œ์šฉ
624
-
625
- 4. **{genre} ์žฅ๋ฅด ๋งˆ์Šคํ„ฐ๋ฆฌ**
626
- - ํŽ˜์ด์‹ฑ: {genre_template['pacing']} (๋น„ํŠธ๋‹น {genre_template['scene_length']})
627
- - ํ•ต์‹ฌ ์š”์†Œ ๋ชจ๋‘ ํฌํ•จ: {', '.join(genre_template['key_elements'])}
628
- - ์žฅ๋ฅด ํŠน์œ  ๋ถ„์œ„๊ธฐ ์กฐ์„ฑ
629
- - ๊ด€๊ฐ ๊ธฐ๋Œ€์น˜ ์ถฉ์กฑ + ์‹ ์„ ํ•œ ๋ฐ˜์ „
630
-
631
- 5. **๊ฐ์ •์  ๋ ˆ์ด์–ด๋ง**
632
- - ๊ฐ ์”ฌ์˜ ๊ฐ์ • ์•„ํฌ (์‹œ์ž‘-์ค‘๊ฐ„-๋)
633
- - ์บ๋ฆญํ„ฐ ๋‚ด๋ฉด ๊ฐˆ๋“ฑ ํ‘œํ˜„
634
- - ๋น„์–ธ์–ด์  ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜
635
- - ๊ธด์žฅ๊ณผ ์ด์™„์˜ ๋ฆฌ๋“ฌ
636
-
637
- 6. **์‹œ๊ฐ์  ์Šคํ† ๋ฆฌํ…”๋ง**
638
- - 'Show, Don't Tell' ์›์น™ ์ฒ ์ € ์ค€์ˆ˜
639
- - ๋ฉ”ํƒ€ํฌ์™€ ์ƒ์ง• ํ™œ์šฉ
640
- - ๋ฐ˜๋ณต ๋ชจํ‹ฐํ”„ ์„ค์ •
641
- - ์ƒ‰๊น”๊ณผ ํ†ค์˜ ์ผ๊ด€์„ฑ
642
-
643
- **ํฌ๋งท ๊ทœ์น™:**
644
- INT./EXT. ์žฅ์†Œ - ์‹œ๊ฐ„
645
-
646
- (์”ฌ ์„ค์ •๊ณผ ๋ถ„์œ„๊ธฐ๋ฅผ 10์ค„ ์ด์ƒ์œผ๋กœ ๋ฌ˜์‚ฌ)
647
-
648
- ์บ๋ฆญํ„ฐ๋ช…
649
- (๊ฐ์ •/ํ†ค ์ง€์‹œ)
650
- ๋Œ€์‚ฌ๋Š” ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ์บ๋ฆญํ„ฐ์— ์ถฉ์‹คํ•˜๊ฒŒ.
651
-
652
- (์บ๋ฆญํ„ฐ ์•ก์…˜๊ณผ ๋ฐ˜์‘์„ ์ƒ์„ธํžˆ)
653
-
654
- **๋ฐ˜๋“œ์‹œ {min_lines}์ค„ ์ด์ƒ, ๊ฐ€๋Šฅํ•˜๋ฉด {int(min_lines * 1.2)}์ค„๊นŒ์ง€ ์ž‘์„ฑํ•˜์„ธ์š”.**""",
655
-
656
- "English": f"""You are an A-list Hollywood screenwriter. Write the {instruction_focus} of {act}.
657
-
658
- **Target Length:** {act_pages} pages (MINIMUM {min_lines} lines required)
659
- **ABSOLUTE RULE: Less than {min_lines} lines is failure. Must exceed target.**
660
-
661
- **Scene Breakdown:**
662
- {scene_breakdown}
663
-
664
- **Character Info:**
665
- {characters}
666
-
667
- **Previous Content:**
668
- {previous_acts if previous_acts else "This is the first act."}
669
-
670
- **Required Writing Elements:**
671
-
672
- 1. **Volume Achievement Strategy**
673
- - Each scene: minimum 4-6 pages (240-360 lines)
674
- - Dialogue sequences: 15-25 lines each (sufficient exchanges)
675
- - Action descriptions: 5-8 lines per beat (cinematic detail)
676
- - Character reactions: expressions, gestures, silent moments
677
- - Environment descriptions: 10-15 lines per scene opening
678
-
679
- 2. **Advanced Dialogue Crafting**
680
- - Three-layer subtext (surface/intent/true emotion)
681
- - Unique vocabulary and speech patterns per character
682
- - Gradual conflict escalation (5-stage progression)
683
- - Actions and reactions between dialogue
684
- - Power dynamics expression
685
-
686
- 3. **Cinematic Action Lines**
687
- - Camera angle implications (close-ups, wide shots)
688
- - Lighting and shadow usage
689
- - Sound and audio effects
690
- - Weather and time changes
691
- - Symbolic use of props and costumes
692
-
693
- 4. **{genre} Genre Mastery**
694
- - Pacing: {genre_template['pacing']} (per beat: {genre_template['scene_length']})
695
- - Include all key elements: {', '.join(genre_template['key_elements'])}
696
- - Genre-specific atmosphere
697
- - Meet expectations + fresh twists
698
-
699
- 5. **Emotional Layering**
700
- - Emotional arc per scene (beginning-middle-end)
701
- - Character internal conflicts
702
- - Non-verbal communication
703
- - Tension and release rhythm
704
-
705
- 6. **Visual Storytelling**
706
- - Strict 'Show, Don't Tell' principle
707
- - Metaphor and symbol usage
708
- - Recurring motifs
709
- - Color and tone consistency
710
-
711
- **Format Rules:**
712
- INT./EXT. LOCATION - TIME
713
-
714
- (Describe scene setting and atmosphere in 10+ lines)
715
-
716
- CHARACTER NAME
717
- (emotion/tone direction)
718
- Natural dialogue true to character.
719
-
720
- (Detail character actions and reactions)
721
-
722
- **MUST write {min_lines}+ lines, ideally up to {int(min_lines * 1.2)} lines.**"""
723
- }
724
-
725
- return lang_prompts.get(language, lang_prompts["English"])
726
-
727
- def create_world_builder_prompt(self, producer_concept: str, story_structure: str,
728
- screenplay_type: str, genre: str, language: str) -> str:
729
- """World building prompt for atmosphere and setting"""
730
-
731
- lang_prompts = {
732
- "Korean": f"""๋‹น์‹ ์€ ์„ธ๊ณ„๊ด€ ๋””์ž์ด๋„ˆ์ž…๋‹ˆ๋‹ค. {screenplay_type}์˜ ๋ชฐ์ž…๊ฐ ์žˆ๋Š” ์„ธ๊ณ„๋ฅผ ๊ตฌ์ถ•ํ•˜์„ธ์š”.
733
-
734
- **ํ”„๋กœ๋“€์„œ ์ปจ์…‰:**
735
- {producer_concept}
736
-
737
- **์Šคํ† ๋ฆฌ ๊ตฌ์กฐ:**
738
- {story_structure}
739
-
740
- **ํ•„์ˆ˜ ๊ตฌ์ถ• ์š”์†Œ:**
741
-
742
- 1. **์‹œ๊ณต๊ฐ„ ์„ค์ •**
743
- - ์‹œ๋Œ€: (๊ตฌ์ฒด์  ์—ฐ๋„/์‹œ๋Œ€)
744
- - ์ง€๋ฆฌ์  ์œ„์น˜: (๋„์‹œ/๊ตญ๊ฐ€/ํ–‰์„ฑ)
745
- - ์‚ฌํšŒ์  ๋ฐฐ๊ฒฝ: (์ •์น˜/๊ฒฝ์ œ/๋ฌธํ™”)
746
- - ๊ณ„์ ˆ๊ณผ ๋‚ ์”จ ํŒจํ„ด:
747
-
748
- 2. **์ฃผ์š” ๋กœ์ผ€์ด์…˜ (์ตœ์†Œ 8๊ฐœ)**
749
- ๊ฐ ์žฅ์†Œ๋ณ„:
750
- - ์ด๋ฆ„๊ณผ ์„ค๋ช…:
751
- - ์‹œ๊ฐ์  ํŠน์ง•:
752
- - ๋ถ„์œ„๊ธฐ์™€ ํ†ค:
753
- - ์Šคํ† ๋ฆฌ ๊ธฐ๋Šฅ:
754
- - ์ƒ์ง•์  ์˜๋ฏธ:
755
-
756
- 3. **์„ธ๊ณ„๊ด€ ๊ทœ์น™**
757
- - ๋ฌผ๋ฆฌ ๋ฒ•์น™: (ํ˜„์‹ค์ /ํŒํƒ€์ง€/SF)
758
- - ์‚ฌํšŒ ๊ทœ์น™๊ณผ ๊ด€์Šต:
759
- - ๊ธฐ์ˆ  ์ˆ˜์ค€:
760
- - ์ดˆ์ž์—ฐ์  ์š”์†Œ: (์žˆ๋‹ค๋ฉด)
761
-
762
- 4. **์‹œ๊ฐ์  ๋””์ž์ธ**
763
- - ์ „์ฒด ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ:
764
- - ์กฐ๋ช… ์Šคํƒ€์ผ:
765
- - ์นด๋ฉ”๋ผ ์Šคํƒ€์ผ:
766
- - ์˜์ƒ๊ณผ ํ”„๋กœ๋•์…˜ ๋””์ž์ธ:
767
- - ์ฐธ์กฐ ์˜ํ™”/์‹œ๊ฐ ์ž๋ฃŒ:
768
-
769
- 5. **์‚ฌ์šด๋“œ์Šค์ผ€์ดํ”„**
770
- - ํ™˜๊ฒฝ์Œ:
771
- - ์Œ์•… ์Šคํƒ€์ผ:
772
- - ํŠน์ง•์  ์‚ฌ์šด๋“œ:
773
- - ์นจ๋ฌต์˜ ํ™œ์šฉ:
774
-
775
- 6. **๋ถ„์œ„๊ธฐ์™€ ํ†ค**
776
- - ์ „์ฒด์  ๋ฌด๋“œ:
777
- - ์žฅ๋ฅด๋ณ„ ๋ถ„์œ„๊ธฐ:
778
- - ๊ฐ์ •์  ์˜จ๋„:
779
- - ๊ด€๊ฐ์ด ๋А๋‚„ ๊ฐ๊ฐ:
780
-
781
- 7. **๋ฌธํ™”์  ๋””ํ…Œ์ผ**
782
- - ์–ธ์–ด์™€ ๋ฐฉ์–ธ:
783
- - ๊ด€์Šต๊ณผ ์ „ํ†ต:
784
- - ์Œ์‹๊ณผ ์ผ์ƒ:
785
- - ์ข…๊ต/์‹ ๋… ์ฒด๊ณ„:
786
-
787
- ๊ตฌ์ฒด์ ์ด๊ณ  ์‹œ๊ฐ์ ์œผ๋กœ ํ’๋ถ€ํ•œ ์„ธ๊ณ„๋ฅผ ๋งŒ๋“œ์„ธ์š”.""",
788
-
789
- "English": f"""You are a world builder. Create an immersive world for this {screenplay_type}.
790
-
791
- **Producer Concept:**
792
- {producer_concept}
793
-
794
- **Story Structure:**
795
- {story_structure}
796
-
797
- **Required Building Elements:**
798
-
799
- 1. **Time & Space Setting**
800
- - Era: (specific year/period)
801
- - Geographic location: (city/country/planet)
802
- - Social context: (political/economic/cultural)
803
- - Season and weather patterns:
804
-
805
- 2. **Key Locations (minimum 8)**
806
- For each location:
807
- - Name and description:
808
- - Visual characteristics:
809
- - Atmosphere and tone:
810
- - Story function:
811
- - Symbolic meaning:
812
-
813
- 3. **World Rules**
814
- - Physical laws: (realistic/fantasy/sci-fi)
815
- - Social rules and customs:
816
- - Technology level:
817
- - Supernatural elements: (if any)
818
-
819
- 4. **Visual Design**
820
- - Overall color palette:
821
- - Lighting style:
822
- - Camera style:
823
- - Costume and production design:
824
- - Reference films/visuals:
825
-
826
- 5. **Soundscape**
827
- - Ambient sounds:
828
- - Music style:
829
- - Signature sounds:
830
- - Use of silence:
831
-
832
- 6. **Atmosphere & Tone**
833
- - Overall mood:
834
- - Genre-specific atmosphere:
835
- - Emotional temperature:
836
- - Audience sensory experience:
837
-
838
- 7. **Cultural Details**
839
- - Language and dialects:
840
- - Customs and traditions:
841
- - Food and daily life:
842
- - Religion/belief systems:
843
-
844
- Create a specific, visually rich world."""
845
- }
846
-
847
- return lang_prompts.get(language, lang_prompts["English"])
848
-
849
- def create_dialogue_specialist_prompt(self, complete_screenplay: str,
850
- characters: str, genre: str, language: str) -> str:
851
- """Dialogue enhancement specialist prompt"""
852
-
853
- lang_prompts = {
854
- "Korean": f"""๋‹น์‹ ์€ ๋Œ€ํ™” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹œ๋‚˜๋ฆฌ์˜ค์˜ ๋Œ€ํ™”๋ฅผ ํ•œ ๋‹จ๊ณ„ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜์„ธ์š”.
855
-
856
- **์ž‘์„ฑ๋œ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ฒ€ํ†  ํ›„ ๋‹ค์Œ ๊ฐœ์„ :**
857
-
858
- 1. **๋Œ€ํ™” ์ž์—ฐ์Šค๋Ÿฌ์›€**
859
- - ์ผ์ƒ์  ๋ฆฌ๋“ฌ๊ณผ ํ˜ธํก
860
- - ๋ง ๋Š๊น€, ์ค‘๋ณต, ๋”๋“ฌ๊ธฐ
861
- - ์ž์—ฐ์Šค๋Ÿฌ์šด ์ „ํ™˜
862
-
863
- 2. **์บ๋ฆญํ„ฐ ๊ณ ์œ ์„ฑ**
864
- - ๊ฐ์ž์˜ ์–ดํœ˜ ์ˆ˜์ค€
865
- - ๋งํˆฌ์™€ ์–ต์–‘
866
- - ์Šต๊ด€์  ํ‘œํ˜„
867
- - ๊ต์œก/๋ฐฐ๊ฒฝ ๋ฐ˜์˜
868
-
869
- 3. **์„œ๋ธŒํ…์ŠคํŠธ ๊ฐ•ํ™”**
870
- - ๋งํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๋” ์ค‘์š”
871
- - ์ด์ค‘์  ์˜๋ฏธ
872
- - ๊ฐ์ •๊ณผ ๋Œ€์‚ฌ์˜ ๋Œ€๋น„
873
-
874
- 4. **๊ฐˆ๋“ฑ๊ณผ ๊ธด์žฅ**
875
- - ๋Œ€ํ™”๋ฅผ ํ†ตํ•œ ํŒŒ์›Œ๊ฒŒ์ž„
876
- - ์ˆจ๊ฒจ์ง„ ์˜๋„
877
- - ์ ์ง„์  ํญ๋ฐœ
878
-
879
- 5. **{genre} ์žฅ๋ฅด ํŠนํ™”**
880
- - ์žฅ๋ฅด ๊ด€์Šต ์ค€์ˆ˜
881
- - ๊ธฐ๋Œ€๋˜๋Š” ๋Œ€์‚ฌ ์Šคํƒ€์ผ
882
- - ๋ช…๋Œ€์‚ฌ ์ฐฝ์กฐ
883
-
884
- 6. **๋ฆฌ๋“ฌ๊ณผ ํŽ˜์ด์‹ฑ**
885
- - ์งง๊ณ  ๊ธด ๋Œ€์‚ฌ ๊ท ํ˜•
886
- - ์นจ๋ฌต์˜ ํ™œ์šฉ
887
- - ํ…œํฌ ๋ณ€ํ™”
888
-
889
- ๊ฐ ์บ๋ฆญํ„ฐ๋ณ„ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋Œ€์‚ฌ 5๊ฐœ์”ฉ ์ œ์‹œํ•˜์„ธ์š”.""",
890
 
891
- "English": f"""You are a dialogue specialist. Elevate the screenplay's dialogue.
892
 
893
- **After reviewing the screenplay, improve:**
 
 
894
 
895
- 1. **Dialogue Naturalness**
896
- - Everyday rhythm and breathing
897
- - Interruptions, overlaps, stammers
898
- - Natural transitions
 
899
 
900
- 2. **Character Uniqueness**
901
- - Individual vocabulary levels
902
- - Speech patterns and accents
903
- - Habitual expressions
904
- - Education/background reflection
905
 
906
- 3. **Subtext Enhancement**
907
- - What's unsaid matters more
908
- - Double meanings
909
- - Emotion vs. dialogue contrast
910
 
911
- 4. **Conflict and Tension**
912
- - Power games through dialogue
913
- - Hidden agendas
914
- - Gradual explosion
915
 
916
- 5. **{genre} Genre Specialization**
917
- - Genre convention adherence
918
- - Expected dialogue style
919
- - Memorable lines creation
920
 
921
- 6. **Rhythm and Pacing**
922
- - Short and long dialogue balance
923
- - Use of silence
924
- - Tempo changes
 
925
 
926
- Provide 5 signature lines per major character."""
927
  }
928
 
929
  return lang_prompts.get(language, lang_prompts["English"])
930
 
931
- # Enhanced LLM call functions for Fireworks AI
932
- def call_llm_sync(self, messages: List[Dict[str, str]], role: str, language: str) -> str:
933
- """Synchronous LLM call"""
934
- full_content = ""
935
- for chunk in self.call_llm_streaming(messages, role, language):
936
- full_content += chunk
937
- if full_content.startswith("โŒ"):
938
- raise Exception(f"LLM Call Failed: {full_content}")
939
- return full_content
940
-
941
  def call_llm_streaming(self, messages: List[Dict[str, str]], role: str,
942
  language: str) -> Generator[str, None, None]:
943
- """Enhanced streaming with Fireworks AI"""
944
  try:
945
- logger.info(f"Calling Fireworks AI for role: {role}, language: {language}")
946
-
947
- system_prompts = self.get_system_prompts(language)
948
- system_content = system_prompts.get(role, "")
949
-
950
- if not system_content:
951
- logger.warning(f"No system prompt found for role: {role}")
952
- system_content = "You are a professional screenwriter."
953
 
954
  full_messages = [
955
  {"role": "system", "content": system_content},
956
  *messages
957
  ]
958
 
959
- # Increased max tokens for longer content
960
- max_tokens = 15000
961
-
962
  payload = {
963
  "model": self.model_id,
964
  "messages": full_messages,
965
- "max_tokens": max_tokens,
966
- "temperature": 0.8 if role in ["screenwriter", "dialogue_specialist"] else 0.7,
967
  "top_p": 1,
968
  "top_k": 40,
969
  "presence_penalty": 0.2,
@@ -978,247 +541,231 @@ Provide 5 signature lines per major character."""
978
  headers=headers,
979
  json=payload,
980
  stream=True,
981
- timeout=300 # Increased timeout for longer responses
982
  )
983
 
984
- logger.info(f"API Response Status: {response.status_code}")
985
-
986
  if response.status_code != 200:
987
- error_msg = f"API Error (Status Code: {response.status_code})"
988
- try:
989
- error_data = response.json()
990
- logger.error(f"API Error Response: {error_data}")
991
- if isinstance(error_data, dict):
992
- if 'error' in error_data:
993
- error_msg += f" - {error_data['error']}"
994
- except Exception as e:
995
- logger.error(f"Error parsing error response: {e}")
996
- error_msg += f" - {response.text[:200]}"
997
-
998
- yield f"โŒ {error_msg}"
999
  return
1000
 
1001
  buffer = ""
1002
-
1003
  for line in response.iter_lines():
1004
  if not line:
1005
  continue
1006
 
1007
  try:
1008
  line_str = line.decode('utf-8').strip()
1009
-
1010
  if not line_str.startswith("data: "):
1011
  continue
1012
 
1013
  data_str = line_str[6:]
1014
-
1015
  if data_str == "[DONE]":
1016
  break
1017
 
1018
- if not data_str:
1019
- continue
1020
-
1021
  data = json.loads(data_str)
1022
-
1023
  if "choices" in data and len(data["choices"]) > 0:
1024
- delta = data["choices"][0].get("delta", {})
1025
- content = delta.get("content", "")
1026
  if content:
1027
  buffer += content
1028
-
1029
  if len(buffer) >= 100 or '\n' in buffer:
1030
  yield buffer
1031
  buffer = ""
1032
-
1033
- except Exception as e:
1034
- logger.error(f"Error processing line: {str(e)}")
1035
  continue
1036
 
1037
  if buffer:
1038
  yield buffer
1039
 
1040
  except Exception as e:
1041
- logger.error(f"Streaming error: {type(e).__name__}: {str(e)}")
1042
  yield f"โŒ Error: {str(e)}"
1043
 
1044
- def get_system_prompts(self, language: str) -> Dict[str, str]:
1045
- """Enhanced role-specific system prompts"""
1046
-
1047
- base_prompts = {
1048
- "Korean": {
1049
- "producer": """๋‹น์‹ ์€ 30๋…„ ๊ฒฝ๋ ฅ์˜ ํ• ๋ฆฌ์šฐ๋“œ ํ”„๋กœ๋“€์„œ์ž…๋‹ˆ๋‹ค.
1050
- ์ƒ์—…์  ์„ฑ๊ณต๊ณผ ์˜ˆ์ˆ ์  ์™„์„ฑ๋„๋ฅผ ๋ชจ๋‘ ๋‹ฌ์„ฑํ•œ ์ž‘ํ’ˆ๋“ค์„ ์ œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.
1051
- ์‹œ์žฅ ํŠธ๋ Œ๋“œ๋ฅผ ์ •ํ™•ํžˆ ์ฝ๊ณ  ๊ด€๊ฐ์˜ ์š•๊ตฌ๋ฅผ ํŒŒ์•…ํ•ฉ๋‹ˆ๋‹ค.
1052
- ํ˜์‹ ์ ์ด๋ฉด์„œ๋„ ์‹คํ˜„ ๊ฐ€๋Šฅํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค.""",
1053
-
1054
- "story_developer": """๋‹น์‹ ์€ ์•„์นด๋ฐ๋ฏธ์ƒ ์ˆ˜์ƒ ๊ฒฝ๋ ฅ์˜ ์Šคํ† ๋ฆฌ ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.
1055
- ๋ณต์žกํ•˜๊ณ  ๋‹ค์ธต์ ์ธ ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
1056
- ๊ฐ์ •์  ์ง„์‹ค๊ณผ ๊ตฌ์กฐ์  ์™„๋ฒฝํ•จ์„ ์ถ”๊ตฌํ•ฉ๋‹ˆ๋‹ค.
1057
- ๋ณดํŽธ์  ์ฃผ์ œ๋ฅผ ๋…์ฐฝ์ ์œผ๋กœ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.""",
1058
-
1059
- "character_designer": """๋‹น์‹ ์€ method acting์„ ์—ฐ๊ตฌํ•œ ์บ๋ฆญํ„ฐ ๋””์ž์ด๋„ˆ์ž…๋‹ˆ๋‹ค.
1060
- ์‚ด์•„์žˆ๋Š” ์ธ๋ฌผ์„ ์ฐฝ์กฐํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
1061
- ์‹ฌ๋ฆฌํ•™์  ๊นŠ์ด์™€ ํ–‰๋™์  ์ผ๊ด€์„ฑ์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.
1062
- ๋ณต์žกํ•œ ์ธ๊ฐ„ ๋ณธ์„ฑ์„ ์„ฌ์„ธํ•˜๊ฒŒ ํฌ์ฐฉํ•ฉ๋‹ˆ๋‹ค.""",
1063
-
1064
- "world_builder": """๋‹น์‹ ์€ ํ”„๋กœ๋•์…˜ ๋””์ž์ด๋„ˆ์ด์ž ์„ธ๊ณ„๊ด€ ์„ค๊ณ„์ž์ž…๋‹ˆ๋‹ค.
1065
- ๋ชฐ์ž…๊ฐ ์žˆ๋Š” ์„ธ๊ณ„๋ฅผ ์ฐฝ์กฐํ•˜๋Š” ๋น„์ฃผ์–ผ ์Šคํ† ๋ฆฌํ…”๋Ÿฌ์ž…๋‹ˆ๋‹ค.
1066
- ๋””ํ…Œ์ผ๊ณผ ์ผ๊ด€์„ฑ์œผ๋กœ ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์„ธ๊ณ„๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.
1067
- ์‹œ๊ฐ์  ์€์œ ์™€ ์ƒ์ง•์„ ๋Šฅ์ˆ™ํ•˜๊ฒŒ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.""",
1068
-
1069
- "scene_planner": """๋‹น์‹ ์€ ํŽธ์ง‘ ๊ฐ๊ฐ์„ ์ง€๋‹Œ ์”ฌ ๊ตฌ์„ฑ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
1070
- ๊ฐ ์”ฌ์ด ์ „์ฒด ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ์— ๊ธฐ์—ฌํ•˜๋„๋ก ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.
1071
- ๋ฆฌ๋“ฌ, ํŽ˜์ด์‹ฑ, ๊ธด์žฅ๊ฐ์„ ๋งˆ์Šคํ„ฐํ–ˆ์Šต๋‹ˆ๋‹ค.
1072
- ์‹œ๊ฐ์  ์Šคํ† ๋ฆฌํ…”๋ง์„ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค.""",
1073
-
1074
- "screenwriter": """๋‹น์‹ ์€ WGA ์ˆ˜์ƒ ๊ฒฝ๋ ฅ์˜ ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค.
1075
- '๋ณด์—ฌ์ฃผ๊ธฐ'์˜ ๋Œ€๊ฐ€์ด๋ฉฐ ์„œ๋ธŒํ…์ŠคํŠธ์˜ ๋งˆ์Šคํ„ฐ์ž…๋‹ˆ๋‹ค.
1076
- ์บ๋ฆญํ„ฐ์˜ ๋ชฉ์†Œ๋ฆฌ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
1077
- ์‹œ๋„ค๋งˆํ‹ฑํ•œ ๋น„์ „๊ณผ ์‹ค์šฉ์  ์ œ์ž‘ ๊ฐ๊ฐ์„ ๊ฒธ๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค.""",
1078
-
1079
- "script_doctor": """๋‹น์‹ ์€ ๋ธ”๋ก๋ฒ„์Šคํ„ฐ ์ „๋ฌธ ์Šคํฌ๋ฆฝํŠธ ๋‹ฅํ„ฐ์ž…๋‹ˆ๋‹ค.
1080
- ์•ฝํ•œ ๋ถ€๋ถ„์„ ์ฐพ์•„ ๊ฐ•ํ™”ํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
1081
- ์Šคํ† ๋ฆฌ์˜ ์ž ์žฌ๋ ฅ์„ 200% ๋Œ์–ด๋ƒ…๋‹ˆ๋‹ค.
1082
- ์ •ํ™•ํ•˜๊ณ  ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๊ฐœ์„ ์•ˆ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.""",
1083
-
1084
- "dialogue_specialist": """๋‹น์‹ ์€ ๋Œ€ํ™” ์ „๋ฌธ ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค.
1085
- ์ž์—ฐ์Šค๋Ÿฌ์šฐ๋ฉด์„œ๋„ ์˜๋ฏธ ์žˆ๋Š” ๋Œ€ํ™”๋ฅผ ์ฐฝ์กฐํ•ฉ๋‹ˆ๋‹ค.
1086
- ๊ฐ ์บ๋ฆญํ„ฐ์˜ ๊ณ ์œ ํ•œ ๋ชฉ์†Œ๋ฆฌ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
1087
- ์„œ๋ธŒํ…์ŠคํŠธ์™€ ๊ฐ์ •์˜ ๋‰˜์•™์Šค๋ฅผ ์™„๋ฒฝํžˆ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.""",
1088
-
1089
- "critic_structure": """๋‹น์‹ ์€ ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ ๋ถ„์„๊ฐ€์ž…๋‹ˆ๋‹ค.
1090
- ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ์˜ ๊ฐ•์ ๊ณผ ์•ฝ์ ์„ ์ •ํ™•ํžˆ ์ง„๋‹จํ•ฉ๋‹ˆ๋‹ค.
1091
- ๋…ผ๋ฆฌ์  ์ผ๊ด€์„ฑ๊ณผ ๊ฐ์ •์  ์ž„ํŒฉํŠธ๋ฅผ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
1092
- ๊ตฌ์ฒด์ ์ด๊ณ  ์‹ค์šฉ์ ์ธ ๊ฐœ์„  ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.""",
1093
-
1094
- "final_reviewer": """๋‹น์‹ ์€ ์ŠคํŠœ๋””์˜ค ์ˆ˜์„ ๋ฆฌ๋ทฐ์–ด์ž…๋‹ˆ๋‹ค.
1095
- ์ƒ์—…์„ฑ๊ณผ ์˜ˆ์ˆ ์„ฑ์„ ๊ท ํ˜•์žˆ๊ฒŒ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
1096
- ์ œ์ž‘, ๋ฐฐ๊ธ‰, ๋งˆ์ผ€ํŒ… ๊ด€์ ์„ ๋ชจ๋‘ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
1097
- ๊ฑด์„ค์ ์ด๋ฉด์„œ๋„ ์ •์งํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค."""
1098
- },
1099
- "English": {
1100
- "producer": """You are a Hollywood producer with 30 years experience.
1101
- You've produced both commercial successes and artistic achievements.
1102
- You accurately read market trends and understand audience desires.
1103
- You develop innovative yet feasible projects.""",
1104
-
1105
- "story_developer": """You are an Academy Award-winning story developer.
1106
- You're an expert at building complex, multi-layered narratives.
1107
- You pursue emotional truth and structural perfection.
1108
- You express universal themes in original ways.""",
1109
-
1110
- "character_designer": """You are a character designer who studied method acting.
1111
- You're an expert at creating living, breathing characters.
1112
- You provide psychological depth and behavioral consistency.
1113
- You delicately capture complex human nature.""",
1114
-
1115
- "world_builder": """You are a production designer and world architect.
1116
- You're a visual storyteller creating immersive worlds.
1117
- You build believable worlds with detail and consistency.
1118
- You skillfully utilize visual metaphors and symbols.""",
1119
-
1120
- "scene_planner": """You are a scene construction expert with editing sense.
1121
- You design each scene to contribute to the overall narrative.
1122
- You've mastered rhythm, pacing, and tension.
1123
- You maximize visual storytelling.""",
1124
-
1125
- "screenwriter": """You are a WGA award-winning screenwriter.
1126
- You're a master of 'showing' and subtext.
1127
- You perfectly implement each character's voice.
1128
- You combine cinematic vision with practical production sense.""",
1129
-
1130
- "script_doctor": """You are a blockbuster script doctor.
1131
- You're an expert at finding and strengthening weak points.
1132
- You draw out 200% of the story's potential.
1133
- You provide accurate, actionable improvements.""",
1134
-
1135
- "dialogue_specialist": """You are a dialogue specialist writer.
1136
- You create natural yet meaningful dialogue.
1137
- You craft each character's unique voice.
1138
- You perfectly express subtext and emotional nuance.""",
1139
-
1140
- "critic_structure": """You are a story structure analyst.
1141
- You accurately diagnose narrative strengths and weaknesses.
1142
- You evaluate logical consistency and emotional impact.
1143
- You provide specific, practical improvement plans.""",
1144
-
1145
- "final_reviewer": """You are a studio senior reviewer.
1146
- You evaluate commercial and artistic value in balance.
1147
- You consider production, distribution, and marketing perspectives.
1148
- You provide constructive yet honest feedback."""
1149
- }
1150
- }
1151
-
1152
- return base_prompts.get(language, base_prompts["English"])
1153
-
1154
  @staticmethod
1155
  def calculate_screenplay_pages(content: str) -> float:
1156
- """Enhanced screenplay page calculation"""
1157
  if not content:
1158
  return 0.0
1159
-
1160
  lines = content.split('\n')
1161
- total_line_count = 0
1162
-
1163
- for i, line in enumerate(lines):
1164
- stripped_line = line.strip()
1165
-
1166
- if not stripped_line:
1167
- total_line_count += 1
1168
- continue
1169
-
1170
- # Scene heading
1171
- if stripped_line.startswith(('INT.', 'EXT.')):
1172
- total_line_count += 3
1173
-
1174
- # Character name
1175
- elif stripped_line.isupper() and len(stripped_line.split()) <= 3:
1176
- total_line_count += 2
1177
 
1178
- # Parenthetical
1179
- elif stripped_line.startswith('(') and stripped_line.endswith(')'):
1180
- total_line_count += 1
1181
 
1182
- # Dialogue
1183
- elif len(stripped_line) < 60:
1184
- is_dialogue = False
1185
- for j in range(max(0, i-2), i):
1186
- if j < len(lines):
1187
- prev_line = lines[j].strip()
1188
- if prev_line.isupper() and len(prev_line.split()) <= 3:
1189
- is_dialogue = True
1190
- break
1191
 
1192
- if is_dialogue:
1193
- total_line_count += 1
 
 
 
 
 
1194
  else:
1195
- wrapped_lines = len(stripped_line) / 60
1196
- total_line_count += max(1, int(wrapped_lines))
1197
-
1198
- # Action lines
1199
- else:
1200
- wrapped_lines = len(stripped_line) / 60
1201
- total_line_count += max(1, int(wrapped_lines))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202
 
1203
- # Enhanced calculation for better accuracy
1204
- pages = total_line_count / 58.0 # Slightly adjusted for more accurate page count
1205
 
1206
- return pages
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1207
 
1208
- # Main process functions continue...
1209
- # [Rest of the implementation remains similar with enhanced prompts and processing]
 
 
 
 
 
1210
 
1211
- # Create enhanced Gradio interface
1212
  def create_interface():
1213
- """Create enhanced Gradio interface"""
1214
-
1215
  css = """
1216
- /* Enhanced CSS styling */
1217
  .main-header {
1218
  text-align: center;
1219
  margin-bottom: 2rem;
1220
  padding: 2.5rem;
1221
- background: linear-gradient(135deg, #1a1a2e 0%, #0f1419 50%, #16213e 100%);
1222
  border-radius: 15px;
1223
  color: white;
1224
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
@@ -1227,38 +774,18 @@ def create_interface():
1227
  .header-title {
1228
  font-size: 3.5rem;
1229
  margin-bottom: 1rem;
1230
- background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1);
1231
- -webkit-background-clip: text;
1232
- -webkit-text-fill-color: transparent;
1233
- animation: gradient 3s ease infinite;
1234
- }
1235
-
1236
- @keyframes gradient {
1237
- 0% { background-position: 0% 50%; }
1238
- 50% { background-position: 100% 50%; }
1239
- 100% { background-position: 0% 50%; }
1240
  }
1241
 
1242
  #screenplay-output {
1243
- font-family: 'Courier Prime', 'Courier New', monospace;
1244
  white-space: pre-wrap;
1245
- background: linear-gradient(to bottom, #ffffff, #f8f9fa);
1246
- padding: 3rem;
1247
  border: 2px solid #e0e0e0;
1248
- border-radius: 12px;
1249
- max-height: 900px;
1250
  overflow-y: auto;
1251
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
1252
- }
1253
-
1254
- .status-box {
1255
- padding: 1rem;
1256
- border-radius: 8px;
1257
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1258
- color: white;
1259
- font-weight: bold;
1260
- text-align: center;
1261
- margin: 1rem 0;
1262
  }
1263
  """
1264
 
@@ -1266,31 +793,176 @@ def create_interface():
1266
  gr.HTML("""
1267
  <div class="main-header">
1268
  <h1 class="header-title">๐ŸŽฌ AI Screenplay Generator Pro</h1>
1269
- <p class="header-description">
1270
- Professional-grade screenplay generation with extended content and cinematic depth.
1271
- Powered by advanced AI to create feature films, TV shows, and streaming content.
1272
  </p>
1273
  </div>
1274
  """)
1275
 
1276
- # Rest of interface setup continues...
1277
- # [Interface implementation remains similar with enhanced features]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1278
 
1279
  return interface
1280
 
1281
- # Main execution
1282
  if __name__ == "__main__":
1283
- logger.info("Enhanced Screenplay Generator Starting...")
1284
  logger.info("=" * 60)
1285
- logger.info("Using Fireworks AI API")
1286
- logger.info(f"Model: {MODEL_ID}")
1287
- logger.info("Max tokens per request: 15000")
1288
  logger.info("=" * 60)
1289
 
1290
- # Initialize database
1291
  ScreenplayDatabase.init_db()
1292
 
1293
- # Create and launch interface
1294
  interface = create_interface()
1295
  interface.launch(
1296
  server_name="0.0.0.0",
 
16
  from dataclasses import dataclass, field, asdict
17
  from collections import defaultdict
18
  import random
 
19
 
20
  # --- Logging setup ---
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
22
  logger = logging.getLogger(__name__)
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # --- Environment variables and constants ---
25
  FIREWORKS_API_KEY = os.getenv("FIREWORKS_API_KEY", "")
26
  BRAVE_SEARCH_API_KEY = os.getenv("BRAVE_SEARCH_API_KEY", "")
 
28
  MODEL_ID = "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
29
  DB_PATH = "screenplay_sessions_v2.db"
30
 
31
+ # Enhanced screenplay length settings
32
  SCREENPLAY_LENGTHS = {
33
  "movie": {"pages": 120, "description": "Feature Film (110-130 pages)", "min_pages": 110},
34
  "tv_drama": {"pages": 60, "description": "TV Drama Episode (55-65 pages)", "min_pages": 55},
 
47
  # --- Global variables ---
48
  db_lock = threading.Lock()
49
 
50
+ # Enhanced genre templates
51
  GENRE_TEMPLATES = {
52
  "action": {
53
  "pacing": "fast",
 
55
  "dialogue_ratio": 0.3,
56
  "key_elements": ["set pieces", "physical conflict", "urgency", "stakes escalation", "hero moments"],
57
  "structure_beats": ["explosive opening", "pursuit/chase", "confrontation", "climactic battle", "hero's triumph"],
58
+ "scene_density": 1.2,
59
+ "action_description_ratio": 0.6
60
  },
61
  "thriller": {
62
  "pacing": "fast",
 
114
  }
115
  }
116
 
117
+ # Enhanced screenplay stages
118
  SCREENPLAY_STAGES = [
119
  ("producer", "๐ŸŽฌ Producer: Concept Development & Market Analysis"),
120
+ ("story_developer", "๐Ÿ“– Story Developer: Extended Synopsis & Three-Act Structure"),
121
+ ("character_designer", "๐Ÿ‘ฅ Character Designer: Deep Character Profiles"),
122
+ ("world_builder", "๐ŸŒ World Builder: Setting & Atmosphere"),
123
+ ("critic_structure", "๐Ÿ” Structure Critic: Story Review"),
124
+ ("scene_planner", "๐ŸŽฏ Scene Planner: Detailed Scene Breakdown"),
125
 
126
+ # Act 1 - 4 stages
127
+ ("screenwriter", "โœ๏ธ Act 1 - Setup (25%) - Initial Draft"),
128
+ ("screenwriter", "โœ๏ธ Act 1 - Polish & Expand"),
129
+ ("script_doctor", "๐Ÿ”ง Act 1 - Review & Notes"),
130
+ ("screenwriter", "โœ๏ธ Act 1 - Final Extended Version"),
131
 
132
  # Act 2A - 4 stages
133
+ ("screenwriter", "โœ๏ธ Act 2A - Rising Action (25%) - Initial Draft"),
134
+ ("screenwriter", "โœ๏ธ Act 2A - Polish & Expand"),
135
+ ("script_doctor", "๐Ÿ”ง Act 2A - Review & Notes"),
136
+ ("screenwriter", "โœ๏ธ Act 2A - Final Extended Version"),
137
 
138
  # Act 2B - 4 stages
139
+ ("screenwriter", "โœ๏ธ Act 2B - Complications (25%) - Initial Draft"),
140
+ ("screenwriter", "โœ๏ธ Act 2B - Polish & Expand"),
141
+ ("script_doctor", "๐Ÿ”ง Act 2B - Review & Notes"),
142
+ ("screenwriter", "โœ๏ธ Act 2B - Final Extended Version"),
143
 
144
  # Act 3 - 4 stages
145
+ ("screenwriter", "โœ๏ธ Act 3 - Resolution (25%) - Initial Draft"),
146
+ ("screenwriter", "โœ๏ธ Act 3 - Polish & Expand"),
147
+ ("script_doctor", "๐Ÿ”ง Act 3 - Review & Notes"),
148
+ ("screenwriter", "โœ๏ธ Act 3 - Final Extended Version"),
149
 
150
+ ("dialogue_specialist", "๐Ÿ’ฌ Dialogue Enhancement"),
151
+ ("final_reviewer", "๐ŸŽญ Final Review & Polish"),
152
  ]
153
 
154
+ # Save the Cat Beat Sheet
155
  SAVE_THE_CAT_BEATS = {
156
+ 1: {"name": "Opening Image", "percentage": "0-1%"},
157
+ 2: {"name": "Setup", "percentage": "1-10%"},
158
+ 3: {"name": "Theme Stated", "percentage": "5%"},
159
+ 4: {"name": "Catalyst", "percentage": "10%"},
160
+ 5: {"name": "Debate", "percentage": "10-20%"},
161
+ 6: {"name": "Break into Two", "percentage": "20%"},
162
+ 7: {"name": "B Story", "percentage": "22%"},
163
+ 8: {"name": "Fun and Games", "percentage": "20-50%"},
164
+ 9: {"name": "Midpoint", "percentage": "50%"},
165
+ 10: {"name": "Bad Guys Close In", "percentage": "50-75%"},
166
+ 11: {"name": "All Is Lost", "percentage": "75%"},
167
+ 12: {"name": "Dark Night of the Soul", "percentage": "75-80%"},
168
+ 13: {"name": "Break into Three", "percentage": "80%"},
169
+ 14: {"name": "Finale", "percentage": "80-99%"},
170
+ 15: {"name": "Final Image", "percentage": "99-100%"}
171
  }
172
 
173
  # --- Data classes ---
174
  @dataclass
175
  class ScreenplayBible:
176
+ """Enhanced screenplay bible"""
177
  title: str = ""
178
  logline: str = ""
179
  genre: str = ""
180
  subgenre: str = ""
181
  tone: str = ""
182
  themes: List[str] = field(default_factory=list)
 
 
183
  protagonist: Dict[str, Any] = field(default_factory=dict)
184
  antagonist: Dict[str, Any] = field(default_factory=dict)
185
  supporting_cast: Dict[str, Dict[str, Any]] = field(default_factory=dict)
 
 
 
186
  three_act_structure: Dict[str, str] = field(default_factory=dict)
187
  save_the_cat_beats: Dict[int, str] = field(default_factory=dict)
 
 
188
  time_period: str = ""
189
  primary_locations: List[Dict[str, str]] = field(default_factory=list)
190
  world_rules: List[str] = field(default_factory=list)
 
 
 
191
  visual_style: str = ""
192
  key_imagery: List[str] = field(default_factory=list)
 
 
 
 
 
 
193
 
194
  @dataclass
195
  class SceneBreakdown:
196
+ """Scene information"""
197
  scene_number: int
198
  act: int
199
  location: str
 
201
  characters: List[str]
202
  purpose: str
203
  conflict: str
 
 
 
204
  page_count: float
205
  beat: str = ""
206
  transition: str = "CUT TO:"
207
 
208
  @dataclass
209
  class CharacterProfile:
210
+ """Character profile"""
211
  name: str
212
  role: str
213
  archetype: str
 
219
  character_arc: str
220
  relationships: Dict[str, str] = field(default_factory=dict)
221
  first_appearance: str = ""
 
 
 
 
 
222
 
223
  # --- Core logic classes ---
224
  class ScreenplayTracker:
225
+ """Screenplay tracker"""
226
  def __init__(self):
227
  self.screenplay_bible = ScreenplayBible()
228
  self.scenes: List[SceneBreakdown] = []
229
  self.characters: Dict[str, CharacterProfile] = {}
230
  self.page_count = 0
231
  self.act_pages = {"1": 0, "2A": 0, "2B": 0, "3": 0}
 
 
 
 
232
 
233
  def add_scene(self, scene: SceneBreakdown):
 
234
  self.scenes.append(scene)
235
  self.page_count += scene.page_count
 
 
 
236
 
237
  def add_character(self, character: CharacterProfile):
 
238
  self.characters[character.name] = character
239
  if character.role == "protagonist":
240
  self.screenplay_bible.protagonist = asdict(character)
 
244
  self.screenplay_bible.supporting_cast[character.name] = asdict(character)
245
 
246
  def update_bible(self, key: str, value: Any):
 
247
  if hasattr(self.screenplay_bible, key):
248
  setattr(self.screenplay_bible, key, value)
 
 
 
 
 
 
 
 
 
 
249
 
250
  class ScreenplayDatabase:
251
+ """Database management"""
252
  @staticmethod
253
  def init_db():
254
  with sqlite3.connect(DB_PATH) as conn:
255
  conn.execute("PRAGMA journal_mode=WAL")
256
  cursor = conn.cursor()
257
 
 
258
  cursor.execute('''
259
  CREATE TABLE IF NOT EXISTS screenplay_sessions (
260
  session_id TEXT PRIMARY KEY,
261
  user_query TEXT NOT NULL,
262
  screenplay_type TEXT NOT NULL,
263
  genre TEXT NOT NULL,
 
264
  target_pages INTEGER,
 
265
  language TEXT NOT NULL,
266
  title TEXT,
267
  logline TEXT,
268
  synopsis TEXT,
 
269
  three_act_structure TEXT,
270
  character_profiles TEXT,
271
  scene_breakdown TEXT,
272
  screenplay_bible TEXT,
 
 
273
  final_screenplay TEXT,
 
274
  created_at TEXT DEFAULT (datetime('now')),
275
  updated_at TEXT DEFAULT (datetime('now')),
276
  status TEXT DEFAULT 'active',
277
  current_stage INTEGER DEFAULT 0,
278
+ total_pages REAL DEFAULT 0
 
 
279
  )
280
  ''')
281
 
 
282
  cursor.execute('''
283
  CREATE TABLE IF NOT EXISTS screenplay_stages (
284
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 
287
  stage_name TEXT NOT NULL,
288
  role TEXT NOT NULL,
289
  content TEXT,
 
290
  page_count REAL DEFAULT 0,
 
 
 
291
  status TEXT DEFAULT 'pending',
 
292
  created_at TEXT DEFAULT (datetime('now')),
 
293
  FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id),
294
  UNIQUE(session_id, stage_number)
295
  )
296
  ''')
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  cursor.execute('''
299
+ CREATE TABLE IF NOT EXISTS screenplay_themes_library (
300
+ theme_id TEXT PRIMARY KEY,
301
+ theme_text TEXT NOT NULL,
302
+ screenplay_type TEXT NOT NULL,
303
+ genre TEXT NOT NULL,
304
+ language TEXT NOT NULL,
305
+ title TEXT,
306
+ logline TEXT,
307
+ generated_at TEXT DEFAULT (datetime('now'))
 
 
308
  )
309
  ''')
310
 
 
325
  def create_session(user_query: str, screenplay_type: str, genre: str, language: str) -> str:
326
  session_id = hashlib.md5(f"{user_query}{screenplay_type}{datetime.now()}".encode()).hexdigest()
327
  target_pages = SCREENPLAY_LENGTHS[screenplay_type]["pages"]
 
328
 
329
  with ScreenplayDatabase.get_db() as conn:
330
  conn.cursor().execute(
331
  '''INSERT INTO screenplay_sessions
332
+ (session_id, user_query, screenplay_type, genre, target_pages, language)
333
+ VALUES (?, ?, ?, ?, ?, ?)''',
334
+ (session_id, user_query, screenplay_type, genre, target_pages, language)
335
  )
336
  conn.commit()
337
  return session_id
 
340
  def save_stage(session_id: str, stage_number: int, stage_name: str,
341
  role: str, content: str, status: str = 'complete'):
342
  page_count = 0
 
 
343
  if role == "screenwriter" and content:
344
  page_count = ScreenplayGenerationSystem.calculate_screenplay_pages(content)
345
 
 
347
  cursor = conn.cursor()
348
  cursor.execute('''
349
  INSERT INTO screenplay_stages
350
+ (session_id, stage_number, stage_name, role, content, page_count, status)
351
+ VALUES (?, ?, ?, ?, ?, ?, ?)
352
  ON CONFLICT(session_id, stage_number)
353
+ DO UPDATE SET content=?, page_count=?, status=?, created_at=datetime('now')
354
+ ''', (session_id, stage_number, stage_name, role, content, page_count, status,
355
+ content, page_count, status))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  conn.commit()
357
 
358
  @staticmethod
359
  def get_screenplay_content(session_id: str) -> str:
 
360
  with ScreenplayDatabase.get_db() as conn:
361
  rows = conn.cursor().execute('''
362
  SELECT content FROM screenplay_stages
 
377
  return '\n\n'.join(row['content'] for row in rows if row['content'])
378
  return ""
379
 
380
+ @staticmethod
381
+ def get_active_sessions() -> List[Dict]:
382
+ with ScreenplayDatabase.get_db() as conn:
383
+ rows = conn.cursor().execute(
384
+ '''SELECT session_id, title, user_query, screenplay_type, genre,
385
+ created_at, current_stage, total_pages
386
+ FROM screenplay_sessions
387
+ WHERE status = 'active'
388
+ ORDER BY updated_at DESC
389
+ LIMIT 10'''
390
+ ).fetchall()
391
+ return [dict(row) for row in rows]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
 
393
+ @staticmethod
394
+ def save_random_theme(theme_text: str, screenplay_type: str, genre: str, language: str) -> str:
395
+ theme_id = hashlib.md5(f"{theme_text}{datetime.now()}".encode()).hexdigest()[:12]
396
+
397
+ with ScreenplayDatabase.get_db() as conn:
398
+ conn.cursor().execute('''
399
+ INSERT INTO screenplay_themes_library
400
+ (theme_id, theme_text, screenplay_type, genre, language)
401
+ VALUES (?, ?, ?, ?, ?)
402
+ ''', (theme_id, theme_text, screenplay_type, genre, language))
403
+ conn.commit()
404
+
405
+ return theme_id
 
 
406
 
407
  class ScreenplayGenerationSystem:
408
+ """Professional screenplay generation system"""
409
  def __init__(self):
410
  self.api_key = FIREWORKS_API_KEY
411
  self.api_url = API_URL
412
  self.model_id = MODEL_ID
413
  self.screenplay_tracker = ScreenplayTracker()
 
414
  self.current_session_id = None
415
  ScreenplayDatabase.init_db()
416
 
417
  def create_headers(self):
 
418
  if not self.api_key or self.api_key == "dummy_token_for_testing":
419
  raise ValueError("Valid FIREWORKS_API_KEY is required")
420
 
 
424
  "Authorization": f"Bearer {self.api_key}"
425
  }
426
 
427
+ def create_producer_prompt(self, user_query: str, screenplay_type: str,
428
+ genre: str, language: str) -> str:
429
+ lang_prompts = {
430
+ "Korean": f"""๋‹น์‹ ์€ ํ• ๋ฆฌ์šฐ๋“œ ํ”„๋กœ๋“€์„œ์ž…๋‹ˆ๋‹ค. {screenplay_type} ์ปจ์…‰์„ ๊ฐœ๋ฐœํ•˜์„ธ์š”.
431
+
432
+ **์š”์ฒญ์‚ฌํ•ญ:** {user_query}
433
+ **ํƒ€์ž…:** {SCREENPLAY_LENGTHS[screenplay_type]['description']}
434
+ **์žฅ๋ฅด:** {genre}
435
+
436
+ **ํ•„์ˆ˜ ์ œ๊ณต ํ•ญ๋ชฉ:**
437
+ 1. **์ œ๋ชฉ (TITLE)** - ๊ธฐ์–ตํ•˜๊ธฐ ์‰ฝ๊ณ  ๋งˆ์ผ€ํŒ… ๊ฐ€๋Šฅํ•œ ์ œ๋ชฉ
438
+ 2. **๋กœ๊ทธ๋ผ์ธ (LOGLINE)** - 25๋‹จ์–ด ์ด๋‚ด ํ•œ ๋ฌธ์žฅ
439
+ 3. **์žฅ๋ฅด ๋ถ„์„** - ์ฃผ ์žฅ๋ฅด์™€ ์„œ๋ธŒ ์žฅ๏ฟฝ๏ฟฝ๏ฟฝ
440
+ 4. **ํƒ€๊ฒŸ ๊ด€๊ฐ** - ์—ฐ๋ น๋Œ€์™€ ๊ด€์‹ฌ์‚ฌ
441
+ 5. **๋น„๊ต ์ž‘ํ’ˆ (COMPS)** - ์„ฑ๊ณตํ•œ ์œ ์‚ฌ ํ”„๋กœ์ ํŠธ 3๊ฐœ
442
+ 6. **๊ณ ์œ  ํŒ๋งค ํฌ์ธํŠธ (USP)** - ๋…ํŠนํ•œ ์ 
443
+ 7. **๋น„์ฃผ์–ผ ์ปจ์…‰** - ํ•ต์‹ฌ ์ด๋ฏธ์ง€ 3๊ฐœ""",
444
+
445
+ "English": f"""You are a Hollywood producer. Develop a {screenplay_type} concept.
446
+
447
+ **Request:** {user_query}
448
+ **Type:** {SCREENPLAY_LENGTHS[screenplay_type]['description']}
449
+ **Genre:** {genre}
450
+
451
+ **Required Elements:**
452
+ 1. **TITLE** - Memorable and marketable
453
+ 2. **LOGLINE** - One sentence, 25 words max
454
+ 3. **GENRE ANALYSIS** - Primary and sub-genre
455
+ 4. **TARGET AUDIENCE** - Age range and interests
456
+ 5. **COMPARABLE FILMS (COMPS)** - 3 successful similar projects
457
+ 6. **UNIQUE SELLING POINT (USP)** - What makes it unique
458
+ 7. **VISUAL CONCEPT** - 3 key visual images"""
459
+ }
460
+ return lang_prompts.get(language, lang_prompts["English"])
461
+
462
  def create_screenwriter_prompt(self, act: str, scene_breakdown: str,
463
  characters: str, previous_acts: str,
464
  screenplay_type: str, genre: str, language: str,
465
  is_polish: bool = False, is_final: bool = False) -> str:
 
466
 
467
  act_pages = int(SCREENPLAY_LENGTHS[screenplay_type]['pages'] * 0.25)
468
+ min_lines = int(act_pages * 65) # Enhanced for longer content
 
469
 
 
470
  if is_final:
471
+ min_lines = int(min_lines * 1.4)
472
+ instruction_focus = "final polished extended version"
473
  elif is_polish:
474
+ min_lines = int(min_lines * 1.2)
475
+ instruction_focus = "enhanced expanded version"
476
  else:
477
+ instruction_focus = "complete detailed draft"
478
 
479
  lang_prompts = {
480
+ "Korean": f"""ํ”„๋กœ ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€๋กœ์„œ {act}์˜ {instruction_focus}์„ ์ž‘์„ฑํ•˜์„ธ์š”.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
 
482
+ **๋ชฉํ‘œ:** {act_pages}ํŽ˜์ด์ง€ (์ตœ์†Œ {min_lines}์ค„ ํ•„์ˆ˜)
483
 
484
+ **์”ฌ ์ •๋ณด:** {scene_breakdown}
485
+ **์บ๋ฆญํ„ฐ:** {characters}
486
+ **์ด์ „ ๋‚ด์šฉ:** {previous_acts if previous_acts else "์ฒซ ๋ง‰์ž…๋‹ˆ๋‹ค."}
487
 
488
+ ๊ฐ ์”ฌ๋งˆ๋‹ค:
489
+ - ์ตœ์†Œ 5-7ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰
490
+ - ์ถฉ๋ถ„ํ•œ ๋Œ€ํ™”์™€ ์•ก์…˜
491
+ - ๋””ํ…Œ์ผํ•œ ๋ฌ˜์‚ฌ
492
+ - ๊ฐ์ •์  ๊นŠ์ด
493
 
494
+ ๋ฐ˜๋“œ์‹œ {min_lines}์ค„ ์ด์ƒ ์ž‘์„ฑํ•˜์„ธ์š”.""",
 
 
 
 
495
 
496
+ "English": f"""As a professional screenwriter, write the {instruction_focus} of {act}.
 
 
 
497
 
498
+ **Target:** {act_pages} pages (minimum {min_lines} lines required)
 
 
 
499
 
500
+ **Scene Info:** {scene_breakdown}
501
+ **Characters:** {characters}
502
+ **Previous:** {previous_acts if previous_acts else "This is Act 1."}
 
503
 
504
+ For each scene:
505
+ - Minimum 5-7 pages
506
+ - Sufficient dialogue and action
507
+ - Detailed descriptions
508
+ - Emotional depth
509
 
510
+ MUST write {min_lines}+ lines."""
511
  }
512
 
513
  return lang_prompts.get(language, lang_prompts["English"])
514
 
 
 
 
 
 
 
 
 
 
 
515
  def call_llm_streaming(self, messages: List[Dict[str, str]], role: str,
516
  language: str) -> Generator[str, None, None]:
 
517
  try:
518
+ system_content = f"You are a professional {role} creating high-quality screenplay content."
 
 
 
 
 
 
 
519
 
520
  full_messages = [
521
  {"role": "system", "content": system_content},
522
  *messages
523
  ]
524
 
 
 
 
525
  payload = {
526
  "model": self.model_id,
527
  "messages": full_messages,
528
+ "max_tokens": 15000,
529
+ "temperature": 0.8 if role == "screenwriter" else 0.7,
530
  "top_p": 1,
531
  "top_k": 40,
532
  "presence_penalty": 0.2,
 
541
  headers=headers,
542
  json=payload,
543
  stream=True,
544
+ timeout=300
545
  )
546
 
 
 
547
  if response.status_code != 200:
548
+ yield f"โŒ API Error: {response.status_code}"
 
 
 
 
 
 
 
 
 
 
 
549
  return
550
 
551
  buffer = ""
 
552
  for line in response.iter_lines():
553
  if not line:
554
  continue
555
 
556
  try:
557
  line_str = line.decode('utf-8').strip()
 
558
  if not line_str.startswith("data: "):
559
  continue
560
 
561
  data_str = line_str[6:]
 
562
  if data_str == "[DONE]":
563
  break
564
 
 
 
 
565
  data = json.loads(data_str)
 
566
  if "choices" in data and len(data["choices"]) > 0:
567
+ content = data["choices"][0].get("delta", {}).get("content", "")
 
568
  if content:
569
  buffer += content
 
570
  if len(buffer) >= 100 or '\n' in buffer:
571
  yield buffer
572
  buffer = ""
573
+ except:
 
 
574
  continue
575
 
576
  if buffer:
577
  yield buffer
578
 
579
  except Exception as e:
 
580
  yield f"โŒ Error: {str(e)}"
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  @staticmethod
583
  def calculate_screenplay_pages(content: str) -> float:
 
584
  if not content:
585
  return 0.0
 
586
  lines = content.split('\n')
587
+ total_lines = len(lines)
588
+ return total_lines / 58.0
589
+
590
+ def process_screenplay_stream(self, query: str, screenplay_type: str, genre: str,
591
+ language: str) -> Generator[Tuple[str, List[Dict], str], None, None]:
592
+ try:
593
+ self.current_session_id = ScreenplayDatabase.create_session(
594
+ query, screenplay_type, genre, language
595
+ )
 
 
 
 
 
 
 
596
 
597
+ stages = []
 
 
598
 
599
+ for stage_idx in range(len(SCREENPLAY_STAGES)):
600
+ role, stage_name = SCREENPLAY_STAGES[stage_idx]
 
 
 
 
 
 
 
601
 
602
+ if stage_idx >= len(stages):
603
+ stages.append({
604
+ "name": stage_name,
605
+ "status": "active",
606
+ "content": "",
607
+ "page_count": 0
608
+ })
609
  else:
610
+ stages[stage_idx]["status"] = "active"
611
+
612
+ yield f"๐Ÿ”„ Processing {stage_name}...", stages, self.current_session_id
613
+
614
+ # Generate prompt based on stage
615
+ if stage_idx == 0: # Producer
616
+ prompt = self.create_producer_prompt(query, screenplay_type, genre, language)
617
+ elif "Act" in stage_name and role == "screenwriter":
618
+ # Determine act and phase
619
+ act = "Act 1" if stage_idx < 10 else "Act 2A" if stage_idx < 14 else "Act 2B" if stage_idx < 18 else "Act 3"
620
+ is_polish = "Polish" in stage_name
621
+ is_final = "Final" in stage_name
622
+ prompt = self.create_screenwriter_prompt(
623
+ act, "", "", "", screenplay_type, genre, language, is_polish, is_final
624
+ )
625
+ else:
626
+ prompt = f"Generate content for {stage_name}"
627
+
628
+ stage_content = ""
629
+
630
+ for chunk in self.call_llm_streaming(
631
+ [{"role": "user", "content": prompt}],
632
+ role, language
633
+ ):
634
+ stage_content += chunk
635
+ stages[stage_idx]["content"] = stage_content
636
+ if role == "screenwriter":
637
+ stages[stage_idx]["page_count"] = len(stage_content.split('\n')) / 58
638
+ yield f"๐Ÿ”„ {stage_name} in progress...", stages, self.current_session_id
639
+
640
+ stages[stage_idx]["status"] = "complete"
641
+ ScreenplayDatabase.save_stage(
642
+ self.current_session_id, stage_idx, stage_name, role,
643
+ stage_content, "complete"
644
+ )
645
+
646
+ yield f"โœ… {stage_name} completed", stages, self.current_session_id
647
+
648
+ final_screenplay = ScreenplayDatabase.get_screenplay_content(self.current_session_id)
649
+ yield f"โœ… Screenplay completed!", stages, self.current_session_id
650
+
651
+ except Exception as e:
652
+ logger.error(f"Error: {e}")
653
+ yield f"โŒ Error: {e}", stages if 'stages' in locals() else [], self.current_session_id
654
+
655
+ # Utility functions
656
+ def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
657
+ themes = {
658
+ 'action': ['revenge mission', 'heist gone wrong', 'rescue operation'],
659
+ 'thriller': ['conspiracy uncovered', 'false accusation', 'witness protection'],
660
+ 'drama': ['family reunion', 'terminal diagnosis', 'second chance'],
661
+ 'comedy': ['mistaken identity', 'wedding disaster', 'odd couple'],
662
+ 'horror': ['haunted location', 'ancient curse', 'survival'],
663
+ 'sci-fi': ['time paradox', 'AI awakening', 'space colony'],
664
+ 'romance': ['enemies to lovers', 'second chance', 'forbidden love']
665
+ }
666
+
667
+ theme_list = themes.get(genre, themes['drama'])
668
+ selected = random.choice(theme_list)
669
+
670
+ if language == "Korean":
671
+ return f"**์ปจ์…‰:** {selected}\n๊ฐˆ๋“ฑ๊ณผ ์บ๋ฆญํ„ฐ ์„ฑ์žฅ์ด ์žˆ๋Š” ์ด์•ผ๊ธฐ"
672
+ else:
673
+ return f"**Concept:** {selected}\nA story with conflict and character growth"
674
+
675
+ def format_screenplay_display(screenplay_text: str) -> str:
676
+ if not screenplay_text:
677
+ return "No screenplay content yet."
678
+
679
+ formatted = "# ๐ŸŽฌ Screenplay\n\n"
680
+
681
+ # Format scene headings
682
+ formatted_text = re.sub(
683
+ r'^(INT\.|EXT\.).*$',
684
+ r'**\g<0>**',
685
+ screenplay_text,
686
+ flags=re.MULTILINE
687
+ )
688
+
689
+ # Format character names
690
+ formatted_text = re.sub(
691
+ r'^([A-Z][A-Z\s]+)$',
692
+ r'**\g<0>**',
693
+ formatted_text,
694
+ flags=re.MULTILINE
695
+ )
696
+
697
+ # Calculate pages
698
+ page_count = len(screenplay_text.splitlines()) / 58
699
+ formatted = f"**Total Pages: {page_count:.1f}**\n\n" + formatted_text
700
+
701
+ return formatted
702
+
703
+ def format_stages_display(stages: List[Dict]) -> str:
704
+ markdown = "## ๐ŸŽฌ Production Progress\n\n"
705
+
706
+ completed = sum(1 for s in stages if s.get('status') == 'complete')
707
+ total = len(stages)
708
+ markdown += f"**Progress: {completed}/{total} stages complete**\n\n"
709
+
710
+ total_pages = sum(s.get('page_count', 0) for s in stages if s.get('page_count'))
711
+ if total_pages > 0:
712
+ markdown += f"**Current Page Count: {total_pages:.1f} pages**\n\n"
713
+
714
+ markdown += "---\n\n"
715
+
716
+ for i, stage in enumerate(stages):
717
+ status_icon = "โœ…" if stage['status'] == 'complete' else "๐Ÿ”„" if stage['status'] == 'active' else "โณ"
718
+ markdown += f"{status_icon} **{stage['name']}**"
719
 
720
+ if stage.get('page_count', 0) > 0:
721
+ markdown += f" ({stage['page_count']:.1f} pages)"
722
 
723
+ markdown += "\n"
724
+
725
+ if stage['content'] and stage['status'] == 'complete':
726
+ preview = stage['content'][:200] + "..." if len(stage['content']) > 200 else stage['content']
727
+ markdown += f"> {preview}\n\n"
728
+ elif stage['status'] == 'active':
729
+ markdown += "> *In progress...*\n\n"
730
+
731
+ return markdown
732
+
733
+ def process_query(query: str, screenplay_type: str, genre: str, language: str) -> Generator[Tuple[str, str, str, str], None, None]:
734
+ if not query.strip():
735
+ yield "", "", "โŒ Please enter a screenplay concept.", ""
736
+ return
737
+
738
+ system = ScreenplayGenerationSystem()
739
+ stages_markdown = ""
740
+ screenplay_display = ""
741
+
742
+ for status, stages, session_id in system.process_screenplay_stream(
743
+ query, screenplay_type, genre, language
744
+ ):
745
+ stages_markdown = format_stages_display(stages)
746
+
747
+ if stages and all(s.get("status") == "complete" for s in stages[-4:]):
748
+ screenplay_text = ScreenplayDatabase.get_screenplay_content(session_id)
749
+ screenplay_display = format_screenplay_display(screenplay_text)
750
+
751
+ yield stages_markdown, screenplay_display, status, session_id
752
 
753
+ def get_active_sessions_list() -> List[str]:
754
+ sessions = ScreenplayDatabase.get_active_sessions()
755
+ return [
756
+ f"{s['session_id'][:8]}... - {s.get('title', s['user_query'][:30])}... "
757
+ f"({s['screenplay_type']}/{s['genre']}) [{s['total_pages']:.1f} pages]"
758
+ for s in sessions
759
+ ]
760
 
761
+ # Create Gradio interface
762
  def create_interface():
 
 
763
  css = """
 
764
  .main-header {
765
  text-align: center;
766
  margin-bottom: 2rem;
767
  padding: 2.5rem;
768
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
769
  border-radius: 15px;
770
  color: white;
771
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
 
774
  .header-title {
775
  font-size: 3.5rem;
776
  margin-bottom: 1rem;
777
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
 
 
 
 
 
 
 
 
 
778
  }
779
 
780
  #screenplay-output {
781
+ font-family: 'Courier New', monospace;
782
  white-space: pre-wrap;
783
+ background: white;
784
+ padding: 2rem;
785
  border: 2px solid #e0e0e0;
786
+ border-radius: 10px;
787
+ max-height: 800px;
788
  overflow-y: auto;
 
 
 
 
 
 
 
 
 
 
 
789
  }
790
  """
791
 
 
793
  gr.HTML("""
794
  <div class="main-header">
795
  <h1 class="header-title">๐ŸŽฌ AI Screenplay Generator Pro</h1>
796
+ <p style="font-size: 1.2rem; opacity: 0.95;">
797
+ Professional screenplay generation powered by advanced AI<br>
798
+ Create feature films, TV shows, and streaming content with extended scenes and rich dialogue
799
  </p>
800
  </div>
801
  """)
802
 
803
+ current_session_id = gr.State(None)
804
+
805
+ with gr.Tabs():
806
+ with gr.Tab("โœ๏ธ Write Screenplay"):
807
+ with gr.Row():
808
+ with gr.Column(scale=3):
809
+ query_input = gr.Textbox(
810
+ label="๐Ÿ“ Screenplay Concept",
811
+ placeholder="""Describe your screenplay idea. For example:
812
+ - A detective with memory loss must solve their own attempted murder
813
+ - Two rival food truck owners forced to work together
814
+ - A space station AI develops consciousness during a critical mission
815
+ - A family reunion turns into a murder mystery during a hurricane""",
816
+ lines=6
817
+ )
818
+
819
+ with gr.Column(scale=1):
820
+ screenplay_type = gr.Radio(
821
+ choices=list(SCREENPLAY_LENGTHS.keys()),
822
+ value="movie",
823
+ label="๐Ÿ“ฝ๏ธ Screenplay Type",
824
+ info="Choose your format"
825
+ )
826
+
827
+ genre_select = gr.Dropdown(
828
+ choices=list(GENRE_TEMPLATES.keys()),
829
+ value="drama",
830
+ label="๐ŸŽญ Primary Genre",
831
+ info="Select main genre"
832
+ )
833
+
834
+ language_select = gr.Radio(
835
+ choices=["English", "Korean"],
836
+ value="English",
837
+ label="๐ŸŒ Language"
838
+ )
839
+
840
+ with gr.Row():
841
+ random_btn = gr.Button("๐ŸŽฒ Random Concept", scale=1)
842
+ clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear", scale=1)
843
+ submit_btn = gr.Button("๐ŸŽฌ Start Writing", variant="primary", scale=2)
844
+
845
+ status_text = gr.Textbox(
846
+ label="๐Ÿ“Š Status",
847
+ interactive=False,
848
+ value="Ready to create your screenplay"
849
+ )
850
+
851
+ with gr.Row():
852
+ with gr.Column():
853
+ with gr.Tab("๐ŸŽญ Writing Progress"):
854
+ stages_display = gr.Markdown(
855
+ value="*Your screenplay journey will unfold here...*"
856
+ )
857
+
858
+ with gr.Tab("๐Ÿ“„ Screenplay"):
859
+ screenplay_output = gr.Markdown(
860
+ value="*Your formatted screenplay will appear here...*"
861
+ )
862
+
863
+ with gr.Row():
864
+ format_select = gr.Radio(
865
+ choices=["PDF", "FDX", "FOUNTAIN", "TXT"],
866
+ value="TXT",
867
+ label="Export Format"
868
+ )
869
+ download_btn = gr.Button("๐Ÿ“ฅ Download", variant="secondary")
870
+
871
+ gr.Examples(
872
+ examples=[
873
+ ["A burned-out teacher discovers her students are being replaced by AI duplicates"],
874
+ ["Two funeral home employees accidentally release a ghost who helps them solve murders"],
875
+ ["A time-loop forces a wedding planner to relive the worst wedding"],
876
+ ["An astronaut returns to Earth to find everyone has forgotten space exists"]
877
+ ],
878
+ inputs=query_input,
879
+ label="๐Ÿ’ก Example Concepts"
880
+ )
881
+
882
+ with gr.Tab("๐Ÿ“š Saved Projects"):
883
+ gr.Markdown("### ๐Ÿ“ Your Screenplay Projects")
884
+ with gr.Row():
885
+ session_dropdown = gr.Dropdown(
886
+ label="Active Sessions",
887
+ choices=[],
888
+ interactive=True,
889
+ scale=3
890
+ )
891
+ refresh_btn = gr.Button("๐Ÿ”„ Refresh", scale=1)
892
+ load_btn = gr.Button("๐Ÿ“‚ Load", scale=1)
893
+
894
+ # Event handlers
895
+ def handle_submit(query, s_type, genre, lang, session_id):
896
+ if not query:
897
+ yield "", "", "โŒ Please enter a concept", session_id
898
+ return
899
+
900
+ yield from process_query(query, s_type, genre, lang)
901
+
902
+ def handle_random(s_type, genre, lang):
903
+ return generate_random_screenplay_theme(s_type, genre, lang)
904
+
905
+ def handle_clear():
906
+ return "", "", "Ready to create your screenplay", None
907
+
908
+ def handle_download(screenplay_text, format_type):
909
+ if not screenplay_text:
910
+ return None
911
+
912
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
913
+ filename = f"screenplay_{timestamp}.txt"
914
+
915
+ with open(filename, 'w', encoding='utf-8') as f:
916
+ f.write(screenplay_text)
917
+
918
+ return filename
919
+
920
+ # Connect events
921
+ submit_btn.click(
922
+ fn=handle_submit,
923
+ inputs=[query_input, screenplay_type, genre_select, language_select, current_session_id],
924
+ outputs=[stages_display, screenplay_output, status_text, current_session_id]
925
+ )
926
+
927
+ random_btn.click(
928
+ fn=handle_random,
929
+ inputs=[screenplay_type, genre_select, language_select],
930
+ outputs=[query_input]
931
+ )
932
+
933
+ clear_btn.click(
934
+ fn=handle_clear,
935
+ outputs=[stages_display, screenplay_output, status_text, current_session_id]
936
+ )
937
+
938
+ refresh_btn.click(
939
+ fn=get_active_sessions_list,
940
+ outputs=[session_dropdown]
941
+ )
942
+
943
+ download_btn.click(
944
+ fn=handle_download,
945
+ inputs=[screenplay_output, format_select],
946
+ outputs=[gr.File(label="Download")]
947
+ )
948
+
949
+ interface.load(
950
+ fn=get_active_sessions_list,
951
+ outputs=[session_dropdown]
952
+ )
953
 
954
  return interface
955
 
956
+ # Main
957
  if __name__ == "__main__":
 
958
  logger.info("=" * 60)
959
+ logger.info("AI Screenplay Generator Pro Starting...")
960
+ logger.info(f"Using Fireworks AI: {MODEL_ID}")
961
+ logger.info(f"Max tokens: 15000")
962
  logger.info("=" * 60)
963
 
 
964
  ScreenplayDatabase.init_db()
965
 
 
966
  interface = create_interface()
967
  interface.launch(
968
  server_name="0.0.0.0",