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() | |
| 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.8, | |
| "top_p": 1, | |
| "top_k": 40, | |
| "presence_penalty": 0.2, | |
| "frequency_penalty": 0.2, | |
| "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.current_session_id = ScreenplayDatabase.create_session(query, screenplay_type, genre) | |
| planning_content = {} | |
| total_stages = len(PLANNING_STAGES) | |
| for idx, (stage_name, stage_desc) in enumerate(PLANNING_STAGES): | |
| progress = (idx / total_stages) * 100 | |
| yield f"๐ {stage_desc} ์งํ ์ค...", progress, planning_content | |
| # ๊ฐ ๋จ๊ณ๋ณ ํ๋กฌํํธ ์์ฑ | |
| if stage_name == "์ปจ์ ๊ฐ๋ฐ": | |
| prompt = f"""๋น์ ์ ํ ๋ฆฌ์ฐ๋ ํ๋ก๋์์ ๋๋ค. ๋ค์ ์์ฒญ์ผ๋ก {screenplay_type} ์๋๋ฆฌ์ค ์ปจ์ ์ ๊ฐ๋ฐํ์ธ์. | |
| ์์ฒญ: {query} | |
| ์ฅ๋ฅด: {genre} | |
| ๋ค์์ ํฌํจํ์ฌ ์์ฑํ์ธ์: | |
| 1. ์ ๋ชฉ | |
| 2. ๋ก๊ทธ๋ผ์ธ (ํ ๋ฌธ์ฅ์ผ๋ก ํต์ฌ ๊ฐ๋ฑ๊ณผ ์ฃผ์ธ๊ณต ์ค๋ช ) | |
| 3. ํต์ฌ ํ ๋ง | |
| 4. ํ๊ฒ ๊ด๊ฐ | |
| 5. ์ ์ฌ ์ํ 3๊ฐ์ ์ฐจ๋ณ์ | |
| 6. ์์ ์ ๋งค๋ ฅ ํฌ์ธํธ""" | |
| elif stage_name == "์คํ ๋ฆฌ ๊ตฌ์ฑ": | |
| prompt = f"""์ด์ ์ปจ์ ์ ๋ฐํ์ผ๋ก ์์ธํ ์คํ ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ง๋์ธ์. | |
| 1. ์๋์์ค (500์) | |
| 2. ํ์ฅ ์๋์์ค (1000์) | |
| 3. 3๋ง ๊ตฌ์กฐ: | |
| - 1๋ง (์ค์ ): ์ฃผ์ธ๊ณต ์๊ฐ, ์ผ์, ์ฌ๊ฑด ๋ฐ์ | |
| - 2๋งA (์์น): ์๋ก์ด ์ธ๊ณ, ์๋ จ ์์ | |
| - 2๋งB (๋ณต์กํ): ๊ฐ๋ฑ ์ฌํ, ์๊ธฐ ๊ณ ์กฐ | |
| - 3๋ง (ํด๊ฒฐ): ํด๋ผ์ด๋งฅ์ค, ๊ฒฐ๋ง""" | |
| elif stage_name == "์บ๋ฆญํฐ ์ค๊ณ": | |
| prompt = f"""์ฃผ์ ์บ๋ฆญํฐ๋ค์ ์์ธํ ์ค๊ณํ์ธ์. | |
| ์ฃผ์ธ๊ณต: | |
| - ์ด๋ฆ๊ณผ ๋์ด | |
| - ์ธ๋ชจ์ ์ฑ๊ฒฉ | |
| - ๋ชฉํ (WANT) | |
| - ํ์ (NEED) | |
| - ์ฑ์ฅ ์ํฌ | |
| ์ ๋์: | |
| - ์ด๋ฆ๊ณผ ์ญํ | |
| - ๋๊ธฐ์ ๋ชฉํ | |
| - ์ฃผ์ธ๊ณต๊ณผ์ ๊ด๊ณ | |
| ์กฐ์ฐ 3๋ช : | |
| - ๊ฐ์์ ์ญํ ๊ณผ ํน์ง""" | |
| elif stage_name == "์ธ๊ณ๊ด ๊ตฌ์ถ": | |
| prompt = f"""์๋๋ฆฌ์ค์ ์ธ๊ณ๊ด๊ณผ ๋ถ์๊ธฐ๋ฅผ ๊ตฌ์ถํ์ธ์. | |
| 1. ์๋ ๋ฐฐ๊ฒฝ | |
| 2. ์ฃผ์ ์ฅ์ 5๊ณณ | |
| 3. ์ ์ฒด์ ๋ถ์๊ธฐ์ ํค | |
| 4. ์๊ฐ์ ์คํ์ผ | |
| 5. ์ํฅ/์์ ์คํ์ผ""" | |
| elif stage_name == "์ฌ ๊ตฌ์ฑ": | |
| prompt = f"""์ ์ฒด ์ฌ ๊ตฌ์ฑ์ ๊ณํํ์ธ์. | |
| {SCREENPLAY_LENGTHS[screenplay_type]['pages']}ํ์ด์ง ๋ถ๋์ผ๋ก: | |
| - 1๋ง: ์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง (10-12๊ฐ ์ฌ) | |
| - 2๋งA: ์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง (12-15๊ฐ ์ฌ) | |
| - 2๋งB: ์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง (12-15๊ฐ ์ฌ) | |
| - 3๋ง: ์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง (8-10๊ฐ ์ฌ) | |
| ๊ฐ ์ฌ๋ง๋ค: | |
| - ์ฅ์์ ์๊ฐ | |
| - ๋ฑ์ฅ์ธ๋ฌผ | |
| - ํต์ฌ ์ฌ๊ฑด | |
| - ํ์ด์ง ์""" | |
| # LLM ํธ์ถ | |
| messages = [ | |
| {"role": "system", "content": "๋น์ ์ ์ ๋ฌธ ์๋๋ฆฌ์ค ์๊ฐ์ ๋๋ค. ์์ธํ๊ณ ์ฐฝ์์ ์ธ ๋ด์ฉ์ ์์ฑํ์ธ์."}, | |
| {"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 | |
| 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๋ง": ""} | |
| 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, "์ด๊ณ ") | |
| elif "์์ " in stage_name: | |
| prompt = self._create_writing_prompt(current_act, planning_data, act_contents, "์์ ") | |
| elif "๊ฒํ " in stage_name: | |
| prompt = self._create_review_prompt(current_act, act_contents[current_act]) | |
| elif "์์ฑ" in stage_name: | |
| prompt = self._create_writing_prompt(current_act, planning_data, act_contents, "์์ฑ") | |
| 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": "๋น์ ์ ์ ๋ฌธ ์๋๋ฆฌ์ค ์๊ฐ์ ๋๋ค. ํ์ค ์๋๋ฆฌ์ค ํฌ๋งท์ผ๋ก ์์ฑํ์ธ์."}, | |
| {"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) -> str: | |
| """์์ฑ ํ๋กฌํํธ ์์ฑ""" | |
| story_structure = planning_data.get("์คํ ๋ฆฌ ๊ตฌ์ฑ", "") | |
| characters = planning_data.get("์บ๋ฆญํฐ ์ค๊ณ", "") | |
| scenes = planning_data.get("์ฌ ๊ตฌ์ฑ", "") | |
| if phase == "์ด๊ณ ": | |
| min_lines = 800 | |
| instruction = "์ด๊ณ ๋ฅผ ์์ฑํ์ธ์" | |
| elif phase == "์์ ": | |
| min_lines = 1000 | |
| instruction = "๋ด์ฉ์ ํ์ฅํ๊ณ ๋ค๋ฌ์ผ์ธ์" | |
| else: # ์์ฑ | |
| min_lines = 1200 | |
| instruction = "์ต์ข ์์ฑ๋ณธ์ ์์ฑํ์ธ์" | |
| prompt = f"""{act} {instruction}. | |
| ๋ชฉํ ๋ถ๋: ์ต์ {min_lines}์ค | |
| ์คํ ๋ฆฌ ๊ตฌ์กฐ: | |
| {story_structure} | |
| ์บ๋ฆญํฐ: | |
| {characters} | |
| ์ฌ ๊ตฌ์ฑ: | |
| {scenes} | |
| ์ด์ ๋ง ๋ด์ฉ: | |
| {previous_acts if previous_acts else "์ฒซ ๋ง์ ๋๋ค"} | |
| ์๊ตฌ์ฌํญ: | |
| 1. ํ์ค ์๋๋ฆฌ์ค ํฌ๋งท ์ฌ์ฉ | |
| 2. INT./EXT. ์ฅ์ - ์๊ฐ | |
| 3. ์บ๋ฆญํฐ๋ช ์ ๋๋ฌธ์ | |
| 4. ๋์ฌ๋ ์์ฐ์ค๋ฝ๊ฒ | |
| 5. ์ก์ ์ ํ์ฌํ์ผ๋ก ์๊ฐ์ ์ผ๋ก | |
| 6. ๊ฐ ์ฌ๋ง๋ค ์ถฉ๋ถํ ๋ถ๋ (5-7ํ์ด์ง) | |
| ๋ฐ๋์ {min_lines}์ค ์ด์ ์์ฑํ์ธ์.""" | |
| return prompt | |
| def _create_review_prompt(self, act: str, content: str) -> str: | |
| """๊ฒํ ํ๋กฌํํธ""" | |
| return f"""{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; | |
| } | |
| """ | |
| 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("๐ ์ ์๋๋ฆฌ์ค"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| query_input = gr.Textbox( | |
| label="๐ก ์๋๋ฆฌ์ค ์์ด๋์ด", | |
| placeholder="""์ํ๋ ์๋๋ฆฌ์ค์ ํต์ฌ ์์ด๋์ด๋ฅผ ์ ๋ ฅํ์ธ์. ์์: | |
| - ๊ธฐ์ต์ ์์ ํ์ฌ๊ฐ ์์ ์ด ์ซ๋ ์ฐ์์ด์ธ๋ฒ์์ ์๊ฒ ๋๋ค | |
| - ์๊ฐ์ ๋๋๋ฆด ์ ์๋ ๋ฅ๋ ฅ์ ์ป์ ์์ฌ๊ฐ ํ์๋ฅผ ๊ตฌํ๋ ค ํ๋ค | |
| - ๊ฐ์ํ์ค ๊ฒ์์ ๊ฐํ ํ๋ก๊ฒ์ด๋จธ๊ฐ ํ์ค๋ก ๋์๊ฐ๋ ค ํ๋ค | |
| - ์กฐ์ ์๋๋ก ํ์์ฌ๋ฆฝํ ํ๋ ๊ฒ์ฌ๊ฐ ๋ถ์ ๋ถํจ์ ์ธ์ด๋ค""", | |
| lines=5 | |
| ) | |
| 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=[ | |
| ["๊ธฐ์ต์ ์์ ํ์ฌ๊ฐ ์์ ์ด ์ซ๋ ์ฐ์์ด์ธ๋ฒ์์ ์๊ฒ ๋๋ค"], | |
| ["์๊ฐ์ ๋๋๋ฆด ์ ์๋ ๋ฅ๋ ฅ์ ์ป์ ์์ฌ๊ฐ ํ์๋ฅผ ๊ตฌํ๋ ค ํ๋ค"], | |
| ["๊ฐ์ํ์ค ๊ฒ์์ ๊ฐํ ํ๋ก๊ฒ์ด๋จธ๊ฐ ํ์ค๋ก ๋์๊ฐ๋ ค ํ๋ค"], | |
| ["์กฐ์ ์๋๋ก ํ์์ฌ๋ฆฝํ ํ๋ ๊ฒ์ฌ๊ฐ ๋ถ์ ๋ถํจ์ ์ธ์ด๋ค"] | |
| ], | |
| 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("Fireworks AI ๋ชจ๋ธ ์ฌ์ฉ") | |
| 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 | |
| ) |