openfree's picture
Update app.py
ba4901b verified
raw
history blame
45.5 kB
import gradio as gr
import os
import json
import requests
from datetime import datetime
import time
from typing import List, Dict, Any, Generator, Tuple, Optional, Set
import logging
import re
import tempfile
from pathlib import Path
import sqlite3
import hashlib
import threading
from contextlib import contextmanager
from dataclasses import dataclass, field, asdict
from collections import defaultdict
import random
# --- ๋กœ๊น… ์„ค์ • ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# --- ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ฐ ์ƒ์ˆ˜ ---
FIREWORKS_API_KEY = os.getenv("FIREWORKS_API_KEY", "")
BRAVE_SEARCH_API_KEY = os.getenv("BRAVE_SEARCH_API_KEY", "")
API_URL = "https://api.fireworks.ai/inference/v1/chat/completions"
MODEL_ID = "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
DB_PATH = "screenplay_sessions_korean.db"
# ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธธ์ด ์„ค์ •
SCREENPLAY_LENGTHS = {
"์˜ํ™”": {"pages": 120, "description": "์žฅํŽธ ์˜ํ™” (110-130ํŽ˜์ด์ง€)", "min_pages": 110},
"๋“œ๋ผ๋งˆ": {"pages": 60, "description": "TV ๋“œ๋ผ๋งˆ (55-65ํŽ˜์ด์ง€)", "min_pages": 55},
"์›น๋“œ๋ผ๋งˆ": {"pages": 50, "description": "์›น/OTT ์‹œ๋ฆฌ์ฆˆ (45-55ํŽ˜์ด์ง€)", "min_pages": 45},
"๋‹จํŽธ": {"pages": 20, "description": "๋‹จํŽธ ์˜ํ™” (15-25ํŽ˜์ด์ง€)", "min_pages": 15}
}
# ํ™˜๊ฒฝ ๊ฒ€์ฆ
if not FIREWORKS_API_KEY:
logger.error("FIREWORKS_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
FIREWORKS_API_KEY = "dummy_token_for_testing"
# ๊ธ€๋กœ๋ฒŒ ๋ณ€์ˆ˜
db_lock = threading.Lock()
# ์žฅ๋ฅด ํ…œํ”Œ๋ฆฟ
GENRE_TEMPLATES = {
"์•ก์…˜": {
"pacing": "๋น ๋ฆ„",
"scene_length": "์งง์Œ",
"dialogue_ratio": 0.3,
"key_elements": ["์•ก์…˜ ์‹œํ€€์Šค", "๋ฌผ๋ฆฌ์  ๊ฐˆ๋“ฑ", "๊ธด๋ฐ•๊ฐ", "์œ„๊ธฐ ๊ณ ์กฐ", "์˜์›…์  ์ˆœ๊ฐ„"],
"structure_beats": ["ํญ๋ฐœ์  ์˜คํ”„๋‹", "์ถ”๊ฒฉ", "๋Œ€๊ฒฐ", "ํด๋ผ์ด๋งฅ์Šค ์ „ํˆฌ", "์˜์›…์˜ ์Šน๋ฆฌ"],
"scene_density": 1.2,
"action_description_ratio": 0.6
},
"์Šค๋ฆด๋Ÿฌ": {
"pacing": "๋น ๋ฆ„",
"scene_length": "์งง์Œ",
"dialogue_ratio": 0.35,
"key_elements": ["์„œ์ŠคํŽœ์Šค", "๋ฐ˜์ „", "ํŽธ์ง‘์ฆ", "์‹œ๊ฐ„ ์••๋ฐ•", "์ง„์‹ค ํญ๋กœ"],
"structure_beats": ["ํ›…", "๋ฏธ์Šคํ„ฐ๋ฆฌ ์‹ฌํ™”", "๊ฑฐ์ง“ ์Šน๋ฆฌ", "์ง„์‹ค ํญ๋กœ", "์ตœ์ข… ๋Œ€๊ฒฐ"],
"scene_density": 1.1,
"action_description_ratio": 0.5
},
"๋“œ๋ผ๋งˆ": {
"pacing": "๋ณดํ†ต",
"scene_length": "์ค‘๊ฐ„",
"dialogue_ratio": 0.55,
"key_elements": ["์บ๋ฆญํ„ฐ ๊นŠ์ด", "๊ฐ์ •์  ์ง„์‹ค", "๊ด€๊ณ„", "๋‚ด์  ๊ฐˆ๋“ฑ", "๋ณ€ํ™”"],
"structure_beats": ["์ผ์ƒ", "์ด‰๋งค", "๊ณ ๋ฏผ", "๊ฒฐ์‹ฌ", "๋ณต์žกํ™”", "์œ„๊ธฐ", "ํ•ด๊ฒฐ"],
"scene_density": 0.9,
"action_description_ratio": 0.3
},
"์ฝ”๋ฏธ๋””": {
"pacing": "๋น ๋ฆ„",
"scene_length": "์งง์Œ",
"dialogue_ratio": 0.65,
"key_elements": ["์…‹์—…๊ณผ ํŽ˜์ด์˜คํ”„", "ํƒ€์ด๋ฐ", "์บ๋ฆญํ„ฐ ์ฝ”๋ฏธ๋””", "์ƒํ™ฉ ๊ณ ์กฐ", "๋Ÿฌ๋‹ ๊ฐœ๊ทธ"],
"structure_beats": ["์›ƒ๊ธด ์˜คํ”„๋‹", "๋ณต์žกํ™”", "์˜คํ•ด ์ฆํญ", "ํ˜ผ๋ˆ์˜ ์ •์ ", "ํ•ด๊ฒฐ๊ณผ ์ฝœ๋ฐฑ"],
"scene_density": 1.0,
"action_description_ratio": 0.35
},
"๊ณตํฌ": {
"pacing": "๊ฐ€๋ณ€์ ",
"scene_length": "ํ˜ผํ•ฉ",
"dialogue_ratio": 0.3,
"key_elements": ["๋ถ„์œ„๊ธฐ", "๊ณตํฌ๊ฐ", "์ ํ”„ ์Šค์ผ€์–ด", "์‹ฌ๋ฆฌ์  ๊ณตํฌ", "๊ณ ๋ฆฝ"],
"structure_beats": ["ํ‰๋ฒ”ํ•œ ์ผ์ƒ", "์ฒซ ์ง•์กฐ", "์กฐ์‚ฌ", "์ฒซ ๊ณต๊ฒฉ", "์ƒ์กด", "์ตœ์ข… ๋Œ€๊ฒฐ"],
"scene_density": 1.0,
"action_description_ratio": 0.55
},
"SF": {
"pacing": "๋ณดํ†ต",
"scene_length": "์ค‘๊ฐ„",
"dialogue_ratio": 0.45,
"key_elements": ["์„ธ๊ณ„๊ด€ ๊ตฌ์ถ•", "๊ธฐ์ˆ ", "๊ฐœ๋…", "์‹œ๊ฐ์  ์ŠคํŽ™ํ„ฐํด", "์ฒ ํ•™์  ์งˆ๋ฌธ"],
"structure_beats": ["์ผ์ƒ ์„ธ๊ณ„", "๋ฐœ๊ฒฌ", "์ƒˆ๋กœ์šด ์„ธ๊ณ„", "๋ณต์žกํ™”", "์ดํ•ด", "์„ ํƒ", "์ƒˆ๋กœ์šด ์ผ์ƒ"],
"scene_density": 0.95,
"action_description_ratio": 0.45
},
"๋กœ๋งจ์Šค": {
"pacing": "๋ณดํ†ต",
"scene_length": "์ค‘๊ฐ„",
"dialogue_ratio": 0.6,
"key_elements": ["์ผ€๋ฏธ์ŠคํŠธ๋ฆฌ", "์žฅ์• ๋ฌผ", "๊ฐ์ •์  ์ˆœ๊ฐ„", "์นœ๋ฐ€๊ฐ", "์ทจ์•ฝ์„ฑ"],
"structure_beats": ["๋งŒ๋‚จ", "๋Œ๋ฆผ", "์ฒซ ๊ฐˆ๋“ฑ", "๊นŠ์–ด์ง", "์œ„๊ธฐ/์ด๋ณ„", "ํ™”ํ•ด", "๊ฒฐํ•ฉ"],
"scene_density": 0.85,
"action_description_ratio": 0.25
},
"ํŒํƒ€์ง€": {
"pacing": "๋ณดํ†ต",
"scene_length": "์ค‘๊ฐ„",
"dialogue_ratio": 0.4,
"key_elements": ["๋งˆ๋ฒ• ์ฒด๊ณ„", "์˜์›…์˜ ์—ฌ์ •", "์‹ ํ™”์  ์š”์†Œ", "์„ธ๊ณ„๊ด€", "์„ฑ์žฅ"],
"structure_beats": ["ํ‰๋ฒ”ํ•œ ์„ธ๊ณ„", "์†Œ๋ช…", "๊ฑฐ๋ถ€", "๋ฉ˜ํ† ", "์ฒซ ์‹œํ—˜", "์‹œ๋ จ", "๋ณด์ƒ", "๊ท€ํ™˜"],
"scene_density": 1.0,
"action_description_ratio": 0.5
}
}
# ์‹œ๋‚˜๋ฆฌ์˜ค ๋‹จ๊ณ„ (๊ธฐํš ๋‹จ๊ณ„์™€ ์ž‘์„ฑ ๋‹จ๊ณ„ ๋ถ„๋ฆฌ)
PLANNING_STAGES = [
("์ปจ์…‰ ๊ฐœ๋ฐœ", "๐ŸŽฌ ํ”„๋กœ๋“€์„œ: ํ•ต์‹ฌ ์ปจ์…‰ ๋ฐ ์‹œ์žฅ์„ฑ ๋ถ„์„"),
("์Šคํ† ๋ฆฌ ๊ตฌ์„ฑ", "๐Ÿ“– ์Šคํ† ๋ฆฌ ๊ฐœ๋ฐœ: ์‹œ๋†‰์‹œ์Šค ๋ฐ 3๋ง‰ ๊ตฌ์กฐ"),
("์บ๋ฆญํ„ฐ ์„ค๊ณ„", "๐Ÿ‘ฅ ์บ๋ฆญํ„ฐ ๋””์ž์ด๋„ˆ: ์ธ๋ฌผ ํ”„๋กœํ•„ ๋ฐ ๊ด€๊ณ„๋„"),
("์„ธ๊ณ„๊ด€ ๊ตฌ์ถ•", "๐ŸŒ ์„ธ๊ณ„๊ด€: ๋ฐฐ๊ฒฝ, ๋ถ„์œ„๊ธฐ, ๋น„์ฃผ์–ผ ์„ค์ •"),
("์”ฌ ๊ตฌ์„ฑ", "๐ŸŽฏ ์”ฌ ํ”Œ๋ž˜๋„ˆ: ์ƒ์„ธ ์”ฌ ๋ธŒ๋ ˆ์ดํฌ๋‹ค์šด"),
]
WRITING_STAGES = [
("1๋ง‰ ์ดˆ๊ณ ", "โœ๏ธ 1๋ง‰ - ์„ค์ • (25%) - ์ดˆ๊ณ  ์ž‘์„ฑ"),
("1๋ง‰ ์ˆ˜์ •", "๐Ÿ“ 1๋ง‰ - ํ™•์žฅ ๋ฐ ๋‹ค๋“ฌ๊ธฐ"),
("1๋ง‰ ๊ฒ€ํ† ", "๐Ÿ”ง 1๋ง‰ - ์ „๋ฌธ๊ฐ€ ๊ฒ€ํ† "),
("1๋ง‰ ์™„์„ฑ", "โœ… 1๋ง‰ - ์ตœ์ข… ์™„์„ฑ๋ณธ"),
("2๋ง‰A ์ดˆ๊ณ ", "โœ๏ธ 2๋ง‰A - ์ƒ์Šน (25%) - ์ดˆ๊ณ  ์ž‘์„ฑ"),
("2๋ง‰A ์ˆ˜์ •", "๐Ÿ“ 2๋ง‰A - ํ™•์žฅ ๋ฐ ๋‹ค๋“ฌ๊ธฐ"),
("2๋ง‰A ๊ฒ€ํ† ", "๐Ÿ”ง 2๋ง‰A - ์ „๋ฌธ๊ฐ€ ๊ฒ€ํ† "),
("2๋ง‰A ์™„์„ฑ", "โœ… 2๋ง‰A - ์ตœ์ข… ์™„์„ฑ๋ณธ"),
("2๋ง‰B ์ดˆ๊ณ ", "โœ๏ธ 2๋ง‰B - ๋ณต์žกํ™” (25%) - ์ดˆ๊ณ  ์ž‘์„ฑ"),
("2๋ง‰B ์ˆ˜์ •", "๐Ÿ“ 2๋ง‰B - ํ™•์žฅ ๋ฐ ๋‹ค๋“ฌ๊ธฐ"),
("2๋ง‰B ๊ฒ€ํ† ", "๐Ÿ”ง 2๋ง‰B - ์ „๋ฌธ๊ฐ€ ๊ฒ€ํ† "),
("2๋ง‰B ์™„์„ฑ", "โœ… 2๋ง‰B - ์ตœ์ข… ์™„์„ฑ๋ณธ"),
("3๋ง‰ ์ดˆ๊ณ ", "โœ๏ธ 3๋ง‰ - ํ•ด๊ฒฐ (25%) - ์ดˆ๊ณ  ์ž‘์„ฑ"),
("3๋ง‰ ์ˆ˜์ •", "๐Ÿ“ 3๋ง‰ - ํ™•์žฅ ๋ฐ ๋‹ค๋“ฌ๊ธฐ"),
("3๋ง‰ ๊ฒ€ํ† ", "๐Ÿ”ง 3๋ง‰ - ์ „๋ฌธ๊ฐ€ ๊ฒ€ํ† "),
("3๋ง‰ ์™„์„ฑ", "โœ… 3๋ง‰ - ์ตœ์ข… ์™„์„ฑ๋ณธ"),
("๋Œ€์‚ฌ ๊ฐ•ํ™”", "๐Ÿ’ฌ ๋Œ€์‚ฌ ์ „๋ฌธ๊ฐ€: ๋Œ€์‚ฌ ๊ฐœ์„  ๋ฐ ์„œ๋ธŒํ…์ŠคํŠธ"),
("์ตœ์ข… ๊ฒ€ํ† ", "๐ŸŽญ ์ตœ์ข… ๋ฆฌ๋ทฐ: ์ „์ฒด ์‹œ๋‚˜๋ฆฌ์˜ค ๋ถ„์„ ๋ฐ ์™„์„ฑ"),
]
# ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค
@dataclass
class ScreenplayPlan:
"""์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐํš์•ˆ"""
title: str = ""
logline: str = ""
genre: str = ""
subgenre: str = ""
themes: List[str] = field(default_factory=list)
# ์‹œ๋†‰์‹œ์Šค
synopsis: str = ""
extended_synopsis: str = ""
# 3๋ง‰ ๊ตฌ์กฐ
act1_summary: str = ""
act2a_summary: str = ""
act2b_summary: str = ""
act3_summary: str = ""
# ์ฃผ์š” ์บ๋ฆญํ„ฐ
protagonist: Dict[str, str] = field(default_factory=dict)
antagonist: Dict[str, str] = field(default_factory=dict)
supporting_characters: List[Dict[str, str]] = field(default_factory=list)
# ์„ธ๊ณ„๊ด€
time_period: str = ""
locations: List[str] = field(default_factory=list)
atmosphere: str = ""
visual_style: str = ""
# ์”ฌ ๊ตฌ์„ฑ
total_scenes: int = 0
scene_breakdown: List[Dict] = field(default_factory=list)
@dataclass
class ScreenplayBible:
"""์‹œ๋‚˜๋ฆฌ์˜ค ๋ฐ”์ด๋ธ”"""
title: str = ""
logline: str = ""
genre: str = ""
subgenre: str = ""
tone: str = ""
themes: List[str] = field(default_factory=list)
protagonist: Dict[str, Any] = field(default_factory=dict)
antagonist: Dict[str, Any] = field(default_factory=dict)
supporting_cast: Dict[str, Dict[str, Any]] = field(default_factory=dict)
three_act_structure: Dict[str, str] = field(default_factory=dict)
time_period: str = ""
primary_locations: List[Dict[str, str]] = field(default_factory=list)
visual_style: str = ""
# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํด๋ž˜์Šค
class ScreenplayDatabase:
@staticmethod
def init_db():
with sqlite3.connect(DB_PATH) as conn:
conn.execute("PRAGMA journal_mode=WAL")
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS screenplay_sessions (
session_id TEXT PRIMARY KEY,
user_query TEXT NOT NULL,
screenplay_type TEXT NOT NULL,
genre TEXT NOT NULL,
target_pages INTEGER,
title TEXT,
logline TEXT,
planning_data TEXT,
screenplay_content TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
status TEXT DEFAULT 'planning',
current_stage INTEGER DEFAULT 0,
total_pages REAL DEFAULT 0
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS screenplay_stages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
stage_type TEXT NOT NULL,
stage_number INTEGER NOT NULL,
stage_name TEXT NOT NULL,
content TEXT,
page_count REAL DEFAULT 0,
status TEXT DEFAULT 'pending',
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id),
UNIQUE(session_id, stage_type, stage_number)
)
''')
conn.commit()
@staticmethod
@contextmanager
def get_db():
with db_lock:
conn = sqlite3.connect(DB_PATH, timeout=30.0)
conn.row_factory = sqlite3.Row
try:
yield conn
finally:
conn.close()
@staticmethod
def create_session(user_query: str, screenplay_type: str, genre: str) -> str:
session_id = hashlib.md5(f"{user_query}{screenplay_type}{datetime.now()}".encode()).hexdigest()
target_pages = SCREENPLAY_LENGTHS[screenplay_type]["pages"]
with ScreenplayDatabase.get_db() as conn:
conn.cursor().execute(
'''INSERT INTO screenplay_sessions
(session_id, user_query, screenplay_type, genre, target_pages)
VALUES (?, ?, ?, ?, ?)''',
(session_id, user_query, screenplay_type, genre, target_pages)
)
conn.commit()
return session_id
@staticmethod
def save_planning_data(session_id: str, planning_data: Dict):
with ScreenplayDatabase.get_db() as conn:
conn.cursor().execute(
'''UPDATE screenplay_sessions
SET planning_data = ?, status = 'planned', updated_at = datetime('now')
WHERE session_id = ?''',
(json.dumps(planning_data, ensure_ascii=False), session_id)
)
conn.commit()
@staticmethod
def get_planning_data(session_id: str) -> Optional[Dict]:
with ScreenplayDatabase.get_db() as conn:
row = conn.cursor().execute(
'SELECT planning_data FROM screenplay_sessions WHERE session_id = ?',
(session_id,)
).fetchone()
if row and row['planning_data']:
return json.loads(row['planning_data'])
return None
@staticmethod
def save_screenplay_content(session_id: str, content: str, title: str, logline: str):
with ScreenplayDatabase.get_db() as conn:
total_pages = len(content.split('\n')) / 58
conn.cursor().execute(
'''UPDATE screenplay_sessions
SET screenplay_content = ?, title = ?, logline = ?,
total_pages = ?, status = 'complete', updated_at = datetime('now')
WHERE session_id = ?''',
(content, title, logline, total_pages, session_id)
)
conn.commit()
# ์‹œ๋‚˜๋ฆฌ์˜ค ์ƒ์„ฑ ์‹œ์Šคํ…œ
class ScreenplayGenerationSystem:
def __init__(self):
self.api_key = FIREWORKS_API_KEY
self.api_url = API_URL
self.model_id = MODEL_ID
self.current_session_id = None
self.current_plan = ScreenplayPlan()
self.original_query = "" # ์›๋ณธ ์š”์ฒญ ์ €์žฅ
self.accumulated_content = {} # ๋ˆ„์  ๋‚ด์šฉ ์ €์žฅ
ScreenplayDatabase.init_db()
def create_headers(self):
if not self.api_key or self.api_key == "dummy_token_for_testing":
raise ValueError("์œ ํšจํ•œ FIREWORKS_API_KEY๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
return {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
def call_llm_streaming(self, messages: List[Dict[str, str]], max_tokens: int = 8000) -> Generator[str, None, None]:
try:
payload = {
"model": self.model_id,
"messages": messages,
"max_tokens": max_tokens,
"temperature": 0.7, # ์˜จ๋„๋ฅผ ๋‚ฎ์ถฐ์„œ ์ผ๊ด€์„ฑ ํ–ฅ์ƒ
"top_p": 0.9, # top_p๋„ ์กฐ์ •
"top_k": 40,
"presence_penalty": 0.3, # ๋ฐ˜๋ณต ๋ฐฉ์ง€
"frequency_penalty": 0.3,
"stream": True
}
headers = self.create_headers()
response = requests.post(
self.api_url,
headers=headers,
json=payload,
stream=True,
timeout=300
)
if response.status_code != 200:
yield f"โŒ API ์˜ค๋ฅ˜: {response.status_code}"
return
buffer = ""
for line in response.iter_lines():
if not line:
continue
try:
line_str = line.decode('utf-8').strip()
if not line_str.startswith("data: "):
continue
data_str = line_str[6:]
if data_str == "[DONE]":
break
data = json.loads(data_str)
if "choices" in data and len(data["choices"]) > 0:
content = data["choices"][0].get("delta", {}).get("content", "")
if content:
buffer += content
if len(buffer) >= 100 or '\n' in buffer:
yield buffer
buffer = ""
except:
continue
if buffer:
yield buffer
except Exception as e:
yield f"โŒ ์˜ค๋ฅ˜: {str(e)}"
def generate_planning(self, query: str, screenplay_type: str, genre: str,
progress_callback=None) -> Generator[Tuple[str, float, Dict], None, None]:
"""๊ธฐํš์•ˆ ์ƒ์„ฑ - ์›๋ณธ ์š”์ฒญ ์œ ์ง€"""
try:
self.original_query = query # ์›๋ณธ ์ €์žฅ
self.current_session_id = ScreenplayDatabase.create_session(query, screenplay_type, genre)
planning_content = {}
self.accumulated_content = {} # ๋ˆ„์  ๋‚ด์šฉ ์ดˆ๊ธฐํ™”
total_stages = len(PLANNING_STAGES)
# ์›๋ณธ ์š”์ฒญ์„ ๋ช…ํ™•ํžˆ ์ •๋ฆฌ
core_request = f"""
ใ€ํ•ต์‹ฌ ์š”์ฒญ์‚ฌํ•ญใ€‘
{query}
ใ€ํ•„์ˆ˜ ์ค€์ˆ˜์‚ฌํ•ญใ€‘
- ์œ„ ์š”์ฒญ์‚ฌํ•ญ์„ ์ ˆ๋Œ€์ ์œผ๋กœ ์ค€์ˆ˜ํ•  ๊ฒƒ
- ์š”์ฒญ๋œ ์„ค์ •, ์ธ๋ฌผ, ์Šคํ† ๋ฆฌ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ๊ฒƒ
- ์ž„์˜๋กœ ๋‚ด์šฉ์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ๋กœ ๋ฐ”๊พธ์ง€ ๋ง ๊ฒƒ
- ์žฅ๋ฅด: {genre}
- ํ˜•์‹: {screenplay_type}
"""
for idx, (stage_name, stage_desc) in enumerate(PLANNING_STAGES):
progress = (idx / total_stages) * 100
yield f"๐Ÿ”„ {stage_desc} ์ง„ํ–‰ ์ค‘...", progress, planning_content
# ์ด์ „ ๋‹จ๊ณ„ ๋‚ด์šฉ ์š”์•ฝ
previous_content = ""
if self.accumulated_content:
previous_content = "\nใ€์ด์ „ ๋‹จ๊ณ„ ๋‚ด์šฉใ€‘\n"
for key, value in self.accumulated_content.items():
previous_content += f"\n[{key}]\n{value[:500]}...\n"
# ๊ฐ ๋‹จ๊ณ„๋ณ„ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
if stage_name == "์ปจ์…‰ ๊ฐœ๋ฐœ":
prompt = f"""{core_request}
๋‹น์‹ ์€ ํ• ๋ฆฌ์šฐ๋“œ ํ”„๋กœ๋“€์„œ์ž…๋‹ˆ๋‹ค. ์œ„ ์š”์ฒญ์‚ฌํ•ญ์— ์ •ํ™•ํžˆ ๋งž๋Š” {screenplay_type} ์‹œ๋‚˜๋ฆฌ์˜ค ์ปจ์…‰์„ ๊ฐœ๋ฐœํ•˜์„ธ์š”.
ใ€์ž‘์„ฑ ํ•ญ๋ชฉใ€‘
1. ์ œ๋ชฉ: (์š”์ฒญ ๋‚ด์šฉ์„ ๋ฐ˜์˜ํ•œ ์ œ๋ชฉ)
2. ๋กœ๊ทธ๋ผ์ธ: (์œ„ ์š”์ฒญ์˜ ํ•ต์‹ฌ ์Šคํ† ๋ฆฌ๋ฅผ ํ•œ ๋ฌธ์žฅ์œผ๋กœ)
3. ํ•ต์‹ฌ ํ…Œ๋งˆ: (์š”์ฒญ์—์„œ ๋‹ค๋ฃจ๊ณ ์ž ํ•˜๋Š” ์ฃผ์ œ)
4. ํƒ€๊ฒŸ ๊ด€๊ฐ: (์ด ์Šคํ† ๋ฆฌ๊ฐ€ ์–ดํ•„ํ•  ๊ด€๊ฐ์ธต)
5. ์œ ์‚ฌ ์ž‘ํ’ˆ 3๊ฐœ์™€ ์ฐจ๋ณ„์ 
6. ์ƒ์—…์  ๋งค๋ ฅ ํฌ์ธํŠธ
โš ๏ธ ์ค‘์š”: ๋ฐ˜๋“œ์‹œ "{query}"์˜ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”."""
elif stage_name == "์Šคํ† ๋ฆฌ ๊ตฌ์„ฑ":
prompt = f"""{core_request}
{previous_content}
์ด์ „ ์ปจ์…‰์„ ๋ฐ”ํƒ•์œผ๋กœ ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“œ์„ธ์š”.
โš ๏ธ ๋ฐ˜๋“œ์‹œ ์›๋ณธ ์š”์ฒญ "{query}"์˜ ์Šคํ† ๋ฆฌ๋ผ์ธ์„ ์œ ์ง€ํ•˜์„ธ์š”.
ใ€์ž‘์„ฑ ํ•ญ๋ชฉใ€‘
1. ์‹œ๋†‰์‹œ์Šค (500์ž): ์›๋ณธ ์š”์ฒญ์˜ ์Šคํ† ๋ฆฌ๋ฅผ ์ถฉ์‹คํžˆ ๋ฐ˜์˜
2. ํ™•์žฅ ์‹œ๋†‰์‹œ์Šค (1000์ž): ์„ธ๋ถ€ ์‚ฌ๊ฑด๊ณผ ์ „๊ฐœ ์ถ”๊ฐ€
3. 3๋ง‰ ๊ตฌ์กฐ:
- 1๋ง‰: ์š”์ฒญ๋œ ์„ค์ •๊ณผ ์ธ๋ฌผ ์†Œ๊ฐœ
- 2๋ง‰A: ์š”์ฒญ๋œ ๊ฐˆ๋“ฑ์˜ ์‹œ์ž‘
- 2๋ง‰B: ์š”์ฒญ๋œ ์œ„๊ธฐ์˜ ์‹ฌํ™”
- 3๋ง‰: ์š”์ฒญ๋œ ๋ฐฉ์‹์˜ ํ•ด๊ฒฐ
์ ˆ๋Œ€ ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ๋กœ ๋ฐ”๋€Œ์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•˜์„ธ์š”."""
elif stage_name == "์บ๋ฆญํ„ฐ ์„ค๊ณ„":
prompt = f"""{core_request}
{previous_content}
์›๋ณธ ์š”์ฒญ์— ๋‚˜์˜จ ์บ๋ฆญํ„ฐ๋“ค์„ ์„ค๊ณ„ํ•˜์„ธ์š”.
โš ๏ธ ์š”์ฒญ์— ๋ช…์‹œ๋œ ์ธ๋ฌผ ์„ค์ •์„ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜์ง€ ๋งˆ์„ธ์š”.
ใ€์ž‘์„ฑ ํ•ญ๋ชฉใ€‘
์ฃผ์ธ๊ณต: (์š”์ฒญ์— ๋‚˜์˜จ ์ฃผ์ธ๊ณต ๊ทธ๋Œ€๋กœ)
- ์ด๋ฆ„๊ณผ ๋‚˜์ด
- ์™ธ๋ชจ์™€ ์„ฑ๊ฒฉ (์š”์ฒญ ๋‚ด์šฉ ๋ฐ˜์˜)
- ๋ชฉํ‘œ (์š”์ฒญ๋œ ๋ชฉํ‘œ)
- ํ•„์š” (๋‚ด์  ์„ฑ์žฅ)
- ์„ฑ์žฅ ์•„ํฌ
์ ๋Œ€์ž: (์š”์ฒญ์— ๋‚˜์˜จ ๋Œ€๋ฆฝ ์ธ๋ฌผ/์„ธ๋ ฅ)
- ์ด๋ฆ„๊ณผ ์—ญํ• 
- ๋™๊ธฐ์™€ ๋ชฉํ‘œ
- ์ฃผ์ธ๊ณต๊ณผ์˜ ๊ด€๊ณ„
์กฐ์—ฐ๋“ค: (์š”์ฒญ์— ์–ธ๊ธ‰๋œ ์ธ๋ฌผ๋“ค)
- ๊ฐ์ž์˜ ์—ญํ• ๊ณผ ํŠน์ง•
์ƒˆ๋กœ์šด ์ธ๋ฌผ์„ ์ž„์˜๋กœ ์ถ”๊ฐ€ํ•˜์ง€ ๋งˆ์„ธ์š”."""
elif stage_name == "์„ธ๊ณ„๊ด€ ๊ตฌ์ถ•":
prompt = f"""{core_request}
{previous_content}
์›๋ณธ ์š”์ฒญ์˜ ๋ฐฐ๊ฒฝ๊ณผ ์„ธ๊ณ„๊ด€์„ ๊ตฌ์ถ•ํ•˜์„ธ์š”.
โš ๏ธ ์š”์ฒญ์— ๋ช…์‹œ๋œ ์‹œ๋Œ€, ์žฅ์†Œ, ๋ถ„์œ„๊ธฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.
ใ€์ž‘์„ฑ ํ•ญ๋ชฉใ€‘
1. ์‹œ๋Œ€ ๋ฐฐ๊ฒฝ: (์š”์ฒญ๋œ ์‹œ๋Œ€)
2. ์ฃผ์š” ์žฅ์†Œ: (์š”์ฒญ์— ๋‚˜์˜จ ์žฅ์†Œ๋“ค)
3. ์ „์ฒด์  ๋ถ„์œ„๊ธฐ: (์š”์ฒญ๋œ ํ†ค)
4. ์‹œ๊ฐ์  ์Šคํƒ€์ผ
5. ์Œํ–ฅ/์Œ์•… ์Šคํƒ€์ผ
์ž„์˜๋กœ ๋ฐฐ๊ฒฝ์„ ๋ฐ”๊พธ์ง€ ๋งˆ์„ธ์š”."""
elif stage_name == "์”ฌ ๊ตฌ์„ฑ":
prompt = f"""{core_request}
{previous_content}
์›๋ณธ ์š”์ฒญ์˜ ์Šคํ† ๋ฆฌ๋ฅผ ์”ฌ์œผ๋กœ ๊ตฌ์„ฑํ•˜์„ธ์š”.
โš ๏ธ ๋ฐ˜๋“œ์‹œ ์š”์ฒญ๋œ ์‚ฌ๊ฑด๋“ค์ด ๋ชจ๋‘ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
{SCREENPLAY_LENGTHS[screenplay_type]['pages']}ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์œผ๋กœ:
- 1๋ง‰: ์š”์ฒญ๋œ ์„ค์ •๊ณผ ์‹œ์ž‘ (์•ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํŽ˜์ด์ง€)
- 2๋ง‰A: ์š”์ฒญ๋œ ๊ฐˆ๋“ฑ ์ „๊ฐœ (์•ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํŽ˜์ด์ง€)
- 2๋ง‰B: ์š”์ฒญ๋œ ์œ„๊ธฐ ์‹ฌํ™” (์•ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํŽ˜์ด์ง€)
- 3๋ง‰: ์š”์ฒญ๋œ ํ•ด๊ฒฐ (์•ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํŽ˜์ด์ง€)
๊ฐ ์”ฌ๋งˆ๋‹ค:
- ์žฅ์†Œ์™€ ์‹œ๊ฐ„
- ๋“ฑ์žฅ์ธ๋ฌผ
- ํ•ต์‹ฌ ์‚ฌ๊ฑด (์š”์ฒญ ๋‚ด์šฉ ๋ฐ˜์˜)
- ์˜ˆ์ƒ ํŽ˜์ด์ง€
์Šคํ† ๋ฆฌ๊ฐ€ ๋‹ค๋ฅธ ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•˜์„ธ์š”."""
# LLM ํ˜ธ์ถœ
messages = [
{"role": "system", "content": f"""๋‹น์‹ ์€ ์ „๋ฌธ ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž์˜ ์›๋ณธ ์š”์ฒญ์„ ์ ˆ๋Œ€์ ์œผ๋กœ ์ค€์ˆ˜ํ•˜์—ฌ ์ž‘์„ฑํ•˜์„ธ์š”.
์ ˆ๋Œ€ ์ž„์˜๋กœ ์Šคํ† ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ๋ฅผ ๋งŒ๋“ค์ง€ ๋งˆ์„ธ์š”.
์›๋ณธ ์š”์ฒญ: {query}"""},
{"role": "user", "content": prompt}
]
content = ""
for chunk in self.call_llm_streaming(messages):
content += chunk
planning_content[stage_name] = content
yield f"โœ… {stage_desc} ์™„๋ฃŒ", progress, planning_content
# ๋ˆ„์  ๋‚ด์šฉ ์ €์žฅ
self.accumulated_content[stage_name] = content
time.sleep(0.5) # API ์ œํ•œ ๊ณ ๋ ค
# ์ตœ์ข… ๊ธฐํš์•ˆ ์ €์žฅ
ScreenplayDatabase.save_planning_data(self.current_session_id, planning_content)
yield "โœ… ๊ธฐํš์•ˆ ์™„์„ฑ!", 100, planning_content
except Exception as e:
yield f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", 0, {}
def generate_screenplay(self, session_id: str, planning_data: Dict,
progress_callback=None) -> Generator[Tuple[str, float, str], None, None]:
"""์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘์„ฑ - ๊ธฐํš์•ˆ ์ถฉ์‹คํžˆ ๋ฐ˜์˜"""
try:
total_stages = len(WRITING_STAGES)
screenplay_content = ""
act_contents = {"1๋ง‰": "", "2๋ง‰A": "", "2๋ง‰B": "", "3๋ง‰": ""}
# ๊ธฐํš์•ˆ์—์„œ ํ•ต์‹ฌ ์ •๋ณด ์ถ”์ถœ
concept = planning_data.get("์ปจ์…‰ ๊ฐœ๋ฐœ", "")
story = planning_data.get("์Šคํ† ๋ฆฌ ๊ตฌ์„ฑ", "")
characters = planning_data.get("์บ๋ฆญํ„ฐ ์„ค๊ณ„", "")
world = planning_data.get("์„ธ๊ณ„๊ด€ ๊ตฌ์ถ•", "")
scenes = planning_data.get("์”ฌ ๊ตฌ์„ฑ", "")
for idx, (stage_name, stage_desc) in enumerate(WRITING_STAGES):
progress = (idx / total_stages) * 100
yield f"๐Ÿ”„ {stage_desc} ์ง„ํ–‰ ์ค‘...", progress, screenplay_content
# ๋ง‰ ๊ฒฐ์ •
current_act = ""
if "1๋ง‰" in stage_name:
current_act = "1๋ง‰"
elif "2๋ง‰A" in stage_name:
current_act = "2๋ง‰A"
elif "2๋ง‰B" in stage_name:
current_act = "2๋ง‰B"
elif "3๋ง‰" in stage_name:
current_act = "3๋ง‰"
# ์ž‘์„ฑ ๋‹จ๊ณ„๋ณ„ ํ”„๋กฌํ”„ํŠธ
if "์ดˆ๊ณ " in stage_name:
prompt = self._create_writing_prompt(
current_act, planning_data, act_contents, "์ดˆ๊ณ ",
self.original_query
)
elif "์ˆ˜์ •" in stage_name:
prompt = self._create_writing_prompt(
current_act, planning_data, act_contents, "์ˆ˜์ •",
self.original_query
)
elif "๊ฒ€ํ† " in stage_name:
prompt = self._create_review_prompt(
current_act, act_contents[current_act],
self.original_query
)
elif "์™„์„ฑ" in stage_name:
prompt = self._create_writing_prompt(
current_act, planning_data, act_contents, "์™„์„ฑ",
self.original_query
)
elif "๋Œ€์‚ฌ ๊ฐ•ํ™”" in stage_name:
prompt = self._create_dialogue_prompt(screenplay_content)
elif "์ตœ์ข… ๊ฒ€ํ† " in stage_name:
prompt = self._create_final_review_prompt(screenplay_content)
else:
continue
# LLM ํ˜ธ์ถœ
messages = [
{"role": "system", "content": f"""๋‹น์‹ ์€ ์ „๋ฌธ ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค.
ํ‘œ์ค€ ์‹œ๋‚˜๋ฆฌ์˜ค ํฌ๋งท์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”.
๊ธฐํš์•ˆ๊ณผ ์›๋ณธ ์š”์ฒญ์„ ์ถฉ์‹คํžˆ ๋ฐ˜์˜ํ•˜์„ธ์š”.
์›๋ณธ ์š”์ฒญ: {self.original_query}"""},
{"role": "user", "content": prompt}
]
content = ""
for chunk in self.call_llm_streaming(messages, max_tokens=15000):
content += chunk
# ๋ง‰ ๋‚ด์šฉ ์—…๋ฐ์ดํŠธ
if current_act and "์™„์„ฑ" in stage_name:
act_contents[current_act] = content
screenplay_content = "\n\n".join([act_contents[act] for act in ["1๋ง‰", "2๋ง‰A", "2๋ง‰B", "3๋ง‰"] if act_contents[act]])
yield f"โœ๏ธ {stage_desc} ์ž‘์„ฑ ์ค‘...", progress, screenplay_content
time.sleep(0.5)
# ์ตœ์ข… ์ €์žฅ
title = planning_data.get("์ปจ์…‰ ๊ฐœ๋ฐœ", "").split("์ œ๋ชฉ")[1].split("\n")[0].strip() if "์ปจ์…‰ ๊ฐœ๋ฐœ" in planning_data else "๋ฌด์ œ"
logline = planning_data.get("์ปจ์…‰ ๊ฐœ๋ฐœ", "").split("๋กœ๊ทธ๋ผ์ธ")[1].split("\n")[0].strip() if "์ปจ์…‰ ๊ฐœ๋ฐœ" in planning_data else ""
ScreenplayDatabase.save_screenplay_content(session_id, screenplay_content, title, logline)
yield "โœ… ์‹œ๋‚˜๋ฆฌ์˜ค ์™„์„ฑ!", 100, screenplay_content
except Exception as e:
yield f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", 0, ""
def _create_writing_prompt(self, act: str, planning_data: Dict,
previous_acts: Dict, phase: str, original_query: str) -> str:
"""์ž‘์„ฑ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ - ์›๋ณธ ์ถฉ์‹ค"""
story_structure = planning_data.get("์Šคํ† ๋ฆฌ ๊ตฌ์„ฑ", "")
characters = planning_data.get("์บ๋ฆญํ„ฐ ์„ค๊ณ„", "")
scenes = planning_data.get("์”ฌ ๊ตฌ์„ฑ", "")
world = planning_data.get("์„ธ๊ณ„๊ด€ ๊ตฌ์ถ•", "")
if phase == "์ดˆ๊ณ ":
min_lines = 800
instruction = "์ดˆ๊ณ ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”"
elif phase == "์ˆ˜์ •":
min_lines = 1000
instruction = "๋‚ด์šฉ์„ ํ™•์žฅํ•˜๊ณ  ๋‹ค๋“ฌ์œผ์„ธ์š”"
else: # ์™„์„ฑ
min_lines = 1200
instruction = "์ตœ์ข… ์™„์„ฑ๋ณธ์„ ์ž‘์„ฑํ•˜์„ธ์š”"
prompt = f"""ใ€์›๋ณธ ์š”์ฒญใ€‘
{original_query}
ใ€{act} {instruction}ใ€‘
๋ชฉํ‘œ ๋ถ„๋Ÿ‰: ์ตœ์†Œ {min_lines}์ค„
ใ€๊ธฐํš์•ˆ ๋‚ด์šฉใ€‘
์Šคํ† ๋ฆฌ ๊ตฌ์กฐ:
{story_structure[:1000]}
์บ๋ฆญํ„ฐ:
{characters[:1000]}
์„ธ๊ณ„๊ด€:
{world[:500]}
์”ฌ ๊ตฌ์„ฑ:
{scenes[:1000]}
ใ€์ด์ „ ๋ง‰ ๋‚ด์šฉใ€‘
{previous_acts if previous_acts else "์ฒซ ๋ง‰์ž…๋‹ˆ๋‹ค"}
ใ€ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญใ€‘
1. ์›๋ณธ ์š”์ฒญ๊ณผ ๊ธฐํš์•ˆ์„ ์ถฉ์‹คํžˆ ๋ฐ˜์˜
2. ํ‘œ์ค€ ์‹œ๋‚˜๋ฆฌ์˜ค ํฌ๋งท ์‚ฌ์šฉ
- INT./EXT. ์žฅ์†Œ - ์‹œ๊ฐ„
- ์บ๋ฆญํ„ฐ๋ช…์€ ๋Œ€๋ฌธ์ž
- ๋Œ€์‚ฌ๋Š” ์บ๋ฆญํ„ฐ ์•„๋ž˜ ๋“ค์—ฌ์“ฐ๊ธฐ
3. ์‹œ๊ฐ์  ๋ฌ˜์‚ฌ๋Š” ํ˜„์žฌํ˜•์œผ๋กœ
4. ๊ฐ ์”ฌ๋งˆ๋‹ค ์ถฉ๋ถ„ํ•œ ๋ถ„๋Ÿ‰ (5-7ํŽ˜์ด์ง€)
5. ์ ˆ๋Œ€ ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ๋กœ ๋ณ€๊ฒฝํ•˜์ง€ ๋ง ๊ฒƒ
โš ๏ธ ์ค‘์š”: ๋ฐ˜๋“œ์‹œ ์›๋ณธ ์š”์ฒญ "{original_query}"์˜ ์Šคํ† ๋ฆฌ๋ฅผ ๊ทธ๋Œ€๋กœ ๊ตฌํ˜„ํ•˜์„ธ์š”.
์ž„์˜๋กœ ์Šคํ† ๋ฆฌ๋ฅผ ๋ฐ”๊พธ์ง€ ๋งˆ์„ธ์š”.
{min_lines}์ค„ ์ด์ƒ ์ž‘์„ฑํ•˜์„ธ์š”."""
return prompt
def _create_review_prompt(self, act: str, content: str, original_query: str) -> str:
"""๊ฒ€ํ†  ํ”„๋กฌํ”„ํŠธ"""
return f"""ใ€์›๋ณธ ์š”์ฒญใ€‘
{original_query}
ใ€{act} ๊ฒ€ํ† ใ€‘
์ž‘์„ฑ๋œ ๋‚ด์šฉ:
{content[:2000]}...
ใ€๊ฒ€ํ†  ํ•ญ๋ชฉใ€‘
1. ์›๋ณธ ์š”์ฒญ๊ณผ์˜ ์ผ์น˜์„ฑ (๊ฐ€์žฅ ์ค‘์š”)
2. ์Šคํ† ๋ฆฌ ๋…ผ๋ฆฌ์„ฑ
3. ์บ๋ฆญํ„ฐ ์ผ๊ด€์„ฑ
4. ๋Œ€์‚ฌ ์ž์—ฐ์Šค๋Ÿฌ์›€
5. ํŽ˜์ด์‹ฑ๊ณผ ๋ฆฌ๋“ฌ
ใ€ํ•„์ˆ˜ ํ™•์ธ์‚ฌํ•ญใ€‘
- ์›๋ณธ ์š”์ฒญ์˜ ์Šคํ† ๋ฆฌ๊ฐ€ ์ œ๋Œ€๋กœ ๊ตฌํ˜„๋˜์—ˆ๋Š”๊ฐ€?
- ์ž„์˜๋กœ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„์€ ์—†๋Š”๊ฐ€?
- ๊ธฐํš์•ˆ๊ณผ ์ผ์น˜ํ•˜๋Š”๊ฐ€?
๊ตฌ์ฒด์ ์ธ ๊ฐœ์„  ์ œ์•ˆ์„ ํ•˜์„ธ์š”."""
def _create_dialogue_prompt(self, screenplay: str) -> str:
"""๋Œ€์‚ฌ ๊ฐ•ํ™” ํ”„๋กฌํ”„ํŠธ"""
return f"""์ „์ฒด ์‹œ๋‚˜๋ฆฌ์˜ค์˜ ๋Œ€์‚ฌ๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ๊ฐœ์„ ํ•˜์„ธ์š”.
ใ€๊ฐœ์„  ํฌ์ธํŠธใ€‘
1. ์บ๋ฆญํ„ฐ๋ณ„ ๊ณ ์œ ํ•œ ๋งํˆฌ ํ™•๋ฆฝ
2. ์„œ๋ธŒํ…์ŠคํŠธ ์ถ”๊ฐ€
3. ๋ถˆํ•„์š”ํ•œ ์„ค๋ช… ๋Œ€์‚ฌ ์ œ๊ฑฐ
4. ๊ฐ์ •์  ์ง„์ •์„ฑ ๊ฐ•ํ™”
5. ์žฅ๋ฅด์— ๋งž๋Š” ํ†ค ์œ ์ง€
ใ€์›๋ณธ ์œ ์ง€ใ€‘
- ์Šคํ† ๋ฆฌ์™€ ์„ค์ •์€ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜์ง€ ๋งˆ์„ธ์š”
- ๋Œ€์‚ฌ๋งŒ ๊ฐœ์„ ํ•˜์„ธ์š”
์ฃผ์š” ๊ฐœ์„ ์ด ํ•„์š”ํ•œ ๋Œ€์‚ฌ๋“ค์„ ์ฐพ์•„ ์ˆ˜์ •์•ˆ์„ ์ œ์‹œํ•˜์„ธ์š”."""
def _create_final_review_prompt(self, screenplay: str) -> str:
"""์ตœ์ข… ๊ฒ€ํ†  ํ”„๋กฌํ”„ํŠธ"""
return f"""์™„์„ฑ๋œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ตœ์ข… ๊ฒ€ํ† ํ•˜์„ธ์š”.
ใ€ํ‰๊ฐ€ ํ•ญ๋ชฉใ€‘
1. ์›๋ณธ ์š”์ฒญ ์ถฉ์‹ค๋„ (์ตœ์šฐ์„ )
2. ์ „์ฒด ๊ตฌ์กฐ์™€ ํŽ˜์ด์‹ฑ
3. ์บ๋ฆญํ„ฐ ์•„ํฌ ์™„์„ฑ๋„
4. ํ…Œ๋งˆ ์ „๋‹ฌ๋ ฅ
5. ์ƒ์—…์„ฑ๊ณผ ์™„์„ฑ๋„
ใ€ํ™•์ธ์‚ฌํ•ญใ€‘
- ์ฒ˜์Œ ์š”์ฒญํ•œ ์Šคํ† ๋ฆฌ๊ฐ€ ์ œ๋Œ€๋กœ ๊ตฌํ˜„๋˜์—ˆ๋Š”๊ฐ€?
- ์ค‘๊ฐ„์— ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ๋กœ ๋ฐ”๋€Œ์ง€ ์•Š์•˜๋Š”๊ฐ€?
- ๊ธฐํš์•ˆ๊ณผ ์ผ์น˜ํ•˜๋Š”๊ฐ€?
์ดํ‰๊ณผ ํ•จ๊ป˜ ์ตœ์ข… ๊ฐœ์„  ์‚ฌํ•ญ์„ ์ œ์‹œํ•˜์„ธ์š”."""
# ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
def format_planning_display(planning_data: Dict) -> str:
"""๊ธฐํš์•ˆ ํ‘œ์‹œ ํฌ๋งท"""
if not planning_data:
return "๊ธฐํš์•ˆ์ด ์•„์ง ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
formatted = "# ๐Ÿ“‹ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐํš์•ˆ\n\n"
for stage_name, content in planning_data.items():
formatted += f"## {stage_name}\n\n"
formatted += content + "\n\n"
formatted += "---\n\n"
return formatted
def format_screenplay_display(screenplay_text: str) -> str:
"""์‹œ๋‚˜๋ฆฌ์˜ค ํ‘œ์‹œ ํฌ๋งท"""
if not screenplay_text:
return "์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ์•„์ง ์ž‘์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
formatted = "# ๐ŸŽฌ ์‹œ๋‚˜๋ฆฌ์˜ค\n\n"
# ์”ฌ ํ—ค๋”ฉ ๊ฐ•์กฐ
formatted_text = re.sub(
r'^(INT\.|EXT\.).*$',
r'**\g<0>**',
screenplay_text,
flags=re.MULTILINE
)
# ์บ๋ฆญํ„ฐ๋ช… ๊ฐ•์กฐ
formatted_text = re.sub(
r'^([๊ฐ€-ํžฃA-Z][๊ฐ€-ํžฃA-Z\s]+)$',
r'**\g<0>**',
formatted_text,
flags=re.MULTILINE
)
# ํŽ˜์ด์ง€ ์ˆ˜ ๊ณ„์‚ฐ
page_count = len(screenplay_text.splitlines()) / 58
formatted = f"**์ด ํŽ˜์ด์ง€: {page_count:.1f}**\n\n" + formatted_text
return formatted
def generate_random_concept(screenplay_type: str, genre: str) -> str:
"""๋žœ๋ค ์ปจ์…‰ ์ƒ์„ฑ"""
concepts = {
'์•ก์…˜': [
"์€ํ‡ดํ•œ ํŠน์ˆ˜์š”์›์ด ๋‚ฉ์น˜๋œ ๋”ธ์„ ๊ตฌํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ํ˜„์žฅ์œผ๋กœ ๋ณต๊ท€ํ•œ๋‹ค",
"ํ‰๋ฒ”ํ•œ ํƒ์‹œ๊ธฐ์‚ฌ๊ฐ€ ์šฐ์—ฐํžˆ ๊ตญ์ œ ํ…Œ๋Ÿฌ ์กฐ์ง์˜ ์Œ๋ชจ์— ํœ˜๋ง๋ฆฐ๋‹ค",
"๋ถ€ํŒจ ๊ฒฝ์ฐฐ๊ณผ ๋งž์„œ ์‹ธ์šฐ๋Š” ์ •์˜๋กœ์šด ํ˜•์‚ฌ์˜ ๊ณ ๋…ํ•œ ์‹ธ์›€"
],
'์Šค๋ฆด๋Ÿฌ': [
"๊ธฐ์–ต์„ ์žƒ์€ ๋‚จ์ž๊ฐ€ ์ž์‹ ์ด ์—ฐ์‡„์‚ด์ธ๋ฒ”์ด๋ผ๋Š” ์ฆ๊ฑฐ๋ฅผ ๋ฐœ๊ฒฌํ•œ๋‹ค",
"์‹ค์ข…๋œ ์•„์ด๋ฅผ ์ฐพ๋Š” ๊ณผ์ •์—์„œ ๋งˆ์„์˜ ๋”์ฐํ•œ ๋น„๋ฐ€์ด ๋“œ๋Ÿฌ๋‚œ๋‹ค",
"์™„๋ฒฝํ•œ ์•Œ๋ฆฌ๋ฐ”์ด๋ฅผ ๊ฐ€์ง„ ์šฉ์˜์ž๋ฅผ ์ซ“๋Š” ๊ฒ€์‚ฌ์˜ ์ง‘์š”ํ•œ ์ถ”์ "
],
'๋“œ๋ผ๋งˆ': [
"๋ง๊ธฐ ์•” ํ™˜์ž๊ฐ€ ๋งˆ์ง€๋ง‰ ์†Œ์›์œผ๋กœ ๊ฐ€์กฑ๊ณผ์˜ ํ™”ํ•ด๋ฅผ ์‹œ๋„ํ•œ๋‹ค",
"์žฌ๊ฐœ๋ฐœ๋กœ ์‚ฌ๋ผ์งˆ ๋™๋„ค๋ฅผ ์ง€ํ‚ค๋ ค๋Š” ์ฃผ๋ฏผ๋“ค์˜ ๋งˆ์ง€๋ง‰ ์‹ธ์›€",
"์ž…์–‘์•„๊ฐ€ ์ƒ๋ชจ๋ฅผ ์ฐพ์•„๊ฐ€๋Š” ๊ฐ€์Šด ์•„ํ”ˆ ์—ฌ์ •"
],
'์ฝ”๋ฏธ๋””': [
"๊ฒฐํ˜ผ์‹์žฅ์„ ์šด์˜ํ•˜๋Š” ์ปคํ”Œ์ด ์ž์‹ ๋“ค์˜ ์ดํ˜ผ์„ ์ˆจ๊ธฐ๋ฉฐ ์ผํ•œ๋‹ค",
"๋ณต๊ถŒ์— ๋‹น์ฒจ๋œ ๊ฐ€์กฑ์ด ์„œ๋กœ ๋ˆ์„ ์ฐจ์ง€ํ•˜๋ ค ์Œ๋ชจ๋ฅผ ๊พธ๋ฏผ๋‹ค",
"์‹ค์ˆ˜๋กœ ๋Œ€ํ†ต๋ น ๊ฒฝํ˜ธ์›์ด ๋œ ํ‰๋ฒ”ํ•œ ํšŒ์‚ฌ์›์˜ ์ขŒ์ถฉ์šฐ๋Œ ์ด์•ผ๊ธฐ"
],
'๊ณตํฌ': [
"ํ๋ณ‘์›์—์„œ ๋ฐค์ƒˆ ์ƒ์กดํ•ด์•ผ ํ•˜๋Š” ์˜๋Œ€์ƒ๋“ค์˜ ๊ณตํฌ ์ฒดํ—˜",
"๊ฑฐ์šธ ์†์—์„œ ๋‚˜ํƒ€๋‚˜๋Š” ๋˜ ๋‹ค๋ฅธ ์ž์‹ ๊ณผ ๋งˆ์ฃผํ•œ ์—ฌ์ž",
"์ €์ฃผ๋ฐ›์€ ๊ณจ๋™ํ’ˆ์„ ํŒ๋งคํ•œ ํ›„ ๋ฒŒ์–ด์ง€๋Š” ๊ธฐ์ดํ•œ ์‚ฌ๊ฑด๋“ค"
],
'SF': [
"์‹œ๊ฐ„ ์—ฌํ–‰์œผ๋กœ ๊ณผ๊ฑฐ๋ฅผ ๋ฐ”๊พธ๋ ค๋‹ค ๋” ํฐ ์žฌ์•™์„ ๋งŒ๋“  ๊ณผํ•™์ž",
"์ธ๊ณต์ง€๋Šฅ์ด ์ธ๊ฐ„์˜ ๊ฐ์ •์„ ํ•™์Šตํ•˜๋ฉฐ ๋ฒŒ์–ด์ง€๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‚ฌ๊ฑด",
"์™ธ๊ณ„ ์‹ ํ˜ธ๋ฅผ ํ•ด๋…ํ•œ ์–ธ์–ดํ•™์ž๊ฐ€ ์ธ๋ฅ˜์˜ ์šด๋ช…์„ ๊ฒฐ์ •ํ•ด์•ผ ํ•œ๋‹ค"
],
'๋กœ๋งจ์Šค': [
"์ฒซ์‚ฌ๋ž‘๊ณผ 20๋…„ ๋งŒ์— ์žฌํšŒํ•œ ๋‘ ์‚ฌ๋žŒ์˜ ๋‘ ๋ฒˆ์งธ ๊ธฐํšŒ",
"๊ณ„์•ฝ๊ฒฐํ˜ผ์œผ๋กœ ์‹œ์ž‘ํ–ˆ๋‹ค๊ฐ€ ์ง„์งœ ์‚ฌ๋ž‘์— ๋น ์ง„ ์ปคํ”Œ",
"์„œ๋กœ ๋‹ค๋ฅธ ์‹œ๋Œ€๋ฅผ ์‚ด์•„๊ฐ€๋Š” ๋‘ ์‚ฌ๋žŒ์˜ ํŽธ์ง€๋ฅผ ํ†ตํ•œ ์‚ฌ๋ž‘"
],
'ํŒํƒ€์ง€': [
"ํ‰๋ฒ”ํ•œ ๊ณ ๋“ฑํ•™์ƒ์ด ํŒํƒ€์ง€ ์„ธ๊ณ„์˜ ์„ ํƒ๋ฐ›์€ ์šฉ์‚ฌ๊ฐ€ ๋œ๋‹ค",
"๋„์‹œ์— ๋‚˜ํƒ€๋‚œ ์šฉ๊ณผ ์†Œํ†ตํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ์‚ฌ๋žŒ",
"๋งˆ๋ฒ•์ด ์‚ฌ๋ผ์ง„ ์„ธ๊ณ„์—์„œ ๋งˆ์ง€๋ง‰ ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ๋˜์–ด๋ฒ„๋ฆฐ ์ฒญ๋…„"
]
}
genre_concepts = concepts.get(genre, concepts['๋“œ๋ผ๋งˆ'])
return random.choice(genre_concepts)
# Gradio ์ธํ„ฐํŽ˜์ด์Šค
def create_interface():
css = """
.main-header {
text-align: center;
margin-bottom: 2rem;
padding: 2.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
color: white;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.header-title {
font-size: 3rem;
margin-bottom: 1rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.planning-section {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 10px;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.screenplay-output {
font-family: 'Nanum Gothic Coding', 'Courier New', monospace;
white-space: pre-wrap;
background: white;
padding: 2rem;
border: 2px solid #e0e0e0;
border-radius: 10px;
max-height: 800px;
overflow-y: auto;
}
.progress-bar {
background: #e0e0e0;
border-radius: 10px;
height: 30px;
position: relative;
margin: 1rem 0;
}
.progress-fill {
background: linear-gradient(90deg, #667eea, #764ba2);
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
}
.warning-box {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 5px;
padding: 1rem;
margin: 1rem 0;
}
"""
with gr.Blocks(theme=gr.themes.Soft(), css=css, title="AI ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€") as interface:
gr.HTML("""
<div class="main-header">
<h1 class="header-title">๐ŸŽฌ AI ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€</h1>
<p style="font-size: 1.2rem; opacity: 0.95;">
์ „๋ฌธ๊ฐ€ ์ˆ˜์ค€์˜ ํ•œ๊ตญ์–ด ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ AI๊ฐ€ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค<br>
์ž…๋ ฅํ•œ ์•„์ด๋””์–ด๋ฅผ ์ถฉ์‹คํžˆ ๋ฐ˜์˜ํ•˜์—ฌ ์™„๋ฒฝํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
</p>
</div>
""")
current_session_id = gr.State(None)
current_planning_data = gr.State({})
with gr.Tabs():
with gr.Tab("๐Ÿ“ ์ƒˆ ์‹œ๋‚˜๋ฆฌ์˜ค"):
# ์ฃผ์˜์‚ฌํ•ญ
gr.HTML("""
<div class="warning-box">
<strong>๐Ÿ’ก ํŒ:</strong> ๊ตฌ์ฒด์ ์ด๊ณ  ๋ช…ํ™•ํ•œ ์„ค์ •์„ ์ž…๋ ฅํ• ์ˆ˜๋ก ์›ํ•˜๋Š” ์Šคํ† ๋ฆฌ๊ฐ€ ์ •ํ™•ํžˆ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.<br>
์ฃผ์ธ๊ณต, ๋ฐฐ๊ฒฝ, ํ•ต์‹ฌ ๊ฐˆ๋“ฑ, ๊ฒฐ๋ง ๋ฐฉํ–ฅ์„ ๊ตฌ์ฒด์ ์œผ๋กœ ์ œ์‹œํ•ด์ฃผ์„ธ์š”.
</div>
""")
with gr.Row():
with gr.Column(scale=2):
query_input = gr.Textbox(
label="๐Ÿ’ก ์‹œ๋‚˜๋ฆฌ์˜ค ์•„์ด๋””์–ด (๊ตฌ์ฒด์ ์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”)",
placeholder="""์›ํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค๋ช…ํ•˜์„ธ์š”. ์˜ˆ์‹œ:
"2045๋…„ ์„œ์šธ, AI๊ฐ€ ์ธ๊ฐ„์˜ ๊ฐ์ •์„ ์™„๋ฒฝํžˆ ๋ชจ๋ฐฉํ•˜๋Š” ์‹œ๋Œ€.
์ฃผ์ธ๊ณต ๊น€๋ฏผ์ค€(35์„ธ, AI ์œค๋ฆฌํ•™์ž)์€ ์ž์‹ ์˜ ์ฃฝ์€ ์•„๋‚ด๋ฅผ ๋ณต์ œํ•œ AI์™€ ์‚ด๊ณ  ์žˆ๋‹ค.
์–ด๋А ๋‚  ์ง„์งœ ์•„๋‚ด๊ฐ€ ์‚ด์•„์žˆ๋‹ค๋Š” ์ฆ๊ฑฐ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ณ ,
AI ์•„๋‚ด์™€ ํ•จ๊ป˜ ์ง„์‹ค์„ ์ฐพ์•„ ๋‚˜์„ ๋‹ค.
๊ฒฐ๋ง์—์„œ ์ง„์งœ์™€ ๊ฐ€์งœ์˜ ๊ฒฝ๊ณ„๊ฐ€ ๋ฌด๋„ˆ์ง€๋ฉฐ
์‚ฌ๋ž‘์˜ ๋ณธ์งˆ์— ๋Œ€ํ•ด ์งˆ๋ฌธ์„ ๋˜์ง„๋‹ค."
๊ตฌ์ฒด์ ์ธ ์„ค์ •์ผ์ˆ˜๋ก ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.""",
lines=8
)
with gr.Column(scale=1):
screenplay_type = gr.Radio(
choices=list(SCREENPLAY_LENGTHS.keys()),
value="์˜ํ™”",
label="๐Ÿ“ฝ๏ธ ์‹œ๋‚˜๋ฆฌ์˜ค ์œ ํ˜•"
)
genre_select = gr.Dropdown(
choices=list(GENRE_TEMPLATES.keys()),
value="๋“œ๋ผ๋งˆ",
label="๐ŸŽญ ์žฅ๋ฅด"
)
with gr.Row():
random_btn = gr.Button("๐ŸŽฒ ๋žœ๋ค ์•„์ด๋””์–ด", scale=1)
clear_btn = gr.Button("๐Ÿ—‘๏ธ ์ดˆ๊ธฐํ™”", scale=1)
planning_btn = gr.Button("๐Ÿ“‹ ๊ธฐํš์•ˆ ์ƒ์„ฑ", variant="primary", scale=2)
# ์ง„ํ–‰ ์ƒํƒœ
progress_bar = gr.HTML(
value='<div class="progress-bar"><div class="progress-fill" style="width: 0%"></div></div>'
)
status_text = gr.Textbox(
label="๐Ÿ“Š ์ง„ํ–‰ ์ƒํƒœ",
interactive=False,
value="์•„์ด๋””์–ด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ธฐํš์•ˆ ์ƒ์„ฑ์„ ์‹œ์ž‘ํ•˜์„ธ์š”"
)
# ๊ธฐํš์•ˆ ์„น์…˜
with gr.Group():
gr.Markdown("### ๐Ÿ“‹ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐํš์•ˆ")
planning_display = gr.Markdown(
value="*๊ธฐํš์•ˆ์ด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค...*",
elem_classes=["planning-section"]
)
with gr.Row():
edit_planning_btn = gr.Button("โœ๏ธ ๊ธฐํš์•ˆ ์ˆ˜์ •", scale=1)
save_planning_btn = gr.Button("๐Ÿ’พ ๊ธฐํš์•ˆ ์ €์žฅ", scale=1)
generate_screenplay_btn = gr.Button("๐ŸŽฌ ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘์„ฑ ์‹œ์ž‘", variant="primary", scale=2)
# ์‹œ๋‚˜๋ฆฌ์˜ค ์ถœ๋ ฅ
with gr.Group():
gr.Markdown("### ๐Ÿ“„ ์™„์„ฑ๋œ ์‹œ๋‚˜๋ฆฌ์˜ค")
screenplay_output = gr.Markdown(
value="*์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค...*",
elem_classes=["screenplay-output"]
)
with gr.Row():
download_btn = gr.Button("๐Ÿ’พ ๋‹ค์šด๋กœ๋“œ (TXT)", scale=1)
pdf_btn = gr.Button("๐Ÿ“‘ PDF ๋ณ€ํ™˜", scale=1)
# ์˜ˆ์‹œ
gr.Examples(
examples=[
["2045๋…„ ์„œ์šธ, AI๊ฐ€ ์ธ๊ฐ„ ๊ฐ์ •์„ ์™„๋ฒฝํžˆ ๋ชจ๋ฐฉํ•˜๋Š” ์‹œ๋Œ€. AI ์œค๋ฆฌํ•™์ž๊ฐ€ ์ฃฝ์€ ์•„๋‚ด๋ฅผ ๋ณต์ œํ•œ AI์™€ ์‚ด๋‹ค๊ฐ€ ์ง„์งœ ์•„๋‚ด๊ฐ€ ์‚ด์•„์žˆ๋‹ค๋Š” ์ฆ๊ฑฐ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ณ  ์ง„์‹ค์„ ์ฐพ์•„ ๋‚˜์„ ๋‹ค."],
["์กฐ์„ ์‹œ๋Œ€ ํ•œ์–‘, ์‹ ๋ถ„์„ ์ˆจ๊ธด ์™•์„ธ์ž๊ฐ€ ์ €์žฃ๊ฑฐ๋ฆฌ์—์„œ ์˜๋…€๋ฅผ ๋งŒ๋‚˜ ์‚ฌ๋ž‘์— ๋น ์ง„๋‹ค. ์—ญ๋ชจ ์‚ฌ๊ฑด์— ํœ˜๋ง๋ ค ํ•จ๊ป˜ ์ง„์‹ค์„ ๋ฐํ˜€์•ผ ํ•œ๋‹ค."],
["์ž‘์€ ํ•ด์•ˆ ๋งˆ์„, 10๋…„ ์ „ ์‹ค์ข…๋œ ๋”ธ์ด ๊ฐ‘์ž๊ธฐ ๋Œ์•„์˜จ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๋…€๋Š” 10๋…„ ์ „ ๋ชจ์Šต ๊ทธ๋Œ€๋กœ๋‹ค. ๋งˆ์„์˜ ์ˆจ๊ฒจ์ง„ ๋น„๋ฐ€์ด ๋“œ๋Ÿฌ๋‚˜๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค."],
["๋Œ€๊ธฐ์—… ๋ฒ•๋ฌดํŒ€ ๋ณ€ํ˜ธ์‚ฌ๊ฐ€ ํšŒ์‚ฌ์˜ ๋ถˆ๋ฒ•์„ ๊ณ ๋ฐœํ•˜๋ ค๋‹ค ๋ชจํ•จ์„ ๋ฐ›๋Š”๋‹ค. ์ˆจ์–ด์„œ ์ฆ๊ฑฐ๋ฅผ ๋ชจ์œผ๋ฉฐ ๊ฑฐ๋Œ€ํ•œ ์Œ๋ชจ์™€ ๋งž์„ ๋‹ค."]
],
inputs=query_input,
label="๐Ÿ’ก ์˜ˆ์‹œ ์•„์ด๋””์–ด (๊ตฌ์ฒด์ ์ธ ์„ค์ • ์ฐธ๊ณ )"
)
with gr.Tab("๐Ÿ“š ๋‚ด ์ž‘ํ’ˆ"):
gr.Markdown("""
### ๐Ÿ“ ์ €์žฅ๋œ ์‹œ๋‚˜๋ฆฌ์˜ค
์ž‘์—… ์ค‘์ด๊ฑฐ๋‚˜ ์™„์„ฑ๋œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
""")
saved_list = gr.Dataframe(
headers=["์ œ๋ชฉ", "์žฅ๋ฅด", "์œ ํ˜•", "ํŽ˜์ด์ง€", "์ƒํƒœ", "์ˆ˜์ •์ผ"],
value=[],
label="์ €์žฅ๋œ ์ž‘ํ’ˆ ๋ชฉ๋ก"
)
with gr.Row():
refresh_btn = gr.Button("๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ")
load_btn = gr.Button("๐Ÿ“‚ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ")
delete_btn = gr.Button("๐Ÿ—‘๏ธ ์‚ญ์ œ")
# ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
def handle_planning(query, s_type, genre):
if not query:
yield "", "โŒ ์•„์ด๋””์–ด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”", "", {}
return
system = ScreenplayGenerationSystem()
for status, progress, planning_data in system.generate_planning(query, s_type, genre):
progress_html = f'<div class="progress-bar"><div class="progress-fill" style="width: {progress}%"></div></div>'
planning_display = format_planning_display(planning_data)
yield progress_html, status, planning_display, planning_data
def handle_screenplay_generation(session_id, planning_data):
if not planning_data:
yield "", "โŒ ๋จผ์ € ๊ธฐํš์•ˆ์„ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”", ""
return
system = ScreenplayGenerationSystem()
for status, progress, screenplay in system.generate_screenplay(session_id, planning_data):
progress_html = f'<div class="progress-bar"><div class="progress-fill" style="width: {progress}%"></div></div>'
screenplay_display = format_screenplay_display(screenplay)
yield progress_html, status, screenplay_display
def handle_random(s_type, genre):
return generate_random_concept(s_type, genre)
def handle_clear():
empty_progress = '<div class="progress-bar"><div class="progress-fill" style="width: 0%"></div></div>'
return "", empty_progress, "์ค€๋น„๋จ", "", "", None, {}
def handle_download(screenplay_text):
if not screenplay_text or screenplay_text == "*์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค...*":
return None
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"์‹œ๋‚˜๋ฆฌ์˜ค_{timestamp}.txt"
with open(filename, 'w', encoding='utf-8') as f:
# ๋งˆํฌ๋‹ค์šด ์ œ๊ฑฐ
clean_text = re.sub(r'\*\*([^*]+)\*\*', r'\1', screenplay_text)
clean_text = re.sub(r'^#+ ', '', clean_text, flags=re.MULTILINE)
f.write(clean_text)
return filename
# ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
planning_btn.click(
fn=handle_planning,
inputs=[query_input, screenplay_type, genre_select],
outputs=[progress_bar, status_text, planning_display, current_planning_data]
)
generate_screenplay_btn.click(
fn=handle_screenplay_generation,
inputs=[current_session_id, current_planning_data],
outputs=[progress_bar, status_text, screenplay_output]
)
random_btn.click(
fn=handle_random,
inputs=[screenplay_type, genre_select],
outputs=[query_input]
)
clear_btn.click(
fn=handle_clear,
outputs=[query_input, progress_bar, status_text, planning_display,
screenplay_output, current_session_id, current_planning_data]
)
download_btn.click(
fn=handle_download,
inputs=[screenplay_output],
outputs=[gr.File(label="๋‹ค์šด๋กœ๋“œ")]
)
return interface
# ๋ฉ”์ธ ์‹คํ–‰
if __name__ == "__main__":
logger.info("=" * 60)
logger.info("AI ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘๊ฐ€ ์‹œ์ž‘...")
logger.info("์›๋ณธ ์š”์ฒญ ์ถฉ์‹ค๋„ ๊ฐ•ํ™” ๋ฒ„์ „")
logger.info("=" * 60)
if not FIREWORKS_API_KEY or FIREWORKS_API_KEY == "dummy_token_for_testing":
logger.warning("โš ๏ธ FIREWORKS_API_KEY๋ฅผ ์„ค์ •ํ•ด์ฃผ์„ธ์š”!")
logger.warning("export FIREWORKS_API_KEY='your-api-key'")
ScreenplayDatabase.init_db()
interface = create_interface()
interface.launch(
server_name="0.0.0.0",
server_port=7860,
share=False
)