Spaces:
Running
Running
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๋ง - ์ต์ข ์์ฑ๋ณธ"), | |
("๋์ฌ ๊ฐํ", "๐ฌ ๋์ฌ ์ ๋ฌธ๊ฐ: ๋์ฌ ๊ฐ์ ๋ฐ ์๋ธํ ์คํธ"), | |
("์ต์ข ๊ฒํ ", "๐ญ ์ต์ข ๋ฆฌ๋ทฐ: ์ ์ฒด ์๋๋ฆฌ์ค ๋ถ์ ๋ฐ ์์ฑ"), | |
] | |
# ๋ฐ์ดํฐ ํด๋์ค | |
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) | |
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: | |
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() | |
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() | |
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 | |
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() | |
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 | |
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 | |
) |