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 | |
| ) |