Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	Update app.py
Browse files
    	
        app.py
    CHANGED
    
    | @@ -44,6 +44,58 @@ if not FIREWORKS_API_KEY: | |
| 44 | 
             
            # ๊ธ๋ก๋ฒ ๋ณ์
         | 
| 45 | 
             
            db_lock = threading.Lock()
         | 
| 46 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 47 | 
             
            # ์ฅ๋ฅด ํ
ํ๋ฆฟ
         | 
| 48 | 
             
            GENRE_TEMPLATES = {
         | 
| 49 | 
             
               "์ก์
": {
         | 
| @@ -120,41 +172,60 @@ GENRE_TEMPLATES = { | |
| 120 | 
             
               }
         | 
| 121 | 
             
            }
         | 
| 122 |  | 
| 123 | 
            -
            #  | 
| 124 | 
             
            PLANNING_STAGES = [
         | 
| 125 | 
            -
                (" | 
| 126 | 
            -
                (" | 
| 127 | 
            -
                (" | 
| 128 | 
            -
                (" | 
| 129 | 
            -
                (" | 
| 130 | 
             
            ]
         | 
| 131 |  | 
|  | |
| 132 | 
             
            WRITING_STAGES = [
         | 
| 133 | 
            -
                ("1๋ง ์ด๊ณ ", "โ๏ธ 1๋ง  | 
| 134 | 
            -
                ("1๋ง  | 
| 135 | 
            -
                ("1๋ง  | 
| 136 | 
            -
                ("1๋ง  | 
|  | |
|  | |
| 137 |  | 
| 138 | 
            -
                ("2๋งA ์ด๊ณ ", "โ๏ธ 2๋งA  | 
| 139 | 
            -
                ("2๋งA  | 
| 140 | 
            -
                ("2๋งA  | 
| 141 | 
            -
                ("2๋งA  | 
|  | |
|  | |
| 142 |  | 
| 143 | 
            -
                ("2๋งB ์ด๊ณ ", "โ๏ธ 2๋งB  | 
| 144 | 
            -
                ("2๋งB  | 
| 145 | 
            -
                ("2๋งB  | 
| 146 | 
            -
                ("2๋งB  | 
|  | |
|  | |
| 147 |  | 
| 148 | 
            -
                ("3๋ง ์ด๊ณ ", "โ๏ธ 3๋ง  | 
| 149 | 
            -
                ("3๋ง  | 
| 150 | 
            -
                ("3๋ง  | 
| 151 | 
            -
                ("3๋ง  | 
|  | |
|  | |
| 152 |  | 
| 153 | 
            -
                (" | 
| 154 | 
            -
                ("์ต์ข
 ๊ฒํ ", " | 
| 155 | 
             
            ]
         | 
| 156 |  | 
| 157 | 
             
            # ๋ฐ์ดํฐ ํด๋์ค
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 158 | 
             
            @dataclass
         | 
| 159 | 
             
            class ScreenplayPlan:
         | 
| 160 | 
             
                """์๋๋ฆฌ์ค ๊ธฐํ์"""
         | 
| @@ -188,23 +259,9 @@ class ScreenplayPlan: | |
| 188 | 
             
                # ์ฌ ๊ตฌ์ฑ
         | 
| 189 | 
             
                total_scenes: int = 0
         | 
| 190 | 
             
                scene_breakdown: List[Dict] = field(default_factory=list)
         | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 194 | 
            -
                """์๋๋ฆฌ์ค ๋ฐ์ด๋ธ"""
         | 
| 195 | 
            -
                title: str = ""
         | 
| 196 | 
            -
                logline: str = ""
         | 
| 197 | 
            -
                genre: str = ""
         | 
| 198 | 
            -
                subgenre: str = ""
         | 
| 199 | 
            -
                tone: str = ""
         | 
| 200 | 
            -
                themes: List[str] = field(default_factory=list)
         | 
| 201 | 
            -
                protagonist: Dict[str, Any] = field(default_factory=dict)
         | 
| 202 | 
            -
                antagonist: Dict[str, Any] = field(default_factory=dict)
         | 
| 203 | 
            -
                supporting_cast: Dict[str, Dict[str, Any]] = field(default_factory=dict)
         | 
| 204 | 
            -
                three_act_structure: Dict[str, str] = field(default_factory=dict)
         | 
| 205 | 
            -
                time_period: str = ""
         | 
| 206 | 
            -
                primary_locations: List[Dict[str, str]] = field(default_factory=list)
         | 
| 207 | 
            -
                visual_style: str = ""
         | 
| 208 |  | 
| 209 | 
             
            # ๋ฐ์ดํฐ๋ฒ ์ด์ค ํด๋์ค
         | 
| 210 | 
             
            class ScreenplayDatabase:
         | 
| @@ -224,6 +281,7 @@ class ScreenplayDatabase: | |
| 224 | 
             
                                title TEXT,
         | 
| 225 | 
             
                                logline TEXT,
         | 
| 226 | 
             
                                planning_data TEXT,
         | 
|  | |
| 227 | 
             
                                screenplay_content TEXT,
         | 
| 228 | 
             
                                created_at TEXT DEFAULT (datetime('now')),
         | 
| 229 | 
             
                                updated_at TEXT DEFAULT (datetime('now')),
         | 
| @@ -234,18 +292,16 @@ class ScreenplayDatabase: | |
| 234 | 
             
                        ''')
         | 
| 235 |  | 
| 236 | 
             
                        cursor.execute('''
         | 
| 237 | 
            -
                            CREATE TABLE IF NOT EXISTS  | 
| 238 | 
             
                                id INTEGER PRIMARY KEY AUTOINCREMENT,
         | 
| 239 | 
             
                                session_id TEXT NOT NULL,
         | 
| 240 | 
            -
                                 | 
| 241 | 
            -
                                 | 
| 242 | 
            -
                                 | 
| 243 | 
            -
                                 | 
| 244 | 
            -
                                 | 
| 245 | 
            -
                                status TEXT DEFAULT 'pending',
         | 
| 246 | 
             
                                created_at TEXT DEFAULT (datetime('now')),
         | 
| 247 | 
            -
                                FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id) | 
| 248 | 
            -
                                UNIQUE(session_id, stage_type, stage_number)
         | 
| 249 | 
             
                            )
         | 
| 250 | 
             
                        ''')
         | 
| 251 |  | 
| @@ -277,6 +333,18 @@ class ScreenplayDatabase: | |
| 277 | 
             
                        conn.commit()
         | 
| 278 | 
             
                    return session_id
         | 
| 279 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 280 | 
             
                @staticmethod
         | 
| 281 | 
             
                def save_planning_data(session_id: str, planning_data: Dict):
         | 
| 282 | 
             
                    with ScreenplayDatabase.get_db() as conn:
         | 
| @@ -288,17 +356,6 @@ class ScreenplayDatabase: | |
| 288 | 
             
                        )
         | 
| 289 | 
             
                        conn.commit()
         | 
| 290 |  | 
| 291 | 
            -
                @staticmethod
         | 
| 292 | 
            -
                def get_planning_data(session_id: str) -> Optional[Dict]:
         | 
| 293 | 
            -
                    with ScreenplayDatabase.get_db() as conn:
         | 
| 294 | 
            -
                        row = conn.cursor().execute(
         | 
| 295 | 
            -
                            'SELECT planning_data FROM screenplay_sessions WHERE session_id = ?',
         | 
| 296 | 
            -
                            (session_id,)
         | 
| 297 | 
            -
                        ).fetchone()
         | 
| 298 | 
            -
                        if row and row['planning_data']:
         | 
| 299 | 
            -
                            return json.loads(row['planning_data'])
         | 
| 300 | 
            -
                    return None
         | 
| 301 | 
            -
             | 
| 302 | 
             
                @staticmethod
         | 
| 303 | 
             
                def save_screenplay_content(session_id: str, content: str, title: str, logline: str):
         | 
| 304 | 
             
                    with ScreenplayDatabase.get_db() as conn:
         | 
| @@ -320,8 +377,9 @@ class ScreenplayGenerationSystem: | |
| 320 | 
             
                    self.model_id = MODEL_ID
         | 
| 321 | 
             
                    self.current_session_id = None
         | 
| 322 | 
             
                    self.current_plan = ScreenplayPlan()
         | 
| 323 | 
            -
                    self.original_query = "" | 
| 324 | 
            -
                    self.accumulated_content = {} | 
|  | |
| 325 | 
             
                    ScreenplayDatabase.init_db()
         | 
| 326 |  | 
| 327 | 
             
                def create_headers(self):
         | 
| @@ -340,10 +398,10 @@ class ScreenplayGenerationSystem: | |
| 340 | 
             
                            "model": self.model_id,
         | 
| 341 | 
             
                            "messages": messages,
         | 
| 342 | 
             
                            "max_tokens": max_tokens,
         | 
| 343 | 
            -
                            "temperature": 0.7, | 
| 344 | 
            -
                            "top_p": 0.9, | 
| 345 | 
             
                            "top_k": 40,
         | 
| 346 | 
            -
                            "presence_penalty": 0.3, | 
| 347 | 
             
                            "frequency_penalty": 0.3,
         | 
| 348 | 
             
                            "stream": True
         | 
| 349 | 
             
                        }
         | 
| @@ -393,185 +451,330 @@ class ScreenplayGenerationSystem: | |
| 393 | 
             
                    except Exception as e:
         | 
| 394 | 
             
                        yield f"โ ์ค๋ฅ: {str(e)}"
         | 
| 395 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 396 | 
             
                def generate_planning(self, query: str, screenplay_type: str, genre: str, 
         | 
| 397 | 
            -
                                     progress_callback=None) -> Generator[Tuple[str, float, Dict], None, None]:
         | 
| 398 | 
            -
                    """ | 
| 399 | 
             
                    try:
         | 
| 400 | 
            -
                        self.original_query = query | 
| 401 | 
             
                        self.current_session_id = ScreenplayDatabase.create_session(query, screenplay_type, genre)
         | 
| 402 |  | 
| 403 | 
             
                        planning_content = {}
         | 
| 404 | 
            -
                        self.accumulated_content = {} | 
|  | |
| 405 | 
             
                        total_stages = len(PLANNING_STAGES)
         | 
| 406 |  | 
| 407 | 
            -
                         | 
| 408 | 
            -
                        core_request = f"""
         | 
| 409 | 
            -
            ใํต์ฌ ์์ฒญ์ฌํญใ
         | 
| 410 | 
            -
            {query}
         | 
| 411 | 
            -
             | 
| 412 | 
            -
            ใํ์ ์ค์์ฌํญใ
         | 
| 413 | 
            -
            - ์ ์์ฒญ์ฌํญ์ ์ ๋์ ์ผ๋ก ์ค์ํ  ๊ฒ
         | 
| 414 | 
            -
            - ์์ฒญ๋ ์ค์ , ์ธ๋ฌผ, ์คํ ๋ฆฌ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ  ๊ฒ
         | 
| 415 | 
            -
            - ์์๋ก ๋ด์ฉ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ๋ค๋ฅธ ์ด์ผ๊ธฐ๋ก ๋ฐ๊พธ์ง ๋ง ๊ฒ
         | 
| 416 | 
            -
            - ์ฅ๋ฅด: {genre}
         | 
| 417 | 
            -
            - ํ์: {screenplay_type}
         | 
| 418 | 
            -
            """
         | 
| 419 | 
            -
                        
         | 
| 420 | 
            -
                        for idx, (stage_name, stage_desc) in enumerate(PLANNING_STAGES):
         | 
| 421 | 
             
                            progress = (idx / total_stages) * 100
         | 
| 422 |  | 
| 423 | 
            -
                            yield f"๐ {stage_desc} ์งํ ์ค...", progress, planning_content
         | 
| 424 |  | 
| 425 | 
            -
                            #  | 
| 426 | 
            -
                             | 
| 427 | 
            -
             | 
| 428 | 
            -
                                 | 
| 429 | 
            -
             | 
| 430 | 
            -
                                    previous_content += f"\n[{key}]\n{value[:500]}...\n"
         | 
| 431 |  | 
| 432 | 
            -
                            # ๊ฐ ๋จ๊ณ๋ณ ํ๋กฌํํธ ์์ฑ
         | 
| 433 | 
            -
                            if stage_name == "์ปจ์
 ๊ฐ๋ฐ":
         | 
| 434 | 
            -
                                prompt = f"""{core_request}
         | 
| 435 | 
            -
             | 
| 436 | 
            -
            ๋น์ ์ ํ ๋ฆฌ์ฐ๋ ํ๋ก๋์์
๋๋ค. ์ ์์ฒญ์ฌํญ์ ์ ํํ ๋ง๋ {screenplay_type} ์๋๋ฆฌ์ค ์ปจ์
์ ๊ฐ๋ฐํ์ธ์.
         | 
| 437 | 
            -
             | 
| 438 | 
            -
            ใ์์ฑ ํญ๋ชฉใ
         | 
| 439 | 
            -
            1. ์ ๋ชฉ: (์์ฒญ ๋ด์ฉ์ ๋ฐ์ํ ์ ๋ชฉ)
         | 
| 440 | 
            -
            2. ๋ก๊ทธ๋ผ์ธ: (์ ์์ฒญ์ ํต์ฌ ์คํ ๋ฆฌ๋ฅผ ํ ๋ฌธ์ฅ์ผ๋ก)
         | 
| 441 | 
            -
            3. ํต์ฌ ํ
๋ง: (์์ฒญ์์ ๋ค๋ฃจ๊ณ ์ ํ๋ ์ฃผ์ )
         | 
| 442 | 
            -
            4. ํ๊ฒ ๊ด๊ฐ: (์ด ์คํ ๋ฆฌ๊ฐ ์ดํํ  ๊ด๊ฐ์ธต)
         | 
| 443 | 
            -
            5. ์ ์ฌ ์ํ 3๊ฐ์ ์ฐจ๋ณ์ 
         | 
| 444 | 
            -
            6. ์์
์  ๋งค๋ ฅ ํฌ์ธํธ
         | 
| 445 | 
            -
             | 
| 446 | 
            -
            โ ๏ธ ์ค์: ๋ฐ๋์ "{query}"์ ๋ด์ฉ์ ๊ทธ๋๋ก ์ฌ์ฉํ์ธ์."""
         | 
| 447 | 
            -
             | 
| 448 | 
            -
                            elif stage_name == "์คํ ๋ฆฌ ๊ตฌ์ฑ":
         | 
| 449 | 
            -
                                prompt = f"""{core_request}
         | 
| 450 | 
            -
            {previous_content}
         | 
| 451 | 
            -
             | 
| 452 | 
            -
            ์ด์  ์ปจ์
์ ๋ฐํ์ผ๋ก ์คํ ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ง๋์ธ์.
         | 
| 453 | 
            -
            โ ๏ธ ๋ฐ๋์ ์๋ณธ ์์ฒญ "{query}"์ ์คํ ๋ฆฌ๋ผ์ธ์ ์ ์งํ์ธ์.
         | 
| 454 | 
            -
             | 
| 455 | 
            -
            ใ์์ฑ ํญ๋ชฉใ
         | 
| 456 | 
            -
            1. ์๋์์ค (500์): ์๋ณธ ์์ฒญ์ ์คํ ๋ฆฌ๋ฅผ ์ถฉ์คํ ๋ฐ์
         | 
| 457 | 
            -
            2. ํ์ฅ ์๋์์ค (1000์): ์ธ๋ถ ์ฌ๊ฑด๊ณผ ์ ๊ฐ ์ถ๊ฐ
         | 
| 458 | 
            -
            3. 3๋ง ๊ตฌ์กฐ:
         | 
| 459 | 
            -
               - 1๋ง: ์์ฒญ๋ ์ค์ ๊ณผ ์ธ๋ฌผ ์๊ฐ
         | 
| 460 | 
            -
               - 2๋งA: ์์ฒญ๋ ๊ฐ๋ฑ์ ์์
         | 
| 461 | 
            -
               - 2๋งB: ์์ฒญ๋ ์๊ธฐ์ ์ฌํ
         | 
| 462 | 
            -
               - 3๋ง: ์์ฒญ๋ ๋ฐฉ์์ ํด๊ฒฐ
         | 
| 463 | 
            -
             | 
| 464 | 
            -
            ์ ๋ ๋ค๋ฅธ ์ด์ผ๊ธฐ๋ก ๋ฐ๋์ง ์๋๋ก ์ฃผ์ํ์ธ์."""
         | 
| 465 | 
            -
             | 
| 466 | 
            -
                            elif stage_name == "์บ๋ฆญํฐ ์ค๊ณ":
         | 
| 467 | 
            -
                                prompt = f"""{core_request}
         | 
| 468 | 
            -
            {previous_content}
         | 
| 469 | 
            -
             | 
| 470 | 
            -
            ์๋ณธ ์์ฒญ์ ๋์จ ์บ๋ฆญํฐ๋ค์ ์ค๊ณํ์ธ์.
         | 
| 471 | 
            -
            โ ๏ธ ์์ฒญ์ ๋ช
์๋ ์ธ๋ฌผ ์ค์ ์ ์ ๋ ๋ณ๊ฒฝํ์ง ๋ง์ธ์.
         | 
| 472 | 
            -
             | 
| 473 | 
            -
            ใ์์ฑ ํญ๋ชฉใ
         | 
| 474 | 
            -
            ์ฃผ์ธ๊ณต: (์์ฒญ์ ๋์จ ์ฃผ์ธ๊ณต ๊ทธ๋๋ก)
         | 
| 475 | 
            -
            - ์ด๋ฆ๊ณผ ๋์ด
         | 
| 476 | 
            -
            - ์ธ๋ชจ์ ์ฑ๊ฒฉ (์์ฒญ ๋ด์ฉ ๋ฐ์)
         | 
| 477 | 
            -
            - ๋ชฉํ (์์ฒญ๋ ๋ชฉํ)
         | 
| 478 | 
            -
            - ํ์ (๋ด์  ์ฑ์ฅ)
         | 
| 479 | 
            -
            - ์ฑ์ฅ ์ํฌ
         | 
| 480 | 
            -
             | 
| 481 | 
            -
            ์ ๋์: (์์ฒญ์ ๋์จ ๋๋ฆฝ ์ธ๋ฌผ/์ธ๋ ฅ)
         | 
| 482 | 
            -
            - ์ด๋ฆ๊ณผ ์ญํ 
         | 
| 483 | 
            -
            - ๋๊ธฐ์ ๋ชฉํ
         | 
| 484 | 
            -
            - ์ฃผ์ธ๊ณต๊ณผ์ ๊ด๊ณ
         | 
| 485 | 
            -
             | 
| 486 | 
            -
            ์กฐ์ฐ๋ค: (์์ฒญ์ ์ธ๊ธ๋ ์ธ๋ฌผ๋ค)
         | 
| 487 | 
            -
            - ๊ฐ์์ ์ญํ ๊ณผ ํน์ง
         | 
| 488 | 
            -
             | 
| 489 | 
            -
            ์๋ก์ด ์ธ๋ฌผ์ ์์๋ก ์ถ๊ฐํ์ง ๋ง์ธ์."""
         | 
| 490 | 
            -
             | 
| 491 | 
            -
                            elif stage_name == "์ธ๊ณ๊ด ๊ตฌ์ถ":
         | 
| 492 | 
            -
                                prompt = f"""{core_request}
         | 
| 493 | 
            -
            {previous_content}
         | 
| 494 | 
            -
             | 
| 495 | 
            -
            ์๋ณธ ์์ฒญ์ ๋ฐฐ๊ฒฝ๊ณผ ์ธ๊ณ๊ด์ ๊ตฌ์ถํ์ธ์.
         | 
| 496 | 
            -
            โ ๏ธ ์์ฒญ์ ๋ช
์๋ ์๋, ์ฅ์, ๋ถ์๊ธฐ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ์ธ์.
         | 
| 497 | 
            -
             | 
| 498 | 
            -
            ใ์์ฑ ํญ๋ชฉใ
         | 
| 499 | 
            -
            1. ์๋ ๋ฐฐ๊ฒฝ: (์์ฒญ๋ ์๋)
         | 
| 500 | 
            -
            2. ์ฃผ์ ์ฅ์: (์์ฒญ์ ๋์จ ์ฅ์๋ค)
         | 
| 501 | 
            -
            3. ์ ์ฒด์  ๋ถ์๊ธฐ: (์์ฒญ๋ ํค)
         | 
| 502 | 
            -
            4. ์๊ฐ์  ์คํ์ผ
         | 
| 503 | 
            -
            5. ์ํฅ/์์
 ์คํ์ผ
         | 
| 504 | 
            -
             | 
| 505 | 
            -
            ์์๋ก ๋ฐฐ๊ฒฝ์ ๋ฐ๊พธ์ง ๋ง์ธ์."""
         | 
| 506 | 
            -
             | 
| 507 | 
            -
                            elif stage_name == "์ฌ ๊ตฌ์ฑ":
         | 
| 508 | 
            -
                                prompt = f"""{core_request}
         | 
| 509 | 
            -
            {previous_content}
         | 
| 510 | 
            -
             | 
| 511 | 
            -
            ์๋ณธ ์์ฒญ์ ์คํ ๋ฆฌ๋ฅผ ์ฌ์ผ๋ก ๊ตฌ์ฑํ์ธ์.
         | 
| 512 | 
            -
            โ ๏ธ ๋ฐ๋์ ์์ฒญ๋ ์ฌ๊ฑด๋ค์ด ๋ชจ๋ ํฌํจ๋์ด์ผ ํฉ๋๋ค.
         | 
| 513 | 
            -
             | 
| 514 | 
            -
            {SCREENPLAY_LENGTHS[screenplay_type]['pages']}ํ์ด์ง ๋ถ๋์ผ๋ก:
         | 
| 515 | 
            -
            - 1๋ง: ์์ฒญ๋ ์ค์ ๊ณผ ์์ (์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง)
         | 
| 516 | 
            -
            - 2๋งA: ์์ฒญ๋ ๊ฐ๋ฑ ์ ๊ฐ (์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง)
         | 
| 517 | 
            -
            - 2๋งB: ์์ฒญ๋ ์๊ธฐ ์ฌํ (์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง)
         | 
| 518 | 
            -
            - 3๋ง: ์์ฒญ๋ ํด๊ฒฐ (์ฝ {int(SCREENPLAY_LENGTHS[screenplay_type]['pages']*0.25)}ํ์ด์ง)
         | 
| 519 | 
            -
             | 
| 520 | 
            -
            ๊ฐ ์ฌ๋ง๋ค:
         | 
| 521 | 
            -
            - ์ฅ์์ ์๊ฐ
         | 
| 522 | 
            -
            - ๋ฑ์ฅ์ธ๋ฌผ
         | 
| 523 | 
            -
            - ํต์ฌ ์ฌ๊ฑด (์์ฒญ ๋ด์ฉ ๋ฐ์)
         | 
| 524 | 
            -
            - ์์ ํ์ด์ง
         | 
| 525 | 
            -
             | 
| 526 | 
            -
            ์คํ ๋ฆฌ๊ฐ ๋ค๋ฅธ ๋ฐฉํฅ์ผ๋ก ๊ฐ์ง ์๋๋ก ์ฃผ์ํ์ธ์."""
         | 
| 527 | 
            -
             | 
| 528 | 
             
                            # LLM ํธ์ถ
         | 
| 529 | 
             
                            messages = [
         | 
| 530 | 
            -
                                {"role": "system", "content": f"""๋น์ ์  | 
| 531 | 
            -
             | 
| 532 | 
            -
             | 
| 533 | 
            -
            ์๋ณธ  | 
| 534 | 
             
                                {"role": "user", "content": prompt}
         | 
| 535 | 
             
                            ]
         | 
| 536 |  | 
| 537 | 
             
                            content = ""
         | 
| 538 | 
             
                            for chunk in self.call_llm_streaming(messages):
         | 
| 539 | 
             
                                content += chunk
         | 
| 540 | 
            -
                                planning_content[ | 
| 541 | 
            -
                                yield f" | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 542 |  | 
| 543 | 
             
                            # ๋์  ๋ด์ฉ ์ ์ฅ
         | 
| 544 | 
            -
                            self.accumulated_content[ | 
| 545 | 
            -
                            time.sleep(0.5) | 
| 546 |  | 
| 547 | 
             
                        # ์ต์ข
 ๊ธฐํ์ ์ ์ฅ
         | 
| 548 | 
             
                        ScreenplayDatabase.save_planning_data(self.current_session_id, planning_content)
         | 
| 549 | 
            -
                        yield "โ
 ๊ธฐํ์ ์์ฑ!", 100, planning_content
         | 
| 550 |  | 
| 551 | 
             
                    except Exception as e:
         | 
| 552 | 
            -
                        yield f"โ ์ค๋ฅ ๋ฐ์: {str(e)}", 0, {}
         | 
| 553 |  | 
| 554 | 
             
                def generate_screenplay(self, session_id: str, planning_data: Dict,
         | 
| 555 | 
            -
                                      progress_callback=None) -> Generator[Tuple[str, float, str], None, None]:
         | 
| 556 | 
            -
                    """ | 
| 557 | 
             
                    try:
         | 
| 558 | 
             
                        total_stages = len(WRITING_STAGES)
         | 
| 559 | 
             
                        screenplay_content = ""
         | 
| 560 | 
             
                        act_contents = {"1๋ง": "", "2๋งA": "", "2๋งB": "", "3๋ง": ""}
         | 
|  | |
| 561 |  | 
| 562 | 
            -
                         | 
| 563 | 
            -
                        concept = planning_data.get("์ปจ์
 ๊ฐ๋ฐ", "")
         | 
| 564 | 
            -
                        story = planning_data.get("์คํ ๋ฆฌ ๊ตฌ์ฑ", "")
         | 
| 565 | 
            -
                        characters = planning_data.get("์บ๋ฆญํฐ ์ค๊ณ", "")
         | 
| 566 | 
            -
                        world = planning_data.get("์ธ๊ณ๊ด ๊ตฌ์ถ", "")
         | 
| 567 | 
            -
                        scenes = planning_data.get("์ฌ ๊ตฌ์ฑ", "")
         | 
| 568 | 
            -
                        
         | 
| 569 | 
            -
                        for idx, (stage_name, stage_desc) in enumerate(WRITING_STAGES):
         | 
| 570 | 
             
                            progress = (idx / total_stages) * 100
         | 
| 571 |  | 
| 572 | 
            -
                            yield f"๐ {stage_desc} ์งํ ์ค...", progress, screenplay_content
         | 
| 573 |  | 
| 574 | 
            -
                            # ๋ง ๊ฒฐ์ 
         | 
| 575 | 
             
                            current_act = ""
         | 
| 576 | 
             
                            if "1๋ง" in stage_name:
         | 
| 577 | 
             
                                current_act = "1๋ง"
         | 
| @@ -582,40 +785,42 @@ class ScreenplayGenerationSystem: | |
| 582 | 
             
                            elif "3๋ง" in stage_name:
         | 
| 583 | 
             
                                current_act = "3๋ง"
         | 
| 584 |  | 
| 585 | 
            -
                            #  | 
| 586 | 
            -
                            if " | 
| 587 | 
            -
                                prompt = self. | 
| 588 | 
            -
                                    current_act, planning_data, act_contents,  | 
| 589 | 
            -
                                    self.original_query
         | 
| 590 | 
             
                                )
         | 
| 591 | 
            -
                            elif " | 
| 592 | 
            -
                                prompt = self. | 
| 593 | 
            -
                                    current_act,  | 
| 594 | 
            -
                                    self.original_query
         | 
| 595 | 
             
                                )
         | 
| 596 | 
            -
                            elif " | 
| 597 | 
            -
                                prompt = self. | 
| 598 | 
            -
                                    current_act, act_contents[current_act] | 
| 599 | 
            -
             | 
|  | |
|  | |
|  | |
| 600 | 
             
                                )
         | 
| 601 | 
            -
                            elif " | 
| 602 | 
            -
                                prompt = self. | 
| 603 | 
            -
                                    current_act,  | 
| 604 | 
             
                                    self.original_query
         | 
| 605 | 
             
                                )
         | 
| 606 | 
            -
                            elif  | 
| 607 | 
            -
                                prompt = self. | 
| 608 | 
            -
             | 
| 609 | 
            -
                                 | 
| 610 | 
             
                            else:
         | 
| 611 | 
             
                                continue
         | 
| 612 |  | 
| 613 | 
             
                            # LLM ํธ์ถ
         | 
|  | |
| 614 | 
             
                            messages = [
         | 
| 615 | 
            -
                                {"role": "system", "content": f"""๋น์ ์  | 
| 616 | 
            -
             | 
| 617 | 
            -
             | 
| 618 | 
            -
             | 
| 619 | 
             
                                {"role": "user", "content": prompt}
         | 
| 620 | 
             
                            ]
         | 
| 621 |  | 
| @@ -623,142 +828,214 @@ class ScreenplayGenerationSystem: | |
| 623 | 
             
                            for chunk in self.call_llm_streaming(messages, max_tokens=15000):
         | 
| 624 | 
             
                                content += chunk
         | 
| 625 |  | 
| 626 | 
            -
                                # ๋ง ๋ด์ฉ ์
๋ฐ์ดํธ
         | 
| 627 | 
             
                                if current_act and "์์ฑ" in stage_name:
         | 
| 628 | 
             
                                    act_contents[current_act] = content
         | 
| 629 | 
            -
                                    screenplay_content = "\n\n".join([ | 
|  | |
|  | |
|  | |
| 630 |  | 
| 631 | 
            -
                                yield f"โ๏ธ {stage_desc} ์์ฑ ์ค...", progress, screenplay_content
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 632 |  | 
| 633 | 
             
                            time.sleep(0.5)
         | 
| 634 |  | 
| 635 | 
             
                        # ์ต์ข
 ์ ์ฅ
         | 
| 636 | 
            -
                        title =  | 
| 637 | 
            -
                        logline =  | 
| 638 |  | 
| 639 | 
             
                        ScreenplayDatabase.save_screenplay_content(session_id, screenplay_content, title, logline)
         | 
| 640 | 
            -
                        yield "โ
 ์๋๋ฆฌ์ค ์์ฑ!", 100, screenplay_content
         | 
| 641 |  | 
| 642 | 
             
                    except Exception as e:
         | 
| 643 | 
            -
                        yield f"โ ์ค๋ฅ ๋ฐ์: {str(e)}", 0, ""
         | 
| 644 |  | 
| 645 | 
            -
                def  | 
| 646 | 
            -
             | 
| 647 | 
            -
                    """ | 
| 648 |  | 
| 649 | 
            -
                     | 
| 650 | 
            -
                    characters = planning_data.get("์บ๋ฆญํฐ ์ค๊ณ", "")
         | 
| 651 | 
            -
                    scenes = planning_data.get("์ฌ ๊ตฌ์ฑ", "")
         | 
| 652 | 
            -
                    world = planning_data.get("์ธ๊ณ๊ด ๊ตฌ์ถ", "")
         | 
| 653 | 
            -
                    
         | 
| 654 | 
            -
                    if phase == "์ด๊ณ ":
         | 
| 655 | 
             
                        min_lines = 800
         | 
| 656 | 
            -
             | 
| 657 | 
            -
                    elif phase == "์์ ":
         | 
| 658 | 
            -
                        min_lines = 1000
         | 
| 659 | 
            -
                        instruction = "๋ด์ฉ์ ํ์ฅํ๊ณ  ๋ค๋ฌ์ผ์ธ์"
         | 
| 660 | 
            -
                    else:  # ์์ฑ
         | 
| 661 | 
             
                        min_lines = 1200
         | 
| 662 | 
            -
             | 
|  | |
| 663 |  | 
| 664 | 
            -
                     | 
| 665 | 
            -
            { | 
| 666 | 
            -
             | 
| 667 | 
            -
            ใ{act} {instruction}ใ
         | 
| 668 | 
            -
             | 
| 669 | 
            -
            ๋ชฉํ ๋ถ๋: ์ต์ {min_lines}์ค
         | 
| 670 |  | 
| 671 | 
            -
            ใ๊ธฐํ์  | 
| 672 | 
            -
             | 
| 673 | 
            -
            {story_structure[:1000]}
         | 
| 674 |  | 
| 675 | 
            -
             | 
| 676 | 
            -
            {characters[:1000]}
         | 
| 677 | 
            -
             | 
| 678 | 
            -
            ์ธ๊ณ๊ด:
         | 
| 679 | 
            -
            {world[:500]}
         | 
| 680 | 
            -
             | 
| 681 | 
            -
            ์ฌ ๊ตฌ์ฑ:
         | 
| 682 | 
            -
            {scenes[:1000]}
         | 
| 683 | 
            -
             | 
| 684 | 
            -
            ใ์ด์  ๋ง ๋ด์ฉใ
         | 
| 685 | 
             
            {previous_acts if previous_acts else "์ฒซ ๋ง์
๋๋ค"}
         | 
| 686 |  | 
| 687 | 
            -
             | 
| 688 | 
            -
            1.  | 
| 689 | 
            -
            2.  | 
| 690 | 
            -
             | 
| 691 | 
            -
             | 
| 692 | 
            -
             | 
| 693 | 
            -
            3. ์๊ฐ์  ๋ฌ์ฌ๋ ํ์ฌํ์ผ๋ก
         | 
| 694 | 
            -
            4. ๊ฐ ์ฌ๋ง๋ค ์ถฉ๋ถํ ๋ถ๋ (5-7ํ์ด์ง)
         | 
| 695 | 
            -
            5. ์ ๋ ๋ค๋ฅธ ์ด์ผ๊ธฐ๋ก ๋ณ๊ฒฝํ์ง ๋ง ๊ฒ
         | 
| 696 | 
            -
             | 
| 697 | 
            -
            โ ๏ธ ์ค์: ๋ฐ๋์ ์๋ณธ ์์ฒญ "{original_query}"์ ์คํ ๋ฆฌ๋ฅผ ๊ทธ๋๋ก ๊ตฌํํ์ธ์.
         | 
| 698 | 
            -
            ์์๋ก ์คํ ๋ฆฌ๋ฅผ ๋ฐ๊พธ์ง ๋ง์ธ์.
         | 
| 699 |  | 
| 700 | 
            -
            {min_lines}์ค ์ด์ ์์ฑํ์ธ์."""
         | 
| 701 | 
            -
                    
         | 
| 702 | 
            -
                    return prompt
         | 
| 703 |  | 
| 704 | 
            -
                def  | 
| 705 | 
            -
                    """ | 
| 706 | 
            -
                    return f""" | 
| 707 | 
            -
            {original_query}
         | 
| 708 |  | 
| 709 | 
            -
             | 
|  | |
| 710 |  | 
| 711 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 712 | 
             
            {content[:2000]}...
         | 
| 713 |  | 
| 714 | 
            -
             | 
| 715 | 
            -
            1.  | 
| 716 | 
            -
            2.  | 
| 717 | 
            -
            3.  | 
| 718 | 
            -
            4.  | 
| 719 | 
            -
            5.  | 
| 720 |  | 
| 721 | 
            -
             | 
| 722 | 
            -
            - ์๋ณธ ์์ฒญ์ ์คํ ๋ฆฌ๊ฐ ์ ๋๋ก ๊ตฌํ๋์๋๊ฐ?
         | 
| 723 | 
            -
            - ์์๋ก ๋ณ๊ฒฝ๋ ๋ถ๋ถ์ ์๋๊ฐ?
         | 
| 724 | 
            -
            - ๊ธฐํ์๊ณผ ์ผ์นํ๋๊ฐ?
         | 
| 725 |  | 
| 726 | 
            -
             | 
|  | |
|  | |
| 727 |  | 
| 728 | 
            -
             | 
| 729 | 
            -
             | 
| 730 | 
            -
                    return f"""์ ์ฒด ์๋๋ฆฌ์ค์ ๋์ฌ๋ฅผ ๊ฒํ ํ๊ณ  ๊ฐ์ ํ์ธ์.
         | 
| 731 |  | 
| 732 | 
            -
            ใ๊ฐ์   | 
| 733 | 
            -
            1. ์บ๋ฆญํฐ๋ณ  | 
| 734 | 
             
            2. ์๋ธํ
์คํธ ์ถ๊ฐ
         | 
| 735 | 
            -
            3. ๋ถํ์ํ ์ค๋ช
  | 
| 736 | 
            -
            4. ๊ฐ์ ์   | 
| 737 | 
            -
            5.  | 
| 738 |  | 
| 739 | 
            -
             | 
| 740 | 
            -
            - ์คํ ๋ฆฌ์ ์ค์ ์ ์ ๋ ๋ณ๊ฒฝํ์ง ๋ง์ธ์
         | 
| 741 | 
            -
            - ๋์ฌ๋ง ๊ฐ์ ํ์ธ์
         | 
| 742 |  | 
| 743 | 
            -
             | 
|  | |
|  | |
| 744 |  | 
| 745 | 
            -
             | 
| 746 | 
            -
             | 
| 747 | 
            -
             | 
|  | |
| 748 |  | 
| 749 | 
             
            ใํ๊ฐ ํญ๋ชฉใ
         | 
| 750 | 
            -
            1. ์๋ณธ  | 
| 751 | 
            -
            2.  | 
| 752 | 
            -
            3. ์บ๋ฆญํฐ  | 
| 753 | 
            -
            4.  | 
| 754 | 
            -
            5.  | 
| 755 |  | 
| 756 | 
            -
             | 
| 757 | 
            -
            - ์ฒ์ ์์ฒญํ ์คํ ๋ฆฌ๊ฐ ์ ๋๋ก ๊ตฌํ๋์๋๊ฐ?
         | 
| 758 | 
            -
            - ์ค๊ฐ์ ๋ค๋ฅธ ์ด์ผ๊ธฐ๋ก ๋ฐ๋์ง ์์๋๊ฐ?
         | 
| 759 | 
            -
            - ๊ธฐํ์๊ณผ ์ผ์นํ๋๊ฐ?
         | 
| 760 |  | 
| 761 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 762 |  | 
| 763 | 
             
            # ์ ํธ๋ฆฌํฐ ํจ์
         | 
| 764 | 
             
            def format_planning_display(planning_data: Dict) -> str:
         | 
| @@ -766,15 +1043,39 @@ def format_planning_display(planning_data: Dict) -> str: | |
| 766 | 
             
                if not planning_data:
         | 
| 767 | 
             
                    return "๊ธฐํ์์ด ์์ง ์์ฑ๋์ง ์์์ต๋๋ค."
         | 
| 768 |  | 
| 769 | 
            -
                formatted = "# ๐ ์๋๋ฆฌ์ค  | 
| 770 |  | 
| 771 | 
            -
                for  | 
| 772 | 
            -
                     | 
|  | |
|  | |
| 773 | 
             
                    formatted += content + "\n\n"
         | 
| 774 | 
             
                    formatted += "---\n\n"
         | 
| 775 |  | 
| 776 | 
             
                return formatted
         | 
| 777 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 778 | 
             
            def format_screenplay_display(screenplay_text: str) -> str:
         | 
| 779 | 
             
                """์๋๋ฆฌ์ค ํ์ ํฌ๋งท"""
         | 
| 780 | 
             
                if not screenplay_text:
         | 
| @@ -810,43 +1111,15 @@ def generate_random_concept(screenplay_type: str, genre: str) -> str: | |
| 810 | 
             
                    '์ก์
': [
         | 
| 811 | 
             
                        "์ํดํ ํน์์์์ด ๋ฉ์น๋ ๋ธ์ ๊ตฌํ๊ธฐ ์ํด ๋ค์ ํ์ฅ์ผ๋ก ๋ณต๊ทํ๋ค",
         | 
| 812 | 
             
                        "ํ๋ฒํ ํ์๊ธฐ์ฌ๊ฐ ์ฐ์ฐํ ๊ตญ์  ํ
๋ฌ ์กฐ์ง์ ์๋ชจ์ ํ๋ง๋ฆฐ๋ค",
         | 
| 813 | 
            -
                        "๋ถํจ ๊ฒฝ์ฐฐ๊ณผ ๋ง์ ์ธ์ฐ๋ ์ ์๋ก์ด ํ์ฌ์ ๊ณ ๋
ํ ์ธ์"
         | 
| 814 | 
             
                    ],
         | 
| 815 | 
             
                    '์ค๋ฆด๋ฌ': [
         | 
| 816 | 
             
                        "๊ธฐ์ต์ ์์ ๋จ์๊ฐ ์์ ์ด ์ฐ์์ด์ธ๋ฒ์ด๋ผ๋ ์ฆ๊ฑฐ๋ฅผ ๋ฐ๊ฒฌํ๋ค",
         | 
| 817 | 
             
                        "์ค์ข
๋ ์์ด๋ฅผ ์ฐพ๋ ๊ณผ์ ์์ ๋ง์์ ๋์ฐํ ๋น๋ฐ์ด ๋๋ฌ๋๋ค",
         | 
| 818 | 
            -
                        "์๋ฒฝํ ์๋ฆฌ๋ฐ์ด๋ฅผ ๊ฐ์ง ์ฉ์์๋ฅผ ์ซ๋ ๊ฒ์ฌ์ ์ง์ํ ์ถ์ "
         | 
| 819 | 
             
                    ],
         | 
| 820 | 
             
                    '๋๋ผ๋ง': [
         | 
| 821 | 
             
                        "๋ง๊ธฐ ์ ํ์๊ฐ ๋ง์ง๋ง ์์์ผ๋ก ๊ฐ์กฑ๊ณผ์ ํํด๋ฅผ ์๋ํ๋ค",
         | 
| 822 | 
             
                        "์ฌ๊ฐ๋ฐ๋ก ์ฌ๋ผ์ง ๋๋ค๋ฅผ ์งํค๋ ค๋ ์ฃผ๋ฏผ๋ค์ ๋ง์ง๋ง ์ธ์",
         | 
| 823 | 
            -
                        "์
์์๊ฐ ์๋ชจ๋ฅผ ์ฐพ์๊ฐ๋ ๊ฐ์ด ์ํ ์ฌ์ "
         | 
| 824 | 
            -
                    ],
         | 
| 825 | 
            -
                    '์ฝ๋ฏธ๋': [
         | 
| 826 | 
            -
                        "๊ฒฐํผ์์ฅ์ ์ด์ํ๋ ์ปคํ์ด ์์ ๋ค์ ์ดํผ์ ์จ๊ธฐ๋ฉฐ ์ผํ๋ค",
         | 
| 827 | 
            -
                        "๋ณต๊ถ์ ๋น์ฒจ๋ ๊ฐ์กฑ์ด ์๋ก ๋์ ์ฐจ์งํ๋ ค ์๋ชจ๋ฅผ ๊พธ๋ฏผ๋ค",
         | 
| 828 | 
            -
                        "์ค์๋ก ๋ํต๋ น ๊ฒฝํธ์์ด ๋ ํ๋ฒํ ํ์ฌ์์ ์ข์ถฉ์ฐ๋ ์ด์ผ๊ธฐ"
         | 
| 829 | 
            -
                    ],
         | 
| 830 | 
            -
                    '๊ณตํฌ': [
         | 
| 831 | 
            -
                        "ํ๋ณ์์์ ๋ฐค์ ์์กดํด์ผ ํ๋ ์๋์๋ค์ ๊ณตํฌ ์ฒดํ",
         | 
| 832 | 
            -
                        "๊ฑฐ์ธ ์์์ ๋ํ๋๋ ๋ ๋ค๋ฅธ ์์ ๊ณผ ๋ง์ฃผํ ์ฌ์",
         | 
| 833 | 
            -
                        "์ ์ฃผ๋ฐ์ ๊ณจ๋ํ์ ํ๋งคํ ํ ๋ฒ์ด์ง๋ ๊ธฐ์ดํ ์ฌ๊ฑด๋ค"
         | 
| 834 | 
             
                    ],
         | 
| 835 | 
            -
                    'SF': [
         | 
| 836 | 
            -
                        "์๊ฐ ์ฌํ์ผ๋ก ๊ณผ๊ฑฐ๋ฅผ ๋ฐ๊พธ๋ ค๋ค ๋ ํฐ ์ฌ์์ ๋ง๋  ๊ณผํ์",
         | 
| 837 | 
            -
                        "์ธ๊ณต์ง๋ฅ์ด ์ธ๊ฐ์ ๊ฐ์ ์ ํ์ตํ๋ฉฐ ๋ฒ์ด์ง๋ ์์์น ๋ชปํ ์ฌ๊ฑด",
         | 
| 838 | 
            -
                        "์ธ๊ณ ์ ํธ๋ฅผ ํด๋
ํ ์ธ์ดํ์๊ฐ ์ธ๋ฅ์ ์ด๋ช
์ ๊ฒฐ์ ํด์ผ ํ๋ค"
         | 
| 839 | 
            -
                    ],
         | 
| 840 | 
            -
                    '๋ก๋งจ์ค': [
         | 
| 841 | 
            -
                        "์ฒซ์ฌ๋๊ณผ 20๋
 ๋ง์ ์ฌํํ ๋ ์ฌ๋์ ๋ ๋ฒ์งธ ๊ธฐํ",
         | 
| 842 | 
            -
                        "๊ณ์ฝ๊ฒฐํผ์ผ๋ก ์์ํ๋ค๊ฐ ์ง์ง ์ฌ๋์ ๋น ์ง ์ปคํ",
         | 
| 843 | 
            -
                        "์๋ก ๋ค๋ฅธ ์๋๋ฅผ ์ด์๊ฐ๋ ๋ ์ฌ๋์ ํธ์ง๋ฅผ ํตํ ์ฌ๋"
         | 
| 844 | 
            -
                    ],
         | 
| 845 | 
            -
                    'ํํ์ง': [
         | 
| 846 | 
            -
                        "ํ๋ฒํ ๊ณ ๋ฑํ์์ด ํํ์ง ์ธ๊ณ์ ์ ํ๋ฐ์ ์ฉ์ฌ๊ฐ ๋๋ค",
         | 
| 847 | 
            -
                        "๋์์ ๋ํ๋ ์ฉ๊ณผ ์ํตํ  ์ ์๋ ์ ์ผํ ์ฌ๋",
         | 
| 848 | 
            -
                        "๋ง๋ฒ์ด ์ฌ๋ผ์ง ์ธ๊ณ์์ ๋ง์ง๋ง ๋ง๋ฒ์ฌ๊ฐ ๋์ด๋ฒ๋ฆฐ ์ฒญ๋
"
         | 
| 849 | 
            -
                    ]
         | 
| 850 | 
             
                }
         | 
| 851 |  | 
| 852 | 
             
                genre_concepts = concepts.get(genre, concepts['๋๋ผ๋ง'])
         | 
| @@ -871,24 +1144,20 @@ def create_interface(): | |
| 871 | 
             
                    text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
         | 
| 872 | 
             
                }
         | 
| 873 |  | 
| 874 | 
            -
                . | 
| 875 | 
            -
                    background: # | 
| 876 | 
            -
                    padding:  | 
| 877 | 
             
                    border-radius: 10px;
         | 
| 878 | 
             
                    margin: 1rem 0;
         | 
| 879 | 
            -
                    max-height: 600px;
         | 
| 880 | 
            -
                    overflow-y: auto;
         | 
| 881 | 
             
                }
         | 
| 882 |  | 
| 883 | 
            -
                . | 
| 884 | 
            -
                     | 
| 885 | 
            -
                     | 
| 886 | 
             
                    background: white;
         | 
| 887 | 
            -
                     | 
| 888 | 
            -
                     | 
| 889 | 
            -
                     | 
| 890 | 
            -
                    max-height: 800px;
         | 
| 891 | 
            -
                    overflow-y: auto;
         | 
| 892 | 
             
                }
         | 
| 893 |  | 
| 894 | 
             
                .progress-bar {
         | 
| @@ -905,55 +1174,50 @@ def create_interface(): | |
| 905 | 
             
                    border-radius: 10px;
         | 
| 906 | 
             
                    transition: width 0.3s ease;
         | 
| 907 | 
             
                }
         | 
| 908 | 
            -
                
         | 
| 909 | 
            -
                .warning-box {
         | 
| 910 | 
            -
                    background: #fff3cd;
         | 
| 911 | 
            -
                    border: 1px solid #ffc107;
         | 
| 912 | 
            -
                    border-radius: 5px;
         | 
| 913 | 
            -
                    padding: 1rem;
         | 
| 914 | 
            -
                    margin: 1rem 0;
         | 
| 915 | 
            -
                }
         | 
| 916 | 
             
                """
         | 
| 917 |  | 
| 918 | 
            -
                with gr.Blocks(theme=gr.themes.Soft(), css=css, title="AI ์๋๋ฆฌ์ค ์๊ฐ") as interface:
         | 
| 919 | 
             
                    gr.HTML("""
         | 
| 920 | 
             
                    <div class="main-header">
         | 
| 921 | 
             
                        <h1 class="header-title">๐ฌ AI ์๋๋ฆฌ์ค ์๊ฐ</h1>
         | 
| 922 | 
             
                        <p style="font-size: 1.2rem; opacity: 0.95;">
         | 
| 923 | 
            -
                             | 
| 924 | 
            -
                             | 
| 925 | 
             
                        </p>
         | 
| 926 | 
             
                    </div>
         | 
| 927 | 
             
                    """)
         | 
| 928 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 929 | 
             
                    current_session_id = gr.State(None)
         | 
| 930 | 
             
                    current_planning_data = gr.State({})
         | 
|  | |
| 931 |  | 
| 932 | 
             
                    with gr.Tabs():
         | 
| 933 | 
             
                        with gr.Tab("๐ ์ ์๋๋ฆฌ์ค"):
         | 
| 934 | 
            -
                            # ์ฃผ์์ฌํญ
         | 
| 935 | 
            -
                            gr.HTML("""
         | 
| 936 | 
            -
                            <div class="warning-box">
         | 
| 937 | 
            -
                                <strong>๐ก ํ:</strong> ๊ตฌ์ฒด์ ์ด๊ณ  ๋ช
ํํ ์ค์ ์ ์
๋ ฅํ ์๋ก ์ํ๋ ์คํ ๋ฆฌ๊ฐ ์ ํํ ๊ตฌํ๋ฉ๋๋ค.<br>
         | 
| 938 | 
            -
                                ์ฃผ์ธ๊ณต, ๋ฐฐ๊ฒฝ, ํต์ฌ ๊ฐ๋ฑ, ๊ฒฐ๋ง ๋ฐฉํฅ์ ๊ตฌ์ฒด์ ์ผ๋ก ์ ์ํด์ฃผ์ธ์.
         | 
| 939 | 
            -
                            </div>
         | 
| 940 | 
            -
                            """)
         | 
| 941 | 
            -
                            
         | 
| 942 | 
             
                            with gr.Row():
         | 
| 943 | 
             
                                with gr.Column(scale=2):
         | 
| 944 | 
             
                                    query_input = gr.Textbox(
         | 
| 945 | 
            -
                                        label="๐ก ์๋๋ฆฌ์ค ์์ด๋์ด | 
| 946 | 
            -
                                        placeholder=""" | 
| 947 | 
            -
             | 
| 948 | 
            -
             | 
| 949 | 
            -
             | 
| 950 | 
            -
             | 
| 951 | 
            -
             | 
| 952 | 
            -
            ๊ฒฐ๋ง์์ ์ง์ง์ ๊ฐ์ง์ ๊ฒฝ๊ณ๊ฐ ๋ฌด๋์ง๋ฉฐ 
         | 
| 953 | 
            -
            ์ฌ๋์ ๋ณธ์ง์ ๋ํด ์ง๋ฌธ์ ๋์ง๋ค."
         | 
| 954 | 
            -
             | 
| 955 | 
            -
            ๊ตฌ์ฒด์ ์ธ ์ค์ ์ผ์๋ก ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ต๋๋ค.""",
         | 
| 956 | 
            -
                                        lines=8
         | 
| 957 | 
             
                                    )
         | 
| 958 |  | 
| 959 | 
             
                                with gr.Column(scale=1):
         | 
| @@ -972,7 +1236,7 @@ AI ์๋ด์ ํจ๊ป ์ง์ค์ ์ฐพ์ ๋์ ๋ค. | |
| 972 | 
             
                            with gr.Row():
         | 
| 973 | 
             
                                random_btn = gr.Button("๐ฒ ๋๋ค ์์ด๋์ด", scale=1)
         | 
| 974 | 
             
                                clear_btn = gr.Button("๐๏ธ ์ด๊ธฐํ", scale=1)
         | 
| 975 | 
            -
                                planning_btn = gr.Button("๐  | 
| 976 |  | 
| 977 | 
             
                            # ์งํ ์ํ
         | 
| 978 | 
             
                            progress_bar = gr.HTML(
         | 
| @@ -981,157 +1245,102 @@ AI ์๋ด์ ํจ๊ป ์ง์ค์ ์ฐพ์ ๋์ ๋ค. | |
| 981 | 
             
                            status_text = gr.Textbox(
         | 
| 982 | 
             
                                label="๐ ์งํ ์ํ",
         | 
| 983 | 
             
                                interactive=False,
         | 
| 984 | 
            -
                                value="์์ด๋์ด๋ฅผ ์
๋ ฅํ๊ณ   | 
| 985 | 
             
                            )
         | 
| 986 |  | 
| 987 | 
            -
                            #  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 988 | 
             
                            with gr.Group():
         | 
| 989 | 
             
                                gr.Markdown("### ๐ ์๋๋ฆฌ์ค ๊ธฐํ์")
         | 
| 990 | 
             
                                planning_display = gr.Markdown(
         | 
| 991 | 
            -
                                    value="*๊ธฐํ์์ด ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*" | 
| 992 | 
            -
                                    elem_classes=["planning-section"]
         | 
| 993 | 
             
                                )
         | 
| 994 |  | 
| 995 | 
             
                                with gr.Row():
         | 
| 996 | 
            -
                                    edit_planning_btn = gr.Button("โ๏ธ ๊ธฐํ์ ์์ ", scale=1)
         | 
| 997 | 
             
                                    save_planning_btn = gr.Button("๐พ ๊ธฐํ์ ์ ์ฅ", scale=1)
         | 
| 998 | 
            -
                                    generate_screenplay_btn = gr.Button("๐ฌ ์๋๋ฆฌ์ค ์์ฑ | 
| 999 |  | 
| 1000 | 
             
                            # ์๋๋ฆฌ์ค ์ถ๋ ฅ
         | 
| 1001 | 
             
                            with gr.Group():
         | 
| 1002 | 
             
                                gr.Markdown("### ๐ ์์ฑ๋ ์๋๋ฆฌ์ค")
         | 
| 1003 | 
             
                                screenplay_output = gr.Markdown(
         | 
| 1004 | 
            -
                                    value="*์๋๋ฆฌ์ค๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*" | 
| 1005 | 
            -
                                    elem_classes=["screenplay-output"]
         | 
| 1006 | 
             
                                )
         | 
| 1007 |  | 
| 1008 | 
            -
                                 | 
| 1009 | 
            -
                                    download_btn = gr.Button("๐พ ๋ค์ด๋ก๋ (TXT)", scale=1)
         | 
| 1010 | 
            -
                                    pdf_btn = gr.Button("๐ PDF ๋ณํ", scale=1)
         | 
| 1011 | 
            -
                            
         | 
| 1012 | 
            -
                            # ์์
         | 
| 1013 | 
            -
                            gr.Examples(
         | 
| 1014 | 
            -
                                examples=[
         | 
| 1015 | 
            -
                                    ["2045๋
 ์์ธ, AI๊ฐ ์ธ๊ฐ ๊ฐ์ ์ ์๋ฒฝํ ๋ชจ๋ฐฉํ๋ ์๋. AI ์ค๋ฆฌํ์๊ฐ ์ฃฝ์ ์๋ด๋ฅผ ๋ณต์ ํ AI์ ์ด๋ค๊ฐ ์ง์ง ์๋ด๊ฐ ์ด์์๋ค๋ ์ฆ๊ฑฐ๋ฅผ ๋ฐ๊ฒฌํ๊ณ  ์ง์ค์ ์ฐพ์ ๋์ ๋ค."],
         | 
| 1016 | 
            -
                                    ["์กฐ์ ์๋ ํ์, ์ ๋ถ์ ์จ๊ธด ์์ธ์๊ฐ ์ ์ฃ๊ฑฐ๋ฆฌ์์ ์๋
๋ฅผ ๋ง๋ ์ฌ๋์ ๋น ์ง๋ค. ์ญ๋ชจ ์ฌ๊ฑด์ ํ๋ง๋ ค ํจ๊ป ์ง์ค์ ๋ฐํ์ผ ํ๋ค."],
         | 
| 1017 | 
            -
                                    ["์์ ํด์ ๋ง์, 10๋
 ์  ์ค์ข
๋ ๋ธ์ด ๊ฐ์๊ธฐ ๋์์จ๋ค. ํ์ง๋ง ๊ทธ๋
๋ 10๋
 ์  ๋ชจ์ต ๊ทธ๋๋ก๋ค. ๋ง์์ ์จ๊ฒจ์ง ๋น๋ฐ์ด ๋๋ฌ๋๊ธฐ ์์ํ๋ค."],
         | 
| 1018 | 
            -
                                    ["๋๊ธฐ์
 ๋ฒ๋ฌดํ ๋ณํธ์ฌ๊ฐ ํ์ฌ์ ๋ถ๋ฒ์ ๊ณ ๋ฐํ๋ ค๋ค ๋ชจํจ์ ๋ฐ๋๋ค. ์จ์ด์ ์ฆ๊ฑฐ๋ฅผ ๋ชจ์ผ๋ฉฐ ๊ฑฐ๋ํ ์๋ชจ์ ๋ง์ ๋ค."]
         | 
| 1019 | 
            -
                                ],
         | 
| 1020 | 
            -
                                inputs=query_input,
         | 
| 1021 | 
            -
                                label="๐ก ์์ ์์ด๋์ด (๊ตฌ์ฒด์ ์ธ ์ค์  ์ฐธ๊ณ )"
         | 
| 1022 | 
            -
                            )
         | 
| 1023 | 
            -
                        
         | 
| 1024 | 
            -
                        with gr.Tab("๐ ๋ด ์ํ"):
         | 
| 1025 | 
            -
                            gr.Markdown("""
         | 
| 1026 | 
            -
                            ### ๐ ์ ์ฅ๋ ์๋๋ฆฌ์ค
         | 
| 1027 | 
            -
                            
         | 
| 1028 | 
            -
                            ์์
 ์ค์ด๊ฑฐ๋ ์์ฑ๋ ์๋๋ฆฌ์ค๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
         | 
| 1029 | 
            -
                            """)
         | 
| 1030 | 
            -
                            
         | 
| 1031 | 
            -
                            saved_list = gr.Dataframe(
         | 
| 1032 | 
            -
                                headers=["์ ๋ชฉ", "์ฅ๋ฅด", "์ ํ", "ํ์ด์ง", "์ํ", "์์ ์ผ"],
         | 
| 1033 | 
            -
                                value=[],
         | 
| 1034 | 
            -
                                label="์ ์ฅ๋ ์ํ ๋ชฉ๋ก"
         | 
| 1035 | 
            -
                            )
         | 
| 1036 | 
            -
                            
         | 
| 1037 | 
            -
                            with gr.Row():
         | 
| 1038 | 
            -
                                refresh_btn = gr.Button("๐ ์๋ก๊ณ ์นจ")
         | 
| 1039 | 
            -
                                load_btn = gr.Button("๐ ๋ถ๋ฌ์ค๊ธฐ")
         | 
| 1040 | 
            -
                                delete_btn = gr.Button("๐๏ธ ์ญ์ ")
         | 
| 1041 |  | 
| 1042 | 
             
                    # ์ด๋ฒคํธ ํธ๋ค๋ฌ
         | 
| 1043 | 
             
                    def handle_planning(query, s_type, genre):
         | 
| 1044 | 
             
                        if not query:
         | 
| 1045 | 
            -
                            yield "", "โ ์์ด๋์ด๋ฅผ ์
๋ ฅํด์ฃผ์ธ์", "", {}
         | 
| 1046 | 
             
                            return
         | 
| 1047 |  | 
| 1048 | 
             
                        system = ScreenplayGenerationSystem()
         | 
| 1049 |  | 
| 1050 | 
            -
                        for status, progress, planning_data in system.generate_planning(query, s_type, genre):
         | 
| 1051 | 
             
                            progress_html = f'<div class="progress-bar"><div class="progress-fill" style="width: {progress}%"></div></div>'
         | 
| 1052 | 
             
                            planning_display = format_planning_display(planning_data)
         | 
|  | |
| 1053 |  | 
| 1054 | 
            -
                            yield progress_html, status, planning_display, planning_data
         | 
| 1055 |  | 
| 1056 | 
             
                    def handle_screenplay_generation(session_id, planning_data):
         | 
| 1057 | 
             
                        if not planning_data:
         | 
| 1058 | 
            -
                            yield "", "โ ๋จผ์  ๊ธฐํ์์ ์์ฑํด์ฃผ์ธ์", ""
         | 
| 1059 | 
             
                            return
         | 
| 1060 |  | 
| 1061 | 
             
                        system = ScreenplayGenerationSystem()
         | 
| 1062 |  | 
| 1063 | 
            -
                        for status, progress, screenplay in system.generate_screenplay(session_id, planning_data):
         | 
| 1064 | 
             
                            progress_html = f'<div class="progress-bar"><div class="progress-fill" style="width: {progress}%"></div></div>'
         | 
| 1065 | 
             
                            screenplay_display = format_screenplay_display(screenplay)
         | 
|  | |
| 1066 |  | 
| 1067 | 
            -
                            yield progress_html, status, screenplay_display
         | 
| 1068 | 
            -
                    
         | 
| 1069 | 
            -
                    def handle_random(s_type, genre):
         | 
| 1070 | 
            -
                        return generate_random_concept(s_type, genre)
         | 
| 1071 | 
            -
                    
         | 
| 1072 | 
            -
                    def handle_clear():
         | 
| 1073 | 
            -
                        empty_progress = '<div class="progress-bar"><div class="progress-fill" style="width: 0%"></div></div>'
         | 
| 1074 | 
            -
                        return "", empty_progress, "์ค๋น๋จ", "", "", None, {}
         | 
| 1075 | 
            -
                    
         | 
| 1076 | 
            -
                    def handle_download(screenplay_text):
         | 
| 1077 | 
            -
                        if not screenplay_text or screenplay_text == "*์๋๋ฆฌ์ค๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*":
         | 
| 1078 | 
            -
                            return None
         | 
| 1079 | 
            -
                        
         | 
| 1080 | 
            -
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
         | 
| 1081 | 
            -
                        filename = f"์๋๋ฆฌ์ค_{timestamp}.txt"
         | 
| 1082 | 
            -
                        
         | 
| 1083 | 
            -
                        with open(filename, 'w', encoding='utf-8') as f:
         | 
| 1084 | 
            -
                            # ๋งํฌ๋ค์ด ์ ๊ฑฐ
         | 
| 1085 | 
            -
                            clean_text = re.sub(r'\*\*([^*]+)\*\*', r'\1', screenplay_text)
         | 
| 1086 | 
            -
                            clean_text = re.sub(r'^#+ ', '', clean_text, flags=re.MULTILINE)
         | 
| 1087 | 
            -
                            f.write(clean_text)
         | 
| 1088 | 
            -
                        
         | 
| 1089 | 
            -
                        return filename
         | 
| 1090 |  | 
| 1091 | 
             
                    # ์ด๋ฒคํธ ์ฐ๊ฒฐ
         | 
| 1092 | 
             
                    planning_btn.click(
         | 
| 1093 | 
             
                        fn=handle_planning,
         | 
| 1094 | 
             
                        inputs=[query_input, screenplay_type, genre_select],
         | 
| 1095 | 
            -
                        outputs=[progress_bar, status_text, planning_display, current_planning_data]
         | 
| 1096 | 
             
                    )
         | 
| 1097 |  | 
| 1098 | 
             
                    generate_screenplay_btn.click(
         | 
| 1099 | 
             
                        fn=handle_screenplay_generation,
         | 
| 1100 | 
             
                        inputs=[current_session_id, current_planning_data],
         | 
| 1101 | 
            -
                        outputs=[progress_bar, status_text, screenplay_output]
         | 
| 1102 | 
             
                    )
         | 
| 1103 |  | 
| 1104 | 
             
                    random_btn.click(
         | 
| 1105 | 
            -
                        fn= | 
| 1106 | 
             
                        inputs=[screenplay_type, genre_select],
         | 
| 1107 | 
             
                        outputs=[query_input]
         | 
| 1108 | 
             
                    )
         | 
| 1109 | 
            -
                    
         | 
| 1110 | 
            -
                    clear_btn.click(
         | 
| 1111 | 
            -
                        fn=handle_clear,
         | 
| 1112 | 
            -
                        outputs=[query_input, progress_bar, status_text, planning_display, 
         | 
| 1113 | 
            -
                                screenplay_output, current_session_id, current_planning_data]
         | 
| 1114 | 
            -
                    )
         | 
| 1115 | 
            -
                    
         | 
| 1116 | 
            -
                    download_btn.click(
         | 
| 1117 | 
            -
                        fn=handle_download,
         | 
| 1118 | 
            -
                        inputs=[screenplay_output],
         | 
| 1119 | 
            -
                        outputs=[gr.File(label="๋ค์ด๋ก๋")]
         | 
| 1120 | 
            -
                    )
         | 
| 1121 |  | 
| 1122 | 
             
                return interface
         | 
| 1123 |  | 
| 1124 | 
             
            # ๋ฉ์ธ ์คํ
         | 
| 1125 | 
             
            if __name__ == "__main__":
         | 
| 1126 | 
             
                logger.info("=" * 60)
         | 
| 1127 | 
            -
                logger.info("AI ์๋๋ฆฌ์ค ์๊ฐ  | 
| 1128 | 
            -
                logger.info(" | 
| 1129 | 
             
                logger.info("=" * 60)
         | 
| 1130 |  | 
| 1131 | 
             
                if not FIREWORKS_API_KEY or FIREWORKS_API_KEY == "dummy_token_for_testing":
         | 
| 1132 | 
             
                    logger.warning("โ ๏ธ FIREWORKS_API_KEY๋ฅผ ์ค์ ํด์ฃผ์ธ์!")
         | 
| 1133 | 
             
                    logger.warning("export FIREWORKS_API_KEY='your-api-key'")
         | 
| 1134 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 1135 | 
             
                ScreenplayDatabase.init_db()
         | 
| 1136 |  | 
| 1137 | 
             
                interface = create_interface()
         | 
|  | |
| 44 | 
             
            # ๊ธ๋ก๋ฒ ๋ณ์
         | 
| 45 | 
             
            db_lock = threading.Lock()
         | 
| 46 |  | 
| 47 | 
            +
            # ์ ๋ฌธ๊ฐ ์ญํ  ์ ์
         | 
| 48 | 
            +
            EXPERT_ROLES = {
         | 
| 49 | 
            +
                "ํ๋ก๋์": {
         | 
| 50 | 
            +
                    "emoji": "๐ฌ",
         | 
| 51 | 
            +
                    "description": "์์
์ฑ๊ณผ ์์ฅ์ฑ ๋ถ์",
         | 
| 52 | 
            +
                    "focus": ["ํ๊ฒ ๊ด๊ฐ", "์ ์ ๊ฐ๋ฅ์ฑ", "์์ฐ ๊ท๋ชจ", "๋ง์ผํ
 ํฌ์ธํธ"],
         | 
| 53 | 
            +
                    "personality": "์ค์ฉ์ ์ด๊ณ  ์์ฅ ์งํฅ์ "
         | 
| 54 | 
            +
                },
         | 
| 55 | 
            +
                "์คํ ๋ฆฌ์๊ฐ": {
         | 
| 56 | 
            +
                    "emoji": "๐",
         | 
| 57 | 
            +
                    "description": "๋ด๋ฌํฐ๋ธ ๊ตฌ์กฐ์ ํ๋กฏ ๊ฐ๋ฐ",
         | 
| 58 | 
            +
                    "focus": ["3๋ง ๊ตฌ์กฐ", "ํ๋กฏ ํฌ์ธํธ", "์์ฌ ์ํฌ", "ํ
๋ง"],
         | 
| 59 | 
            +
                    "personality": "์ฐฝ์์ ์ด๊ณ  ๊ตฌ์กฐ์ "
         | 
| 60 | 
            +
                },
         | 
| 61 | 
            +
                "์บ๋ฆญํฐ๋์์ด๋": {
         | 
| 62 | 
            +
                    "emoji": "๐ฅ",
         | 
| 63 | 
            +
                    "description": "์ธ๋ฌผ ์ฐฝ์กฐ์ ๊ด๊ณ ์ค๊ณ",
         | 
| 64 | 
            +
                    "focus": ["์บ๋ฆญํฐ ์ํฌ", "๋๊ธฐ๋ถ์ฌ", "๊ด๊ณ ์ญํ", "๋ํ ์คํ์ผ"],
         | 
| 65 | 
            +
                    "personality": "์ฌ๋ฆฌํ์ ์ด๊ณ  ๊ณต๊ฐ์ "
         | 
| 66 | 
            +
                },
         | 
| 67 | 
            +
                "๊ฐ๋
": {
         | 
| 68 | 
            +
                    "emoji": "๐ญ",
         | 
| 69 | 
            +
                    "description": "๋น์ฃผ์ผ ์คํ ๋ฆฌํ
๋ง๊ณผ ์ฐ์ถ",
         | 
| 70 | 
            +
                    "focus": ["์๊ฐ์  ๊ตฌ์ฑ", "์นด๋ฉ๋ผ ์ํฌ", "๋ฏธ์ฅ์ผ", "๋ฆฌ๋ฌ๊ณผ ํ์ด์ฑ"],
         | 
| 71 | 
            +
                    "personality": "๋น์ฃผ์ผ ์ค์ฌ์ ์ด๊ณ  ์์ ์ "
         | 
| 72 | 
            +
                },
         | 
| 73 | 
            +
                "๋นํ๊ฐ": {
         | 
| 74 | 
            +
                    "emoji": "๐",
         | 
| 75 | 
            +
                    "description": "๊ฐ๊ด์  ๋ถ์๊ณผ ๊ฐ์ ์  ์ ์",
         | 
| 76 | 
            +
                    "focus": ["๋
ผ๋ฆฌ์  ์ผ๊ด์ฑ", "๊ฐ์ ์  ์ํฉํธ", "์์ ์ถฉ์ค๋", "์์ฑ๋"],
         | 
| 77 | 
            +
                    "personality": "๋ถ์์ ์ด๊ณ  ๋นํ์ "
         | 
| 78 | 
            +
                },
         | 
| 79 | 
            +
                "ํธ์ง์": {
         | 
| 80 | 
            +
                    "emoji": "โ๏ธ",
         | 
| 81 | 
            +
                    "description": "ํ์ด์ฑ๊ณผ ๊ตฌ์กฐ ์ต์ ํ",
         | 
| 82 | 
            +
                    "focus": ["์ฌ ์ ํ", "๋ฆฌ๋ฌ", "๊ธด์ฅ๊ฐ ์กฐ์ ", "๋ถํ์ํ ๋ถ๋ถ ์ ๊ฑฐ"],
         | 
| 83 | 
            +
                    "personality": "์ ๋ฐํ๊ณ  ํจ์จ์ "
         | 
| 84 | 
            +
                },
         | 
| 85 | 
            +
                "๋ํ์ ๋ฌธ๊ฐ": {
         | 
| 86 | 
            +
                    "emoji": "๐ฌ",
         | 
| 87 | 
            +
                    "description": "๋์ฌ์ ์๋ธํ
์คํธ ๊ฐํ",
         | 
| 88 | 
            +
                    "focus": ["์์ฐ์ค๋ฌ์ด ๋ํ", "์บ๋ฆญํฐ ๋ณด์ด์ค", "์๋ธํ
์คํธ", "๊ฐ์  ์ ๋ฌ"],
         | 
| 89 | 
            +
                    "personality": "์ธ์ด์ ์ด๊ณ  ๋์์ค ์ค์ฌ"
         | 
| 90 | 
            +
                },
         | 
| 91 | 
            +
                "์ฅ๋ฅด์ ๋ฌธ๊ฐ": {
         | 
| 92 | 
            +
                    "emoji": "๐ฏ",
         | 
| 93 | 
            +
                    "description": "์ฅ๋ฅด ๊ด์ต๊ณผ ๊ธฐ๋์น ์ถฉ์กฑ",
         | 
| 94 | 
            +
                    "focus": ["์ฅ๋ฅด ๊ด์ต", "๊ด๊ฐ ๊ธฐ๋", "์ฅ๋ฅด ํน์  ์์", "ํธ๋กํ ํ์ฉ"],
         | 
| 95 | 
            +
                    "personality": "์ฅ๋ฅด์ ์ ํตํ"
         | 
| 96 | 
            +
                }
         | 
| 97 | 
            +
            }
         | 
| 98 | 
            +
             | 
| 99 | 
             
            # ์ฅ๋ฅด ํ
ํ๋ฆฟ
         | 
| 100 | 
             
            GENRE_TEMPLATES = {
         | 
| 101 | 
             
               "์ก์
": {
         | 
|  | |
| 172 | 
             
               }
         | 
| 173 | 
             
            }
         | 
| 174 |  | 
| 175 | 
            +
            # ๊ธฐํ ๋จ๊ณ - ๋ค์ค ์ญํ  ํ์
         | 
| 176 | 
             
            PLANNING_STAGES = [
         | 
| 177 | 
            +
                ("ํ๋ก๋์", "producer", "๐ฌ ํ๋ก๋์: ํต์ฌ ์ปจ์
 ๋ฐ ์์ฅ์ฑ ๋ถ์"),
         | 
| 178 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "story_writer", "๐ ์คํ ๋ฆฌ ์๊ฐ: ์๋์์ค ๋ฐ 3๋ง ๊ตฌ์กฐ"),
         | 
| 179 | 
            +
                ("์บ๋ฆญํฐ๋์์ด๋", "character_designer", "๐ฅ ์บ๋ฆญํฐ ๋์์ด๋: ์ธ๋ฌผ ํ๋กํ ๋ฐ ๊ด๊ณ๋"),
         | 
| 180 | 
            +
                ("๊ฐ๋
", "director", "๐ญ ๊ฐ๋
: ๋น์ฃผ์ผ ์ปจ์
 ๋ฐ ์ฐ์ถ ๋ฐฉํฅ"),
         | 
| 181 | 
            +
                ("๋นํ๊ฐ", "critic", "๐ ๋นํ๊ฐ: ๊ธฐํ์ ์ข
ํฉ ๊ฒํ  ๋ฐ ๊ฐ์ ์ "),
         | 
| 182 | 
             
            ]
         | 
| 183 |  | 
| 184 | 
            +
            # ์์ฑ ๋จ๊ณ - ๋ค์ค ์ญํ  ํ์
         | 
| 185 | 
             
            WRITING_STAGES = [
         | 
| 186 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "1๋ง ์ด๊ณ ", "โ๏ธ 1๋ง ์ด๊ณ  ์์ฑ"),
         | 
| 187 | 
            +
                ("ํธ์ง์", "1๋ง ํธ์ง", "โ๏ธ 1๋ง ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "),
         | 
| 188 | 
            +
                ("๊ฐ๋
", "1๋ง ์ฐ์ถ", "๐ญ 1๋ง ๋น์ฃผ์ผ ๊ฐํ"),
         | 
| 189 | 
            +
                ("๋ํ์ ๋ฌธ๊ฐ", "1๋ง ๋์ฌ", "๐ฌ 1๋ง ๋์ฌ ๊ฐ์ "),
         | 
| 190 | 
            +
                ("๋นํ๊ฐ", "1๋ง ๊ฒํ ", "๐ 1๋ง ์ข
ํฉ ๊ฒํ "),
         | 
| 191 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "1๋ง ์์ฑ", "โ
 1๋ง ์ต์ข
 ์์ฑ๋ณธ"),
         | 
| 192 |  | 
| 193 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "2๋งA ์ด๊ณ ", "โ๏ธ 2๋งA ์ด๊ณ  ์์ฑ"),
         | 
| 194 | 
            +
                ("ํธ์ง์", "2๋งA ํธ์ง", "โ๏ธ 2๋งA ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "),
         | 
| 195 | 
            +
                ("๊ฐ๋
", "2๋งA ์ฐ์ถ", "๐ญ 2๋งA ๋น์ฃผ์ผ ๊ฐํ"),
         | 
| 196 | 
            +
                ("๋ํ์ ๋ฌธ๊ฐ", "2๋งA ๋์ฌ", "๐ฌ 2๋งA ๋์ฌ ๊ฐ์ "),
         | 
| 197 | 
            +
                ("๋นํ๊ฐ", "2๋งA ๊ฒํ ", "๐ 2๋งA ์ข
ํฉ ๊ฒํ "),
         | 
| 198 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "2๋งA ์์ฑ", "โ
 2๋งA ์ต์ข
 ์์ฑ๋ณธ"),
         | 
| 199 |  | 
| 200 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "2๋งB ์ด๊ณ ", "โ๏ธ 2๋งB ์ด๊ณ  ์์ฑ"),
         | 
| 201 | 
            +
                ("ํธ์ง๏ฟฝ๏ฟฝ", "2๋งB ํธ์ง", "โ๏ธ 2๋งB ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "),
         | 
| 202 | 
            +
                ("๊ฐ๋
", "2๋งB ์ฐ์ถ", "๐ญ 2๋งB ๋น์ฃผ์ผ ๊ฐํ"),
         | 
| 203 | 
            +
                ("๋ํ์ ๋ฌธ๊ฐ", "2๋งB ๋์ฌ", "๐ฌ 2๋งB ๋์ฌ ๊ฐ์ "),
         | 
| 204 | 
            +
                ("๋นํ๊ฐ", "2๋งB ๊ฒํ ", "๐ 2๋งB ์ข
ํฉ ๊ฒํ "),
         | 
| 205 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "2๋งB ์์ฑ", "โ
 2๋งB ์ต์ข
 ์์ฑ๋ณธ"),
         | 
| 206 |  | 
| 207 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "3๋ง ์ด๊ณ ", "โ๏ธ 3๋ง ์ด๊ณ  ์์ฑ"),
         | 
| 208 | 
            +
                ("ํธ์ง์", "3๋ง ํธ์ง", "โ๏ธ 3๋ง ํธ์ง ๋ฐ ํ์ด์ฑ ์กฐ์ "),
         | 
| 209 | 
            +
                ("๊ฐ๋
", "3๋ง ์ฐ์ถ", "๐ญ 3๋ง ๋น์ฃผ์ผ ๊ฐํ"),
         | 
| 210 | 
            +
                ("๋ํ์ ๋ฌธ๊ฐ", "3๋ง ๋์ฌ", "๐ฌ 3๋ง ๋์ฌ ๊ฐ์ "),
         | 
| 211 | 
            +
                ("๋นํ๊ฐ", "3๋ง ๊ฒํ ", "๐ 3๋ง ์ข
ํฉ ๊ฒํ "),
         | 
| 212 | 
            +
                ("์คํ ๋ฆฌ์๊ฐ", "3๋ง ์์ฑ", "โ
 3๋ง ์ต์ข
 ์์ฑ๋ณธ"),
         | 
| 213 |  | 
| 214 | 
            +
                ("์ฅ๋ฅด์ ๋ฌธ๊ฐ", "์ฅ๋ฅด ์ต์ ํ", "๐ฏ ์ฅ๋ฅด ํน์ฑ ์ต์ ํ"),
         | 
| 215 | 
            +
                ("๋นํ๊ฐ", "์ต์ข
 ๊ฒํ ", "๐ ์ต์ข
 ๊ฒํ  ๋ฐ ์์ฑ๋ ํ๊ฐ"),
         | 
| 216 | 
             
            ]
         | 
| 217 |  | 
| 218 | 
             
            # ๋ฐ์ดํฐ ํด๋์ค
         | 
| 219 | 
            +
            @dataclass
         | 
| 220 | 
            +
            class ExpertFeedback:
         | 
| 221 | 
            +
                """์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ"""
         | 
| 222 | 
            +
                role: str
         | 
| 223 | 
            +
                stage: str
         | 
| 224 | 
            +
                feedback: str
         | 
| 225 | 
            +
                suggestions: List[str]
         | 
| 226 | 
            +
                score: float
         | 
| 227 | 
            +
                timestamp: datetime = field(default_factory=datetime.now)
         | 
| 228 | 
            +
             | 
| 229 | 
             
            @dataclass
         | 
| 230 | 
             
            class ScreenplayPlan:
         | 
| 231 | 
             
                """์๋๋ฆฌ์ค ๊ธฐํ์"""
         | 
|  | |
| 259 | 
             
                # ์ฌ ๊ตฌ์ฑ
         | 
| 260 | 
             
                total_scenes: int = 0
         | 
| 261 | 
             
                scene_breakdown: List[Dict] = field(default_factory=list)
         | 
| 262 | 
            +
                
         | 
| 263 | 
            +
                # ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ
         | 
| 264 | 
            +
                expert_feedbacks: List[ExpertFeedback] = field(default_factory=list)
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 265 |  | 
| 266 | 
             
            # ๋ฐ์ดํฐ๋ฒ ์ด์ค ํด๋์ค
         | 
| 267 | 
             
            class ScreenplayDatabase:
         | 
|  | |
| 281 | 
             
                                title TEXT,
         | 
| 282 | 
             
                                logline TEXT,
         | 
| 283 | 
             
                                planning_data TEXT,
         | 
| 284 | 
            +
                                expert_feedbacks TEXT,
         | 
| 285 | 
             
                                screenplay_content TEXT,
         | 
| 286 | 
             
                                created_at TEXT DEFAULT (datetime('now')),
         | 
| 287 | 
             
                                updated_at TEXT DEFAULT (datetime('now')),
         | 
|  | |
| 292 | 
             
                        ''')
         | 
| 293 |  | 
| 294 | 
             
                        cursor.execute('''
         | 
| 295 | 
            +
                            CREATE TABLE IF NOT EXISTS expert_reviews (
         | 
| 296 | 
             
                                id INTEGER PRIMARY KEY AUTOINCREMENT,
         | 
| 297 | 
             
                                session_id TEXT NOT NULL,
         | 
| 298 | 
            +
                                role TEXT NOT NULL,
         | 
| 299 | 
            +
                                stage TEXT NOT NULL,
         | 
| 300 | 
            +
                                feedback TEXT,
         | 
| 301 | 
            +
                                suggestions TEXT,
         | 
| 302 | 
            +
                                score REAL,
         | 
|  | |
| 303 | 
             
                                created_at TEXT DEFAULT (datetime('now')),
         | 
| 304 | 
            +
                                FOREIGN KEY (session_id) REFERENCES screenplay_sessions(session_id)
         | 
|  | |
| 305 | 
             
                            )
         | 
| 306 | 
             
                        ''')
         | 
| 307 |  | 
|  | |
| 333 | 
             
                        conn.commit()
         | 
| 334 | 
             
                    return session_id
         | 
| 335 |  | 
| 336 | 
            +
                @staticmethod
         | 
| 337 | 
            +
                def save_expert_feedback(session_id: str, role: str, stage: str, 
         | 
| 338 | 
            +
                                       feedback: str, suggestions: List[str], score: float):
         | 
| 339 | 
            +
                    with ScreenplayDatabase.get_db() as conn:
         | 
| 340 | 
            +
                        conn.cursor().execute(
         | 
| 341 | 
            +
                            '''INSERT INTO expert_reviews 
         | 
| 342 | 
            +
                               (session_id, role, stage, feedback, suggestions, score)
         | 
| 343 | 
            +
                               VALUES (?, ?, ?, ?, ?, ?)''',
         | 
| 344 | 
            +
                            (session_id, role, stage, feedback, json.dumps(suggestions, ensure_ascii=False), score)
         | 
| 345 | 
            +
                        )
         | 
| 346 | 
            +
                        conn.commit()
         | 
| 347 | 
            +
             | 
| 348 | 
             
                @staticmethod
         | 
| 349 | 
             
                def save_planning_data(session_id: str, planning_data: Dict):
         | 
| 350 | 
             
                    with ScreenplayDatabase.get_db() as conn:
         | 
|  | |
| 356 | 
             
                        )
         | 
| 357 | 
             
                        conn.commit()
         | 
| 358 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 359 | 
             
                @staticmethod
         | 
| 360 | 
             
                def save_screenplay_content(session_id: str, content: str, title: str, logline: str):
         | 
| 361 | 
             
                    with ScreenplayDatabase.get_db() as conn:
         | 
|  | |
| 377 | 
             
                    self.model_id = MODEL_ID
         | 
| 378 | 
             
                    self.current_session_id = None
         | 
| 379 | 
             
                    self.current_plan = ScreenplayPlan()
         | 
| 380 | 
            +
                    self.original_query = ""
         | 
| 381 | 
            +
                    self.accumulated_content = {}
         | 
| 382 | 
            +
                    self.expert_feedbacks = []
         | 
| 383 | 
             
                    ScreenplayDatabase.init_db()
         | 
| 384 |  | 
| 385 | 
             
                def create_headers(self):
         | 
|  | |
| 398 | 
             
                            "model": self.model_id,
         | 
| 399 | 
             
                            "messages": messages,
         | 
| 400 | 
             
                            "max_tokens": max_tokens,
         | 
| 401 | 
            +
                            "temperature": 0.7,
         | 
| 402 | 
            +
                            "top_p": 0.9,
         | 
| 403 | 
             
                            "top_k": 40,
         | 
| 404 | 
            +
                            "presence_penalty": 0.3,
         | 
| 405 | 
             
                            "frequency_penalty": 0.3,
         | 
| 406 | 
             
                            "stream": True
         | 
| 407 | 
             
                        }
         | 
|  | |
| 451 | 
             
                    except Exception as e:
         | 
| 452 | 
             
                        yield f"โ ์ค๋ฅ: {str(e)}"
         | 
| 453 |  | 
| 454 | 
            +
                def get_expert_prompt(self, role: str, stage: str, query: str, 
         | 
| 455 | 
            +
                                     previous_content: Dict, genre: str, screenplay_type: str) -> str:
         | 
| 456 | 
            +
                    """๊ฐ ์ ๋ฌธ๊ฐ๋ณ ๋ง์ถค ํ๋กฌํํธ ์์ฑ"""
         | 
| 457 | 
            +
                    
         | 
| 458 | 
            +
                    expert = EXPERT_ROLES[role]
         | 
| 459 | 
            +
                    focus_areas = ", ".join(expert["focus"])
         | 
| 460 | 
            +
                    
         | 
| 461 | 
            +
                    # ์ด์  ์ ๋ฌธ๊ฐ๋ค์ ํผ๋๋ฐฑ ์์ฝ
         | 
| 462 | 
            +
                    previous_feedbacks = ""
         | 
| 463 | 
            +
                    if self.expert_feedbacks:
         | 
| 464 | 
            +
                        previous_feedbacks = "\nใ์ด์  ์ ๋ฌธ๊ฐ ์๊ฒฌใ\n"
         | 
| 465 | 
            +
                        for fb in self.expert_feedbacks[-3:]:  # ์ต๊ทผ 3๊ฐ๋ง
         | 
| 466 | 
            +
                            previous_feedbacks += f"[{fb.role}]: {fb.feedback[:200]}...\n"
         | 
| 467 | 
            +
                    
         | 
| 468 | 
            +
                    base_prompt = f"""
         | 
| 469 | 
            +
            ใ๋น์ ์ ์ญํ ใ
         | 
| 470 | 
            +
            ๋น์ ์ {role}์
๋๋ค. {expert['description']}
         | 
| 471 | 
            +
            ์ฑ๊ฒฉ: {expert['personality']}
         | 
| 472 | 
            +
            ์ง์ค ์์ญ: {focus_areas}
         | 
| 473 | 
            +
             | 
| 474 | 
            +
            ใ์๋ณธ ์์ฒญใ
         | 
| 475 | 
            +
            {query}
         | 
| 476 | 
            +
             | 
| 477 | 
            +
            ใ์ฅ๋ฅดใ {genre}
         | 
| 478 | 
            +
            ใํ์ใ {screenplay_type}
         | 
| 479 | 
            +
             | 
| 480 | 
            +
            {previous_feedbacks}
         | 
| 481 | 
            +
             | 
| 482 | 
            +
            ใ์ด์  ์์
 ๋ด์ฉใ
         | 
| 483 | 
            +
            {self._summarize_previous_content(previous_content)}
         | 
| 484 | 
            +
            """
         | 
| 485 | 
            +
                    
         | 
| 486 | 
            +
                    # ์ญํ ๋ณ ํนํ ํ๋กฌํํธ
         | 
| 487 | 
            +
                    if role == "ํ๋ก๋์":
         | 
| 488 | 
            +
                        return base_prompt + f"""
         | 
| 489 | 
            +
            ใํ๋ก๋์๋ก์ ๋ถ์ํ  ๋ด์ฉใ
         | 
| 490 | 
            +
            1. ์์
์  ๊ฐ์น์ ์์ฅ์ฑ
         | 
| 491 | 
            +
               - ํ๊ฒ ๊ด๊ฐ์ธต ๋ช
ํํ
         | 
| 492 | 
            +
               - ์์ ์ ์๋น ๊ท๋ชจ
         | 
| 493 | 
            +
               - ๋ฐฐ๊ธ ์ ๋ต
         | 
| 494 | 
            +
               
         | 
| 495 | 
            +
            2. ์ ๋ชฉ๊ณผ ๋ก๊ทธ๋ผ์ธ
         | 
| 496 | 
            +
               - ๋ง์ผํ
 ๊ฐ๋ฅํ ๋งค๋ ฅ์ ์ธ ์ ๋ชฉ
         | 
| 497 | 
            +
               - ํ ๋ฌธ์ฅ์ผ๋ก ํต์ฌ ๊ฐ๋ฑ ํํ
         | 
| 498 | 
            +
               
         | 
| 499 | 
            +
            3. ์ ์ฌ ์ฑ๊ณต์ ๋ถ์
         | 
| 500 | 
            +
               - ์ต๊ทผ 3๋
 ๋ด ์ ์ฌ ์ํ
         | 
| 501 | 
            +
               - ์ฐจ๋ณํ ํฌ์ธํธ
         | 
| 502 | 
            +
               
         | 
| 503 | 
            +
            4. ์ ์ ๋ฆฌ์คํฌ ํ๊ฐ
         | 
| 504 | 
            +
               - ๊ธฐ์ ์  ๋์ด๋
         | 
| 505 | 
            +
               - ์บ์คํ
 ์๊ตฌ์ฌํญ
         | 
| 506 | 
            +
             | 
| 507 | 
            +
            โ ๏ธ ์๋ณธ ์์ฒญ์ ์์
์ ์ผ๋ก ์ต์ ํํ๋ ํต์ฌ์ ์ ์งํ์ธ์."""
         | 
| 508 | 
            +
             | 
| 509 | 
            +
                    elif role == "์คํ ๋ฆฌ์๊ฐ":
         | 
| 510 | 
            +
                        return base_prompt + f"""
         | 
| 511 | 
            +
            ใ์คํ ๋ฆฌ ์๊ฐ๋ก์ ๊ตฌ์ฑํ  ๋ด์ฉใ
         | 
| 512 | 
            +
            1. ์๋์์ค (500-800์)
         | 
| 513 | 
            +
               - ์๋ณธ ์์ฒญ์ ํต์ฌ ์คํ ๋ฆฌ
         | 
| 514 | 
            +
               - ๋ช
ํํ ์์-์ค๊ฐ-๋
         | 
| 515 | 
            +
               
         | 
| 516 | 
            +
            2. 3๋ง ๊ตฌ์กฐ ์์ธ
         | 
| 517 | 
            +
               - 1๋ง (25%): ์ค์ ๊ณผ ์ด๋ฐ ์ฌ๊ฑด
         | 
| 518 | 
            +
               - 2๋งA (25%): ์๋ก์ด ์ธ๊ณ์ ์ฌ๋ฏธ
         | 
| 519 | 
            +
               - 2๋งB (25%): ์๋ จ๊ณผ ์๊ธฐ
         | 
| 520 | 
            +
               - 3๋ง (25%): ํด๋ผ์ด๋งฅ์ค์ ํด๊ฒฐ
         | 
| 521 | 
            +
               
         | 
| 522 | 
            +
            3. ์ฃผ์ ํ๋กฏ ํฌ์ธํธ
         | 
| 523 | 
            +
               - 10๊ฐ์ ํต์ฌ ์ ํ์ 
         | 
| 524 | 
            +
               - ๊ฐ ์ ํ์ ์ ๊ฐ์ ์  ์ํฉํธ
         | 
| 525 | 
            +
               
         | 
| 526 | 
            +
            4. ํ
๋ง์ ๋ฉ์์ง
         | 
| 527 | 
            +
               - ์ค์ฌ ํ
๋ง
         | 
| 528 | 
            +
               - ์๋ธ ํ
๋ง๋ค
         | 
| 529 | 
            +
             | 
| 530 | 
            +
            โ ๏ธ ์๋ณธ ์คํ ๋ฆฌ์ ๋
ผ๋ฆฌ์  ํ๋ฆ์ ์๋ฒฝํ๊ฒ ๊ตฌํํ์ธ์."""
         | 
| 531 | 
            +
             | 
| 532 | 
            +
                    elif role == "์บ๋ฆญํฐ๋์์ด๋":
         | 
| 533 | 
            +
                        return base_prompt + f"""
         | 
| 534 | 
            +
            ใ์บ๋ฆญํฐ ๋์์ด๋๋ก์ ์ฐฝ์กฐํ  ๋ด์ฉใ
         | 
| 535 | 
            +
            1. ์ฃผ์ธ๊ณต ์์ธ ํ๋กํ
         | 
| 536 | 
            +
               - ์ด๋ฆ, ๋์ด, ์ง์
         | 
| 537 | 
            +
               - ์ฑ๊ฒฉ (MBTI ํฌํจ)
         | 
| 538 | 
            +
               - ํต์ฌ ์๊ตฌ(Want)์ ํ์(Need)
         | 
| 539 | 
            +
               - ์น๋ช
์  ๊ฒฐํจ
         | 
| 540 | 
            +
               - ๋ณํ ์ํฌ
         | 
| 541 | 
            +
               - ํน์ง์  ๋งํฌ์ ํ๋
         | 
| 542 | 
            +
               
         | 
| 543 | 
            +
            2. ์ ๋์ ํ๋กํ
         | 
| 544 | 
            +
               - ์ฃผ์ธ๊ณต๊ณผ์ ๋๋ฆฝ ๊ตฌ์กฐ
         | 
| 545 | 
            +
               - ๋๋ฆ์ ์ ๋น์ฑ
         | 
| 546 | 
            +
               - ์ฝ์ ๊ณผ ๋งน์ 
         | 
| 547 | 
            +
               
         | 
| 548 | 
            +
            3. ์กฐ์ฐ ์บ๋ฆญํฐ๋ค (3-5๋ช
)
         | 
| 549 | 
            +
               - ๊ฐ์์ ์ญํ ๊ณผ ๊ธฐ๋ฅ
         | 
| 550 | 
            +
               - ์ฃผ์ธ๊ณต๊ณผ์ ๊ด๊ณ
         | 
| 551 | 
            +
               - ๊ณ ์ ํ ํน์ง
         | 
| 552 | 
            +
               
         | 
| 553 | 
            +
            4. ์บ๋ฆญํฐ ๊ด๊ณ๋
         | 
| 554 | 
            +
               - ์ธ๋ฌผ๊ฐ ์ญํ ๊ด๊ณ
         | 
| 555 | 
            +
               - ๊ฐ๋ฑ ๊ตฌ์กฐ
         | 
| 556 | 
            +
             | 
| 557 | 
            +
            โ ๏ธ ์๋ณธ์ ๋ช
์๋ ์ธ๋ฌผ ์ค์ ์ ์ถฉ์คํ ๋ฐ์ ์ํค์ธ์."""
         | 
| 558 | 
            +
             | 
| 559 | 
            +
                    elif role == "๊ฐ๋
":
         | 
| 560 | 
            +
                        return base_prompt + f"""
         | 
| 561 | 
            +
            ใ๊ฐ๋
์ผ๋ก์ ์ฐ์ถํ  ๋ด์ฉใ
         | 
| 562 | 
            +
            1. ๋น์ฃผ์ผ ์ปจ์
         | 
| 563 | 
            +
               - ์ ์ฒด์ ์ธ ๋ฃฉ์คํ
         | 
| 564 | 
            +
               - ์๊ฐ๊ณผ ํค
         | 
| 565 | 
            +
               - ์ฐธ์กฐ ์ํ/์ด๋ฏธ์ง
         | 
| 566 | 
            +
               
         | 
| 567 | 
            +
            2. ํต์ฌ ๋น์ฃผ์ผ ์ฌ (5๊ฐ)
         | 
| 568 | 
            +
               - ์คํ๋ ์ด๋ฏธ์ง
         | 
| 569 | 
            +
               - ์ฃผ์ ์ก์
/๊ฐ์  ์ฌ
         | 
| 570 | 
            +
               - ํด๋ผ์ด๋งฅ์ค ๋น์ฃผ์ผ
         | 
| 571 | 
            +
               - ์๋ฉ ์ด๋ฏธ์ง
         | 
| 572 | 
            +
               
         | 
| 573 | 
            +
            3. ์นด๋ฉ๋ผ ์คํ์ผ
         | 
| 574 | 
            +
               - ์ฃผ์ ์ท ๊ตฌ์ฑ
         | 
| 575 | 
            +
               - ์์ง์๊ณผ ์ต๊ธ
         | 
| 576 | 
            +
               - ํน์ ์ดฌ์ ๊ธฐ๋ฒ
         | 
| 577 | 
            +
               
         | 
| 578 | 
            +
            4. ์ฌ์ด๋ ๋์์ธ
         | 
| 579 | 
            +
               - ์์
 ์คํ์ผ
         | 
| 580 | 
            +
               - ํต์ฌ ์ฌ์ด๋ ๋ชจํฐํ
         | 
| 581 | 
            +
               - ์นจ๋ฌต์ ํ์ฉ
         | 
| 582 | 
            +
             | 
| 583 | 
            +
            โ ๏ธ ์๋ณธ์ ๋ถ์๊ธฐ์ ํค์ ์๊ฐ์ ์ผ๋ก ๊ตฌํํ์ธ์."""
         | 
| 584 | 
            +
             | 
| 585 | 
            +
                    elif role == "๋นํ๊ฐ":
         | 
| 586 | 
            +
                        return base_prompt + f"""
         | 
| 587 | 
            +
            ใ๋นํ๊ฐ๋ก์ ๊ฒํ ํ  ๋ด์ฉใ
         | 
| 588 | 
            +
            1. ์๋ณธ ์ถฉ์ค๋ ํ๊ฐ (40์ )
         | 
| 589 | 
            +
               - ํต์ฌ ์์ฒญ์ฌํญ ๋ฐ์๋
         | 
| 590 | 
            +
               - ์์ ๋ณ๊ฒฝ ์ฌํญ ์ง์ 
         | 
| 591 | 
            +
               
         | 
| 592 | 
            +
            2. ์คํ ๋ฆฌ ์์ฑ๋ (20์ )
         | 
| 593 | 
            +
               - ๋
ผ๋ฆฌ์  ์ผ๊ด์ฑ
         | 
| 594 | 
            +
               - ๊ฐ์ ์  ํธ์๋ ฅ
         | 
| 595 | 
            +
               - ํ์ด์ฑ๊ณผ ๋ฆฌ๋ฌ
         | 
| 596 | 
            +
               
         | 
| 597 | 
            +
            3. ์บ๋ฆญํฐ ํ๊ฐ (20์ )
         | 
| 598 | 
            +
               - ์
์ฒด์ฑ๊ณผ ๋งค๋ ฅ
         | 
| 599 | 
            +
               - ์ฑ์ฅ ์ํฌ
         | 
| 600 | 
            +
               - ๊ด๊ณ ์ญํ
         | 
| 601 | 
            +
               
         | 
| 602 | 
            +
            4. ์์
์ฑ๊ณผ ์์ ์ฑ (20์ )
         | 
| 603 | 
            +
               - ์์ฅ ์ดํ
         | 
| 604 | 
            +
               - ๋
์ฐฝ์ฑ
         | 
| 605 | 
            +
               - ์ ์ ๊ฐ๋ฅ์ฑ
         | 
| 606 | 
            +
               
         | 
| 607 | 
            +
            5. ๊ตฌ์ฒด์  ๊ฐ์  ์ ์
         | 
| 608 | 
            +
               - ์ฐ์ ์์๋ณ ๊ฐ์ ์ 
         | 
| 609 | 
            +
               - ์คํ ๊ฐ๋ฅํ ํด๊ฒฐ์ฑ
         | 
| 610 | 
            +
             | 
| 611 | 
            +
            ์ด์ : /100์ 
         | 
| 612 | 
            +
            โ ๏ธ ๊ฑด์ค์ ์ด๋ฉด์๋ ์ ์งํ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ธ์."""
         | 
| 613 | 
            +
             | 
| 614 | 
            +
                    elif role == "ํธ์ง์":
         | 
| 615 | 
            +
                        return base_prompt + f"""
         | 
| 616 | 
            +
            ใํธ์ง์๋ก์ ์กฐ์ ํ  ๋ด์ฉใ
         | 
| 617 | 
            +
            1. ํ์ด์ฑ ๋ถ์
         | 
| 618 | 
            +
               - ๊ฐ ์ฌ์ ๊ธธ์ด ์ ์ ์ฑ
         | 
| 619 | 
            +
               - ๊ธด์ฅ๊ณผ ์ด์์ ๋ฆฌ๋ฌ
         | 
| 620 | 
            +
               - ๋ถํ์ํ ๋ถ๋ถ ์๋ณ
         | 
| 621 | 
            +
               
         | 
| 622 | 
            +
            2. ๊ตฌ์กฐ ์ต์ ํ
         | 
| 623 | 
            +
               - ์ฌ ์์ ์กฐ์  ์ ์
         | 
| 624 | 
            +
               - ์ ํ์ ์์ฐ์ค๋ฌ์
         | 
| 625 | 
            +
               - ์ ๋ณด ๊ณต๊ฐ ํ์ด๋ฐ
         | 
| 626 | 
            +
               
         | 
| 627 | 
            +
            3. ํธ๋ฆฌ๋ฐ ์ ์
         | 
| 628 | 
            +
               - ์ญ์  ๊ฐ๋ฅํ ์ฌ/๋์ฌ
         | 
| 629 | 
            +
               - ์์ถ ๊ฐ๋ฅํ ๋ถ๋ถ
         | 
| 630 | 
            +
               - ์ค๋ณต ์ ๊ฑฐ
         | 
| 631 | 
            +
               
         | 
| 632 | 
            +
            4. ์ํฉํธ ๊ฐํ
         | 
| 633 | 
            +
               - ํด๋ผ์ด๋งฅ์ค ๋น๋์
         | 
| 634 | 
            +
               - ๊ฐ์ ์  ๋นํธ ๊ฐํ
         | 
| 635 | 
            +
             | 
| 636 | 
            +
            โ ๏ธ ์๋ณธ ์คํ ๋ฆฌ๋ฅผ ๋ ํ์ดํธํ๊ณ  ์ํฉํธ์๊ฒ ๋ง๋์ธ์."""
         | 
| 637 | 
            +
             | 
| 638 | 
            +
                    elif role == "๋ํ์ ๋ฌธ๊ฐ":
         | 
| 639 | 
            +
                        return base_prompt + f"""
         | 
| 640 | 
            +
            ใ๋ํ ์ ๋ฌธ๊ฐ๋ก์ ๊ฐ์ ํ  ๋ด์ฉใ
         | 
| 641 | 
            +
            1. ์บ๋ฆญํฐ๋ณ ๊ณ ์  ํ๋ฒ
         | 
| 642 | 
            +
               - ์ดํ ์์ค๊ณผ ์คํ์ผ
         | 
| 643 | 
            +
               - ๋ง๋ฒ๋ฆ๊ณผ ํจํด
         | 
| 644 | 
            +
               - ๊ฐ์  ํํ ๋ฐฉ์
         | 
| 645 | 
            +
               
         | 
| 646 | 
            +
            2. ์๋ธํ
์คํธ ๊ฐํ
         | 
| 647 | 
            +
               - ํ๋ฉด ๋์ฌ vs ์ง์ง ์๋ฏธ
         | 
| 648 | 
            +
               - ์นจ๋ฌต๊ณผ ํ๊ฐ
         | 
| 649 | 
            +
               - ๋น์ธ์ด์  ์ํต
         | 
| 650 | 
            +
               
         | 
| 651 | 
            +
            3. ํต์ฌ ๋์ฌ ์ฐฝ์กฐ
         | 
| 652 | 
            +
               - ๋ช
๋์ฌ 5๊ฐ
         | 
| 653 | 
            +
               - ์บ๋ฆญํฐ ์ ์ฒด์ฑ ๋๋ฌ๋ด๊ธฐ
         | 
| 654 | 
            +
               - ํ
๋ง ์ ๋ฌ ๋์ฌ
         | 
| 655 | 
            +
               
         | 
| 656 | 
            +
            4. ๋ํ ๋ฆฌ๋ฌ
         | 
| 657 | 
            +
               - ์์ฐ์ค๋ฌ์ด ์ฃผ๊ณ ๋ฐ๊ธฐ
         | 
| 658 | 
            +
               - ๊ธด์ฅ ๊ณ ์กฐ ํจํด
         | 
| 659 | 
            +
               - ์ ๋จธ์ ์ํธ
         | 
| 660 | 
            +
             | 
| 661 | 
            +
            โ ๏ธ ๊ฐ ์บ๋ฆญํฐ์ ๋ชฉ์๋ฆฌ๋ฅผ ๋๋ ทํ๊ฒ ๊ตฌ๋ถํ์ธ์."""
         | 
| 662 | 
            +
             | 
| 663 | 
            +
                    elif role == "์ฅ๋ฅด์ ๋ฌธ๊ฐ":
         | 
| 664 | 
            +
                        genre_template = GENRE_TEMPLATES.get(genre, GENRE_TEMPLATES["๋๋ผ๋ง"])
         | 
| 665 | 
            +
                        return base_prompt + f"""
         | 
| 666 | 
            +
            ใ{genre} ์ฅ๋ฅด ์ ๋ฌธ๊ฐ๋ก์ ์ต์ ํํ  ๋ด์ฉใ
         | 
| 667 | 
            +
            1. ์ฅ๋ฅด ํ์ ์์ ์ฒดํฌ
         | 
| 668 | 
            +
               - ํต์ฌ ์์: {', '.join(genre_template['key_elements'])}
         | 
| 669 | 
            +
               - ๊ตฌ์กฐ ๋นํธ: {', '.join(genre_template['structure_beats'])}
         | 
| 670 | 
            +
               
         | 
| 671 | 
            +
            2. ์ฅ๋ฅด ๊ด์ต ์ถฉ์กฑ๋
         | 
| 672 | 
            +
               - ๊ด๊ฐ ๊ธฐ๋ ์ถฉ์กฑ
         | 
| 673 | 
            +
               - ํด๋ฆฌ์
ฐ ํ์ฉ๊ณผ ์ ๋ณต
         | 
| 674 | 
            +
               - ์ฅ๋ฅด ํน์  ์ฅ์น
         | 
| 675 | 
            +
               
         | 
| 676 | 
            +
            3. ํค๊ณผ ๋ถ์๊ธฐ
         | 
| 677 | 
            +
               - ํ์ด์ฑ: {genre_template['pacing']}
         | 
| 678 | 
            +
               - ์ฌ ๊ธธ์ด: {genre_template['scene_length']}
         | 
| 679 | 
            +
               - ๋์ฌ ๋น์จ: {genre_template['dialogue_ratio']}
         | 
| 680 | 
            +
               
         | 
| 681 | 
            +
            4. ์ฅ๋ฅด ํนํ ๊ฐํ
         | 
| 682 | 
            +
               - ๋ถ์กฑํ ์์ ์ถ๊ฐ
         | 
| 683 | 
            +
               - ๊ณผ๋ํ ๋ถ๋ถ ์กฐ์ 
         | 
| 684 | 
            +
               - ์ฅ๋ฅด ์ ์ฒด์ฑ ๋ช
ํํ
         | 
| 685 | 
            +
             | 
| 686 | 
            +
            โ ๏ธ {genre} ์ฅ๋ฅด์ ์ ์๋ฅผ ์๋ฒฝํ๊ฒ ๊ตฌํํ์ธ์."""
         | 
| 687 | 
            +
             | 
| 688 | 
            +
                    return base_prompt
         | 
| 689 | 
            +
             | 
| 690 | 
            +
                def _summarize_previous_content(self, content: Dict) -> str:
         | 
| 691 | 
            +
                    """์ด์  ๋ด์ฉ ์์ฝ"""
         | 
| 692 | 
            +
                    summary = ""
         | 
| 693 | 
            +
                    for key, value in content.items():
         | 
| 694 | 
            +
                        if value:
         | 
| 695 | 
            +
                            summary += f"\n[{key}]\n{value[:300]}...\n"
         | 
| 696 | 
            +
                    return summary if summary else "์ฒซ ์์
์
๋๋ค."
         | 
| 697 | 
            +
             | 
| 698 | 
             
                def generate_planning(self, query: str, screenplay_type: str, genre: str, 
         | 
| 699 | 
            +
                                     progress_callback=None) -> Generator[Tuple[str, float, Dict, List], None, None]:
         | 
| 700 | 
            +
                    """๋ค์ค ์ ๋ฌธ๊ฐ ํ์
 ๊ธฐํ์ ์์ฑ"""
         | 
| 701 | 
             
                    try:
         | 
| 702 | 
            +
                        self.original_query = query
         | 
| 703 | 
             
                        self.current_session_id = ScreenplayDatabase.create_session(query, screenplay_type, genre)
         | 
| 704 |  | 
| 705 | 
             
                        planning_content = {}
         | 
| 706 | 
            +
                        self.accumulated_content = {}
         | 
| 707 | 
            +
                        self.expert_feedbacks = []
         | 
| 708 | 
             
                        total_stages = len(PLANNING_STAGES)
         | 
| 709 |  | 
| 710 | 
            +
                        for idx, (role, stage_key, stage_desc) in enumerate(PLANNING_STAGES):
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 711 | 
             
                            progress = (idx / total_stages) * 100
         | 
| 712 |  | 
| 713 | 
            +
                            yield f"๐ {stage_desc} ์งํ ์ค...", progress, planning_content, self.expert_feedbacks
         | 
| 714 |  | 
| 715 | 
            +
                            # ์ ๋ฌธ๊ฐ๋ณ ํ๋กฌํํธ ์์ฑ
         | 
| 716 | 
            +
                            prompt = self.get_expert_prompt(
         | 
| 717 | 
            +
                                role, stage_key, query, 
         | 
| 718 | 
            +
                                self.accumulated_content, genre, screenplay_type
         | 
| 719 | 
            +
                            )
         | 
|  | |
| 720 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 721 | 
             
                            # LLM ํธ์ถ
         | 
| 722 | 
             
                            messages = [
         | 
| 723 | 
            +
                                {"role": "system", "content": f"""๋น์ ์ {role}์
๋๋ค.
         | 
| 724 | 
            +
            {EXPERT_ROLES[role]['description']}
         | 
| 725 | 
            +
            ์ ๋ฌธ ๋ถ์ผ: {', '.join(EXPERT_ROLES[role]['focus'])}
         | 
| 726 | 
            +
            ์๋ณธ ์์ฒญ์ ์ ๋์ ์ผ๋ก ์ค์ํ๋ฉด์ ์ ๋ฌธ์ฑ์ ๋ฐํํ์ธ์."""},
         | 
| 727 | 
             
                                {"role": "user", "content": prompt}
         | 
| 728 | 
             
                            ]
         | 
| 729 |  | 
| 730 | 
             
                            content = ""
         | 
| 731 | 
             
                            for chunk in self.call_llm_streaming(messages):
         | 
| 732 | 
             
                                content += chunk
         | 
| 733 | 
            +
                                planning_content[f"{role}_{stage_key}"] = content
         | 
| 734 | 
            +
                                yield f"โ๏ธ {stage_desc} ์์ฑ ์ค...", progress, planning_content, self.expert_feedbacks
         | 
| 735 | 
            +
                            
         | 
| 736 | 
            +
                            # ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ ์ ์ฅ
         | 
| 737 | 
            +
                            feedback = ExpertFeedback(
         | 
| 738 | 
            +
                                role=role,
         | 
| 739 | 
            +
                                stage=stage_key,
         | 
| 740 | 
            +
                                feedback=content[:500],
         | 
| 741 | 
            +
                                suggestions=self._extract_suggestions(content),
         | 
| 742 | 
            +
                                score=self._calculate_score(content, query)
         | 
| 743 | 
            +
                            )
         | 
| 744 | 
            +
                            self.expert_feedbacks.append(feedback)
         | 
| 745 | 
            +
                            
         | 
| 746 | 
            +
                            # DB์ ํผ๋๋ฐฑ ์ ์ฅ
         | 
| 747 | 
            +
                            ScreenplayDatabase.save_expert_feedback(
         | 
| 748 | 
            +
                                self.current_session_id, role, stage_key,
         | 
| 749 | 
            +
                                feedback.feedback, feedback.suggestions, feedback.score
         | 
| 750 | 
            +
                            )
         | 
| 751 |  | 
| 752 | 
             
                            # ๋์  ๋ด์ฉ ์ ์ฅ
         | 
| 753 | 
            +
                            self.accumulated_content[f"{role}_{stage_key}"] = content
         | 
| 754 | 
            +
                            time.sleep(0.5)
         | 
| 755 |  | 
| 756 | 
             
                        # ์ต์ข
 ๊ธฐํ์ ์ ์ฅ
         | 
| 757 | 
             
                        ScreenplayDatabase.save_planning_data(self.current_session_id, planning_content)
         | 
| 758 | 
            +
                        yield "โ
 ์ ๋ฌธ๊ฐ ํ์
 ๊ธฐํ์ ์์ฑ!", 100, planning_content, self.expert_feedbacks
         | 
| 759 |  | 
| 760 | 
             
                    except Exception as e:
         | 
| 761 | 
            +
                        yield f"โ ์ค๋ฅ ๋ฐ์: {str(e)}", 0, {}, []
         | 
| 762 |  | 
| 763 | 
             
                def generate_screenplay(self, session_id: str, planning_data: Dict,
         | 
| 764 | 
            +
                                      progress_callback=None) -> Generator[Tuple[str, float, str, List], None, None]:
         | 
| 765 | 
            +
                    """๋ค์ค ์ ๋ฌธ๊ฐ ํ์
 ์๋๋ฆฌ์ค ์์ฑ"""
         | 
| 766 | 
             
                    try:
         | 
| 767 | 
             
                        total_stages = len(WRITING_STAGES)
         | 
| 768 | 
             
                        screenplay_content = ""
         | 
| 769 | 
             
                        act_contents = {"1๋ง": "", "2๋งA": "", "2๋งB": "", "3๋ง": ""}
         | 
| 770 | 
            +
                        self.expert_feedbacks = []
         | 
| 771 |  | 
| 772 | 
            +
                        for idx, (role, stage_name, stage_desc) in enumerate(WRITING_STAGES):
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 773 | 
             
                            progress = (idx / total_stages) * 100
         | 
| 774 |  | 
| 775 | 
            +
                            yield f"๐ {stage_desc} ์งํ ์ค...", progress, screenplay_content, self.expert_feedbacks
         | 
| 776 |  | 
| 777 | 
            +
                            # ํ์ฌ ๋ง ๊ฒฐ์ 
         | 
| 778 | 
             
                            current_act = ""
         | 
| 779 | 
             
                            if "1๋ง" in stage_name:
         | 
| 780 | 
             
                                current_act = "1๋ง"
         | 
|  | |
| 785 | 
             
                            elif "3๋ง" in stage_name:
         | 
| 786 | 
             
                                current_act = "3๋ง"
         | 
| 787 |  | 
| 788 | 
            +
                            # ์ญํ ๋ณ ํ๋กฌํํธ ์์ฑ
         | 
| 789 | 
            +
                            if role == "์คํ ๋ฆฌ์๊ฐ":
         | 
| 790 | 
            +
                                prompt = self._create_writer_prompt(
         | 
| 791 | 
            +
                                    current_act, planning_data, act_contents, stage_name
         | 
|  | |
| 792 | 
             
                                )
         | 
| 793 | 
            +
                            elif role == "ํธ์ง์":
         | 
| 794 | 
            +
                                prompt = self._create_editor_prompt(
         | 
| 795 | 
            +
                                    current_act, act_contents[current_act] if current_act else screenplay_content
         | 
|  | |
| 796 | 
             
                                )
         | 
| 797 | 
            +
                            elif role == "๊ฐ๋
":
         | 
| 798 | 
            +
                                prompt = self._create_director_prompt(
         | 
| 799 | 
            +
                                    current_act, act_contents[current_act] if current_act else screenplay_content
         | 
| 800 | 
            +
                                )
         | 
| 801 | 
            +
                            elif role == "๋ํ์ ๋ฌธ๊ฐ":
         | 
| 802 | 
            +
                                prompt = self._create_dialogue_prompt(
         | 
| 803 | 
            +
                                    current_act, act_contents[current_act] if current_act else screenplay_content
         | 
| 804 | 
             
                                )
         | 
| 805 | 
            +
                            elif role == "๋นํ๊ฐ":
         | 
| 806 | 
            +
                                prompt = self._create_critic_prompt(
         | 
| 807 | 
            +
                                    current_act, act_contents[current_act] if current_act else screenplay_content,
         | 
| 808 | 
             
                                    self.original_query
         | 
| 809 | 
             
                                )
         | 
| 810 | 
            +
                            elif role == "์ฅ๋ฅด์ ๋ฌธ๊ฐ":
         | 
| 811 | 
            +
                                prompt = self._create_genre_expert_prompt(
         | 
| 812 | 
            +
                                    screenplay_content, planning_data
         | 
| 813 | 
            +
                                )
         | 
| 814 | 
             
                            else:
         | 
| 815 | 
             
                                continue
         | 
| 816 |  | 
| 817 | 
             
                            # LLM ํธ์ถ
         | 
| 818 | 
            +
                            expert = EXPERT_ROLES.get(role, EXPERT_ROLES["์คํ ๋ฆฌ์๊ฐ"])
         | 
| 819 | 
             
                            messages = [
         | 
| 820 | 
            +
                                {"role": "system", "content": f"""๋น์ ์ {role}์
๋๋ค.
         | 
| 821 | 
            +
            {expert['description']}
         | 
| 822 | 
            +
            ์๋ณธ ์์ฒญ: {self.original_query}
         | 
| 823 | 
            +
            ํ์ค ์๋๋ฆฌ์ค ํฌ๋งท์ ์ค์ํ์ธ์."""},
         | 
| 824 | 
             
                                {"role": "user", "content": prompt}
         | 
| 825 | 
             
                            ]
         | 
| 826 |  | 
|  | |
| 828 | 
             
                            for chunk in self.call_llm_streaming(messages, max_tokens=15000):
         | 
| 829 | 
             
                                content += chunk
         | 
| 830 |  | 
| 831 | 
            +
                                # ์์ฑ ๋จ๊ณ์์๋ง ๋ง ๋ด์ฉ ์
๋ฐ์ดํธ
         | 
| 832 | 
             
                                if current_act and "์์ฑ" in stage_name:
         | 
| 833 | 
             
                                    act_contents[current_act] = content
         | 
| 834 | 
            +
                                    screenplay_content = "\n\n".join([
         | 
| 835 | 
            +
                                        act_contents[act] for act in ["1๋ง", "2๋งA", "2๋งB", "3๋ง"] 
         | 
| 836 | 
            +
                                        if act_contents[act]
         | 
| 837 | 
            +
                                    ])
         | 
| 838 |  | 
| 839 | 
            +
                                yield f"โ๏ธ {stage_desc} ์์ฑ ์ค...", progress, screenplay_content, self.expert_feedbacks
         | 
| 840 | 
            +
                            
         | 
| 841 | 
            +
                            # ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ ์์ฑ
         | 
| 842 | 
            +
                            feedback = ExpertFeedback(
         | 
| 843 | 
            +
                                role=role,
         | 
| 844 | 
            +
                                stage=stage_name,
         | 
| 845 | 
            +
                                feedback=f"{role}๊ฐ {stage_name}์ ๊ฒํ /์์
ํ์ต๋๋ค.",
         | 
| 846 | 
            +
                                suggestions=[],
         | 
| 847 | 
            +
                                score=85.0
         | 
| 848 | 
            +
                            )
         | 
| 849 | 
            +
                            self.expert_feedbacks.append(feedback)
         | 
| 850 |  | 
| 851 | 
             
                            time.sleep(0.5)
         | 
| 852 |  | 
| 853 | 
             
                        # ์ต์ข
 ์ ์ฅ
         | 
| 854 | 
            +
                        title = self._extract_title(planning_data)
         | 
| 855 | 
            +
                        logline = self._extract_logline(planning_data)
         | 
| 856 |  | 
| 857 | 
             
                        ScreenplayDatabase.save_screenplay_content(session_id, screenplay_content, title, logline)
         | 
| 858 | 
            +
                        yield "โ
 ์ ๋ฌธ๊ฐ ํ์
 ์๋๋ฆฌ์ค ์์ฑ!", 100, screenplay_content, self.expert_feedbacks
         | 
| 859 |  | 
| 860 | 
             
                    except Exception as e:
         | 
| 861 | 
            +
                        yield f"โ ์ค๋ฅ ๋ฐ์: {str(e)}", 0, "", []
         | 
| 862 |  | 
| 863 | 
            +
                def _create_writer_prompt(self, act: str, planning_data: Dict, 
         | 
| 864 | 
            +
                                         previous_acts: Dict, stage_name: str) -> str:
         | 
| 865 | 
            +
                    """์คํ ๋ฆฌ ์๊ฐ ํ๋กฌํํธ"""
         | 
| 866 |  | 
| 867 | 
            +
                    if "์ด๊ณ " in stage_name:
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 868 | 
             
                        min_lines = 800
         | 
| 869 | 
            +
                    elif "์์ฑ" in stage_name:
         | 
|  | |
|  | |
|  | |
|  | |
| 870 | 
             
                        min_lines = 1200
         | 
| 871 | 
            +
                    else:
         | 
| 872 | 
            +
                        min_lines = 1000
         | 
| 873 |  | 
| 874 | 
            +
                    return f"""ใ{act} ์์ฑใ
         | 
| 875 | 
            +
            ๋ชฉํ ๋ถ๋: {min_lines}์ค ์ด์
         | 
|  | |
|  | |
|  | |
|  | |
| 876 |  | 
| 877 | 
            +
            ใ๊ธฐํ์ ํต์ฌใ
         | 
| 878 | 
            +
            {self._extract_planning_core(planning_data)}
         | 
|  | |
| 879 |  | 
| 880 | 
            +
            ใ์ด์  ๋งใ
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 881 | 
             
            {previous_acts if previous_acts else "์ฒซ ๋ง์
๋๋ค"}
         | 
| 882 |  | 
| 883 | 
            +
            ใ์์ฑ ์๊ตฌ์ฌํญใ
         | 
| 884 | 
            +
            1. ํ์ค ์๋๋ฆฌ์ค ํฌ๋งท
         | 
| 885 | 
            +
            2. ์ถฉ๋ถํ ์ฌ ๋ถ๋ (๊ฐ 5-7ํ์ด์ง)
         | 
| 886 | 
            +
            3. ์๊ฐ์  ์ก์
 ๋ฌ์ฌ
         | 
| 887 | 
            +
            4. ์์ฐ์ค๋ฌ์ด ๋ํ
         | 
| 888 | 
            +
            5. ์๋ณธ ์คํ ๋ฆฌ ์ถฉ์ค
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 889 |  | 
| 890 | 
            +
            ๋ฐ๋์ {min_lines}์ค ์ด์ ์์ฑํ์ธ์."""
         | 
|  | |
|  | |
| 891 |  | 
| 892 | 
            +
                def _create_editor_prompt(self, act: str, content: str) -> str:
         | 
| 893 | 
            +
                    """ํธ์ง์ ํ๋กฌํํธ"""
         | 
| 894 | 
            +
                    return f"""ใ{act} ํธ์งใ
         | 
|  | |
| 895 |  | 
| 896 | 
            +
            ํ์ฌ ๋ด์ฉ:
         | 
| 897 | 
            +
            {content[:2000]}...
         | 
| 898 |  | 
| 899 | 
            +
            ใํธ์ง ํฌ์ธํธใ
         | 
| 900 | 
            +
            1. ํ์ด์ฑ ์กฐ์ 
         | 
| 901 | 
            +
               - ๋๋ฆฐ ๋ถ๋ถ ๊ฐ์
         | 
| 902 | 
            +
               - ๊ธํ ๋ถ๋ถ ์ํ
         | 
| 903 | 
            +
               
         | 
| 904 | 
            +
            2. ๋ถํ์ํ ๋ถ๋ถ ์ ๊ฑฐ
         | 
| 905 | 
            +
               - ์ค๋ณต ๋์ฌ/์ก์
         | 
| 906 | 
            +
               - ๊ณผ๋ํ ์ค๋ช
         | 
| 907 | 
            +
               
         | 
| 908 | 
            +
            3. ์ฌ ์ ํ ๊ฐ์ 
         | 
| 909 | 
            +
               - ์์ฐ์ค๋ฌ์ด ์ฐ๊ฒฐ
         | 
| 910 | 
            +
               - ์๊ฐ/๊ณต๊ฐ ์ด๋
         | 
| 911 | 
            +
               
         | 
| 912 | 
            +
            4. ๊ธด์ฅ๊ฐ ์กฐ์ 
         | 
| 913 | 
            +
               - ํด๋ผ์ด๋งฅ์ค ๋น๋์
         | 
| 914 | 
            +
               - ์๊ธ ์กฐ์ 
         | 
| 915 | 
            +
             | 
| 916 | 
            +
            ๊ตฌ์ฒด์ ์ธ ํธ์ง ์ ์์ ํ์ธ์."""
         | 
| 917 | 
            +
             | 
| 918 | 
            +
                def _create_director_prompt(self, act: str, content: str) -> str:
         | 
| 919 | 
            +
                    """๊ฐ๋
 ํ๋กฌํํธ"""
         | 
| 920 | 
            +
                    return f"""ใ{act} ์ฐ์ถ ๊ฐํใ
         | 
| 921 | 
            +
             | 
| 922 | 
            +
            ํ์ฌ ์๋๋ฆฌ์ค:
         | 
| 923 | 
             
            {content[:2000]}...
         | 
| 924 |  | 
| 925 | 
            +
            ใ๋น์ฃผ์ผ ๊ฐํ ํฌ์ธํธใ
         | 
| 926 | 
            +
            1. ์คํ๋ ์ฌ ๋น์ฃผ์ผ
         | 
| 927 | 
            +
            2. ํต์ฌ ์ก์
 ์ํ์ค
         | 
| 928 | 
            +
            3. ๊ฐ์ ์  ํด๋ก์ฆ์
         | 
| 929 | 
            +
            4. ํ๊ฒฝ/๋ถ์๊ธฐ ๋ฌ์ฌ
         | 
| 930 | 
            +
            5. ์์ง์  ์ด๋ฏธ์ง
         | 
| 931 |  | 
| 932 | 
            +
            ๊ฐ ์ฌ์ ์๊ฐ์  ๋ํ
์ผ์ ์ถ๊ฐํ์ธ์."""
         | 
|  | |
|  | |
|  | |
| 933 |  | 
| 934 | 
            +
                def _create_dialogue_prompt(self, act: str, content: str) -> str:
         | 
| 935 | 
            +
                    """๋ํ ์ ๋ฌธ๊ฐ ํ๋กฌํํธ"""
         | 
| 936 | 
            +
                    return f"""ใ{act} ๋์ฌ ๊ฐ์ ใ
         | 
| 937 |  | 
| 938 | 
            +
            ํ์ฌ ๋์ฌ ๊ฒํ :
         | 
| 939 | 
            +
            {content[:2000]}...
         | 
|  | |
| 940 |  | 
| 941 | 
            +
            ใ๊ฐ์  ๋ฐฉํฅใ
         | 
| 942 | 
            +
            1. ์บ๋ฆญํฐ๋ณ ๋งํฌ ์ฐจ๋ณํ
         | 
| 943 | 
             
            2. ์๋ธํ
์คํธ ์ถ๊ฐ
         | 
| 944 | 
            +
            3. ๋ถํ์ํ ์ค๋ช
 ์ ๊ฑฐ
         | 
| 945 | 
            +
            4. ๊ฐ์ ์  ๋์์ค ๊ฐํ
         | 
| 946 | 
            +
            5. ๋ฆฌ๋ฌ๊ณผ ํ
ํฌ ์กฐ์ 
         | 
| 947 |  | 
| 948 | 
            +
            ๊ฐ์ ๋ ๋์ฌ ์์๋ฅผ ์ ์ํ์ธ์."""
         | 
|  | |
|  | |
| 949 |  | 
| 950 | 
            +
                def _create_critic_prompt(self, act: str, content: str, original_query: str) -> str:
         | 
| 951 | 
            +
                    """๋นํ๊ฐ ํ๋กฌํํธ"""
         | 
| 952 | 
            +
                    return f"""ใ{act} ์ข
ํฉ ๊ฒํ ใ
         | 
| 953 |  | 
| 954 | 
            +
            ์๋ณธ ์์ฒญ: {original_query}
         | 
| 955 | 
            +
             | 
| 956 | 
            +
            ํ์ฌ ๋ด์ฉ:
         | 
| 957 | 
            +
            {content[:2000]}...
         | 
| 958 |  | 
| 959 | 
             
            ใํ๊ฐ ํญ๋ชฉใ
         | 
| 960 | 
            +
            1. ์๋ณธ ์ถฉ์ค๋ (40์ )
         | 
| 961 | 
            +
            2. ์คํ ๋ฆฌ ์์ฑ๋ (20์ )
         | 
| 962 | 
            +
            3. ์บ๋ฆญํฐ ๋งค๋ ฅ (20์ )
         | 
| 963 | 
            +
            4. ๋์ฌ ํ์ง (10์ )
         | 
| 964 | 
            +
            5. ๋น์ฃผ์ผ ์ํฉํธ (10์ )
         | 
| 965 |  | 
| 966 | 
            +
            ์ด์ : /100
         | 
|  | |
|  | |
|  | |
| 967 |  | 
| 968 | 
            +
            ใ๊ฐ์  ํ์์ฌํญใ
         | 
| 969 | 
            +
            ๊ตฌ์ฒด์ ์ด๊ณ  ์คํ ๊ฐ๋ฅํ ์ ์์ ํ์ธ์."""
         | 
| 970 | 
            +
             | 
| 971 | 
            +
                def _create_genre_expert_prompt(self, screenplay: str, planning_data: Dict) -> str:
         | 
| 972 | 
            +
                    """์ฅ๋ฅด ์ ๋ฌธ๊ฐ ํ๋กฌํํธ"""
         | 
| 973 | 
            +
                    genre = planning_data.get("์ฅ๋ฅด", "๋๋ผ๋ง")
         | 
| 974 | 
            +
                    genre_template = GENRE_TEMPLATES.get(genre, GENRE_TEMPLATES["๋๋ผ๋ง"])
         | 
| 975 | 
            +
                    
         | 
| 976 | 
            +
                    return f"""ใ{genre} ์ฅ๋ฅด ์ต์ ํใ
         | 
| 977 | 
            +
             | 
| 978 | 
            +
            ใ์ฅ๋ฅด ํน์ฑใ
         | 
| 979 | 
            +
            - ํต์ฌ ์์: {', '.join(genre_template['key_elements'])}
         | 
| 980 | 
            +
            - ๊ตฌ์กฐ ๋นํธ: {', '.join(genre_template['structure_beats'])}
         | 
| 981 | 
            +
             | 
| 982 | 
            +
            ใ์ต์ ํ ํฌ์ธํธใ
         | 
| 983 | 
            +
            1. ์ฅ๋ฅด ๊ด์ต ์ถฉ์กฑ๋
         | 
| 984 | 
            +
            2. ํ์ด์ฑ ์กฐ์ 
         | 
| 985 | 
            +
            3. ํน์  ์์ ๊ฐํ
         | 
| 986 | 
            +
            4. ํค ์ผ๊ด์ฑ
         | 
| 987 | 
            +
             | 
| 988 | 
            +
            ์ฅ๋ฅด ํน์ฑ์ ๊ทน๋ํํ๋ ์ ์์ ํ์ธ์."""
         | 
| 989 | 
            +
             | 
| 990 | 
            +
                def _extract_suggestions(self, content: str) -> List[str]:
         | 
| 991 | 
            +
                    """๋ด์ฉ์์ ์ ์์ฌํญ ์ถ์ถ"""
         | 
| 992 | 
            +
                    suggestions = []
         | 
| 993 | 
            +
                    lines = content.split('\n')
         | 
| 994 | 
            +
                    for line in lines:
         | 
| 995 | 
            +
                        if any(keyword in line for keyword in ['์ ์', '๊ฐ์ ', '์ถ์ฒ', '๊ถ์ฅ']):
         | 
| 996 | 
            +
                            suggestions.append(line.strip())
         | 
| 997 | 
            +
                    return suggestions[:5]
         | 
| 998 | 
            +
             | 
| 999 | 
            +
                def _calculate_score(self, content: str, original_query: str) -> float:
         | 
| 1000 | 
            +
                    """์ถฉ์ค๋ ์ ์ ๊ณ์ฐ"""
         | 
| 1001 | 
            +
                    score = 70.0  # ๊ธฐ๋ณธ ์ ์
         | 
| 1002 | 
            +
                    
         | 
| 1003 | 
            +
                    # ์๋ณธ ํค์๋ ํฌํจ๋ ์ฒดํฌ
         | 
| 1004 | 
            +
                    keywords = original_query.split()
         | 
| 1005 | 
            +
                    matched = sum(1 for keyword in keywords if keyword in content)
         | 
| 1006 | 
            +
                    score += (matched / len(keywords)) * 20 if keywords else 0
         | 
| 1007 | 
            +
                    
         | 
| 1008 | 
            +
                    # ๊ธธ์ด ์ฒดํฌ
         | 
| 1009 | 
            +
                    if len(content) > 500:
         | 
| 1010 | 
            +
                        score += 10
         | 
| 1011 | 
            +
                    
         | 
| 1012 | 
            +
                    return min(score, 100.0)
         | 
| 1013 | 
            +
             | 
| 1014 | 
            +
                def _extract_title(self, planning_data: Dict) -> str:
         | 
| 1015 | 
            +
                    """๊ธฐํ์์์ ์ ๋ชฉ ์ถ์ถ"""
         | 
| 1016 | 
            +
                    for key, value in planning_data.items():
         | 
| 1017 | 
            +
                        if "์ ๋ชฉ" in value:
         | 
| 1018 | 
            +
                            match = re.search(r'์ ๋ชฉ[:\s]*([^\n]+)', value)
         | 
| 1019 | 
            +
                            if match:
         | 
| 1020 | 
            +
                                return match.group(1).strip()
         | 
| 1021 | 
            +
                    return "๋ฌด์ "
         | 
| 1022 | 
            +
             | 
| 1023 | 
            +
                def _extract_logline(self, planning_data: Dict) -> str:
         | 
| 1024 | 
            +
                    """๊ธฐํ์์์ ๋ก๊ทธ๋ผ์ธ ์ถ์ถ"""
         | 
| 1025 | 
            +
                    for key, value in planning_data.items():
         | 
| 1026 | 
            +
                        if "๋ก๊ทธ๋ผ์ธ" in value:
         | 
| 1027 | 
            +
                            match = re.search(r'๋ก๊ทธ๋ผ์ธ[:\s]*([^\n]+)', value)
         | 
| 1028 | 
            +
                            if match:
         | 
| 1029 | 
            +
                                return match.group(1).strip()
         | 
| 1030 | 
            +
                    return ""
         | 
| 1031 | 
            +
             | 
| 1032 | 
            +
                def _extract_planning_core(self, planning_data: Dict) -> str:
         | 
| 1033 | 
            +
                    """๊ธฐํ์ ํต์ฌ ๋ด์ฉ ์ถ์ถ"""
         | 
| 1034 | 
            +
                    core = ""
         | 
| 1035 | 
            +
                    for key, value in planning_data.items():
         | 
| 1036 | 
            +
                        if any(k in key for k in ["์คํ ๋ฆฌ", "์บ๋ฆญํฐ", "๊ตฌ์กฐ"]):
         | 
| 1037 | 
            +
                            core += f"\n[{key}]\n{value[:500]}...\n"
         | 
| 1038 | 
            +
                    return core
         | 
| 1039 |  | 
| 1040 | 
             
            # ์ ํธ๋ฆฌํฐ ํจ์
         | 
| 1041 | 
             
            def format_planning_display(planning_data: Dict) -> str:
         | 
|  | |
| 1043 | 
             
                if not planning_data:
         | 
| 1044 | 
             
                    return "๊ธฐํ์์ด ์์ง ์์ฑ๋์ง ์์์ต๋๋ค."
         | 
| 1045 |  | 
| 1046 | 
            +
                formatted = "# ๐ ์๋๋ฆฌ์ค ๊ธฐํ์ (์ ๋ฌธ๊ฐ ํ์
)\n\n"
         | 
| 1047 |  | 
| 1048 | 
            +
                for key, content in planning_data.items():
         | 
| 1049 | 
            +
                    role = key.split('_')[0] if '_' in key else key
         | 
| 1050 | 
            +
                    emoji = EXPERT_ROLES.get(role, {}).get("emoji", "๐")
         | 
| 1051 | 
            +
                    formatted += f"## {emoji} {key}\n\n"
         | 
| 1052 | 
             
                    formatted += content + "\n\n"
         | 
| 1053 | 
             
                    formatted += "---\n\n"
         | 
| 1054 |  | 
| 1055 | 
             
                return formatted
         | 
| 1056 |  | 
| 1057 | 
            +
            def format_expert_feedbacks(feedbacks: List[ExpertFeedback]) -> str:
         | 
| 1058 | 
            +
                """์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ ํ์"""
         | 
| 1059 | 
            +
                if not feedbacks:
         | 
| 1060 | 
            +
                    return ""
         | 
| 1061 | 
            +
                
         | 
| 1062 | 
            +
                formatted = "## ๐ฏ ์ ๋ฌธ๊ฐ ํ๊ฐ\n\n"
         | 
| 1063 | 
            +
                
         | 
| 1064 | 
            +
                for fb in feedbacks:
         | 
| 1065 | 
            +
                    emoji = EXPERT_ROLES.get(fb.role, {}).get("emoji", "๐")
         | 
| 1066 | 
            +
                    formatted += f"### {emoji} {fb.role}\n"
         | 
| 1067 | 
            +
                    formatted += f"**์ ์:** {fb.score:.1f}/100\n"
         | 
| 1068 | 
            +
                    formatted += f"**ํผ๋๋ฐฑ:** {fb.feedback[:200]}...\n"
         | 
| 1069 | 
            +
                    
         | 
| 1070 | 
            +
                    if fb.suggestions:
         | 
| 1071 | 
            +
                        formatted += "**์ ์์ฌํญ:**\n"
         | 
| 1072 | 
            +
                        for suggestion in fb.suggestions[:3]:
         | 
| 1073 | 
            +
                            formatted += f"- {suggestion}\n"
         | 
| 1074 | 
            +
                    
         | 
| 1075 | 
            +
                    formatted += "\n"
         | 
| 1076 | 
            +
                
         | 
| 1077 | 
            +
                return formatted
         | 
| 1078 | 
            +
             | 
| 1079 | 
             
            def format_screenplay_display(screenplay_text: str) -> str:
         | 
| 1080 | 
             
                """์๋๋ฆฌ์ค ํ์ ํฌ๋งท"""
         | 
| 1081 | 
             
                if not screenplay_text:
         | 
|  | |
| 1111 | 
             
                    '์ก์
': [
         | 
| 1112 | 
             
                        "์ํดํ ํน์์์์ด ๋ฉ์น๋ ๋ธ์ ๊ตฌํ๊ธฐ ์ํด ๋ค์ ํ์ฅ์ผ๋ก ๋ณต๊ทํ๋ค",
         | 
| 1113 | 
             
                        "ํ๋ฒํ ํ์๊ธฐ์ฌ๊ฐ ์ฐ์ฐํ ๊ตญ์  ํ
๋ฌ ์กฐ์ง์ ์๋ชจ์ ํ๋ง๋ฆฐ๋ค",
         | 
|  | |
| 1114 | 
             
                    ],
         | 
| 1115 | 
             
                    '์ค๋ฆด๋ฌ': [
         | 
| 1116 | 
             
                        "๊ธฐ์ต์ ์์ ๋จ์๊ฐ ์์ ์ด ์ฐ์์ด์ธ๋ฒ์ด๋ผ๋ ์ฆ๊ฑฐ๋ฅผ ๋ฐ๊ฒฌํ๋ค",
         | 
| 1117 | 
             
                        "์ค์ข
๋ ์์ด๋ฅผ ์ฐพ๋ ๊ณผ์ ์์ ๋ง์์ ๋์ฐํ ๋น๋ฐ์ด ๋๋ฌ๋๋ค",
         | 
|  | |
| 1118 | 
             
                    ],
         | 
| 1119 | 
             
                    '๋๋ผ๋ง': [
         | 
| 1120 | 
             
                        "๋ง๊ธฐ ์ ํ์๊ฐ ๋ง์ง๋ง ์์์ผ๋ก ๊ฐ์กฑ๊ณผ์ ํํด๋ฅผ ์๋ํ๋ค",
         | 
| 1121 | 
             
                        "์ฌ๊ฐ๋ฐ๋ก ์ฌ๋ผ์ง ๋๋ค๋ฅผ ์งํค๋ ค๋ ์ฃผ๋ฏผ๋ค์ ๋ง์ง๋ง ์ธ์",
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1122 | 
             
                    ],
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1123 | 
             
                }
         | 
| 1124 |  | 
| 1125 | 
             
                genre_concepts = concepts.get(genre, concepts['๋๋ผ๋ง'])
         | 
|  | |
| 1144 | 
             
                    text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
         | 
| 1145 | 
             
                }
         | 
| 1146 |  | 
| 1147 | 
            +
                .expert-panel {
         | 
| 1148 | 
            +
                    background: #f0f4f8;
         | 
| 1149 | 
            +
                    padding: 1rem;
         | 
| 1150 | 
             
                    border-radius: 10px;
         | 
| 1151 | 
             
                    margin: 1rem 0;
         | 
|  | |
|  | |
| 1152 | 
             
                }
         | 
| 1153 |  | 
| 1154 | 
            +
                .expert-badge {
         | 
| 1155 | 
            +
                    display: inline-block;
         | 
| 1156 | 
            +
                    padding: 0.3rem 0.8rem;
         | 
| 1157 | 
             
                    background: white;
         | 
| 1158 | 
            +
                    border-radius: 20px;
         | 
| 1159 | 
            +
                    margin: 0.2rem;
         | 
| 1160 | 
            +
                    font-size: 0.9rem;
         | 
|  | |
|  | |
| 1161 | 
             
                }
         | 
| 1162 |  | 
| 1163 | 
             
                .progress-bar {
         | 
|  | |
| 1174 | 
             
                    border-radius: 10px;
         | 
| 1175 | 
             
                    transition: width 0.3s ease;
         | 
| 1176 | 
             
                }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1177 | 
             
                """
         | 
| 1178 |  | 
| 1179 | 
            +
                with gr.Blocks(theme=gr.themes.Soft(), css=css, title="AI ์๋๋ฆฌ์ค ์๊ฐ - ์ ๋ฌธ๊ฐ ํ์
 ์์คํ
") as interface:
         | 
| 1180 | 
             
                    gr.HTML("""
         | 
| 1181 | 
             
                    <div class="main-header">
         | 
| 1182 | 
             
                        <h1 class="header-title">๐ฌ AI ์๋๋ฆฌ์ค ์๊ฐ</h1>
         | 
| 1183 | 
             
                        <p style="font-size: 1.2rem; opacity: 0.95;">
         | 
| 1184 | 
            +
                            8๋ช
์ ์ ๋ฌธ๊ฐ๊ฐ ํ์
ํ์ฌ ์๋ฒฝํ ์๋๋ฆฌ์ค๋ฅผ ์์ฑํฉ๋๋ค<br>
         | 
| 1185 | 
            +
                            ํ๋ก๋์, ๊ฐ๋
, ์๊ฐ, ๋นํ๊ฐ๊ฐ ํจ๊ป ๋ง๋๋ ์ ๋ฌธ ์๋๋ฆฌ์ค
         | 
| 1186 | 
             
                        </p>
         | 
| 1187 | 
             
                    </div>
         | 
| 1188 | 
             
                    """)
         | 
| 1189 |  | 
| 1190 | 
            +
                    # ์ ๋ฌธ๊ฐ ์๊ฐ
         | 
| 1191 | 
            +
                    gr.HTML("""
         | 
| 1192 | 
            +
                    <div class="expert-panel">
         | 
| 1193 | 
            +
                        <h3>๐ฅ ์ฐธ์ฌ ์ ๋ฌธ๊ฐ</h3>
         | 
| 1194 | 
            +
                        <span class="expert-badge">๐ฌ ํ๋ก๋์</span>
         | 
| 1195 | 
            +
                        <span class="expert-badge">๐ ์คํ ๋ฆฌ์๊ฐ</span>
         | 
| 1196 | 
            +
                        <span class="expert-badge">๐ฅ ์บ๋ฆญํฐ๋์์ด๋</span>
         | 
| 1197 | 
            +
                        <span class="expert-badge">๐ญ ๊ฐ๋
</span>
         | 
| 1198 | 
            +
                        <span class="expert-badge">๐ ๋นํ๊ฐ</span>
         | 
| 1199 | 
            +
                        <span class="expert-badge">โ๏ธ ํธ์ง์</span>
         | 
| 1200 | 
            +
                        <span class="expert-badge">๐ฌ ๋ํ์ ๋ฌธ๊ฐ</span>
         | 
| 1201 | 
            +
                        <span class="expert-badge">๐ฏ ์ฅ๋ฅด์ ๋ฌธ๊ฐ</span>
         | 
| 1202 | 
            +
                    </div>
         | 
| 1203 | 
            +
                    """)
         | 
| 1204 | 
            +
                    
         | 
| 1205 | 
             
                    current_session_id = gr.State(None)
         | 
| 1206 | 
             
                    current_planning_data = gr.State({})
         | 
| 1207 | 
            +
                    current_feedbacks = gr.State([])
         | 
| 1208 |  | 
| 1209 | 
             
                    with gr.Tabs():
         | 
| 1210 | 
             
                        with gr.Tab("๐ ์ ์๋๋ฆฌ์ค"):
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1211 | 
             
                            with gr.Row():
         | 
| 1212 | 
             
                                with gr.Column(scale=2):
         | 
| 1213 | 
             
                                    query_input = gr.Textbox(
         | 
| 1214 | 
            +
                                        label="๐ก ์๋๋ฆฌ์ค ์์ด๋์ด",
         | 
| 1215 | 
            +
                                        placeholder="""๊ตฌ์ฒด์ ์ธ ์คํ ๋ฆฌ๋ฅผ ์
๋ ฅํ์ธ์. ์์:
         | 
| 1216 | 
            +
            "2045๋
 ์์ธ, AI๊ฐ ์ธ๊ฐ ๊ฐ์ ์ ์๋ฒฝํ ๋ชจ๋ฐฉํ๋ ์๋. 
         | 
| 1217 | 
            +
            AI ์ค๋ฆฌํ์๊ฐ ์ฃฝ์ ์๋ด๋ฅผ ๋ณต์ ํ AI์ ์ด๋ค๊ฐ 
         | 
| 1218 | 
            +
            ์ง์ง ์๋ด๊ฐ ์ด์์๋ค๋ ์ฆ๊ฑฐ๋ฅผ ๋ฐ๊ฒฌํ๊ณ  ์ง์ค์ ์ฐพ์ ๋์ ๋ค."
         | 
| 1219 | 
            +
            """,
         | 
| 1220 | 
            +
                                        lines=6
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 1221 | 
             
                                    )
         | 
| 1222 |  | 
| 1223 | 
             
                                with gr.Column(scale=1):
         | 
|  | |
| 1236 | 
             
                            with gr.Row():
         | 
| 1237 | 
             
                                random_btn = gr.Button("๐ฒ ๋๋ค ์์ด๋์ด", scale=1)
         | 
| 1238 | 
             
                                clear_btn = gr.Button("๐๏ธ ์ด๊ธฐํ", scale=1)
         | 
| 1239 | 
            +
                                planning_btn = gr.Button("๐ ์ ๋ฌธ๊ฐ ๊ธฐํ ํ์", variant="primary", scale=2)
         | 
| 1240 |  | 
| 1241 | 
             
                            # ์งํ ์ํ
         | 
| 1242 | 
             
                            progress_bar = gr.HTML(
         | 
|  | |
| 1245 | 
             
                            status_text = gr.Textbox(
         | 
| 1246 | 
             
                                label="๐ ์งํ ์ํ",
         | 
| 1247 | 
             
                                interactive=False,
         | 
| 1248 | 
            +
                                value="์์ด๋์ด๋ฅผ ์
๋ ฅํ๊ณ  ์ ๋ฌธ๊ฐ ๊ธฐํ ํ์๋ฅผ ์์ํ์ธ์"
         | 
| 1249 | 
             
                            )
         | 
| 1250 |  | 
| 1251 | 
            +
                            # ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ
         | 
| 1252 | 
            +
                            with gr.Group():
         | 
| 1253 | 
            +
                                gr.Markdown("### ๐ฏ ์ ๋ฌธ๊ฐ ํผ๋๋ฐฑ")
         | 
| 1254 | 
            +
                                expert_feedback_display = gr.Markdown(
         | 
| 1255 | 
            +
                                    value="*์ ๋ฌธ๊ฐ๋ค์ ์๊ฒฌ์ด ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*"
         | 
| 1256 | 
            +
                                )
         | 
| 1257 | 
            +
                            
         | 
| 1258 | 
            +
                            # ๊ธฐํ์
         | 
| 1259 | 
             
                            with gr.Group():
         | 
| 1260 | 
             
                                gr.Markdown("### ๐ ์๋๋ฆฌ์ค ๊ธฐํ์")
         | 
| 1261 | 
             
                                planning_display = gr.Markdown(
         | 
| 1262 | 
            +
                                    value="*๊ธฐํ์์ด ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*"
         | 
|  | |
| 1263 | 
             
                                )
         | 
| 1264 |  | 
| 1265 | 
             
                                with gr.Row():
         | 
|  | |
| 1266 | 
             
                                    save_planning_btn = gr.Button("๐พ ๊ธฐํ์ ์ ์ฅ", scale=1)
         | 
| 1267 | 
            +
                                    generate_screenplay_btn = gr.Button("๐ฌ ์ ๋ฌธ๊ฐ ์๋๋ฆฌ์ค ์์ฑ", variant="primary", scale=2)
         | 
| 1268 |  | 
| 1269 | 
             
                            # ์๋๋ฆฌ์ค ์ถ๋ ฅ
         | 
| 1270 | 
             
                            with gr.Group():
         | 
| 1271 | 
             
                                gr.Markdown("### ๐ ์์ฑ๋ ์๋๋ฆฌ์ค")
         | 
| 1272 | 
             
                                screenplay_output = gr.Markdown(
         | 
| 1273 | 
            +
                                    value="*์๋๋ฆฌ์ค๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...*"
         | 
|  | |
| 1274 | 
             
                                )
         | 
| 1275 |  | 
| 1276 | 
            +
                                download_btn = gr.Button("๐พ ๋ค์ด๋ก๋ (TXT)")
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1277 |  | 
| 1278 | 
             
                    # ์ด๋ฒคํธ ํธ๋ค๋ฌ
         | 
| 1279 | 
             
                    def handle_planning(query, s_type, genre):
         | 
| 1280 | 
             
                        if not query:
         | 
| 1281 | 
            +
                            yield "", "โ ์์ด๋์ด๋ฅผ ์
๋ ฅํด์ฃผ์ธ์", "", "", {}
         | 
| 1282 | 
             
                            return
         | 
| 1283 |  | 
| 1284 | 
             
                        system = ScreenplayGenerationSystem()
         | 
| 1285 |  | 
| 1286 | 
            +
                        for status, progress, planning_data, feedbacks in system.generate_planning(query, s_type, genre):
         | 
| 1287 | 
             
                            progress_html = f'<div class="progress-bar"><div class="progress-fill" style="width: {progress}%"></div></div>'
         | 
| 1288 | 
             
                            planning_display = format_planning_display(planning_data)
         | 
| 1289 | 
            +
                            feedback_display = format_expert_feedbacks(feedbacks)
         | 
| 1290 |  | 
| 1291 | 
            +
                            yield progress_html, status, feedback_display, planning_display, planning_data
         | 
| 1292 |  | 
| 1293 | 
             
                    def handle_screenplay_generation(session_id, planning_data):
         | 
| 1294 | 
             
                        if not planning_data:
         | 
| 1295 | 
            +
                            yield "", "โ ๋จผ์  ๊ธฐํ์์ ์์ฑํด์ฃผ์ธ์", "", ""
         | 
| 1296 | 
             
                            return
         | 
| 1297 |  | 
| 1298 | 
             
                        system = ScreenplayGenerationSystem()
         | 
| 1299 |  | 
| 1300 | 
            +
                        for status, progress, screenplay, feedbacks in system.generate_screenplay(session_id, planning_data):
         | 
| 1301 | 
             
                            progress_html = f'<div class="progress-bar"><div class="progress-fill" style="width: {progress}%"></div></div>'
         | 
| 1302 | 
             
                            screenplay_display = format_screenplay_display(screenplay)
         | 
| 1303 | 
            +
                            feedback_display = format_expert_feedbacks(feedbacks)
         | 
| 1304 |  | 
| 1305 | 
            +
                            yield progress_html, status, feedback_display, screenplay_display
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1306 |  | 
| 1307 | 
             
                    # ์ด๋ฒคํธ ์ฐ๊ฒฐ
         | 
| 1308 | 
             
                    planning_btn.click(
         | 
| 1309 | 
             
                        fn=handle_planning,
         | 
| 1310 | 
             
                        inputs=[query_input, screenplay_type, genre_select],
         | 
| 1311 | 
            +
                        outputs=[progress_bar, status_text, expert_feedback_display, planning_display, current_planning_data]
         | 
| 1312 | 
             
                    )
         | 
| 1313 |  | 
| 1314 | 
             
                    generate_screenplay_btn.click(
         | 
| 1315 | 
             
                        fn=handle_screenplay_generation,
         | 
| 1316 | 
             
                        inputs=[current_session_id, current_planning_data],
         | 
| 1317 | 
            +
                        outputs=[progress_bar, status_text, expert_feedback_display, screenplay_output]
         | 
| 1318 | 
             
                    )
         | 
| 1319 |  | 
| 1320 | 
             
                    random_btn.click(
         | 
| 1321 | 
            +
                        fn=generate_random_concept,
         | 
| 1322 | 
             
                        inputs=[screenplay_type, genre_select],
         | 
| 1323 | 
             
                        outputs=[query_input]
         | 
| 1324 | 
             
                    )
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1325 |  | 
| 1326 | 
             
                return interface
         | 
| 1327 |  | 
| 1328 | 
             
            # ๋ฉ์ธ ์คํ
         | 
| 1329 | 
             
            if __name__ == "__main__":
         | 
| 1330 | 
             
                logger.info("=" * 60)
         | 
| 1331 | 
            +
                logger.info("AI ์๋๋ฆฌ์ค ์๊ฐ - ์ ๋ฌธ๊ฐ ํ์
 ์์คํ
")
         | 
| 1332 | 
            +
                logger.info("8๋ช
์ ์ ๋ฌธ๊ฐ๊ฐ ํ์
ํ์ฌ ์๋๋ฆฌ์ค๋ฅผ ์์ฑํฉ๋๋ค")
         | 
| 1333 | 
             
                logger.info("=" * 60)
         | 
| 1334 |  | 
| 1335 | 
             
                if not FIREWORKS_API_KEY or FIREWORKS_API_KEY == "dummy_token_for_testing":
         | 
| 1336 | 
             
                    logger.warning("โ ๏ธ FIREWORKS_API_KEY๋ฅผ ์ค์ ํด์ฃผ์ธ์!")
         | 
| 1337 | 
             
                    logger.warning("export FIREWORKS_API_KEY='your-api-key'")
         | 
| 1338 |  | 
| 1339 | 
            +
                # ์ ๋ฌธ๊ฐ ์ญํ  ์๊ฐ
         | 
| 1340 | 
            +
                logger.info("\n์ฐธ์ฌ ์ ๋ฌธ๊ฐ:")
         | 
| 1341 | 
            +
                for role, info in EXPERT_ROLES.items():
         | 
| 1342 | 
            +
                    logger.info(f"  {info['emoji']} {role}: {info['description']}")
         | 
| 1343 | 
            +
                
         | 
| 1344 | 
             
                ScreenplayDatabase.init_db()
         | 
| 1345 |  | 
| 1346 | 
             
                interface = create_interface()
         | 
