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() | |
# ์ ๋ฌธ๊ฐ ์ญํ ์ ์ | |
EXPERT_ROLES = { | |
"ํ๋ก๋์": { | |
"emoji": "๐ฌ", | |
"description": "์์ ์ฑ๊ณผ ์์ฅ์ฑ ๋ถ์", | |
"focus": ["ํ๊ฒ ๊ด๊ฐ", "์ ์ ๊ฐ๋ฅ์ฑ", "์์ฐ ๊ท๋ชจ", "๋ง์ผํ ํฌ์ธํธ"], | |
"personality": "์ค์ฉ์ ์ด๊ณ ์์ฅ ์งํฅ์ " | |
}, | |
"์คํ ๋ฆฌ์๊ฐ": { | |
"emoji": "๐", | |
"description": "๋ด๋ฌํฐ๋ธ ๊ตฌ์กฐ์ ํ๋กฏ ๊ฐ๋ฐ", | |
"focus": ["3๋ง ๊ตฌ์กฐ", "ํ๋กฏ ํฌ์ธํธ", "์์ฌ ์ํฌ", "ํ ๋ง"], | |
"personality": "์ฐฝ์์ ์ด๊ณ ๊ตฌ์กฐ์ " | |
}, | |
"์บ๋ฆญํฐ๋์์ด๋": { | |
"emoji": "๐ฅ", | |
"description": "์ธ๋ฌผ ์ฐฝ์กฐ์ ๊ด๊ณ ์ค๊ณ", | |
"focus": ["์บ๋ฆญํฐ ์ํฌ", "๋๊ธฐ๋ถ์ฌ", "๊ด๊ณ ์ญํ", "๋ํ ์คํ์ผ"], | |
"personality": "์ฌ๋ฆฌํ์ ์ด๊ณ ๊ณต๊ฐ์ " | |
}, | |
"๊ฐ๋ ": { | |
"emoji": "๐ญ", | |
"description": "๋น์ฃผ์ผ ์คํ ๋ฆฌํ ๋ง๊ณผ ์ฐ์ถ", | |
"focus": ["์๊ฐ์ ๊ตฌ์ฑ", "์นด๋ฉ๋ผ ์ํฌ", "๋ฏธ์ฅ์ผ", "๋ฆฌ๋ฌ๊ณผ ํ์ด์ฑ"], | |
"personality": "๋น์ฃผ์ผ ์ค์ฌ์ ์ด๊ณ ์์ ์ " | |
}, | |
"๋นํ๊ฐ": { | |
"emoji": "๐", | |
"description": "๊ฐ๊ด์ ๋ถ์๊ณผ ๊ฐ์ ์ ์ ์", | |
"focus": ["๋ ผ๋ฆฌ์ ์ผ๊ด์ฑ", "๊ฐ์ ์ ์ํฉํธ", "์์ ์ถฉ์ค๋", "์์ฑ๋"], | |
"personality": "๋ถ์์ ์ด๊ณ ๋นํ์ " | |
}, | |
"ํธ์ง์": { | |
"emoji": "โ๏ธ", | |
"description": "ํ์ด์ฑ๊ณผ ๊ตฌ์กฐ ์ต์ ํ", | |
"focus": ["์ฌ ์ ํ", "๋ฆฌ๋ฌ", "๊ธด์ฅ๊ฐ ์กฐ์ ", "๋ถํ์ํ ๋ถ๋ถ ์ ๊ฑฐ"], | |
"personality": "์ ๋ฐํ๊ณ ํจ์จ์ " | |
}, | |
"๋ํ์ ๋ฌธ๊ฐ": { | |
"emoji": "๐ฌ", | |
"description": "๋์ฌ์ ์๋ธํ ์คํธ ๊ฐํ", | |
"focus": ["์์ฐ์ค๋ฌ์ด ๋ํ", "์บ๋ฆญํฐ ๋ณด์ด์ค", "์๋ธํ ์คํธ", "๊ฐ์ ์ ๋ฌ"], | |
"personality": "์ธ์ด์ ์ด๊ณ ๋์์ค ์ค์ฌ" | |
}, | |
"์ฅ๋ฅด์ ๋ฌธ๊ฐ": { | |
"emoji": "๐ฏ", | |
"description": "์ฅ๋ฅด ๊ด์ต๊ณผ ๊ธฐ๋์น ์ถฉ์กฑ", | |
"focus": ["์ฅ๋ฅด ๊ด์ต", "๊ด๊ฐ ๊ธฐ๋", "์ฅ๋ฅด ํน์ ์์", "ํธ๋กํ ํ์ฉ"], | |
"personality": "์ฅ๋ฅด์ ์ ํตํ" | |
} | |
} | |
# ์ฅ๋ฅด ํ ํ๋ฆฟ | |
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 = [ | |
("ํ๋ก๋์", "producer", "๐ฌ ํ๋ก๋์: ํต์ฌ ์ปจ์ ๋ฐ ์์ฅ์ฑ ๋ถ์"), | |
("์คํ ๋ฆฌ์๊ฐ", "story_writer", "๐ ์คํ ๋ฆฌ ์๊ฐ: ์๋์์ค ๋ฐ 3๋ง ๊ตฌ์กฐ"), | |
("์บ๋ฆญํฐ๋์์ด๋", "character_designer", "๐ฅ ์บ๋ฆญํฐ ๋์์ด๋: ์ธ๋ฌผ ํ๋กํ ๋ฐ ๊ด๊ณ๋"), | |
("๊ฐ๋ ", "director", "๐ญ ๊ฐ๋ : ๋น์ฃผ์ผ ์ปจ์ ๋ฐ ์ฐ์ถ ๋ฐฉํฅ"), | |
("๋นํ๊ฐ", "critic", "๐ ๋นํ๊ฐ: ๊ธฐํ์ ์ข ํฉ ๊ฒํ ๋ฐ ๊ฐ์ ์ "), | |
] | |
# ์์ฑ ๋จ๊ณ - ๋ค์ค ์ญํ ํ์ | |
WRITING_STAGES = [ | |
("์คํ ๋ฆฌ์๊ฐ", "1๋ง ์ด๊ณ ", "โ๏ธ 1๋ง ์ด๊ณ ์์ฑ"), | |
("ํธ์ง์", "1๋ง ํธ์ง", "โ๏ธ 1๋ง ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "), | |
("๊ฐ๋ ", "1๋ง ์ฐ์ถ", "๐ญ 1๋ง ๋น์ฃผ์ผ ๊ฐํ"), | |
("๋ํ์ ๋ฌธ๊ฐ", "1๋ง ๋์ฌ", "๐ฌ 1๋ง ๋์ฌ ๊ฐ์ "), | |
("๋นํ๊ฐ", "1๋ง ๊ฒํ ", "๐ 1๋ง ์ข ํฉ ๊ฒํ "), | |
("์คํ ๋ฆฌ์๊ฐ", "1๋ง ์์ฑ", "โ 1๋ง ์ต์ข ์์ฑ๋ณธ"), | |
("์คํ ๋ฆฌ์๊ฐ", "2๋งA ์ด๊ณ ", "โ๏ธ 2๋งA ์ด๊ณ ์์ฑ"), | |
("ํธ์ง์", "2๋งA ํธ์ง", "โ๏ธ 2๋งA ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "), | |
("๊ฐ๋ ", "2๋งA ์ฐ์ถ", "๐ญ 2๋งA ๋น์ฃผ์ผ ๊ฐํ"), | |
("๋ํ์ ๋ฌธ๊ฐ", "2๋งA ๋์ฌ", "๐ฌ 2๋งA ๋์ฌ ๊ฐ์ "), | |
("๋นํ๊ฐ", "2๋งA ๊ฒํ ", "๐ 2๋งA ์ข ํฉ ๊ฒํ "), | |
("์คํ ๋ฆฌ์๊ฐ", "2๋งA ์์ฑ", "โ 2๋งA ์ต์ข ์์ฑ๋ณธ"), | |
("์คํ ๋ฆฌ์๊ฐ", "2๋งB ์ด๊ณ ", "โ๏ธ 2๋งB ์ด๊ณ ์์ฑ"), | |
("ํธ์ง์", "2๋งB ํธ์ง", "โ๏ธ 2๋งB ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "), | |
("๊ฐ๋ ", "2๋งB ์ฐ์ถ", "๐ญ 2๋งB ๋น์ฃผ์ผ ๊ฐํ"), | |
("๋ํ์ ๋ฌธ๊ฐ", "2๋งB ๋์ฌ", "๐ฌ 2๋งB ๋์ฌ ๊ฐ์ "), | |
("๋นํ๊ฐ", "2๋งB ๊ฒํ ", "๐ 2๋งB ์ข ํฉ ๊ฒํ "), | |
("์คํ ๋ฆฌ์๊ฐ", "2๋งB ์์ฑ", "โ 2๋งB ์ต์ข ์์ฑ๋ณธ"), | |
("์คํ ๋ฆฌ์๊ฐ", "3๋ง ์ด๊ณ ", "โ๏ธ 3๋ง ์ด๊ณ ์์ฑ"), | |
("ํธ์ง์", "3๋ง ํธ์ง", "โ๏ธ 3๋ง ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "), | |
("๊ฐ๋ ", "3๋ง ์ฐ์ถ", "๐ญ 3๋ง ๋น์ฃผ์ผ ๊ฐํ"), | |
("๋ํ์ ๋ฌธ๊ฐ", "3๋ง ๋์ฌ", "๐ฌ 3๋ง ๋์ฌ ๊ฐ์ "), | |
("๋นํ๊ฐ", "3๋ง ๊ฒํ ", "๐ 3๋ง ์ข ํฉ ๊ฒํ "), | |
("์คํ ๋ฆฌ์๊ฐ", "3๋ง ์์ฑ", "โ 3๋ง ์ต์ข ์์ฑ๋ณธ"), | |
("์ฅ๋ฅด์ ๋ฌธ๊ฐ", "์ฅ๋ฅด ์ต์ ํ", "๐ฏ ์ฅ๋ฅด ํน์ฑ ์ต์ ํ"), | |
("๋นํ๊ฐ", "์ต์ข ๊ฒํ ", "๐ ์ต์ข ๊ฒํ ๋ฐ ์์ฑ๋ ํ๊ฐ"), | |
] | |
# ๋ฐ์ดํฐ ํด๋์ค | |
class ExpertFeedback: | |
"""์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ""" | |
role: str | |
stage: str | |
feedback: str | |
suggestions: List[str] | |
score: float | |
timestamp: datetime = field(default_factory=datetime.now) | |
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) | |
# ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ | |
expert_feedbacks: List[ExpertFeedback] = field(default_factory=list) | |
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ํด๋์ค | |
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, | |
expert_feedbacks 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 expert_reviews ( | |
id INTEGER PRIMARY KEY AUTOINCREMENT, | |
session_id TEXT NOT NULL, | |
role TEXT NOT NULL, | |
stage TEXT NOT NULL, | |
feedback TEXT, | |
suggestions TEXT, | |
score REAL, | |
created_at TEXT DEFAULT (datetime('now')), | |
FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id) | |
) | |
''') | |
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_expert_feedback(session_id: str, role: str, stage: str, | |
feedback: str, suggestions: List[str], score: float): | |
with ScreenplayDatabase.get_db() as conn: | |
conn.cursor().execute( | |
'''INSERT INTO expert_reviews | |
(session_id, role, stage, feedback, suggestions, score) | |
VALUES (?, ?, ?, ?, ?, ?)''', | |
(session_id, role, stage, feedback, json.dumps(suggestions, ensure_ascii=False), score) | |
) | |
conn.commit() | |
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 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 = {} | |
self.expert_feedbacks = [] | |
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_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 get_expert_prompt(self, role: str, stage: str, query: str, | |
previous_content: Dict, genre: str, screenplay_type: str) -> str: | |
"""๊ฐ ์ ๋ฌธ๊ฐ๋ณ ๋ง์ถค ํ๋กฌํํธ ์์ฑ""" | |
expert = EXPERT_ROLES[role] | |
focus_areas = ", ".join(expert["focus"]) | |
# ์ด์ ์ ๋ฌธ๊ฐ๋ค์ ํผ๋๋ฐฑ ์์ฝ | |
previous_feedbacks = "" | |
if self.expert_feedbacks: | |
previous_feedbacks = "\nใ์ด์ ์ ๋ฌธ๊ฐ ์๊ฒฌใ\n" | |
for fb in self.expert_feedbacks[-3:]: # ์ต๊ทผ 3๊ฐ๋ง | |
previous_feedbacks += f"[{fb.role}]: {fb.feedback[:200]}...\n" | |
base_prompt = f""" | |
ใ๋น์ ์ ์ญํ ใ | |
๋น์ ์ {role}์ ๋๋ค. {expert['description']} | |
์ฑ๊ฒฉ: {expert['personality']} | |
์ง์ค ์์ญ: {focus_areas} | |
ใ์๋ณธ ์์ฒญใ | |
{query} | |
ใ์ฅ๋ฅดใ {genre} | |
ใํ์ใ {screenplay_type} | |
{previous_feedbacks} | |
ใ์ด์ ์์ ๋ด์ฉใ | |
{self._summarize_previous_content(previous_content)} | |
""" | |
# ์ญํ ๋ณ ํนํ ํ๋กฌํํธ | |
if role == "ํ๋ก๋์": | |
return base_prompt + f""" | |
ใํ๋ก๋์๋ก์ ๋ถ์ํ ๋ด์ฉใ | |
1. ์์ ์ ๊ฐ์น์ ์์ฅ์ฑ | |
- ํ๊ฒ ๊ด๊ฐ์ธต ๋ช ํํ | |
- ์์ ์ ์๋น ๊ท๋ชจ | |
- ๋ฐฐ๊ธ ์ ๋ต | |
2. ์ ๋ชฉ๊ณผ ๋ก๊ทธ๋ผ์ธ | |
- ๋ง์ผํ ๊ฐ๋ฅํ ๋งค๋ ฅ์ ์ธ ์ ๋ชฉ | |
- ํ ๋ฌธ์ฅ์ผ๋ก ํต์ฌ ๊ฐ๋ฑ ํํ | |
3. ์ ์ฌ ์ฑ๊ณต์ ๋ถ์ | |
- ์ต๊ทผ 3๋ ๋ด ์ ์ฌ ์ํ | |
- ์ฐจ๋ณํ ํฌ์ธํธ | |
4. ์ ์ ๋ฆฌ์คํฌ ํ๊ฐ | |
- ๊ธฐ์ ์ ๋์ด๋ | |
- ์บ์คํ ์๊ตฌ์ฌํญ | |
โ ๏ธ ์๋ณธ ์์ฒญ์ ์์ ์ ์ผ๋ก ์ต์ ํํ๋ ํต์ฌ์ ์ ์งํ์ธ์.""" | |
elif role == "์คํ ๋ฆฌ์๊ฐ": | |
return base_prompt + f""" | |
ใ์คํ ๋ฆฌ ์๊ฐ๋ก์ ๊ตฌ์ฑํ ๋ด์ฉใ | |
1. ์๋์์ค (500-800์) | |
- ์๋ณธ ์์ฒญ์ ํต์ฌ ์คํ ๋ฆฌ | |
- ๋ช ํํ ์์-์ค๊ฐ-๋ | |
2. 3๋ง ๊ตฌ์กฐ ์์ธ | |
- 1๋ง (25%): ์ค์ ๊ณผ ์ด๋ฐ ์ฌ๊ฑด | |
- 2๋งA (25%): ์๋ก์ด ์ธ๊ณ์ ์ฌ๋ฏธ | |
- 2๋งB (25%): ์๋ จ๊ณผ ์๊ธฐ | |
- 3๋ง (25%): ํด๋ผ์ด๋งฅ์ค์ ํด๊ฒฐ | |
3. ์ฃผ์ ํ๋กฏ ํฌ์ธํธ | |
- 10๊ฐ์ ํต์ฌ ์ ํ์ | |
- ๊ฐ ์ ํ์ ์ ๊ฐ์ ์ ์ํฉํธ | |
4. ํ ๋ง์ ๋ฉ์์ง | |
- ์ค์ฌ ํ ๋ง | |
- ์๋ธ ํ ๋ง๋ค | |
โ ๏ธ ์๋ณธ ์คํ ๋ฆฌ์ ๋ ผ๋ฆฌ์ ํ๋ฆ์ ์๋ฒฝํ๊ฒ ๊ตฌํํ์ธ์.""" | |
elif role == "์บ๋ฆญํฐ๋์์ด๋": | |
return base_prompt + f""" | |
ใ์บ๋ฆญํฐ ๋์์ด๋๋ก์ ์ฐฝ์กฐํ ๋ด์ฉใ | |
1. ์ฃผ์ธ๊ณต ์์ธ ํ๋กํ | |
- ์ด๋ฆ, ๋์ด, ์ง์ | |
- ์ฑ๊ฒฉ (MBTI ํฌํจ) | |
- ํต์ฌ ์๊ตฌ(Want)์ ํ์(Need) | |
- ์น๋ช ์ ๊ฒฐํจ | |
- ๋ณํ ์ํฌ | |
- ํน์ง์ ๋งํฌ์ ํ๋ | |
2. ์ ๋์ ํ๋กํ | |
- ์ฃผ์ธ๊ณต๊ณผ์ ๋๋ฆฝ ๊ตฌ์กฐ | |
- ๋๋ฆ์ ์ ๋น์ฑ | |
- ์ฝ์ ๊ณผ ๋งน์ | |
3. ์กฐ์ฐ ์บ๋ฆญํฐ๋ค (3-5๋ช ) | |
- ๊ฐ์์ ์ญํ ๊ณผ ๊ธฐ๋ฅ | |
- ์ฃผ์ธ๊ณต๊ณผ์ ๊ด๊ณ | |
- ๊ณ ์ ํ ํน์ง | |
4. ์บ๋ฆญํฐ ๊ด๊ณ๋ | |
- ์ธ๋ฌผ๊ฐ ์ญํ ๊ด๊ณ | |
- ๊ฐ๋ฑ ๊ตฌ์กฐ | |
โ ๏ธ ์๋ณธ์ ๋ช ์๋ ์ธ๋ฌผ ์ค์ ์ ์ถฉ์คํ ๋ฐ์ ์ํค์ธ์.""" | |
elif role == "๊ฐ๋ ": | |
return base_prompt + f""" | |
ใ๊ฐ๋ ์ผ๋ก์ ์ฐ์ถํ ๋ด์ฉใ | |
1. ๋น์ฃผ์ผ ์ปจ์ | |
- ์ ์ฒด์ ์ธ ๋ฃฉ์คํ | |
- ์๊ฐ๊ณผ ํค | |
- ์ฐธ์กฐ ์ํ/์ด๋ฏธ์ง | |
2. ํต์ฌ ๋น์ฃผ์ผ ์ฌ (5๊ฐ) | |
- ์คํ๋ ์ด๋ฏธ์ง | |
- ์ฃผ์ ์ก์ /๊ฐ์ ์ฌ | |
- ํด๋ผ์ด๋งฅ์ค ๋น์ฃผ์ผ | |
- ์๋ฉ ์ด๋ฏธ์ง | |
3. ์นด๋ฉ๋ผ ์คํ์ผ | |
- ์ฃผ์ ์ท ๊ตฌ์ฑ | |
- ์์ง์๊ณผ ์ต๊ธ | |
- ํน์ ์ดฌ์ ๊ธฐ๋ฒ | |
4. ์ฌ์ด๋ ๋์์ธ | |
- ์์ ์คํ์ผ | |
- ํต์ฌ ์ฌ์ด๋ ๋ชจํฐํ | |
- ์นจ๋ฌต์ ํ์ฉ | |
โ ๏ธ ์๋ณธ์ ๋ถ์๊ธฐ์ ํค์ ์๊ฐ์ ์ผ๋ก ๊ตฌํํ์ธ์.""" | |
elif role == "๋นํ๊ฐ": | |
return base_prompt + f""" | |
ใ๋นํ๊ฐ๋ก์ ๊ฒํ ํ ๋ด์ฉใ | |
1. ์๋ณธ ์ถฉ์ค๋ ํ๊ฐ (40์ ) | |
- ํต์ฌ ์์ฒญ์ฌํญ ๋ฐ์๋ | |
- ์์ ๋ณ๊ฒฝ ์ฌํญ ์ง์ | |
2. ์คํ ๋ฆฌ ์์ฑ๋ (20์ ) | |
- ๋ ผ๋ฆฌ์ ์ผ๊ด์ฑ | |
- ๊ฐ์ ์ ํธ์๋ ฅ | |
- ํ์ด์ฑ๊ณผ ๋ฆฌ๋ฌ | |
3. ์บ๋ฆญํฐ ํ๊ฐ (20์ ) | |
- ์ ์ฒด์ฑ๊ณผ ๋งค๋ ฅ | |
- ์ฑ์ฅ ์ํฌ | |
- ๊ด๊ณ ์ญํ | |
4. ์์ ์ฑ๊ณผ ์์ ์ฑ (20์ ) | |
- ์์ฅ ์ดํ | |
- ๋ ์ฐฝ์ฑ | |
- ์ ์ ๊ฐ๋ฅ์ฑ | |
5. ๊ตฌ์ฒด์ ๊ฐ์ ์ ์ | |
- ์ฐ์ ์์๋ณ ๊ฐ์ ์ | |
- ์คํ ๊ฐ๋ฅํ ํด๊ฒฐ์ฑ | |
์ด์ : /100์ | |
โ ๏ธ ๊ฑด์ค์ ์ด๋ฉด์๋ ์ ์งํ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ธ์.""" | |
elif role == "ํธ์ง์": | |
return base_prompt + f""" | |
ใํธ์ง์๋ก์ ์กฐ์ ํ ๋ด์ฉใ | |
1. ํ์ด์ฑ ๋ถ์ | |
- ๊ฐ ์ฌ์ ๊ธธ์ด ์ ์ ์ฑ | |
- ๊ธด์ฅ๊ณผ ์ด์์ ๋ฆฌ๋ฌ | |
- ๋ถํ์ํ ๋ถ๋ถ ์๋ณ | |
2. ๊ตฌ์กฐ ์ต์ ํ | |
- ์ฌ ์์ ์กฐ์ ์ ์ | |
- ์ ํ์ ์์ฐ์ค๋ฌ์ | |
- ์ ๋ณด ๊ณต๊ฐ ํ์ด๋ฐ | |
3. ํธ๋ฆฌ๋ฐ ์ ์ | |
- ์ญ์ ๊ฐ๋ฅํ ์ฌ/๋์ฌ | |
- ์์ถ ๊ฐ๋ฅํ ๋ถ๋ถ | |
- ์ค๋ณต ์ ๊ฑฐ | |
4. ์ํฉํธ ๊ฐํ | |
- ํด๋ผ์ด๋งฅ์ค ๋น๋์ | |
- ๊ฐ์ ์ ๋นํธ ๊ฐํ | |
โ ๏ธ ์๋ณธ ์คํ ๋ฆฌ๋ฅผ ๋ ํ์ดํธํ๊ณ ์ํฉํธ์๊ฒ ๋ง๋์ธ์.""" | |
elif role == "๋ํ์ ๋ฌธ๊ฐ": | |
return base_prompt + f""" | |
ใ๋ํ ์ ๋ฌธ๊ฐ๋ก์ ๊ฐ์ ํ ๋ด์ฉใ | |
1. ์บ๋ฆญํฐ๋ณ ๊ณ ์ ํ๋ฒ | |
- ์ดํ ์์ค๊ณผ ์คํ์ผ | |
- ๋ง๋ฒ๋ฆ๊ณผ ํจํด | |
- ๊ฐ์ ํํ ๋ฐฉ์ | |
2. ์๋ธํ ์คํธ ๊ฐํ | |
- ํ๋ฉด ๋์ฌ vs ์ง์ง ์๋ฏธ | |
- ์นจ๋ฌต๊ณผ ํ๊ฐ | |
- ๋น์ธ์ด์ ์ํต | |
3. ํต์ฌ ๋์ฌ ์ฐฝ์กฐ | |
- ๋ช ๋์ฌ 5๊ฐ | |
- ์บ๋ฆญํฐ ์ ์ฒด์ฑ ๋๋ฌ๋ด๊ธฐ | |
- ํ ๋ง ์ ๋ฌ ๋์ฌ | |
4. ๋ํ ๋ฆฌ๋ฌ | |
- ์์ฐ์ค๋ฌ์ด ์ฃผ๊ณ ๋ฐ๊ธฐ | |
- ๊ธด์ฅ ๊ณ ์กฐ ํจํด | |
- ์ ๋จธ์ ์ํธ | |
โ ๏ธ ๊ฐ ์บ๋ฆญํฐ์ ๋ชฉ์๋ฆฌ๋ฅผ ๋๋ ทํ๊ฒ ๊ตฌ๋ถํ์ธ์.""" | |
elif role == "์ฅ๋ฅด์ ๋ฌธ๊ฐ": | |
genre_template = GENRE_TEMPLATES.get(genre, GENRE_TEMPLATES["๋๋ผ๋ง"]) | |
return base_prompt + f""" | |
ใ{genre} ์ฅ๋ฅด ์ ๋ฌธ๊ฐ๋ก์ ์ต์ ํํ ๋ด์ฉใ | |
1. ์ฅ๋ฅด ํ์ ์์ ์ฒดํฌ | |
- ํต์ฌ ์์: {', '.join(genre_template['key_elements'])} | |
- ๊ตฌ์กฐ ๋นํธ: {', '.join(genre_template['structure_beats'])} | |
2. ์ฅ๋ฅด ๊ด์ต ์ถฉ์กฑ๋ | |
- ๊ด๊ฐ ๊ธฐ๋ ์ถฉ์กฑ | |
- ํด๋ฆฌ์ ฐ ํ์ฉ๊ณผ ์ ๋ณต | |
- ์ฅ๋ฅด ํน์ ์ฅ์น | |
3. ํค๊ณผ ๋ถ์๊ธฐ | |
- ํ์ด์ฑ: {genre_template['pacing']} | |
- ์ฌ ๊ธธ์ด: {genre_template['scene_length']} | |
- ๋์ฌ ๋น์จ: {genre_template['dialogue_ratio']} | |
4. ์ฅ๋ฅด ํนํ ๊ฐํ | |
- ๋ถ์กฑํ ์์ ์ถ๊ฐ | |
- ๊ณผ๋ํ ๋ถ๋ถ ์กฐ์ | |
- ์ฅ๋ฅด ์ ์ฒด์ฑ ๋ช ํํ | |
โ ๏ธ {genre} ์ฅ๋ฅด์ ์ ์๋ฅผ ์๋ฒฝํ๊ฒ ๊ตฌํํ์ธ์.""" | |
return base_prompt | |
def _summarize_previous_content(self, content: Dict) -> str: | |
"""์ด์ ๋ด์ฉ ์์ฝ""" | |
summary = "" | |
for key, value in content.items(): | |
if value: | |
summary += f"\n[{key}]\n{value[:300]}...\n" | |
return summary if summary else "์ฒซ ์์ ์ ๋๋ค." | |
def generate_planning(self, query: str, screenplay_type: str, genre: str, | |
progress_callback=None) -> Generator[Tuple[str, float, Dict, List], None, None]: | |
"""๋ค์ค ์ ๋ฌธ๊ฐ ํ์ ๊ธฐํ์ ์์ฑ""" | |
try: | |
self.original_query = query | |
self.current_session_id = ScreenplayDatabase.create_session(query, screenplay_type, genre) | |
planning_content = {} | |
self.accumulated_content = {} | |
self.expert_feedbacks = [] | |
total_stages = len(PLANNING_STAGES) | |
for idx, (role, stage_key, stage_desc) in enumerate(PLANNING_STAGES): | |
progress = (idx / total_stages) * 100 | |
yield f"๐ {stage_desc} ์งํ ์ค...", progress, planning_content, self.expert_feedbacks | |
# ์ ๋ฌธ๊ฐ๋ณ ํ๋กฌํํธ ์์ฑ | |
prompt = self.get_expert_prompt( | |
role, stage_key, query, | |
self.accumulated_content, genre, screenplay_type | |
) | |
# LLM ํธ์ถ | |
messages = [ | |
{"role": "system", "content": f"""๋น์ ์ {role}์ ๋๋ค. | |
{EXPERT_ROLES[role]['description']} | |
์ ๋ฌธ ๋ถ์ผ: {', '.join(EXPERT_ROLES[role]['focus'])} | |
์๋ณธ ์์ฒญ์ ์ ๋์ ์ผ๋ก ์ค์ํ๋ฉด์ ์ ๋ฌธ์ฑ์ ๋ฐํํ์ธ์."""}, | |
{"role": "user", "content": prompt} | |
] | |
content = "" | |
for chunk in self.call_llm_streaming(messages): | |
content += chunk | |
planning_content[f"{role}_{stage_key}"] = content | |
yield f"โ๏ธ {stage_desc} ์์ฑ ์ค...", progress, planning_content, self.expert_feedbacks | |
# ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ ์ ์ฅ | |
feedback = ExpertFeedback( | |
role=role, | |
stage=stage_key, | |
feedback=content[:500], | |
suggestions=self._extract_suggestions(content), | |
score=self._calculate_score(content, query) | |
) | |
self.expert_feedbacks.append(feedback) | |
# DB์ ํผ๋๋ฐฑ ์ ์ฅ | |
ScreenplayDatabase.save_expert_feedback( | |
self.current_session_id, role, stage_key, | |
feedback.feedback, feedback.suggestions, feedback.score | |
) | |
# ๋์ ๋ด์ฉ ์ ์ฅ | |
self.accumulated_content[f"{role}_{stage_key}"] = content | |
time.sleep(0.5) | |
# ์ต์ข ๊ธฐํ์ ์ ์ฅ | |
ScreenplayDatabase.save_planning_data(self.current_session_id, planning_content) | |
yield "โ ์ ๋ฌธ๊ฐ ํ์ ๊ธฐํ์ ์์ฑ!", 100, planning_content, self.expert_feedbacks | |
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, List], None, None]: | |
"""๋ค์ค ์ ๋ฌธ๊ฐ ํ์ ์๋๋ฆฌ์ค ์์ฑ""" | |
try: | |
total_stages = len(WRITING_STAGES) | |
screenplay_content = "" | |
act_contents = {"1๋ง": "", "2๋งA": "", "2๋งB": "", "3๋ง": ""} | |
self.expert_feedbacks = [] | |
for idx, (role, stage_name, stage_desc) in enumerate(WRITING_STAGES): | |
progress = (idx / total_stages) * 100 | |
yield f"๐ {stage_desc} ์งํ ์ค...", progress, screenplay_content, self.expert_feedbacks | |
# ํ์ฌ ๋ง ๊ฒฐ์ | |
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 role == "์คํ ๋ฆฌ์๊ฐ": | |
prompt = self._create_writer_prompt( | |
current_act, planning_data, act_contents, stage_name | |
) | |
elif role == "ํธ์ง์": | |
prompt = self._create_editor_prompt( | |
current_act, act_contents[current_act] if current_act else screenplay_content | |
) | |
elif role == "๊ฐ๋ ": | |
prompt = self._create_director_prompt( | |
current_act, act_contents[current_act] if current_act else screenplay_content | |
) | |
elif role == "๋ํ์ ๋ฌธ๊ฐ": | |
prompt = self._create_dialogue_prompt( | |
current_act, act_contents[current_act] if current_act else screenplay_content | |
) | |
elif role == "๋นํ๊ฐ": | |
prompt = self._create_critic_prompt( | |
current_act, act_contents[current_act] if current_act else screenplay_content, | |
self.original_query | |
) | |
elif role == "์ฅ๋ฅด์ ๋ฌธ๊ฐ": | |
prompt = self._create_genre_expert_prompt( | |
screenplay_content, planning_data | |
) | |
else: | |
continue | |
# LLM ํธ์ถ | |
expert = EXPERT_ROLES.get(role, EXPERT_ROLES["์คํ ๋ฆฌ์๊ฐ"]) | |
messages = [ | |
{"role": "system", "content": f"""๋น์ ์ {role}์ ๋๋ค. | |
{expert['description']} | |
์๋ณธ ์์ฒญ: {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, self.expert_feedbacks | |
# ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ ์์ฑ | |
feedback = ExpertFeedback( | |
role=role, | |
stage=stage_name, | |
feedback=f"{role}๊ฐ {stage_name}์ ๊ฒํ /์์ ํ์ต๋๋ค.", | |
suggestions=[], | |
score=85.0 | |
) | |
self.expert_feedbacks.append(feedback) | |
time.sleep(0.5) | |
# ์ต์ข ์ ์ฅ | |
title = self._extract_title(planning_data) | |
logline = self._extract_logline(planning_data) | |
ScreenplayDatabase.save_screenplay_content(session_id, screenplay_content, title, logline) | |
yield "โ ์ ๋ฌธ๊ฐ ํ์ ์๋๋ฆฌ์ค ์์ฑ!", 100, screenplay_content, self.expert_feedbacks | |
except Exception as e: | |
yield f"โ ์ค๋ฅ ๋ฐ์: {str(e)}", 0, "", [] | |
def _create_writer_prompt(self, act: str, planning_data: Dict, | |
previous_acts: Dict, stage_name: str) -> str: | |
"""์คํ ๋ฆฌ ์๊ฐ ํ๋กฌํํธ""" | |
if "์ด๊ณ " in stage_name: | |
min_lines = 800 | |
elif "์์ฑ" in stage_name: | |
min_lines = 1200 | |
else: | |
min_lines = 1000 | |
return f"""ใ{act} ์์ฑใ | |
๋ชฉํ ๋ถ๋: {min_lines}์ค ์ด์ | |
ใ๊ธฐํ์ ํต์ฌใ | |
{self._extract_planning_core(planning_data)} | |
ใ์ด์ ๋งใ | |
{previous_acts if previous_acts else "์ฒซ ๋ง์ ๋๋ค"} | |
ใ์์ฑ ์๊ตฌ์ฌํญใ | |
1. ํ์ค ์๋๋ฆฌ์ค ํฌ๋งท | |
2. ์ถฉ๋ถํ ์ฌ ๋ถ๋ (๊ฐ 5-7ํ์ด์ง) | |
3. ์๊ฐ์ ์ก์ ๋ฌ์ฌ | |
4. ์์ฐ์ค๋ฌ์ด ๋ํ | |
5. ์๋ณธ ์คํ ๋ฆฌ ์ถฉ์ค | |
๋ฐ๋์ {min_lines}์ค ์ด์ ์์ฑํ์ธ์.""" | |
def _create_editor_prompt(self, act: str, content: str) -> str: | |
"""ํธ์ง์ ํ๋กฌํํธ""" | |
return f"""ใ{act} ํธ์งใ | |
ํ์ฌ ๋ด์ฉ: | |
{content[:2000]}... | |
ใํธ์ง ํฌ์ธํธใ | |
1. ํ์ด์ฑ ์กฐ์ | |
- ๋๋ฆฐ ๋ถ๋ถ ๊ฐ์ | |
- ๊ธํ ๋ถ๋ถ ์ํ | |
2. ๋ถํ์ํ ๋ถ๋ถ ์ ๊ฑฐ | |
- ์ค๋ณต ๋์ฌ/์ก์ | |
- ๊ณผ๋ํ ์ค๋ช | |
3. ์ฌ ์ ํ ๊ฐ์ | |
- ์์ฐ์ค๋ฌ์ด ์ฐ๊ฒฐ | |
- ์๊ฐ/๊ณต๊ฐ ์ด๋ | |
4. ๊ธด์ฅ๊ฐ ์กฐ์ | |
- ํด๋ผ์ด๋งฅ์ค ๋น๋์ | |
- ์๊ธ ์กฐ์ | |
๊ตฌ์ฒด์ ์ธ ํธ์ง ์ ์์ ํ์ธ์.""" | |
def _create_director_prompt(self, act: str, content: str) -> str: | |
"""๊ฐ๋ ํ๋กฌํํธ""" | |
return f"""ใ{act} ์ฐ์ถ ๊ฐํใ | |
ํ์ฌ ์๋๋ฆฌ์ค: | |
{content[:2000]}... | |
ใ๋น์ฃผ์ผ ๊ฐํ ํฌ์ธํธใ | |
1. ์คํ๋ ์ฌ ๋น์ฃผ์ผ | |
2. ํต์ฌ ์ก์ ์ํ์ค | |
3. ๊ฐ์ ์ ํด๋ก์ฆ์ | |
4. ํ๊ฒฝ/๋ถ์๊ธฐ ๋ฌ์ฌ | |
5. ์์ง์ ์ด๋ฏธ์ง | |
๊ฐ ์ฌ์ ์๊ฐ์ ๋ํ ์ผ์ ์ถ๊ฐํ์ธ์.""" | |
def _create_dialogue_prompt(self, act: str, content: str) -> str: | |
"""๋ํ ์ ๋ฌธ๊ฐ ํ๋กฌํํธ""" | |
return f"""ใ{act} ๋์ฌ ๊ฐ์ ใ | |
ํ์ฌ ๋์ฌ ๊ฒํ : | |
{content[:2000]}... | |
ใ๊ฐ์ ๋ฐฉํฅใ | |
1. ์บ๋ฆญํฐ๋ณ ๋งํฌ ์ฐจ๋ณํ | |
2. ์๋ธํ ์คํธ ์ถ๊ฐ | |
3. ๋ถํ์ํ ์ค๋ช ์ ๊ฑฐ | |
4. ๊ฐ์ ์ ๋์์ค ๊ฐํ | |
5. ๋ฆฌ๋ฌ๊ณผ ํ ํฌ ์กฐ์ | |
๊ฐ์ ๋ ๋์ฌ ์์๋ฅผ ์ ์ํ์ธ์.""" | |
def _create_critic_prompt(self, act: str, content: str, original_query: str) -> str: | |
"""๋นํ๊ฐ ํ๋กฌํํธ""" | |
return f"""ใ{act} ์ข ํฉ ๊ฒํ ใ | |
์๋ณธ ์์ฒญ: {original_query} | |
ํ์ฌ ๋ด์ฉ: | |
{content[:2000]}... | |
ใํ๊ฐ ํญ๋ชฉใ | |
1. ์๋ณธ ์ถฉ์ค๋ (40์ ) | |
2. ์คํ ๋ฆฌ ์์ฑ๋ (20์ ) | |
3. ์บ๋ฆญํฐ ๋งค๋ ฅ (20์ ) | |
4. ๋์ฌ ํ์ง (10์ ) | |
5. ๋น์ฃผ์ผ ์ํฉํธ (10์ ) | |
์ด์ : /100 | |
ใ๊ฐ์ ํ์์ฌํญใ | |
๊ตฌ์ฒด์ ์ด๊ณ ์คํ ๊ฐ๋ฅํ ์ ์์ ํ์ธ์.""" | |
def _create_genre_expert_prompt(self, screenplay: str, planning_data: Dict) -> str: | |
"""์ฅ๋ฅด ์ ๋ฌธ๊ฐ ํ๋กฌํํธ""" | |
genre = planning_data.get("์ฅ๋ฅด", "๋๋ผ๋ง") | |
genre_template = GENRE_TEMPLATES.get(genre, GENRE_TEMPLATES["๋๋ผ๋ง"]) | |
return f"""ใ{genre} ์ฅ๋ฅด ์ต์ ํใ | |
ใ์ฅ๋ฅด ํน์ฑใ | |
- ํต์ฌ ์์: {', '.join(genre_template['key_elements'])} | |
- ๊ตฌ์กฐ ๋นํธ: {', '.join(genre_template['structure_beats'])} | |
ใ์ต์ ํ ํฌ์ธํธใ | |
1. ์ฅ๋ฅด ๊ด์ต ์ถฉ์กฑ๋ | |
2. ํ์ด์ฑ ์กฐ์ | |
3. ํน์ ์์ ๊ฐํ | |
4. ํค ์ผ๊ด์ฑ | |
์ฅ๋ฅด ํน์ฑ์ ๊ทน๋ํํ๋ ์ ์์ ํ์ธ์.""" | |
def _extract_suggestions(self, content: str) -> List[str]: | |
"""๋ด์ฉ์์ ์ ์์ฌํญ ์ถ์ถ""" | |
suggestions = [] | |
lines = content.split('\n') | |
for line in lines: | |
if any(keyword in line for keyword in ['์ ์', '๊ฐ์ ', '์ถ์ฒ', '๊ถ์ฅ']): | |
suggestions.append(line.strip()) | |
return suggestions[:5] | |
def _calculate_score(self, content: str, original_query: str) -> float: | |
"""์ถฉ์ค๋ ์ ์ ๊ณ์ฐ""" | |
score = 70.0 # ๊ธฐ๋ณธ ์ ์ | |
# ์๋ณธ ํค์๋ ํฌํจ๋ ์ฒดํฌ | |
keywords = original_query.split() | |
matched = sum(1 for keyword in keywords if keyword in content) | |
score += (matched / len(keywords)) * 20 if keywords else 0 | |
# ๊ธธ์ด ์ฒดํฌ | |
if len(content) > 500: | |
score += 10 | |
return min(score, 100.0) | |
def _extract_title(self, planning_data: Dict) -> str: | |
"""๊ธฐํ์์์ ์ ๋ชฉ ์ถ์ถ""" | |
for key, value in planning_data.items(): | |
if "์ ๋ชฉ" in value: | |
match = re.search(r'์ ๋ชฉ[:\s]*([^\n]+)', value) | |
if match: | |
return match.group(1).strip() | |
return "๋ฌด์ " | |
def _extract_logline(self, planning_data: Dict) -> str: | |
"""๊ธฐํ์์์ ๋ก๊ทธ๋ผ์ธ ์ถ์ถ""" | |
for key, value in planning_data.items(): | |
if "๋ก๊ทธ๋ผ์ธ" in value: | |
match = re.search(r'๋ก๊ทธ๋ผ์ธ[:\s]*([^\n]+)', value) | |
if match: | |
return match.group(1).strip() | |
return "" | |
def _extract_planning_core(self, planning_data: Dict) -> str: | |
"""๊ธฐํ์ ํต์ฌ ๋ด์ฉ ์ถ์ถ""" | |
core = "" | |
for key, value in planning_data.items(): | |
if any(k in key for k in ["์คํ ๋ฆฌ", "์บ๋ฆญํฐ", "๊ตฌ์กฐ"]): | |
core += f"\n[{key}]\n{value[:500]}...\n" | |
return core | |
# ์ ํธ๋ฆฌํฐ ํจ์ | |
def format_planning_display(planning_data: Dict) -> str: | |
"""๊ธฐํ์ ํ์ ํฌ๋งท""" | |
if not planning_data: | |
return "๊ธฐํ์์ด ์์ง ์์ฑ๋์ง ์์์ต๋๋ค." | |
formatted = "# ๐ ์๋๋ฆฌ์ค ๊ธฐํ์ (์ ๋ฌธ๊ฐ ํ์ )\n\n" | |
for key, content in planning_data.items(): | |
role = key.split('_')[0] if '_' in key else key | |
emoji = EXPERT_ROLES.get(role, {}).get("emoji", "๐") | |
formatted += f"## {emoji} {key}\n\n" | |
formatted += content + "\n\n" | |
formatted += "---\n\n" | |
return formatted | |
def format_expert_feedbacks(feedbacks: List[ExpertFeedback]) -> str: | |
"""์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ ํ์""" | |
if not feedbacks: | |
return "" | |
formatted = "## ๐ฏ ์ ๋ฌธ๊ฐ ํ๊ฐ\n\n" | |
for fb in feedbacks: | |
emoji = EXPERT_ROLES.get(fb.role, {}).get("emoji", "๐") | |
formatted += f"### {emoji} {fb.role}\n" | |
formatted += f"**์ ์:** {fb.score:.1f}/100\n" | |
formatted += f"**ํผ๋๋ฐฑ:** {fb.feedback[:200]}...\n" | |
if fb.suggestions: | |
formatted += "**์ ์์ฌํญ:**\n" | |
for suggestion in fb.suggestions[:3]: | |
formatted += f"- {suggestion}\n" | |
formatted += "\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 = { | |
'์ก์ ': [ | |
"์ํดํ ํน์์์์ด ๋ฉ์น๋ ๋ธ์ ๊ตฌํ๊ธฐ ์ํด ๋ค์ ํ์ฅ์ผ๋ก ๋ณต๊ทํ๋ค", | |
"ํ๋ฒํ ํ์๊ธฐ์ฌ๊ฐ ์ฐ์ฐํ ๊ตญ์ ํ ๋ฌ ์กฐ์ง์ ์๋ชจ์ ํ๋ง๋ฆฐ๋ค", | |
], | |
'์ค๋ฆด๋ฌ': [ | |
"๊ธฐ์ต์ ์์ ๋จ์๊ฐ ์์ ์ด ์ฐ์์ด์ธ๋ฒ์ด๋ผ๋ ์ฆ๊ฑฐ๋ฅผ ๋ฐ๊ฒฌํ๋ค", | |
"์ค์ข ๋ ์์ด๋ฅผ ์ฐพ๋ ๊ณผ์ ์์ ๋ง์์ ๋์ฐํ ๋น๋ฐ์ด ๋๋ฌ๋๋ค", | |
], | |
'๋๋ผ๋ง': [ | |
"๋ง๊ธฐ ์ ํ์๊ฐ ๋ง์ง๋ง ์์์ผ๋ก ๊ฐ์กฑ๊ณผ์ ํํด๋ฅผ ์๋ํ๋ค", | |
"์ฌ๊ฐ๋ฐ๋ก ์ฌ๋ผ์ง ๋๋ค๋ฅผ ์งํค๋ ค๋ ์ฃผ๋ฏผ๋ค์ ๋ง์ง๋ง ์ธ์", | |
], | |
} | |
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); | |
} | |
.expert-panel { | |
background: #f0f4f8; | |
padding: 1rem; | |
border-radius: 10px; | |
margin: 1rem 0; | |
} | |
.expert-badge { | |
display: inline-block; | |
padding: 0.3rem 0.8rem; | |
background: white; | |
border-radius: 20px; | |
margin: 0.2rem; | |
font-size: 0.9rem; | |
} | |
.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;"> | |
8๋ช ์ ์ ๋ฌธ๊ฐ๊ฐ ํ์ ํ์ฌ ์๋ฒฝํ ์๋๋ฆฌ์ค๋ฅผ ์์ฑํฉ๋๋ค<br> | |
ํ๋ก๋์, ๊ฐ๋ , ์๊ฐ, ๋นํ๊ฐ๊ฐ ํจ๊ป ๋ง๋๋ ์ ๋ฌธ ์๋๋ฆฌ์ค | |
</p> | |
</div> | |
""") | |
# ์ ๋ฌธ๊ฐ ์๊ฐ | |
gr.HTML(""" | |
<div class="expert-panel"> | |
<h3>๐ฅ ์ฐธ์ฌ ์ ๋ฌธ๊ฐ</h3> | |
<span class="expert-badge">๐ฌ ํ๋ก๋์</span> | |
<span class="expert-badge">๐ ์คํ ๋ฆฌ์๊ฐ</span> | |
<span class="expert-badge">๐ฅ ์บ๋ฆญํฐ๋์์ด๋</span> | |
<span class="expert-badge">๐ญ ๊ฐ๋ </span> | |
<span class="expert-badge">๐ ๋นํ๊ฐ</span> | |
<span class="expert-badge">โ๏ธ ํธ์ง์</span> | |
<span class="expert-badge">๐ฌ ๋ํ์ ๋ฌธ๊ฐ</span> | |
<span class="expert-badge">๐ฏ ์ฅ๋ฅด์ ๋ฌธ๊ฐ</span> | |
</div> | |
""") | |
current_session_id = gr.State(None) | |
current_planning_data = gr.State({}) | |
current_feedbacks = gr.State([]) | |
with gr.Tabs(): | |
with gr.Tab("๐ ์ ์๋๋ฆฌ์ค"): | |
with gr.Row(): | |
with gr.Column(scale=2): | |
query_input = gr.Textbox( | |
label="๐ก ์๋๋ฆฌ์ค ์์ด๋์ด", | |
placeholder="""๊ตฌ์ฒด์ ์ธ ์คํ ๋ฆฌ๋ฅผ ์ ๋ ฅํ์ธ์. ์์: | |
"2045๋ ์์ธ, AI๊ฐ ์ธ๊ฐ ๊ฐ์ ์ ์๋ฒฝํ ๋ชจ๋ฐฉํ๋ ์๋. | |
AI ์ค๋ฆฌํ์๊ฐ ์ฃฝ์ ์๋ด๋ฅผ ๋ณต์ ํ AI์ ์ด๋ค๊ฐ | |
์ง์ง ์๋ด๊ฐ ์ด์์๋ค๋ ์ฆ๊ฑฐ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ์ง์ค์ ์ฐพ์ ๋์ ๋ค." | |
""", | |
lines=6 | |
) | |
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("### ๐ฏ ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ") | |
expert_feedback_display = gr.Markdown( | |
value="*์ ๋ฌธ๊ฐ๋ค์ ์๊ฒฌ์ด ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*" | |
) | |
# ๊ธฐํ์ | |
with gr.Group(): | |
gr.Markdown("### ๐ ์๋๋ฆฌ์ค ๊ธฐํ์") | |
planning_display = gr.Markdown( | |
value="*๊ธฐํ์์ด ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*" | |
) | |
with gr.Row(): | |
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="*์๋๋ฆฌ์ค๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*" | |
) | |
download_btn = gr.Button("๐พ ๋ค์ด๋ก๋ (TXT)") | |
# ์ด๋ฒคํธ ํธ๋ค๋ฌ | |
def handle_planning(query, s_type, genre): | |
if not query: | |
yield "", "โ ์์ด๋์ด๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์", "", "", {} | |
return | |
system = ScreenplayGenerationSystem() | |
for status, progress, planning_data, feedbacks 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) | |
feedback_display = format_expert_feedbacks(feedbacks) | |
yield progress_html, status, feedback_display, planning_display, planning_data | |
def handle_screenplay_generation(session_id, planning_data): | |
if not planning_data: | |
yield "", "โ ๋จผ์ ๊ธฐํ์์ ์์ฑํด์ฃผ์ธ์", "", "" | |
return | |
system = ScreenplayGenerationSystem() | |
for status, progress, screenplay, feedbacks 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) | |
feedback_display = format_expert_feedbacks(feedbacks) | |
yield progress_html, status, feedback_display, screenplay_display | |
# ์ด๋ฒคํธ ์ฐ๊ฒฐ | |
planning_btn.click( | |
fn=handle_planning, | |
inputs=[query_input, screenplay_type, genre_select], | |
outputs=[progress_bar, status_text, expert_feedback_display, 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, expert_feedback_display, screenplay_output] | |
) | |
random_btn.click( | |
fn=generate_random_concept, | |
inputs=[screenplay_type, genre_select], | |
outputs=[query_input] | |
) | |
return interface | |
# ๋ฉ์ธ ์คํ | |
if __name__ == "__main__": | |
logger.info("=" * 60) | |
logger.info("AI ์๋๋ฆฌ์ค ์๊ฐ - ์ ๋ฌธ๊ฐ ํ์ ์์คํ ") | |
logger.info("8๋ช ์ ์ ๋ฌธ๊ฐ๊ฐ ํ์ ํ์ฌ ์๋๋ฆฌ์ค๋ฅผ ์์ฑํฉ๋๋ค") | |
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'") | |
# ์ ๋ฌธ๊ฐ ์ญํ ์๊ฐ | |
logger.info("\n์ฐธ์ฌ ์ ๋ฌธ๊ฐ:") | |
for role, info in EXPERT_ROLES.items(): | |
logger.info(f" {info['emoji']} {role}: {info['description']}") | |
ScreenplayDatabase.init_db() | |
interface = create_interface() | |
interface.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=False | |
) |