openfree commited on
Commit
f01f7da
ยท
verified ยท
1 Parent(s): 32c778b

Delete app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +0 -2033
app-backup.py DELETED
@@ -1,2033 +0,0 @@
1
- import gradio as gr
2
- import os
3
- import json
4
- import requests
5
- from datetime import datetime
6
- import time
7
- from typing import List, Dict, Any, Generator, Tuple, Optional, Set
8
- import logging
9
- import re
10
- import tempfile
11
- from pathlib import Path
12
- import sqlite3
13
- import hashlib
14
- import threading
15
- from contextlib import contextmanager
16
- from dataclasses import dataclass, field, asdict
17
- from collections import defaultdict
18
- import random
19
- from huggingface_hub import HfApi, upload_file, hf_hub_download
20
-
21
- # --- Logging setup ---
22
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
- logger = logging.getLogger(__name__)
24
-
25
- # --- Document export imports ---
26
- try:
27
- from docx import Document
28
- from docx.shared import Inches, Pt, RGBColor, Mm
29
- from docx.enum.text import WD_ALIGN_PARAGRAPH
30
- from docx.enum.style import WD_STYLE_TYPE
31
- from docx.oxml.ns import qn
32
- from docx.oxml import OxmlElement
33
- DOCX_AVAILABLE = True
34
- except ImportError:
35
- DOCX_AVAILABLE = False
36
- logger.warning("python-docx not installed. DOCX export will be disabled.")
37
-
38
- import io
39
-
40
- # --- Environment variables and constants ---
41
- FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN", "")
42
- BRAVE_SEARCH_API_KEY = os.getenv("BRAVE_SEARCH_API_KEY", "")
43
- API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
44
- MODEL_ID = "dep86pjolcjjnv8"
45
- DB_PATH = "webnovel_sessions_v2.db"
46
-
47
- # Target settings for web novel
48
- TARGET_EPISODES = 40 # 40ํ™” ์™„๊ฒฐ
49
- WORDS_PER_EPISODE = 500 # ๊ฐ ํ™”๋‹น 500 ๋‹จ์–ด
50
- TARGET_WORDS = TARGET_EPISODES * WORDS_PER_EPISODE # ์ด 20000 ๋‹จ์–ด
51
-
52
- # Web novel genres - from paste-2.txt
53
- WEBNOVEL_GENRES = {
54
- "๋กœ๋งจ์Šค": "Romance",
55
- "๋กœํŒ": "Romance Fantasy",
56
- "ํŒํƒ€์ง€": "Fantasy",
57
- "ํ˜„ํŒ": "Modern Fantasy",
58
- "๋ฌดํ˜‘": "Martial Arts",
59
- "๋ฏธ์Šคํ„ฐ๋ฆฌ": "Mystery",
60
- "๋ผ์ดํŠธ๋…ธ๋ฒจ": "Light Novel"
61
- }
62
-
63
- # Complete word list from paste.txt
64
- complete_word_list = {
65
- "์บ๋ฆญํ„ฐ": [
66
- # ์ผ์ƒ ์ง์—… (40๊ฐœ)
67
- "์˜์‚ฌ", "๊ฐ„ํ˜ธ์‚ฌ", "๊ต์‚ฌ", "ํ•™์ƒ", "ํšŒ์‚ฌ์›", "ํ”„๋กœ๊ทธ๋ž˜๋จธ", "๋””์ž์ด๋„ˆ", "์š”๋ฆฌ์‚ฌ", "๋ฐ”๋ฆฌ์Šคํƒ€", "๋ฏธ์šฉ์‚ฌ",
68
- "ํƒ์‹œ๊ธฐ์‚ฌ", "๋ฐฐ๋‹ฌ์›", "๊ฒฝ์ฐฐ๊ด€", "์†Œ๋ฐฉ๊ด€", "๊ตฐ์ธ", "ํŒ์‚ฌ", "๋ณ€ํ˜ธ์‚ฌ", "๊ธฐ์ž", "์ž‘๊ฐ€", "ํ™”๊ฐ€",
69
- "๊ฐ€์ˆ˜", "๋ฐฐ์šฐ", "์šด๋™์„ ์ˆ˜", "๋†๋ถ€", "์–ด๋ถ€", "์ƒ์ธ", "์€ํ–‰์›", "๋ถ€๋™์‚ฐ์ค‘๊ฐœ์ธ", "์ฒญ์†Œ๋ถ€", "๊ฒฝ๋น„์›",
70
- "์œ ํŠœ๋ฒ„", "๋ธ”๋กœ๊ฑฐ", "์‚ฌ์ง„์ž‘๊ฐ€", "๋ฒˆ์—ญ๊ฐ€", "์ˆ˜์˜์‚ฌ", "์•ฝ์‚ฌ", "๊ฑด์ถ•๊ฐ€", "๊ณผํ•™์ž", "์—”์ง€๋‹ˆ์–ด", "์กฐ์ข…์‚ฌ",
71
-
72
- # ํŒํƒ€์ง€/๋ชจํ—˜ (25๊ฐœ)
73
- "๊ธฐ์‚ฌ", "๋งˆ๋ฒ•์‚ฌ", "๋„๋‘‘", "ํ•ด์ ", "๋ชจํ—˜๊ฐ€", "์šฉ์‚ฌ", "ํ˜„์ž", "์—ฐ๊ธˆ์ˆ ์‚ฌ", "๋งˆ๋…€", "๋“œ๋ฃจ์ด๋“œ",
74
- "์Œ์œ ์‹œ์ธ", "๋Œ€์žฅ์žฅ์ด", "์ƒ์ธ๊ธธ๋“œ์žฅ", "์™•", "์—ฌ์™•", "์™•์ž", "๊ณต์ฃผ", "๊ท€์กฑ", "๊ธฐ์‚ฌ๋‹จ์žฅ", "๋งˆ๋ฒ•ํ•™๊ต๊ต์žฅ",
75
- "๋ณด๋ฌผ์‚ฌ๋ƒฅ๊พผ", "์ง€๋„์ œ์ž‘์ž", "๋ชฌ์Šคํ„ฐ์‚ฌ๋ƒฅ๊พผ", "๋˜์ „ํƒํ—˜๊ฐ€", "๊ธธ๋“œ๋งˆ์Šคํ„ฐ",
76
-
77
- # ๋ฏธ์Šคํ„ฐ๋ฆฌ/๋ฒ”์ฃ„ (15๊ฐœ)
78
- "ํƒ์ •", "ํ˜•์‚ฌ", "ํ”„๋กœํŒŒ์ผ๋Ÿฌ", "๋ฒ•์˜ํ•™์ž", "์ŠคํŒŒ์ด", "ํ•ด์ปค", "์‚ฌ๊ธฐ๊พผ", "๋ชฉ๊ฒฉ์ž", "์šฉ์˜์ž", "์ •๋ณด์›",
79
- "๋ฐ€์ˆ˜์—…์ž", "์œ„์กฐ์ „๋ฌธ๊ฐ€", "๊ธˆ๊ณ ํ„ธ์ด๋ฒ”", "์ถ”๋ฆฌ์ž‘๊ฐ€", "์‚ฌ๋ฆฝํƒ์ •",
80
-
81
- # ํ˜ธ๋Ÿฌ/์ดˆ์ž์—ฐ (10๊ฐœ)
82
- "ํ‡ด๋งˆ์‚ฌ", "์˜๋งค", "๋ฌด๋‹น", "์‹ฌ๋ น์—ฐ๊ตฌ๊ฐ€", "๋ฑ€ํŒŒ์ด์–ด", "๋Š‘๋Œ€์ธ๊ฐ„", "์œ ๋ น", "์•…๋งˆ์‚ฌ๋ƒฅ๊พผ", "์ €์ฃผ๋ฐ›์€์ž", "์˜ˆ์–ธ์ž",
83
-
84
- # SF/๋ฏธ๋ž˜ (10๊ฐœ)
85
- "์šฐ์ฃผ๋น„ํ–‰์‚ฌ", "๋กœ๋ด‡๊ณตํ•™์ž", "AI๊ฐœ๋ฐœ์ž", "์‚ฌ์ด๋ณด๊ทธ", "ํƒ€์ž„ํŠธ๋ž˜๋ธ”๋Ÿฌ", "์šฐ์ฃผํ•ด์ ", "ํ…Œ๋ผํฌ๋จธ", "์œ ์ „๊ณตํ•™์ž", "ํ™€๋กœ๊ทธ๋žจ๊ธฐ์ˆ ์ž", "์–‘์ž๋ฌผ๋ฆฌํ•™์ž"
86
- ],
87
-
88
- "๋ฌผ๊ฑด": [
89
- # ์ผ์ƒ์šฉํ’ˆ (40๊ฐœ)
90
- "์—ด์‡ ", "์ง€๊ฐ‘", "ํœด๋Œ€ํฐ", "์•ˆ๊ฒฝ", "์‹œ๊ณ„", "๊ฐ€๋ฐฉ", "์šฐ์‚ฐ", "์‹ ๋ฐœ", "๋ชจ์ž", "์Šค์นดํ”„",
91
- "์ฑ…", "๋…ธํŠธ", "ํŽœ", "์นด๋ฉ”๋ผ", "์ด์–ดํฐ", "๋…ธํŠธ๋ถ", "์ž์ „๊ฑฐ", "์ž๋™์ฐจ์—ด์‡ ", "์‹ ๋ถ„์ฆ", "์‹ ์šฉ์นด๋“œ",
92
- "๊ฑฐ์šธ", "๋น—", "์†์ˆ˜๊ฑด", "๋ผ์ดํ„ฐ", "์†์ „๋“ฑ", "๊ฐ€์œ„", "ํ…Œ์ดํ”„", "์ถฉ์ „๊ธฐ", "๋งˆ์Šคํฌ", "์žฅ๊ฐ‘",
93
- "๋ฌผ๋ณ‘", "๋„์‹œ๋ฝ", "์•ฝํ†ต", "ํ™”์žฅํ’ˆ", "ํ–ฅ์ˆ˜", "์„ ๊ธ€๋ผ์Šค", "๋ฐฐ๋‚ญ", "์—ฌ๊ถŒ", "ํ‹ฐ์ผ“", "๋™์ „",
94
-
95
- # ํŠน๋ณ„ํ•œ ๋ฌผ๊ฑด (25๊ฐœ)
96
- "๋ณด๋ฌผ์ง€๋„", "๊ณ ๋Œ€์œ ๋ฌผ", "๋งˆ๋ฒ•๋ฐ˜์ง€", "์ˆ˜์ •๊ตฌ์Šฌ", "๋น„๋ฐ€์ผ๊ธฐ", "์•”ํ˜ธ๋ฌธ์„œ", "๊ธˆํ™”", "๋ณด์„", "์™•๊ด€", "๊ฒ€",
97
- "๋งˆ๋ฒ•์ง€ํŒก์ด", "๋ฌผ์•ฝ", "์ฃผ๋ฌธ์„œ", "๋ด‰์ธ๋œ์ƒ์ž", "์‹ ๋น„ํ•œ์—ด์‡ ", "๊ณ ๋Œ€์„œ์ ", "๋‚˜์นจ๋ฐ˜", "๋ง์›๊ฒฝ", "๋ชจ๋ž˜์‹œ๊ณ„", "์˜ค๋ฅด๊ณจ",
98
- "๋ถ€์ ", "๋ฉ”๋‹ฌ", "์ธ์žฅ", "๋‘๋ฃจ๋งˆ๋ฆฌ", "์‹ ์„ฑํ•œ์œ ๋ฌผ",
99
-
100
- # ํ˜„๋Œ€ ๊ธฐ์ˆ  (15๊ฐœ)
101
- "๋“œ๋ก ", "VRํ—ค๋“œ์…‹", "์Šค๋งˆํŠธ์›Œ์น˜", "ํƒœ๋ธ”๋ฆฟ", "๋ฌด์„ ์ด์–ดํฐ", "์•ก์…˜์บ ", "์ „์ž์ฑ…๋ฆฌ๋”", "๊ฒŒ์ž„๊ธฐ", "์Šค๋งˆํŠธํ™ˆ๊ธฐ๊ธฐ", "3Dํ”„๋ฆฐํ„ฐ",
102
- "ํ™€๋กœ๊ทธ๋žจ์žฅ์น˜", "AI์Šคํ”ผ์ปค", "์ „๊ธฐ์ž๋™์ฐจํ‚ค", "์ƒ์ฒด์ธ์‹์žฅ์น˜", "๋‚˜๋…ธ์นฉ",
103
-
104
- # ์ฆ๊ฑฐ/๋‹จ์„œ (10๊ฐœ)
105
- "ํŽธ์ง€", "์‚ฌ์ง„", "๋…น์Œ๊ธฐ", "ํ˜ˆํ”", "์ง€๋ฌธ", "ํƒ„ํ”ผ", "์ฐข์–ด์ง„์˜ท", "๋ช…ํ•จ", "์˜์ˆ˜์ฆ", "๋ฉ”๋ชจ",
106
-
107
- # ๋ฌด๊ธฐ/๋„๊ตฌ (10๊ฐœ)
108
- "๋‹จ๊ฒ€", "ํ™œ", "๋ฐฉํŒจ", "ํญํƒ„", "ํ•จ์ •", "๋ฐง์ค„", "์ž๋ฌผ์‡ ", "์ฒ ์‚ฌ", "๋ง์น˜", "ํšƒ๋ถˆ"
109
- ],
110
-
111
- "์žฅ์†Œ": [
112
- # ์ผ์ƒ ์žฅ์†Œ (35๊ฐœ)
113
- "์ง‘", "ํ•™๊ต", "ํšŒ์‚ฌ", "๋ณ‘์›", "์€ํ–‰", "๋งˆํŠธ", "์นดํŽ˜", "๋ ˆ์Šคํ† ๋ž‘", "๊ณต์›", "๋„์„œ๊ด€",
114
- "์˜ํ™”๊ด€", "ํ—ฌ์Šค์žฅ", "๋ฏธ์ˆ ๊ด€", "๋ฐฑํ™”์ ", "์ง€ํ•˜์ฒ ์—ญ", "๋ฒ„์Šค์ •๋ฅ˜์žฅ", "๊ณตํ•ญ", "ํ˜ธํ…”", "ํŽธ์˜์ ", "์ฃผ์ฐจ์žฅ",
115
- "๋†€์ดํ„ฐ", "์•„ํŒŒํŠธ", "์‚ฌ๋ฌด์‹ค", "๊ต์‹ค", "์˜ฅ์ƒ", "์ง€ํ•˜์‹ค", "์—˜๋ฆฌ๋ฒ ์ดํ„ฐ", "๋ณต๋„", "ํ™”์žฅ์‹ค", "์ฃผ๋ฐฉ",
116
- "๋ฐœ์ฝ”๋‹ˆ", "์ •์›", "์ฐจ๊ณ ", "์ฐฝ๊ณ ", "๋‹ค๋ฆฌ",
117
-
118
- # ์ž์—ฐ ์žฅ์†Œ (15๊ฐœ)
119
- "์ˆฒ", "๋ฐ”๋‹ค", "์‚ฐ", "๊ฐ•", "ํ˜ธ์ˆ˜", "๋™๊ตด", "์ ˆ๋ฒฝ", "ํญํฌ", "๊ณ„๊ณก", "์‚ฌ๋ง‰",
120
- "์„ฌ", "ํ•ด๋ณ€", "๋“คํŒ", "๋Šช์ง€", "๋น™ํ•˜",
121
-
122
- # ํŠน๋ณ„ํ•œ ์žฅ์†Œ (15๊ฐœ)
123
- "์„ฑ", "๋˜์ „", "๋งˆ๋ฒ•ํ•™๊ต", "์‹ ์ „", "์ง€ํ•˜๋ฌ˜์ง€", "๊ณ ๋Œ€์œ ์ ", "๋ฏธ๋กœ", "ํƒ‘", "๊ฐ์˜ฅ", "์™•๊ถ",
124
- "๋น„๋ฐ€๊ธฐ์ง€", "์‹คํ—˜์‹ค", "์ฒœ๋ฌธ๋Œ€", "๋“ฑ๋Œ€", "ํํ—ˆ",
125
-
126
- # ๋„์‹œ ์žฅ์†Œ (10๊ฐœ)
127
- "๊ด‘์žฅ", "์‹œ์žฅ", "ํ•ญ๊ตฌ", "๊ธฐ์ฐจ์—ญ", "๋‹ค๋ฆฌ", "๊ณจ๋ชฉ๊ธธ", "๋Œ€๋กœ", "๊ต์ฐจ๋กœ", "๋ถ„์ˆ˜๋Œ€", "๊ณต์‚ฌ์žฅ",
128
-
129
- # ๋ฏธ์Šคํ„ฐ๋ฆฌ ์žฅ์†Œ (5๊ฐœ)
130
- "๋ฒ”์ฃ„ํ˜„์žฅ", "์ทจ์กฐ์‹ค", "๋ฒ•์›", "์ฆ๊ฑฐ๋ณด๊ด€์‹ค", "๋ฐ€์‹ค"
131
- ],
132
-
133
- "์‚ฌ๊ฑด": [
134
- # ์ผ์ƒ ์‚ฌ๊ฑด (30๊ฐœ)
135
- "๋งŒ๋‚จ", "์ด๋ณ„", "์•ฝ์†", "์ง€๊ฐ", "์‹ค์ˆ˜", "์˜คํ•ด", "ํ™”ํ•ด", "์„ ๋ฌผ", "ํŒŒํ‹ฐ", "์ด์‚ฌ",
136
- "์ทจ์—…", "ํ‡ด์‚ฌ", "์‹œํ—˜", "์กธ์—…", "๊ฒฐํ˜ผ", "์ƒ์ผ", "์‚ฌ๊ณ ", "์ž…์›", "์—ฌํ–‰", "๊ท€๊ตญ",
137
- "๋ถ„์‹ค", "๋ฐœ๊ฒฌ", "๊ณ ๋ฐฑ", "๊ฑฐ์ ˆ", "์„ฑ๊ณต", "์‹คํŒจ", "๋„์ „", "ํฌ๊ธฐ", "์‹œ์ž‘", "๋",
138
-
139
- # ๋ชจํ—˜/์•ก์…˜ (20๊ฐœ)
140
- "์ถ”๊ฒฉ", "ํƒˆ์ถœ", "์ž ์ž…", "๊ตฌ์ถœ", "์ „ํˆฌ", "๊ฒฐํˆฌ", "๋งค๋ณต", "์Šต๊ฒฉ", "๋ฐฉ์–ด", "ํ›„ํ‡ด",
141
- "ํƒํ—˜", "๋ฐœ๊ฒฌ", "ํ•จ์ •", "๋ฐฐ์‹ ", "๋™๋งน", "๊ฑฐ๋ž˜", "ํ˜‘์ƒ", "์œ„๊ธฐ", "์—ญ์ „", "์Šน๋ฆฌ",
142
-
143
- # ๋ฏธ์Šคํ„ฐ๋ฆฌ (15๊ฐœ)
144
- "์‹ค์ข…", "์‚ด์ธ", "๋„๋‚œ", "์œ„์กฐ", "ํ˜‘๋ฐ•", "๋‚ฉ์น˜", "๋ชฉ๊ฒฉ", "์ถ”๋ฆฌ", "์‹ฌ๋ฌธ", "์žฌํŒ",
145
- "์ฆ๊ฑฐ๋ฐœ๊ฒฌ", "์•Œ๋ฆฌ๋ฐ”์ด๋ถ•๊ดด", "์ •์ฒดํญ๋กœ", "์ž๋ฐฑ", "๋ฌด์ฃ„์„๋ฐฉ",
146
-
147
- # ์ดˆ์ž์—ฐ/ํŒํƒ€์ง€ (10๊ฐœ)
148
- "์ €์ฃผ", "์ถ•๋ณต", "์†Œํ™˜", "๋ด‰์ธ", "๊ฐ์„ฑ", "๋ณ€์‹ ", "์˜ˆ์–ธ", "๊ธฐ์ ", "๋ถ€ํ™œ", "์†Œ๋ฉธ",
149
-
150
- # ๋กœ๋งจ์Šค/๊ฐ์ • (5๊ฐœ)
151
- "์ฒซ๋งŒ๋‚จ", "๋ฐ์ดํŠธ", "ํ”„๋กœํฌ์ฆˆ", "์ด๋ณ„", "์žฌํšŒ"
152
- ],
153
-
154
- "๊ฐ์ •/์ƒํƒœ": [
155
- # ๊ธฐ๋ณธ ๊ฐ์ • (25๊ฐœ)
156
- "๊ธฐ์จ", "์Šฌํ””", "๋ถ„๋…ธ", "๋‘๋ ค์›€", "๋†€๋žŒ", "ํ˜์˜ค", "์‹ ๋ขฐ", "๊ธฐ๋Œ€", "์ˆ˜์น˜์‹ฌ", "์ฃ„์ฑ…๊ฐ",
157
- "์ž๋ถ€์‹ฌ", "์งˆํˆฌ", "๋ถ€๋Ÿฌ์›€", "ํฌ๋ง", "์ ˆ๋ง", "์‚ฌ๋ž‘", "์ฆ์˜ค", "๋™์ •", "๊ฐ์‚ฌ", "ํ›„ํšŒ",
158
- "์•ˆ๋„", "์ดˆ์กฐ", "๋‹นํ™ฉ", "ํฅ๋ถ„", "์‹ค๋ง",
159
-
160
- # ์ƒํƒœ (20๊ฐœ)
161
- "ํ”ผ๊ณค", "ํ™œ๊ธฐ์ฐธ", "์กธ๋ฆผ", "๋ฐฐ๊ณ ํ””", "๋ชฉ๋งˆ๋ฆ„", "์•„ํ””", "๊ฑด๊ฐ•ํ•จ", "์ทจํ•จ", "๋ฉ€๋ฏธ", "ํ˜„๊ธฐ์ฆ",
162
- "์ง‘์ค‘", "์‚ฐ๋งŒ", "๊ธด์žฅ", "ํŽธ์•ˆ", "๋ถˆ์•ˆ", "ํ‰์˜จ", "ํ˜ผ๋ž€", "ํ™•์‹ ", "์˜์‹ฌ", "๋ง์„ค์ž„",
163
-
164
- # ๋ณต์žกํ•œ ๊ฐ์ • (10๊ฐœ)
165
- "๊ทธ๋ฆฌ์›€", "ํ—ˆ์ „ํ•จ", "๋ฟŒ๋“ฏํ•จ", "์ฐœ์ฐœํ•จ", "์„œ์šดํ•จ", "๋ฏธ์•ˆํ•จ", "๊ณ ๋งˆ์›€", "์–ด์ƒ‰ํ•จ", "์‘ฅ์Šค๋Ÿฌ์›€", "๋‹ต๋‹ตํ•จ",
166
-
167
- # ๊ทน๋‹จ์  ๊ฐ์ • (5๊ฐœ)
168
- "ํ™˜ํฌ", "์ ˆ๊ทœ", "๊ณตํฌ", "์—ด์ •", "๋ฌด๊ธฐ๋ ฅ"
169
- ],
170
-
171
- "์‹œ๊ฐ„/๋•Œ": [
172
- # ํ•˜๋ฃจ ์‹œ๊ฐ„ (10๊ฐœ)
173
- "์ƒˆ๋ฒฝ", "์•„์นจ", "์˜ค์ „", "์ •์˜ค", "์˜คํ›„", "์ €๋…", "๋ฐค", "์ž์ •", "๋™ํ‹€๋…˜", "ํ•ด์งˆ๋…˜",
174
-
175
- # ๊ณ„์ ˆ/๋‚ ์”จ์‹œ๊ธฐ (8๊ฐœ)
176
- "๋ด„", "์—ฌ๋ฆ„", "๊ฐ€์„", "๊ฒจ์šธ", "์žฅ๋งˆ์ฒ ", "ํ™˜์ ˆ๊ธฐ", "์—ฐ๋ง", "์—ฐ์ดˆ",
177
-
178
- # ํŠน๋ณ„ํ•œ ๋‚  (12๊ฐœ)
179
- "์ƒ์ผ", "๊ธฐ๋…์ผ", "๋ช…์ ˆ", "์ฃผ๋ง", "ํœด์ผ", "์‹œํ—˜๋‚ ", "๋ฉด์ ‘๋‚ ", "์ด์‚ฌ๋‚ ", "๊ฒฐํ˜ผ์‹๋‚ ", "์กธ์—…์‹๋‚ ",
180
- "์ฒซ์ถœ๊ทผ", "๋งˆ์ง€๋ง‰๋‚ ",
181
-
182
- # ์‹œ๊ฐ„ ํ‘œํ˜„ (10๊ฐœ)
183
- "์ง€๊ธˆ", "๋ฐฉ๊ธˆ", "์กฐ๊ธˆ์ „", "๋‚˜์ค‘์—", "์–ธ์  ๊ฐ€", "์˜›๋‚ ", "๋ฏธ๋ž˜", "๊ณผ๊ฑฐ", "ํ˜„์žฌ", "์˜์›"
184
- ],
185
-
186
- "๋‚ ์”จ/์ž์—ฐํ˜„์ƒ": [
187
- # ์ผ๋ฐ˜ ๋‚ ์”จ (20๊ฐœ)
188
- "๋ง‘์Œ", "ํ๋ฆผ", "๋น„", "๋ˆˆ", "๋ฐ”๋žŒ", "์•ˆ๊ฐœ", "์„œ๋ฆฌ", "์ด์Šฌ", "๋ฌด์ง€๊ฐœ", "๋ฒˆ๊ฐœ",
189
- "์ฒœ๋‘ฅ", "์šฐ๋ฐ•", "ํƒœํ’", "ํญ์„ค", "ํญ์—ผ", "ํ•œํŒŒ", "๋ฏธ์„ธ๋จผ์ง€", "ํ™ฉ์‚ฌ", "๊ตฌ๋ฆ„", "ํ–‡๋น›",
190
-
191
- # ์ž์—ฐํ˜„์ƒ (10๊ฐœ)
192
- "์ผ์ถœ", "์ผ๋ชฐ", "์›”์‹", "์ผ์‹", "์œ ์„ฑ", "์˜ค๋กœ๋ผ", "์กฐ์ˆ˜", "ํŒŒ๋„", "์ง€์ง„", "ํ™”์‚ฐ"
193
- ],
194
-
195
- "์ƒ‰๊น”/๋น›": [
196
- # ๊ธฐ๋ณธ์ƒ‰ (15๊ฐœ)
197
- "๋นจ๊ฐ•", "ํŒŒ๋ž‘", "๋…ธ๋ž‘", "์ดˆ๋ก", "์ฃผํ™ฉ", "๋ณด๋ผ", "๋ถ„ํ™", "ํ•˜์–‘", "๊ฒ€์ •", "ํšŒ์ƒ‰",
198
- "๊ฐˆ์ƒ‰", "ํ•˜๋Š˜์ƒ‰", "์—ฐ๋‘์ƒ‰", "๋‚จ์ƒ‰", "๋ฒ ์ด์ง€",
199
-
200
- # ๋น›/์งˆ๊ฐ (10๊ฐœ)
201
- "๋ฐ˜์ง์ž„", "์–ด๋‘ ", "๊ทธ๋ฆผ์ž", "๋น›", "ํˆฌ๋ช…", "๋ถˆํˆฌ๋ช…", "๋ฌด๊ด‘", "์œ ๊ด‘", "ํ˜•๊ด‘", "์•ผ๊ด‘"
202
- ],
203
-
204
- "์†Œ๏ฟฝ๏ฟฝ/์Œํ–ฅ": [
205
- # ์ผ์ƒ์†Œ๋ฆฌ (20๊ฐœ)
206
- "๋ฐœ์†Œ๋ฆฌ", "๋ฌธ์†Œ๋ฆฌ", "์ข…์†Œ๋ฆฌ", "์•Œ๋žŒ์†Œ๋ฆฌ", "์ „ํ™”๋ฒจ", "๋…ธํฌ์†Œ๋ฆฌ", "๋ฐ•์ˆ˜์†Œ๋ฆฌ", "ํœ˜ํŒŒ๋žŒ", "์ฝง๋…ธ๋ž˜", "ํ•œ์ˆจ",
207
- "์›ƒ์Œ์†Œ๋ฆฌ", "์šธ์Œ์†Œ๋ฆฌ", "๊ณ ํ•จ์†Œ๋ฆฌ", "์†์‚ญ์ž„", "์‹ฌ์žฅ์†Œ๋ฆฌ", "์ˆจ์†Œ๋ฆฌ", "๊ธฐ์นจ์†Œ๋ฆฌ", "์žฌ์ฑ„๊ธฐ", "ํ•˜ํ’ˆ์†Œ๋ฆฌ", "์ฝ”๊ณจ์ด",
208
-
209
- # ์ž์—ฐ์†Œ๋ฆฌ (10๊ฐœ)
210
- "๋ฐ”๋žŒ์†Œ๋ฆฌ", "๋น—์†Œ๋ฆฌ", "ํŒŒ๋„์†Œ๋ฆฌ", "์ƒˆ์†Œ๋ฆฌ", "๋ฒŒ๋ ˆ์†Œ๋ฆฌ", "์ฒœ๋‘ฅ์†Œ๋ฆฌ", "๋‚˜๋ญ‡์žŽ์†Œ๋ฆฌ", "๋ฌผ์†Œ๋ฆฌ", "๋ถˆํƒ€๋Š”์†Œ๋ฆฌ", "์ง€์ง„์†Œ๋ฆฌ"
211
- ],
212
-
213
- "์Œ์‹/์š”๋ฆฌ": [
214
- # ํ•œ์‹ (20๊ฐœ)
215
- "๊น€์น˜", "๋œ์žฅ์ฐŒ๊ฐœ", "๋ถˆ๊ณ ๊ธฐ", "๋น„๋น”๋ฐฅ", "๋–ก๋ณถ์ด", "๊น€๋ฐฅ", "๋ผ๋ฉด", "์‚ผ๊ฒน์‚ด", "๋ƒ‰๋ฉด", "๊ฐˆ๋น„ํƒ•",
216
- "์žก์ฑ„", "์ „", "์ˆœ๋‘๋ถ€์ฐŒ๊ฐœ", "์‚ผ๊ณ„ํƒ•", "๊ตญ๋ฐฅ", "์Œˆ๋ฐฅ", "๋ณถ์Œ๋ฐฅ", "๋งŒ๋‘", "๋ถ€์นจ๊ฐœ", "์ฃฝ",
217
-
218
- # ์™ธ๊ตญ์Œ์‹ (15๊ฐœ)
219
- "ํŒŒ์Šคํƒ€", "ํ”ผ์ž", "์Šคํ…Œ์ดํฌ", "์Šค์‹œ", "๋ผ๋ฉ˜", "์ƒ๋Ÿฌ๋“œ", "์ƒŒ๋“œ์œ„์น˜", "ํ–„๋ฒ„๊ฑฐ", "ํƒ€์ฝ”", "์ปค๋ฆฌ",
220
- "์Œ€๊ตญ์ˆ˜", "๋”ค์„ฌ", "์ผ€๋ฐฅ", "๋ฆฌ์กฐ๋˜", "๋ธŒ๋Ÿฐ์น˜",
221
-
222
- # ๊ฐ„์‹/์Œ๋ฃŒ (15๊ฐœ)
223
- "์ปคํ”ผ", "์ฐจ", "์ฃผ์Šค", "๋งฅ์ฃผ", "์™€์ธ", "๋นต", "์ผ€์ดํฌ", "์ฟ ํ‚ค", "์ดˆ์ฝœ๋ฆฟ", "์•„์ด์Šคํฌ๋ฆผ",
224
- "๊ณผ์ผ", "ํŒ์ฝ˜", "๊ฐ์ž์นฉ", "์ ค๋ฆฌ", "์‚ฌํƒ•"
225
- ],
226
-
227
- "๋™์ž‘/ํ–‰๋™": [
228
- # ์ผ์ƒ ๋™์ž‘ (25๊ฐœ)
229
- "๊ฑท๊ธฐ", "๋›ฐ๊ธฐ", "์•‰๊ธฐ", "์„œ๊ธฐ", "๋ˆ•๊ธฐ", "์ผ์–ด๋‚˜๊ธฐ", "๋Œ์•„๋ณด๊ธฐ", "์†ํ”๋“ค๊ธฐ", "๊ณ ๊ฐœ๋„๋•์ด๊ธฐ", "๋ฏธ์†Œ์ง“๊ธฐ",
230
- "์ฐก๊ทธ๋ฆฌ๊ธฐ", "๋ˆˆ๊ฐ๊ธฐ", "ํ•˜ํ’ˆํ•˜๊ธฐ", "๊ธฐ์ง€๊ฐœ์ผœ๊ธฐ", "์ˆจ์‰ฌ๊ธฐ", "๋จน๊ธฐ", "๋งˆ์‹œ๊ธฐ", "๋งํ•˜๊ธฐ", "๋“ฃ๊ธฐ", "๋ณด๊ธฐ",
231
- "๋งŒ์ง€๊ธฐ", "์žก๊ธฐ", "๋†“๊ธฐ", "์—ด๊ธฐ", "๋‹ซ๊ธฐ",
232
-
233
- # ํŠน๋ณ„ํ•œ ๋™์ž‘ (15๊ฐœ)
234
- "์ˆจ๊ธฐ", "์ฐพ๊ธฐ", "๋„๋ง์น˜๊ธฐ", "์ซ“๊ธฐ", "์‹ธ์šฐ๊ธฐ", "๋ฐฉ์–ดํ•˜๊ธฐ", "๊ณต๊ฒฉํ•˜๊ธฐ", "์ ํ”„ํ•˜๊ธฐ", "๊ธฐ์–ด๊ฐ€๊ธฐ", "์ˆ˜์˜ํ•˜๊ธฐ",
235
- "์ถค์ถ”๊ธฐ", "๋…ธ๋ž˜ํ•˜๊ธฐ", "๊ทธ๋ฆฌ๊ธฐ", "์“ฐ๊ธฐ", "์—ฐ์ฃผํ•˜๊ธฐ"
236
- ],
237
-
238
- "๊ด€๊ณ„": [
239
- # ๊ฐ€์กฑ (10๊ฐœ)
240
- "๋ถ€๋ชจ", "์ž๋…€", "ํ˜•์ œ", "์ž๋งค", "์กฐ๋ถ€๋ชจ", "์†์ž", "๋ฐฐ์šฐ์ž", "์‚ฌ์ดŒ", "์‚ผ์ดŒ", "์ด๋ชจ",
241
-
242
- # ์‚ฌํšŒ๊ด€๊ณ„ (15๊ฐœ)
243
- "์นœ๊ตฌ", "์—ฐ์ธ", "๋™๋ฃŒ", "์ƒ์‚ฌ", "๋ถ€ํ•˜", "์ด์›ƒ", "์Šค์Šน", "์ œ์ž", "ํŒŒํŠธ๋„ˆ", "๋ผ์ด๋ฒŒ",
244
- "์ ", "๋™์ง€", "์ง€์ธ", "๋‚ฏ์„ ์‚ฌ๋žŒ", "์†๋‹˜"
245
- ],
246
-
247
- "๋Šฅ๋ ฅ/ํŠน์„ฑ": [
248
- # ์ผ๋ฐ˜ ๋Šฅ๋ ฅ (15๊ฐœ)
249
- "์ง€๋Šฅ", "์ฒด๋ ฅ", "์†๋„", "ํž˜", "๋ฏผ์ฒฉ์„ฑ", "์ฐฝ์˜๋ ฅ", "๊ธฐ์–ต๋ ฅ", "์ง‘์ค‘๋ ฅ", "๋ฆฌ๋”์‹ญ", "๊ณต๊ฐ๋Šฅ๋ ฅ",
250
- "์œ ๋จธ๊ฐ๊ฐ", "์ธ๋‚ด์‹ฌ", "์šฉ๊ธฐ", "์ง๊ด€", "์„ค๋“๋ ฅ",
251
-
252
- # ํŠน์ˆ˜ ๋Šฅ๋ ฅ (10๊ฐœ)
253
- "ํˆฌ์‹œ", "ํ…”๋ ˆํŒŒ์‹œ", "์˜ˆ์ง€๋ ฅ", "์น˜์œ ๋Šฅ๋ ฅ", "๋ณ€์‹ ", "ํˆฌ๋ช…ํ™”", "๋น„ํ–‰", "์‹œ๊ฐ„์กฐ์ž‘", "์ •์‹ ์กฐ์ž‘", "์›์†Œ์กฐ์ž‘"
254
- ],
255
-
256
- "์ƒํ™ฉ/์กฐ๊ฑด": [
257
- # ์ผ๋ฐ˜ ์ƒํ™ฉ (20๊ฐœ)
258
- "๋น„์ƒ์‚ฌํƒœ", "์ผ์ƒ", "ํœด๊ฐ€", "๊ทผ๋ฌด์ค‘", "์ˆ˜์—…์ค‘", "์šด์ „์ค‘", "๋Œ€๊ธฐ์ค‘", "ํšŒ์˜์ค‘", "์‹์‚ฌ์ค‘", "์šด๋™์ค‘",
259
- "์ž ์ž๋Š”์ค‘", "๊ณต๋ถ€์ค‘", "์ค€๋น„์ค‘", "์—ฌํ–‰์ค‘", "์น˜๋ฃŒ์ค‘", "ํ›ˆ๋ จ์ค‘", "๊ฒฝ์Ÿ์ค‘", "ํ˜‘์ƒ์ค‘", "์กฐ์‚ฌ์ค‘", "๋„ํ”ผ์ค‘"
260
- ],
261
-
262
- "๋ชฉ์ /๋™๊ธฐ": [
263
- # ๊ธฐ๋ณธ ๋™๊ธฐ (20๊ฐœ)
264
- "์ƒ์กด", "๋ณต์ˆ˜", "์‚ฌ๋ž‘", "๋ˆ", "๋ช…์˜ˆ", "๊ถŒ๋ ฅ", "์ง€์‹", "์ž์œ ", "์ •์˜", "ํ‰ํ™”",
265
- "ํ–‰๋ณต", "์„ฑ๊ณต", "์ธ์ •", "๋ณดํ˜ธ", "๋ฐœ๊ฒฌ", "์„ฑ์žฅ", "์น˜์œ ", "ํ™”ํ•ด", "์ง„์‹ค", "ํฌ๋ง"
266
- ]
267
- }
268
-
269
- # Genre elements from paste-2.txt
270
- GENRE_ELEMENTS = {
271
- "๋กœ๋งจ์Šค": {
272
- "key_elements": ["๊ฐ์ •์„ ", "์˜คํ•ด์™€ ํ™”ํ•ด", "๋‹ฌ์ฝคํ•œ ์ˆœ๊ฐ„", "์งˆํˆฌ", "๊ณ ๋ฐฑ"],
273
- "popular_tropes": ["๊ณ„์•ฝ์—ฐ์• ", "์žฌ๋ฒŒ๊ณผ ํ‰๋ฏผ", "์ฒซ์‚ฌ๋ž‘ ์žฌํšŒ", "์ง์‚ฌ๋ž‘", "์‚ผ๊ฐ๊ด€๊ณ„"],
274
- "must_have": ["์‹ฌ์ฟต ํฌ์ธํŠธ", "๋‹ฌ๋‹ฌํ•œ ๋Œ€์‚ฌ", "๊ฐ์ • ๋ฌ˜์‚ฌ", "์Šคํ‚จ์‹ญ", "ํ•ดํ”ผ์—”๋”ฉ"],
275
- "episode_structure": "๊ฐ์ •์˜ ๋กค๋Ÿฌ์ฝ”์Šคํ„ฐ, ๋งค ํ™” ๋ ์„ค๋ ˜ ํฌ์ธํŠธ"
276
- },
277
- "๋กœํŒ": {
278
- "key_elements": ["ํšŒ๊ท€/๋น™์˜", "์›์ž‘ ์ง€์‹", "์šด๋ช… ๋ณ€๊ฒฝ", "๋งˆ๋ฒ•/๊ฒ€์ˆ ", "์‹ ๋ถ„ ์ƒ์Šน"],
279
- "popular_tropes": ["์•…๋…€๊ฐ€ ๋˜์—ˆ๋‹ค", "ํ๋…€ ๊ฐ์„ฑ", "๊ณ„์•ฝ๊ฒฐํ˜ผ", "์ง‘์ฐฉ๋‚จ์ฃผ", "์—ญํ•˜๋ ˜"],
280
- "must_have": ["์ฐจ์›์ด๋™ ์„ค์ •", "๋จผ์น˜ํ‚จ ์š”์†Œ", "๋กœ๋งจ์Šค", "๋ณต์ˆ˜", "์„ฑ์žฅ"],
281
- "episode_structure": "์›์ž‘ ์ „๊ฐœ ๋น„ํ‹€๊ธฐ, ๋งค ํ™” ์ƒˆ๋กœ์šด ๋ณ€์ˆ˜"
282
- },
283
- "ํŒํƒ€์ง€": {
284
- "key_elements": ["๋งˆ๋ฒ•์ฒด๊ณ„", "๋ ˆ๋ฒจ์—…", "๋˜์ „", "๊ธธ๋“œ", "๋ชจํ—˜"],
285
- "popular_tropes": ["ํšŒ๊ท€", "์‹œ์Šคํ…œ", "๋จผ์น˜ํ‚จ", "ํžˆ๋“ ํ”ผ์Šค", "๊ฐ์„ฑ"],
286
- "must_have": ["์„ฑ์žฅ ๊ณก์„ ", "์ „ํˆฌ์”ฌ", "์„ธ๊ณ„๊ด€", "๋™๋ฃŒ", "์ตœ์ข…๋ณด์Šค"],
287
- "episode_structure": "์ ์ง„์  ๊ฐ•ํ•ด์ง, ์ƒˆ๋กœ์šด ๋„์ „๊ณผ ๊ทน๋ณต"
288
- },
289
- "ํ˜„ํŒ": {
290
- "key_elements": ["์ˆจ๊ฒจ์ง„ ๋Šฅ๋ ฅ", "์ผ์ƒ๊ณผ ๋น„์ผ์ƒ", "๋„์‹œ ํŒํƒ€์ง€", "๋Šฅ๋ ฅ์ž ์‚ฌํšŒ", "๊ฐ์„ฑ"],
291
- "popular_tropes": ["ํ—Œํ„ฐ", "๊ฒŒ์ดํŠธ", "๊ธธ๋“œ", "๋žญํ‚น", "์•„์ดํ…œ"],
292
- "must_have": ["ํ˜„์‹ค๊ฐ", "๋Šฅ๋ ฅ ๊ฐ์„ฑ", "์‚ฌํšŒ ์‹œ์Šคํ…œ", "์•ก์…˜", "์„ฑ์žฅ"],
293
- "episode_structure": "์ผ์ƒ ์† ๋น„์ผ์ƒ ๋ฐœ๊ฒฌ, ์ ์ง„์  ์„ธ๊ณ„๊ด€ ํ™•์žฅ"
294
- },
295
- "๋ฌดํ˜‘": {
296
- "key_elements": ["๋ฌด๊ณต", "๋ฌธํŒŒ", "๊ฐ•ํ˜ธ", "๋ณต์ˆ˜", "์˜ํ˜‘"],
297
- "popular_tropes": ["์ฒœ์žฌ", "ํ๊ธ‰์—์„œ ์ตœ๊ฐ•", "๊ธฐ์—ฐ", "ํ™˜์ƒ", "๋งˆ๊ต"],
298
- "must_have": ["๋ฌด๊ณต ์ˆ˜๋ จ", "๋Œ€๊ฒฐ", "๋ฌธํŒŒ ์„ค์ •", "๊ฒฝ์ง€", "์ตœ์ข… ๊ฒฐ์ „"],
299
- "episode_structure": "์ˆ˜๋ จ๊ณผ ๋Œ€๊ฒฐ์˜ ๋ฐ˜๋ณต, ์ ์ง„์  ๊ฒฝ์ง€ ์ƒ์Šน"
300
- },
301
- "๋ฏธ์Šคํ„ฐ๋ฆฌ": {
302
- "key_elements": ["๋‹จ์„œ", "์ถ”๋ฆฌ", "๋ฐ˜์ „", "์„œ์ŠคํŽœ์Šค", "์ง„์‹ค"],
303
- "popular_tropes": ["ํƒ์ •", "์—ฐ์‡„ ์‚ฌ๊ฑด", "๊ณผ๊ฑฐ์˜ ๋น„๋ฐ€", "๋ณต์ˆ˜๊ทน", "์‹ฌ๋ฆฌ์ „"],
304
- "must_have": ["๋ณต์„ ", "๋ถ‰์€ ์ฒญ์–ด", "๋…ผ๋ฆฌ์  ์ถ”๋ฆฌ", "์ถฉ๊ฒฉ ๋ฐ˜์ „", "ํ•ด๊ฒฐ"],
305
- "episode_structure": "๋‹จ์„œ์˜ ์ ์ง„์  ๊ณต๊ฐœ, ๊ธด์žฅ๊ฐ ์ƒ์Šน"
306
- },
307
- "๋ผ์ดํŠธ๋…ธ๋ฒจ": {
308
- "key_elements": ["ํ•™์›", "์ผ์ƒ", "์ฝ”๋ฏธ๋””", "๋ชจ์—", "๋ฐฐํ‹€"],
309
- "popular_tropes": ["์ด์„ธ๊ณ„", "ํ•˜๋ ˜", "์ธค๋ฐ๋ ˆ", "์น˜ํŠธ", "๊ธธ๋“œ"],
310
- "must_have": ["๊ฐ€๋ฒผ์šด ๋ฌธ์ฒด", "์œ ๋จธ", "์บ๋ฆญํ„ฐ์„ฑ", "์ผ๋Ÿฌ์ŠคํŠธ์  ๋ฌ˜์‚ฌ", "์™์ž์ง€๊ป„"],
311
- "episode_structure": "์—ํ”ผ์†Œ๋“œ ์ค‘์‹ฌ, ๊ฐœ๊ทธ์™€ ์ง„์ง€์˜ ๊ท ํ˜•"
312
- }
313
- }
314
-
315
- # Episode hooks by genre
316
- EPISODE_HOOKS = {
317
- "๋กœ๋งจ์Šค": [
318
- "๊ทธ์˜ ์ž…์ˆ ์ด ๋‚ด ๊ท€์— ๋‹ฟ์„ ๋“ฏ ๊ฐ€๊นŒ์›Œ์กŒ๋‹ค.",
319
- "'์‚ฌ์‹ค... ๋„ˆ๋ฅผ ์ฒ˜์Œ ๋ณธ ์ˆœ๊ฐ„๋ถ€ํ„ฐ...'",
320
- "๊ทธ๋•Œ, ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‚ฌ๋žŒ์ด ๋ฌธ์„ ์—ด๊ณ  ๋“ค์–ด์™”๋‹ค.",
321
- "๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•œ ์ˆœ๊ฐ„, ์‹ฌ์žฅ์ด ๋ฉˆ์ถœ ๊ฒƒ ๊ฐ™์•˜๋‹ค."
322
- ],
323
- "๋กœํŒ": [
324
- "๊ทธ ์ˆœ๊ฐ„, ์›์ž‘์—๋Š” ์—†๋˜ ์ธ๋ฌผ์ด ๋‚˜ํƒ€๋‚ฌ๋‹ค.",
325
- "'ํํ•˜, ๊ณ„์•ฝ์„ ํŒŒ๊ธฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.'",
326
- "๊ฒ€์€ ์˜ค๋ผ๊ฐ€ ๊ทธ๋ฅผ ๊ฐ์‹ธ๋ฉฐ ๋ˆˆ๋น›์ด ๋ณ€ํ–ˆ๋‹ค.",
327
- "ํšŒ๊ท€ ์ „์—๋Š” ๋ชฐ๋ž๋˜ ์ง„์‹ค์ด ๋“œ๋Ÿฌ๋‚ฌ๋‹ค."
328
- ],
329
- "ํŒํƒ€์ง€": [
330
- "[์ƒˆ๋กœ์šด ์Šคํ‚ฌ์„ ํš๋“ํ–ˆ์Šต๋‹ˆ๋‹ค!]",
331
- "๋˜์ „ ์ตœ์‹ฌ๋ถ€์—์„œ ๋ฐœ๊ฒฌํ•œ ๊ฒƒ์€...",
332
- "'์ด๊ฑด... SSS๊ธ‰ ์•„์ดํ…œ์ด๋‹ค!'",
333
- "์‹œ์Šคํ…œ ์ฐฝ์— ๋œฌ ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๊ณ  ๊ฒฝ์•…ํ–ˆ๋‹ค."
334
- ],
335
- "ํ˜„ํŒ": [
336
- "ํ‰๋ฒ”ํ•œ ํ•™์ƒ์ธ ์ค„ ์•Œ์•˜๋˜ ๊ทธ์˜ ๋ˆˆ์ด ๋ถ‰๊ฒŒ ๋น›๋‚ฌ๋‹ค.",
337
- "๊ฐ‘์ž๊ธฐ ํ•˜๋Š˜์— ๊ฑฐ๋Œ€ํ•œ ๊ท ์—ด์ด ์ƒ๊ฒผ๋‹ค.",
338
- "'๋‹น์‹ ๋„... ๋Šฅ๋ ฅ์ž์˜€๊ตฐ์š”.'",
339
- "ํ•ธ๋“œํฐ์— ๋œฌ ๊ธด๊ธ‰ ์žฌ๋‚œ ๋ฌธ์ž๋ฅผ ๋ณด๊ณ  ์–ผ์–ด๋ถ™์—ˆ๋‹ค."
340
- ],
341
- "๋ฌดํ˜‘": [
342
- "๊ทธ์˜ ๊ฒ€์—์„œ ํ˜๋Ÿฌ๋‚˜์˜จ ๊ฒ€๊ธฐ๋ฅผ ๋ณด๊ณ  ๋ชจ๋‘๊ฐ€ ๊ฒฝ์•…ํ–ˆ๋‹ค.",
343
- "'์ด๊ฒƒ์ด... ์ „์„ค์˜ ์ฒœ๋งˆ์‹ ๊ณต?!'",
344
- "ํ”ผ๋ฅผ ํ† ํ•˜๋ฉฐ ์“ฐ๋Ÿฌ์ง„ ์‚ฌ๋ถ€๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋‚จ๊ธด ๋ง์€...",
345
- "๊ทธ๋•Œ, ํ•˜๋Š˜์—์„œ ํ•œ ์ค„๊ธฐ ๋น›์ด ๋‚ด๋ ค์™”๋‹ค."
346
- ],
347
- "๋ฏธ์Šคํ„ฐ๋ฆฌ": [
348
- "๊ทธ๋ฆฌ๊ณ  ์‹œ์ฒด ์˜†์—์„œ ๋ฐœ๊ฒฌ๋œ ๊ฒƒ์€...",
349
- "'๋ฒ”์ธ์€ ์ด ์•ˆ์— ์žˆ์Šต๋‹ˆ๋‹ค.'",
350
- "์ผ๊ธฐ์žฅ์˜ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๋ฅผ ๋„˜๊ธฐ์ž...",
351
- "CCTV์— ์ฐํžŒ ๊ทธ ์ˆœ๊ฐ„, ๋ชจ๋“  ๊ฒƒ์ด ๋’ค๋ฐ”๋€Œ์—ˆ๋‹ค."
352
- ],
353
- "๋ผ์ดํŠธ๋…ธ๋ฒจ": [
354
- "'์„ ๋ฐฐ! ์‚ฌ์‹ค ์ €... ๋งˆ์™•์ด์—์š”!'",
355
- "์ „ํ•™์ƒ์˜ ์ •์ฒด๋Š” ๋‹ค๋ฆ„ ์•„๋‹Œ...",
356
- "๊ทธ๋…€์˜ ๊ฐ€๋ฐฉ์—์„œ ๋–จ์–ด์ง„ ๊ฒƒ์„ ๋ณด๊ณ  ๊ฒฝ์•…ํ–ˆ๋‹ค.",
357
- "'์–ด๋ผ? ์ด๊ฑฐ... ๊ฒŒ์ž„ ์•„์ดํ…œ์ด ํ˜„์‹ค์—?'"
358
- ]
359
- }
360
-
361
- # --- Global variables ---
362
- db_lock = threading.Lock()
363
- current_state = {
364
- "selected_words": [],
365
- "genre": "",
366
- "loglines": [],
367
- "evaluations": [],
368
- "ai_selected": "",
369
- "ai_reason": "",
370
- "user_prompt": ""
371
- }
372
-
373
- # --- Data classes ---
374
- @dataclass
375
- class StoryStructure:
376
- """Story structure for web novel"""
377
- title: str = ""
378
- genre: str = ""
379
- synopsis: str = ""
380
- characters: Dict[str, Dict[str, Any]] = field(default_factory=dict)
381
- world_setting: str = ""
382
- power_system: Dict[str, Any] = field(default_factory=dict)
383
- episode_outlines: Dict[int, Dict[str, str]] = field(default_factory=dict) # 40ํ™” ๊ตฌ์„ฑ
384
- key_events: List[Dict[str, Any]] = field(default_factory=list)
385
- climax_points: List[int] = field(default_factory=list) # ํด๋ผ์ด๋งฅ์Šค ํ™”์ˆ˜
386
-
387
- @dataclass
388
- class NovelSession:
389
- """Session data for a novel"""
390
- session_id: str
391
- user_id: str
392
- genre: str
393
- title: str
394
- story_structure: StoryStructure
395
- current_episode: int = 0
396
- created_at: str = ""
397
- updated_at: str = ""
398
- status: str = "active" # active, completed, paused
399
-
400
- # --- Database schema update ---
401
- class WebNovelDatabase:
402
- """Database management for web novel system"""
403
- @staticmethod
404
- def init_db():
405
- with sqlite3.connect(DB_PATH) as conn:
406
- conn.execute("PRAGMA journal_mode=WAL")
407
- cursor = conn.cursor()
408
-
409
- # Users table
410
- cursor.execute('''
411
- CREATE TABLE IF NOT EXISTS users (
412
- user_id TEXT PRIMARY KEY,
413
- username TEXT NOT NULL,
414
- email TEXT,
415
- created_at TEXT DEFAULT (datetime('now')),
416
- last_login TEXT DEFAULT (datetime('now'))
417
- )
418
- ''')
419
-
420
- # Sessions table with user ownership
421
- cursor.execute('''
422
- CREATE TABLE IF NOT EXISTS sessions (
423
- session_id TEXT PRIMARY KEY,
424
- user_id TEXT NOT NULL,
425
- genre TEXT NOT NULL,
426
- title TEXT,
427
- logline TEXT,
428
- story_structure TEXT, -- JSON
429
- current_episode INTEGER DEFAULT 0,
430
- total_episodes INTEGER DEFAULT 40,
431
- created_at TEXT DEFAULT (datetime('now')),
432
- updated_at TEXT DEFAULT (datetime('now')),
433
- status TEXT DEFAULT 'active',
434
- FOREIGN KEY (user_id) REFERENCES users(user_id)
435
- )
436
- ''')
437
-
438
- # Episodes table
439
- cursor.execute('''
440
- CREATE TABLE IF NOT EXISTS episodes (
441
- id INTEGER PRIMARY KEY AUTOINCREMENT,
442
- session_id TEXT NOT NULL,
443
- episode_number INTEGER NOT NULL,
444
- title TEXT,
445
- content TEXT,
446
- hook TEXT,
447
- word_count INTEGER DEFAULT 0,
448
- created_at TEXT DEFAULT (datetime('now')),
449
- FOREIGN KEY (session_id) REFERENCES sessions(session_id),
450
- UNIQUE(session_id, episode_number)
451
- )
452
- ''')
453
-
454
- # Story planning table
455
- cursor.execute('''
456
- CREATE TABLE IF NOT EXISTS story_plans (
457
- id INTEGER PRIMARY KEY AUTOINCREMENT,
458
- session_id TEXT NOT NULL,
459
- selected_words TEXT, -- JSON
460
- logline TEXT,
461
- synopsis TEXT,
462
- character_profiles TEXT, -- JSON
463
- episode_outlines TEXT, -- JSON
464
- created_at TEXT DEFAULT (datetime('now')),
465
- FOREIGN KEY (session_id) REFERENCES sessions(session_id)
466
- )
467
- ''')
468
-
469
- conn.commit()
470
-
471
- @staticmethod
472
- @contextmanager
473
- def get_db():
474
- with db_lock:
475
- conn = sqlite3.connect(DB_PATH, timeout=30.0)
476
- conn.row_factory = sqlite3.Row
477
- try:
478
- yield conn
479
- finally:
480
- conn.close()
481
-
482
- @staticmethod
483
- def create_or_update_user(user_id: str, username: str, email: str = None):
484
- with WebNovelDatabase.get_db() as conn:
485
- conn.cursor().execute('''
486
- INSERT INTO users (user_id, username, email)
487
- VALUES (?, ?, ?)
488
- ON CONFLICT(user_id) DO UPDATE SET
489
- username = excluded.username,
490
- email = excluded.email,
491
- last_login = datetime('now')
492
- ''', (user_id, username, email))
493
- conn.commit()
494
-
495
- @staticmethod
496
- def create_session(user_id: str, genre: str, title: str = None, logline: str = None) -> str:
497
- session_id = hashlib.md5(f"{user_id}{genre}{datetime.now()}".encode()).hexdigest()
498
- with WebNovelDatabase.get_db() as conn:
499
- conn.cursor().execute(
500
- '''INSERT INTO sessions (session_id, user_id, genre, title, logline)
501
- VALUES (?, ?, ?, ?, ?)''',
502
- (session_id, user_id, genre, title, logline)
503
- )
504
- conn.commit()
505
- return session_id
506
-
507
- @staticmethod
508
- def get_user_sessions(user_id: str) -> List[Dict]:
509
- with WebNovelDatabase.get_db() as conn:
510
- rows = conn.cursor().execute(
511
- '''SELECT * FROM sessions WHERE user_id = ?
512
- ORDER BY updated_at DESC''',
513
- (user_id,)
514
- ).fetchall()
515
- return [dict(row) for row in rows]
516
-
517
- @staticmethod
518
- def get_session(session_id: str) -> Optional[Dict]:
519
- with WebNovelDatabase.get_db() as conn:
520
- row = conn.cursor().execute(
521
- 'SELECT * FROM sessions WHERE session_id = ?',
522
- (session_id,)
523
- ).fetchone()
524
- return dict(row) if row else None
525
-
526
- @staticmethod
527
- def save_story_structure(session_id: str, story_structure: StoryStructure):
528
- with WebNovelDatabase.get_db() as conn:
529
- conn.cursor().execute(
530
- '''UPDATE sessions
531
- SET story_structure = ?, title = ?, updated_at = datetime('now')
532
- WHERE session_id = ?''',
533
- (json.dumps(asdict(story_structure)), story_structure.title, session_id)
534
- )
535
- conn.commit()
536
-
537
- @staticmethod
538
- def save_episode(session_id: str, episode_num: int, title: str,
539
- content: str, hook: str):
540
- word_count = len(content.split()) if content else 0
541
- with WebNovelDatabase.get_db() as conn:
542
- cursor = conn.cursor()
543
- cursor.execute('''
544
- INSERT INTO episodes (session_id, episode_number, title,
545
- content, hook, word_count)
546
- VALUES (?, ?, ?, ?, ?, ?)
547
- ON CONFLICT(session_id, episode_number)
548
- DO UPDATE SET title=?, content=?, hook=?, word_count=?
549
- ''', (session_id, episode_num, title, content, hook, word_count,
550
- title, content, hook, word_count))
551
-
552
- # Update session progress
553
- cursor.execute('''
554
- UPDATE sessions
555
- SET current_episode = ?, updated_at = datetime('now')
556
- WHERE session_id = ?
557
- ''', (episode_num, session_id))
558
-
559
- conn.commit()
560
-
561
- @staticmethod
562
- def get_episodes(session_id: str) -> List[Dict]:
563
- with WebNovelDatabase.get_db() as conn:
564
- rows = conn.cursor().execute(
565
- '''SELECT * FROM episodes WHERE session_id = ?
566
- ORDER BY episode_number''',
567
- (session_id,)
568
- ).fetchall()
569
- return [dict(row) for row in rows]
570
-
571
- @staticmethod
572
- def check_ownership(session_id: str, user_id: str) -> bool:
573
- with WebNovelDatabase.get_db() as conn:
574
- row = conn.cursor().execute(
575
- 'SELECT user_id FROM sessions WHERE session_id = ?',
576
- (session_id,)
577
- ).fetchone()
578
- return row and row['user_id'] == user_id
579
-
580
- @staticmethod
581
- def save_story_plan(session_id: str, selected_words: List[Tuple[str, str]],
582
- logline: str, synopsis: str = None):
583
- with WebNovelDatabase.get_db() as conn:
584
- conn.cursor().execute('''
585
- INSERT INTO story_plans (session_id, selected_words, logline, synopsis)
586
- VALUES (?, ?, ?, ?)
587
- ''', (session_id, json.dumps(selected_words), logline, synopsis))
588
- conn.commit()
589
-
590
- # --- LLM Integration ---
591
- class WebNovelSystem:
592
- """Web novel generation system"""
593
- def __init__(self):
594
- self.token = FRIENDLI_TOKEN
595
- self.api_url = API_URL
596
- self.model_id = MODEL_ID
597
- WebNovelDatabase.init_db()
598
-
599
- def create_headers(self):
600
- return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
601
-
602
- # --- Step 1: Logline generation (from paste.txt) ---
603
- def interpret_user_prompt(self, user_prompt: str) -> Dict[str, any]:
604
- """์‚ฌ์šฉ์ž์˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ•ด์„ํ•˜์—ฌ ์นดํ…Œ๊ณ ๋ฆฌ, ๋‹จ์–ด, ์žฅ๋ฅด ๋“ฑ์„ ์ถ”์ถœ"""
605
- logger.info(f"ํ”„๋กฌํ”„ํŠธ ํ•ด์„ ์‹œ์ž‘: {user_prompt[:50]}...")
606
-
607
- prompt = f"""๋‹ค์Œ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ถ„์„ํ•˜์—ฌ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ์š”์†Œ๋“ค์„ ์ถ”์ถœํ•ด์ฃผ์„ธ์š”.
608
-
609
- ์‚ฌ์šฉ์ž ์š”์ฒญ: "{user_prompt}"
610
-
611
- ๊ฐ€๋Šฅํ•œ ์นดํ…Œ๊ณ ๋ฆฌ: {', '.join(complete_word_list.keys())}
612
-
613
- ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”:
614
-
615
- [์ถ”์ถœ๋œ ์š”์†Œ]
616
- ์นดํ…Œ๊ณ ๋ฆฌ1: (์นดํ…Œ๊ณ ๋ฆฌ๋ช…) - (๋‹จ์–ด)
617
- ์นดํ…Œ๊ณ ๋ฆฌ2: (์นดํ…Œ๊ณ ๋ฆฌ๋ช…) - (๋‹จ์–ด)
618
- ์นดํ…Œ๊ณ ๋ฆฌ3: (์นดํ…Œ๊ณ ๋ฆฌ๋ช…) - (๋‹จ์–ด)
619
-
620
- [์žฅ๋ฅด]
621
- (์ถ”์ถœ๋œ ์žฅ๋ฅด ๋˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๋ถ„์œ„๊ธฐ)
622
-
623
- [ํŠน๋ณ„ ์ง€์‹œ์‚ฌํ•ญ]
624
- (์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ•์กฐํ•œ ํŠน๋ณ„ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ด๋‚˜ ์Šคํ† ๋ฆฌ ๋ฐฉํ–ฅ์„ฑ)
625
-
626
- ์ฃผ์˜์‚ฌํ•ญ:
627
- 1. ์‚ฌ์šฉ์ž๊ฐ€ ๊ตฌ์ฒด์ ์œผ๋กœ ์–ธ๊ธ‰ํ•œ ๋‹จ์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ทธ์— ๋งž๋Š” ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ ์„ ํƒ
628
- 2. ์–ธ๊ธ‰์ด ์—†๋Š” ๋ถ€๋ถ„์€ ๋ฌธ๋งฅ์— ๋งž์ถฐ ์ ์ ˆํžˆ ์„ ํƒ
629
- 3. ๊ฐ€๋Šฅํ•œ ํ•œ ๋‹ค์–‘ํ•œ ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ ์„ ํƒํ•˜์—ฌ ํ’๋ถ€ํ•œ ์Šคํ† ๋ฆฌ๊ฐ€ ๋˜๋„๋ก ํ•จ"""
630
-
631
- try:
632
- result = self.call_llm_sync([{"role": "user", "content": prompt}], "planner")
633
- logger.info("LLM ์‘๋‹ต ์ˆ˜์‹  ์™„๋ฃŒ")
634
-
635
- # Parse result
636
- lines = result.split('\n')
637
- extracted = {
638
- "words": [],
639
- "genre": "",
640
- "special_instructions": ""
641
- }
642
-
643
- current_section = None
644
- for line in lines:
645
- line = line.strip()
646
- if '[์ถ”์ถœ๋œ ์š”์†Œ]' in line:
647
- current_section = 'elements'
648
- elif '[์žฅ๋ฅด]' in line:
649
- current_section = 'genre'
650
- elif '[ํŠน๋ณ„ ์ง€์‹œ์‚ฌํ•ญ]' in line:
651
- current_section = 'instructions'
652
- elif line and current_section:
653
- if current_section == 'elements' and '์นดํ…Œ๊ณ ๋ฆฌ' in line and ':' in line:
654
- parts = line.split(':', 1)[1].strip()
655
- if ' - ' in parts:
656
- category, word = parts.split(' - ', 1)
657
- extracted["words"].append((category.strip(), word.strip()))
658
- logger.info(f"์ถ”์ถœ๋œ ๋‹จ์–ด: {category.strip()} - {word.strip()}")
659
- elif current_section == 'genre':
660
- extracted["genre"] = line
661
- logger.info(f"์ถ”์ถœ๋œ ์žฅ๋ฅด: {line}")
662
- elif current_section == 'instructions':
663
- extracted["special_instructions"] += line + " "
664
-
665
- logger.info(f"ํ”„๋กฌํ”„ํŠธ ํ•ด์„ ์™„๋ฃŒ: {len(extracted['words'])}๊ฐœ ๋‹จ์–ด ์ถ”์ถœ")
666
- return extracted
667
-
668
- except Exception as e:
669
- logger.error(f"ํ”„๋กฌํ”„ํŠธ ํ•ด์„ ์˜ค๋ฅ˜: {str(e)}", exc_info=True)
670
- raise
671
-
672
- def generate_loglines_and_evaluate(self, categories_words: List[Tuple[str, str]]) -> Dict[str, any]:
673
- """10๊ฐœ์˜ ๋กœ๊ทธ๋ผ์ธ์„ ์ƒ์„ฑํ•˜๊ณ  ํ‰๊ฐ€ํ•˜์—ฌ ์ตœ์ ์˜ ๊ฒƒ์„ ์„ ํƒ"""
674
- logger.info(f"๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ์‹œ์ž‘ - ๋‹จ์–ด: {categories_words}")
675
-
676
- prompt = f"""๋‹ค์Œ 3๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹จ์–ด๋“ค์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋กœ ๋‹ค๋ฅธ ๊ด€์ ๊ณผ ์žฅ๋ฅด์˜ ๋กœ๊ทธ๋ผ์ธ์„ 10๊ฐœ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
677
-
678
- ์นดํ…Œ๊ณ ๋ฆฌ์™€ ์„ ํƒ๋œ ๋‹จ์–ด:
679
- 1. {categories_words[0][0]}: {categories_words[0][1]}
680
- 2. {categories_words[1][0]}: {categories_words[1][1]}
681
- 3. {categories_words[2][0]}: {categories_words[2][1]}
682
-
683
- ์š”๊ตฌ์‚ฌํ•ญ:
684
- 1. ๊ฐ ๋กœ๊ทธ๋ผ์ธ์€ 1-2๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑ
685
- 2. 3๊ฐœ ๋‹จ์–ด๋ฅผ ๋ชจ๋‘ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํฌํ•จ
686
- 3. ์„œ๋กœ ๋‹ค๋ฅธ ์žฅ๋ฅด์™€ ํ†ค์œผ๋กœ ์ž‘์„ฑ (์Šค๋ฆด๋Ÿฌ, ๋กœ๋งจ์Šค, ์ฝ”๋ฏธ๋””, ํ˜ธ๋Ÿฌ, SF, ํŒํƒ€์ง€, ๋ฏธ์Šคํ„ฐ๋ฆฌ ๋“ฑ)
687
- 4. ๋…์ฐฝ์ ์ด๊ณ  ํฅ๋ฏธ๋กœ์šด ๊ด€์  ์ œ์‹œ
688
-
689
- ๊ทธ ๋‹ค์Œ, ๊ฐ ๋กœ๊ทธ๋ผ์ธ์„ ๋‹ค์Œ 4๊ฐ€์ง€ ๊ธฐ์ค€์œผ๋กœ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š” (๊ฐ 10์  ๋งŒ์ ):
690
- - ๋…์ฐฝ์„ฑ: ๊ธฐ์กด ์ž‘ํ’ˆ๊ณผ์˜ ์ฐจ๋ณ„์„ฑ, ์‹ ์„ ํ•จ
691
- - ํฅ๋ฏธ๋„: ๊ด€๊ฐ์˜ ํ˜ธ๊ธฐ์‹ฌ์„ ์œ ๋ฐœํ•˜๋Š” ์ •๋„
692
- - ์„œ์‚ฌ์„ฑ: ์ด์•ผ๊ธฐ๋กœ ๋ฐœ์ „์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์ž ์žฌ๋ ฅ
693
- - ๊ฐœ์—ฐ์„ฑ: ๋…ผ๋ฆฌ์  ํƒ€๋‹น์„ฑ๊ณผ ํ˜„์‹ค์„ฑ
694
-
695
- ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”:
696
-
697
- [๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ]
698
- ๋กœ๊ทธ๋ผ์ธ 1: (๋‚ด์šฉ)
699
- ๋กœ๊ทธ๋ผ์ธ 2: (๋‚ด์šฉ)
700
- ๋กœ๊ทธ๋ผ์ธ 3: (๋‚ด์šฉ)
701
- ๋กœ๊ทธ๋ผ์ธ 4: (๋‚ด์šฉ)
702
- ๋กœ๊ทธ๋ผ์ธ 5: (๋‚ด์šฉ)
703
- ๋กœ๊ทธ๋ผ์ธ 6: (๋‚ด์šฉ)
704
- ๋กœ๊ทธ๋ผ์ธ 7: (๋‚ด์šฉ)
705
- ๋กœ๊ทธ๋ผ์ธ 8: (๋‚ด์šฉ)
706
- ๋กœ๊ทธ๋ผ์ธ 9: (๋‚ด์šฉ)
707
- ๋กœ๊ทธ๋ผ์ธ 10: (๋‚ด์šฉ)
708
-
709
- [ํ‰๊ฐ€ ๊ฒฐ๊ณผ]
710
- ๋กœ๊ทธ๋ผ์ธ 1: ๋…์ฐฝ์„ฑ(X/10), ํฅ๋ฏธ๋„(X/10), ์„œ์‚ฌ์„ฑ(X/10), ๊ฐœ์—ฐ์„ฑ(X/10), ์ด์ (XX/40)
711
- ๋กœ๊ทธ๋ผ์ธ 2: ๋…์ฐฝ์„ฑ(X/10), ํฅ๋ฏธ๋„(X/10), ์„œ์‚ฌ์„ฑ(X/10), ๊ฐœ์—ฐ์„ฑ(X/10), ์ด์ (XX/40)
712
- ... (๋ชจ๋“  ๋กœ๊ทธ๋ผ์ธ ํ‰๊ฐ€)
713
-
714
- [์ตœ์ข… ์„ ํƒ]
715
- ์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ: (๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋ฅผ ๋ฐ›์€ ๋กœ๊ทธ๋ผ์ธ ๋ฒˆํ˜ธ์™€ ๋‚ด์šฉ)
716
-
717
- [์„ ํƒ ์ด์œ ]
718
- (์™œ ์ด ๋กœ๊ทธ๋ผ์ธ์ด ๊ฐ€์žฅ ์ข‹์€์ง€ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค๋ช…)"""
719
-
720
- try:
721
- logger.info("LLM ํ˜ธ์ถœ ์ค‘...")
722
- result = self.call_llm_sync([{"role": "user", "content": prompt}], "planner")
723
- logger.info("LLM ์‘๋‹ต ์ˆ˜์‹  ์™„๋ฃŒ")
724
-
725
- # Parse result
726
- sections = {
727
- "loglines": [],
728
- "evaluations": [],
729
- "selected": "",
730
- "reason": ""
731
- }
732
-
733
- lines = result.split('\n')
734
- current_section = None
735
-
736
- for line in lines:
737
- line = line.strip()
738
-
739
- if '[๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ]' in line:
740
- current_section = 'loglines'
741
- logger.info("๋กœ๊ทธ๋ผ์ธ ์„น์…˜ ํŒŒ์‹ฑ ์‹œ์ž‘")
742
- elif '[ํ‰๊ฐ€ ๊ฒฐ๊ณผ]' in line:
743
- current_section = 'evaluations'
744
- logger.info("ํ‰๊ฐ€ ์„น์…˜ ํŒŒ์‹ฑ ์‹œ์ž‘")
745
- elif '[์ตœ์ข… ์„ ํƒ]' in line:
746
- current_section = 'selected'
747
- logger.info("์ตœ์ข… ์„ ํƒ ์„น์…˜ ํŒŒ์‹ฑ ์‹œ์ž‘")
748
- elif '[์„ ํƒ ์ด์œ ]' in line:
749
- current_section = 'reason'
750
- elif line and current_section:
751
- if current_section == 'loglines' and line.startswith('๋กœ๊ทธ๋ผ์ธ'):
752
- parts = line.split(':', 1)
753
- if len(parts) == 2:
754
- sections['loglines'].append(parts[1].strip())
755
- elif current_section == 'evaluations' and line.startswith('๋กœ๊ทธ๋ผ์ธ'):
756
- sections['evaluations'].append(line)
757
- elif current_section == 'selected':
758
- if '์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ:' in line:
759
- sections['selected'] = line.replace('์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ:', '').strip()
760
- else:
761
- sections['selected'] += ' ' + line
762
- elif current_section == 'reason':
763
- sections['reason'] += line + ' '
764
-
765
- logger.info(f"ํŒŒ์‹ฑ ์™„๋ฃŒ - ๋กœ๊ทธ๋ผ์ธ {len(sections['loglines'])}๊ฐœ ์ƒ์„ฑ")
766
- return sections
767
-
768
- except Exception as e:
769
- logger.error(f"๋กœ๊ทธ๋ผ๏ฟฝ๏ฟฝ ์ƒ์„ฑ ์˜ค๋ฅ˜: {str(e)}", exc_info=True)
770
- raise
771
-
772
- def generate_10_loglines_with_prompt(self, user_prompt: str, categories_words: List[Tuple[str, str]],
773
- genre: str, special_instructions: str) -> Dict[str, any]:
774
- """์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋ฐ˜์˜ํ•˜์—ฌ 10๊ฐœ์˜ ๋กœ๊ทธ๋ผ์ธ์„ ์ƒ์„ฑํ•˜๊ณ  ํ‰๊ฐ€"""
775
- prompt = f"""์‚ฌ์šฉ์ž์˜ ๋‹ค์Œ ์š”์ฒญ์„ ๋ฐ˜์˜ํ•˜์—ฌ ๋กœ๊ทธ๋ผ์ธ์„ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
776
-
777
- ์‚ฌ์šฉ์ž ์š”์ฒญ: "{user_prompt}"
778
-
779
- ์ถ”์ถœ๋œ ์š”์†Œ:
780
- 1. {categories_words[0][0]}: {categories_words[0][1]}
781
- 2. {categories_words[1][0]}: {categories_words[1][1]}
782
- 3. {categories_words[2][0]}: {categories_words[2][1]}
783
-
784
- ์žฅ๋ฅด/๋ถ„์œ„๊ธฐ: {genre}
785
- ํŠน๋ณ„ ์ง€์‹œ์‚ฌํ•ญ: {special_instructions}
786
-
787
- ์œ„ ์š”์†Œ๋“ค์„ ๋ชจ๋‘ ๋ฐ˜์˜ํ•˜์—ฌ ์„œ๋กœ ๋‹ค๋ฅธ ๊ด€์ ๊ณผ ์Šคํƒ€์ผ์˜ ๋กœ๊ทธ๋ผ์ธ์„ 10๊ฐœ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
788
-
789
- ์š”๊ตฌ์‚ฌํ•ญ:
790
- 1. ๊ฐ ๋กœ๊ทธ๋ผ์ธ์€ 1-2๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑ
791
- 2. 3๊ฐœ ๋‹จ์–ด๋ฅผ ๋ชจ๋‘ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํฌํ•จ
792
- 3. ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ ์˜๋„๋ฅผ ์ถฉ์‹คํžˆ ๋ฐ˜์˜
793
- 4. ๋‹ค์–‘ํ•œ ํ•ด์„๊ณผ ์ ‘๊ทผ์œผ๋กœ ์ฐจ๋ณ„ํ™”
794
-
795
- ๊ทธ ๋‹ค์Œ, ๊ฐ ๋กœ๊ทธ๋ผ์ธ์„ ๋‹ค์Œ 4๊ฐ€์ง€ ๊ธฐ์ค€์œผ๋กœ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š” (๊ฐ 10์  ๋งŒ์ ):
796
- - ๋…์ฐฝ์„ฑ: ๊ธฐ์กด ์ž‘ํ’ˆ๊ณผ์˜ ์ฐจ๋ณ„์„ฑ, ์‹ ์„ ํ•จ
797
- - ํฅ๋ฏธ๋„: ๊ด€๊ฐ์˜ ํ˜ธ๊ธฐ์‹ฌ์„ ์œ ๋ฐœํ•˜๋Š” ์ •๋„
798
- - ์„œ์‚ฌ์„ฑ: ์ด์•ผ๊ธฐ๋กœ ๋ฐœ์ „์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์ž ์žฌ๋ ฅ
799
- - ๊ฐœ์—ฐ์„ฑ: ๋…ผ๋ฆฌ์  ํƒ€๋‹น์„ฑ๊ณผ ํ˜„์‹ค์„ฑ
800
-
801
- ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”:
802
-
803
- [๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ]
804
- ๋กœ๊ทธ๋ผ์ธ 1: (๋‚ด์šฉ)
805
- ๋กœ๊ทธ๋ผ์ธ 2: (๋‚ด์šฉ)
806
- ... (10๊ฐœ๊นŒ์ง€)
807
-
808
- [ํ‰๊ฐ€ ๊ฒฐ๊ณผ]
809
- ๋กœ๊ทธ๋ผ์ธ 1: ๋…์ฐฝ์„ฑ(X/10), ํฅ๋ฏธ๋„(X/10), ์„œ์‚ฌ์„ฑ(X/10), ๊ฐœ์—ฐ์„ฑ(X/10), ์ด์ (XX/40)
810
- ... (๋ชจ๋“  ๋กœ๊ทธ๋ผ์ธ ํ‰๊ฐ€)
811
-
812
- [์ตœ์ข… ์„ ํƒ]
813
- ์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ: (๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋ฅผ ๋ฐ›์€ ๋กœ๊ทธ๋ผ์ธ ๋ฒˆํ˜ธ์™€ ๋‚ด์šฉ)
814
-
815
- [์„ ํƒ ์ด์œ ]
816
- (์™œ ์ด ๋กœ๊ทธ๋ผ์ธ์ด ๊ฐ€์žฅ ์ข‹์€์ง€ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค๋ช…)"""
817
-
818
- result = self.call_llm_sync([{"role": "user", "content": prompt}], "planner")
819
-
820
- # Parse result (same as generate_loglines_and_evaluate)
821
- sections = {
822
- "loglines": [],
823
- "evaluations": [],
824
- "selected": "",
825
- "reason": ""
826
- }
827
-
828
- lines = result.split('\n')
829
- current_section = None
830
-
831
- for line in lines:
832
- line = line.strip()
833
-
834
- if '[๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ]' in line:
835
- current_section = 'loglines'
836
- elif '[ํ‰๊ฐ€ ๊ฒฐ๊ณผ]' in line:
837
- current_section = 'evaluations'
838
- elif '[์ตœ์ข… ์„ ํƒ]' in line:
839
- current_section = 'selected'
840
- elif '[์„ ํƒ ์ด์œ ]' in line:
841
- current_section = 'reason'
842
- elif line and current_section:
843
- if current_section == 'loglines' and line.startswith('๋กœ๊ทธ๋ผ์ธ'):
844
- parts = line.split(':', 1)
845
- if len(parts) == 2:
846
- sections['loglines'].append(parts[1].strip())
847
- elif current_section == 'evaluations' and line.startswith('๋กœ๊ทธ๋ผ์ธ'):
848
- sections['evaluations'].append(line)
849
- elif current_section == 'selected':
850
- if '์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ:' in line:
851
- sections['selected'] = line.replace('์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ:', '').strip()
852
- else:
853
- sections['selected'] += ' ' + line
854
- elif current_section == 'reason':
855
- sections['reason'] += line + ' '
856
-
857
- return sections
858
-
859
- def generate_story_from_prompt(self, user_prompt: str) -> Dict[str, any]:
860
- """์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ง์ ‘ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ"""
861
- prompt = f"""๋‹ค์Œ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ฐ”ํƒ•์œผ๋กœ ์™„์ „ํ•œ ์Šคํ† ๋ฆฌ ๊ธฐํš์•ˆ์„ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.
862
-
863
- ์‚ฌ์šฉ์ž ์š”์ฒญ: "{user_prompt}"
864
-
865
- ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”:
866
-
867
- [์Šคํ† ๋ฆฌ ๊ฐœ์š”]
868
- (์‚ฌ์šฉ์ž ์š”์ฒญ์„ ๋ฐ˜์˜ํ•œ ์ „์ฒด ์Šคํ† ๋ฆฌ ๊ฐœ์š”)
869
-
870
- [3๋‹จ๊ณ„ ๊ฐˆ๋“ฑ๊ตฌ์กฐ]
871
- โ€ข ์™ธ์  ๊ฐˆ๋“ฑ: (์ฃผ์ธ๊ณต vs ์ ๋Œ€์ž/ํ™˜๊ฒฝ)
872
- โ€ข ๋‚ด์  ๊ฐˆ๋“ฑ: (์ฃผ์ธ๊ณต์˜ ๋‚ด๋ฉด์  ๋”œ๋ ˆ๋งˆ)
873
- โ€ข ๊ด€๊ณ„์  ๊ฐˆ๋“ฑ: (์ฃผ์ธ๊ณต๊ณผ ์ฃผ๋ณ€ ์ธ๋ฌผ๋“ค ๊ฐ„์˜ ๊ฐˆ๋“ฑ)
874
-
875
- [์Šคํ† ๋ฆฌ ์•„ํฌ - 3๋ง‰ ๊ตฌ์กฐ]
876
- ์ œ1๋ง‰ (์„ค์ •):
877
- - ์˜คํ”„๋‹: (์ฒซ ์žฅ๋ฉด ๋ฌ˜์‚ฌ)
878
- - ์ผ์ƒ์˜ ์„ธ๊ณ„: (์ฃผ์ธ๊ณต์˜ ํ‰๋ฒ”ํ•œ ์ผ์ƒ)
879
- - ์ด‰๋ฐœ ์‚ฌ๊ฑด: (๋ชจํ—˜์˜ ์‹œ์ž‘)
880
- - ๊ฑฐ๋ถ€์™€ ๊ณ ๋ฏผ: (์ฃผ์ธ๊ณต์˜ ๋ง์„ค์ž„)
881
-
882
- ์ œ2๋ง‰ (๋Œ€๋ฆฝ):
883
- - ์ฒซ ๋ฒˆ์งธ ๊ด€๋ฌธ: (์ฒซ ๋„์ „)
884
- - ๋™๋ฃŒ์™€ ์ : (์กฐ๋ ฅ์ž์™€ ์ ๋Œ€์ž ๋“ฑ์žฅ)
885
- - ์‹œ๋ จ์˜ ์—ฐ์†: (์ ์  ์‹ฌํ™”๋˜๋Š” ๊ฐˆ๋“ฑ)
886
- - ๊ฐ€์งœ ์Šน๋ฆฌ/ํŒจ๋ฐฐ: (๋ฐ˜์ „ ์ง์ „)
887
- - ์ ˆ๋ง์˜ ์ˆœ๊ฐ„: (์ตœ์ €์ )
888
-
889
- ์ œ3๋ง‰ (ํ•ด๊ฒฐ):
890
- - ๊ฐ์„ฑ๊ณผ ๊นจ๋‹ฌ์Œ: (์ฃผ์ธ๊ณต์˜ ๋ณ€ํ™”)
891
- - ์ตœ์ข… ๋Œ€๊ฒฐ: (ํด๋ผ์ด๋งฅ์Šค)
892
- - ์ƒˆ๋กœ์šด ์„ธ๊ณ„: (๋ณ€ํ™”๋œ ์ผ์ƒ)
893
-
894
- [์บ๋ฆญํ„ฐ ์•„ํฌ]
895
- ์ฃผ์ธ๊ณต:
896
- โ€ข ์‹œ์ž‘์ : (์บ๋ฆญํ„ฐ์˜ ์ดˆ๊ธฐ ์ƒํƒœ)
897
- โ€ข ์š•๋ง: (์›ํ•˜๋Š” ๊ฒƒ)
898
- โ€ข ์•ฝ์ : (๊ทน๋ณตํ•ด์•ผ ํ•  ๋‹จ์ )
899
- โ€ข ์„ฑ์žฅ: (์–ด๋–ป๊ฒŒ ๋ณ€ํ™”ํ•˜๋Š”๊ฐ€)
900
- โ€ข ์ตœ์ข… ์ƒํƒœ: (๋ณ€ํ™” ํ›„ ๋ชจ์Šต)
901
-
902
- [ํ•ต์‹ฌ ํ…Œ๋งˆ์™€ ๋ฉ”์‹œ์ง€]
903
- โ€ข ํ‘œ๋ฉด ํ…Œ๋งˆ: (๊ฒ‰์œผ๋กœ ๋“œ๋Ÿฌ๋‚˜๋Š” ์ฃผ์ œ)
904
- โ€ข ์‹ฌ์ธต ํ…Œ๋งˆ: (๊นŠ์€ ์˜๋ฏธ์˜ ์ฃผ์ œ)
905
- โ€ข ์ฒ ํ•™์  ์งˆ๋ฌธ: (๊ด€๊ฐ์—๊ฒŒ ๋˜์ง€๋Š” ์งˆ๋ฌธ)"""
906
-
907
- result = self.call_llm_sync([{"role": "user", "content": prompt}], "planner")
908
- return {"content": result}
909
-
910
- # --- Step 2: Story structure generation ---
911
- def generate_story_structure(self, logline: str, genre: str) -> StoryStructure:
912
- """Generate detailed 40-episode story structure"""
913
- genre_info = GENRE_ELEMENTS.get(genre, {})
914
-
915
- prompt = f"""์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ {genre} ์žฅ๋ฅด์˜ 40ํ™” ์™„๊ฒฐ ์›น์†Œ์„ค ๊ตฌ์กฐ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
916
-
917
- ๋กœ๊ทธ๋ผ์ธ: {logline}
918
- ์žฅ๋ฅด: {genre}
919
-
920
- ์žฅ๋ฅด ํŠน์„ฑ:
921
- - ํ•ต์‹ฌ ์š”์†Œ: {', '.join(genre_info.get('key_elements', []))}
922
- - ์ธ๊ธฐ ํŠธ๋กœํ”„: {', '.join(genre_info.get('popular_tropes', []))}
923
- - ํ•„์ˆ˜ ํฌํ•จ: {', '.join(genre_info.get('must_have', []))}
924
- - ์—ํ”ผ์†Œ๋“œ ๊ตฌ์กฐ: {genre_info.get('episode_structure', '')}
925
-
926
- ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”:
927
-
928
- [์ œ๋ชฉ]
929
- ๋งค๋ ฅ์ ์ด๊ณ  ๊ธฐ์–ต์— ๋‚จ๋Š” ์ œ๋ชฉ
930
-
931
- [์‹œ๋†‰์‹œ์Šค]
932
- ์ „์ฒด ์ค„๊ฑฐ๋ฆฌ๋ฅผ 3-5๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝ
933
-
934
- [์ฃผ์š” ๋“ฑ์žฅ์ธ๋ฌผ]
935
- 1. ์ฃผ์ธ๊ณต: [์ด๋ฆ„] - [๋‚˜์ด, ์ง์—…/์‹ ๋ถ„, ์„ฑ๊ฒฉ, ๋ชฉํ‘œ]
936
- 2. ์ฃผ์š”์ธ๋ฌผ2: [์ด๋ฆ„] - [๊ด€๊ณ„, ์—ญํ• , ํŠน์ง•]
937
- 3. ์ฃผ์š”์ธ๋ฌผ3: [์ด๋ฆ„] - [๊ด€๊ณ„, ์—ญํ• , ํŠน์ง•]
938
- (ํ•„์š”ํ•œ ๋งŒํผ ์ถ”๊ฐ€)
939
-
940
- [์„ธ๊ณ„๊ด€ ์„ค์ •]
941
- ์ž‘ํ’ˆ์˜ ๋ฐฐ๊ฒฝ์ด ๋˜๋Š” ์„ธ๊ณ„ ์„ค์ • (์‹œ๋Œ€, ์žฅ์†Œ, ํŠน์ˆ˜ ์„ค์ • ๋“ฑ)
942
-
943
- [40ํ™” ์ƒ์„ธ ๊ตฌ์„ฑ]
944
- ๊ฐ ํ™”๋งˆ๋‹ค ๋‹ค์Œ ๋‚ด์šฉ ํฌํ•จ:
945
- - ์ œ๋ชฉ
946
- - ์ฃผ์š” ์‚ฌ๊ฑด
947
- - ๊ฐˆ๋“ฑ/์ „ํ™˜์ 
948
- - ์—”๋”ฉ ํ›…
949
-
950
- 1ํ™”: [์ œ๋ชฉ]
951
- - ์ฃผ์š” ์‚ฌ๊ฑด:
952
- - ๊ฐˆ๋“ฑ/์ „ํ™˜์ :
953
- - ์—”๋”ฉ ํ›…:
954
-
955
- 2ํ™”: [์ œ๋ชฉ]
956
- ... (40ํ™”๊นŒ์ง€)
957
-
958
- [์ฃผ์š” ์ „ํ™˜์ ]
959
- - 10ํ™”: 1์ฐจ ์ „ํ™˜์ 
960
- - 20ํ™”: ์ค‘๊ฐ„ ํด๋ผ์ด๋งฅ์Šค
961
- - 30ํ™”: ์ตœ์ข… ๊ฐˆ๋“ฑ ์ง„์ž…
962
- - 40ํ™”: ๋Œ€๋‹จ์›
963
-
964
- ๋ชจ๋“  ํ™”๋Š” 500๋‹จ์–ด ๋ถ„๋Ÿ‰์œผ๋กœ ์ž‘์„ฑ๋  ์˜ˆ์ •์ด๋ฉฐ, ๊ฐ ํ™”๋งˆ๋‹ค ๋…์ž๋ฅผ ์‚ฌ๋กœ์žก๋Š” ํ›…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."""
965
-
966
- response = self.call_llm_sync([{"role": "user", "content": prompt}], "planner")
967
-
968
- # Parse response into StoryStructure
969
- story = StoryStructure()
970
- story.genre = genre
971
-
972
- # Parse the response and fill StoryStructure
973
- lines = response.split('\n')
974
- current_section = ""
975
- episode_num = 0
976
-
977
- for line in lines:
978
- line = line.strip()
979
- if '[์ œ๋ชฉ]' in line:
980
- current_section = 'title'
981
- elif '[์‹œ๋†‰์‹œ์Šค]' in line:
982
- current_section = 'synopsis'
983
- elif '[์ฃผ์š” ๋“ฑ์žฅ์ธ๋ฌผ]' in line:
984
- current_section = 'characters'
985
- elif '[์„ธ๊ณ„๊ด€ ์„ค์ •]' in line:
986
- current_section = 'world'
987
- elif '[40ํ™” ์ƒ์„ธ ๊ตฌ์„ฑ]' in line:
988
- current_section = 'episodes'
989
- elif line and current_section:
990
- if current_section == 'title' and not story.title:
991
- story.title = line
992
- elif current_section == 'synopsis':
993
- story.synopsis += line + " "
994
- elif current_section == 'world':
995
- story.world_setting += line + " "
996
- elif current_section == 'episodes':
997
- # Parse episode structure
998
- if 'ํ™”:' in line:
999
- # Extract episode number more robustly
1000
- episode_part = line.split('ํ™”:')[0].strip()
1001
- # Remove any asterisks or special characters
1002
- episode_part = episode_part.replace('*', '').replace('#', '').strip()
1003
- try:
1004
- episode_num = int(episode_part)
1005
- title = line.split('ํ™”:')[1].strip() if ':' in line else ""
1006
- story.episode_outlines[episode_num] = {"title": title}
1007
- except ValueError:
1008
- # Try to extract number using regex
1009
- import re
1010
- numbers = re.findall(r'\d+', episode_part)
1011
- if numbers:
1012
- episode_num = int(numbers[0])
1013
- title = line.split('ํ™”:')[1].strip() if ':' in line else ""
1014
- story.episode_outlines[episode_num] = {"title": title}
1015
- elif episode_num > 0:
1016
- if '์ฃผ์š” ์‚ฌ๊ฑด:' in line:
1017
- story.episode_outlines[episode_num]["main_event"] = line.split(':', 1)[1].strip()
1018
- elif '๊ฐˆ๋“ฑ/์ „ํ™˜์ :' in line:
1019
- story.episode_outlines[episode_num]["conflict"] = line.split(':', 1)[1].strip()
1020
- elif '์—”๋”ฉ ํ›…:' in line:
1021
- story.episode_outlines[episode_num]["hook"] = line.split(':', 1)[1].strip()
1022
- elif episode_num in story.episode_outlines and '์ฃผ์š” ์‚ฌ๊ฑด' not in line and '๊ฐˆ๋“ฑ' not in line and '์—”๋”ฉ' not in line:
1023
- # Handle continuation lines
1024
- if 'main_event' in story.episode_outlines[episode_num] and not story.episode_outlines[episode_num].get('conflict'):
1025
- story.episode_outlines[episode_num]["main_event"] += " " + line
1026
- elif 'conflict' in story.episode_outlines[episode_num] and not story.episode_outlines[episode_num].get('hook'):
1027
- story.episode_outlines[episode_num]["conflict"] += " " + line
1028
- elif 'hook' in story.episode_outlines[episode_num]:
1029
- story.episode_outlines[episode_num]["hook"] += " " + line
1030
-
1031
- return story
1032
-
1033
- # --- Step 3: Episode writing ---
1034
- def write_episode(self, story_structure: StoryStructure, episode_num: int,
1035
- previous_episodes: List[str] = None) -> Dict[str, str]:
1036
- """Write a single episode based on story structure"""
1037
- genre_info = GENRE_ELEMENTS.get(story_structure.genre, {})
1038
- episode_outline = story_structure.episode_outlines.get(episode_num, {})
1039
- hooks = EPISODE_HOOKS.get(story_structure.genre, ["๋‹ค์Œ ์ˆœ๊ฐ„, ์ถฉ๊ฒฉ์ ์ธ ์ผ์ด..."])
1040
-
1041
- # Get previous context
1042
- prev_context = ""
1043
- if previous_episodes and len(previous_episodes) > 0:
1044
- # Get last 2 episodes for context
1045
- recent_episodes = previous_episodes[-2:]
1046
- prev_context = "\n\n".join(recent_episodes)
1047
-
1048
- prompt = f"""์›น์†Œ์„ค {story_structure.title}์˜ {episode_num}ํ™”๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
1049
-
1050
- ์žฅ๋ฅด: {story_structure.genre}
1051
- ์ „์ฒด ์‹œ๋†‰์‹œ์Šค: {story_structure.synopsis}
1052
-
1053
- {episode_num}ํ™” ๊ตฌ์„ฑ:
1054
- - ์ œ๋ชฉ: {episode_outline.get('title', '')}
1055
- - ์ฃผ์š” ์‚ฌ๊ฑด: {episode_outline.get('main_event', '')}
1056
- - ๊ฐˆ๋“ฑ/์ „ํ™˜์ : {episode_outline.get('conflict', '')}
1057
- - ์—”๋”ฉ ํ›…: {episode_outline.get('hook', '')}
1058
-
1059
- ์ด์ „ ๋‚ด์šฉ:
1060
- {prev_context if prev_context else "์ฒซ ํ™”์ž…๋‹ˆ๋‹ค."}
1061
-
1062
- ์ž‘์„ฑ ์š”๊ตฌ์‚ฌํ•ญ:
1063
- 1. ๋ถ„๋Ÿ‰: 500๋‹จ์–ด (์—„๊ฒฉํžˆ ์ค€์ˆ˜)
1064
- 2. ๋ฌธ์ฒด: {story_structure.genre} ์žฅ๋ฅด์— ๋งž๋Š” ๋ฌธ์ฒด
1065
- 3. ๊ตฌ์„ฑ:
1066
- - ๋„์ž…๋ถ€ (์ด์ „ ํ™” ์—ฐ๊ฒฐ)
1067
- - ์ „๊ฐœ๋ถ€ (์ฃผ์š” ์‚ฌ๊ฑด ์ง„ํ–‰)
1068
- - ํด๋ผ์ด๋งฅ์Šค (๊ฐˆ๋“ฑ/์ „ํ™˜์ )
1069
- - ์—”๋”ฉ (๊ฐ•๋ ฅํ•œ ํ›…์œผ๋กœ ๋งˆ๋ฌด๋ฆฌ)
1070
- 4. ํ•„์ˆ˜ ํฌํ•จ:
1071
- - ์ƒ์ƒํ•œ ๋Œ€ํ™”
1072
- - ์บ๋ฆญํ„ฐ ๊ฐ์ • ๋ฌ˜์‚ฌ
1073
- - ์žฅ๋ฉด ์ „ํ™˜
1074
- - ๋…์ž ๋ชฐ์ž… ์š”์†Œ
1075
-
1076
- ์ฐธ๊ณ  ํ›… ์˜ˆ์‹œ:
1077
- {random.choice(hooks)}
1078
-
1079
- ํ˜•์‹:
1080
- ์ œ{episode_num}ํ™”. {episode_outline.get('title', '์ œ๋ชฉ')}
1081
-
1082
- (๋ณธ๋ฌธ ๋‚ด์šฉ)
1083
-
1084
- ๋งˆ์ง€๋ง‰์€ ๋ฐ˜๋“œ์‹œ ๋‹ค์Œ ํ™”๋ฅผ ๊ธฐ๋Œ€ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฐ•๋ ฅํ•œ ํ›…์œผ๋กœ ๋๋‚ด์„ธ์š”."""
1085
-
1086
- response = self.call_llm_sync([{"role": "user", "content": prompt}], "writer")
1087
-
1088
- # Extract title and content
1089
- lines = response.strip().split('\n')
1090
- title = lines[0] if lines else f"์ œ{episode_num}ํ™”"
1091
- content = '\n'.join(lines[1:]) if len(lines) > 1 else response
1092
-
1093
- # Extract hook (last sentence)
1094
- sentences = content.split('.')
1095
- hook = sentences[-2] + '.' if len(sentences) > 1 else sentences[-1]
1096
-
1097
- return {
1098
- "title": title,
1099
- "content": content,
1100
- "hook": hook,
1101
- "word_count": len(content.split())
1102
- }
1103
-
1104
- # --- LLM call functions ---
1105
- def call_llm_sync(self, messages: List[Dict[str, str]], role: str) -> str:
1106
- logger.info(f"LLM ํ˜ธ์ถœ ์‹œ์ž‘ - ์—ญํ• : {role}")
1107
- logger.debug(f"๋ฉ”์‹œ์ง€ ์ˆ˜: {len(messages)}")
1108
-
1109
- try:
1110
- system_prompts = self.get_system_prompts()
1111
- full_messages = [{"role": "system", "content": system_prompts.get(role, "")}, *messages]
1112
-
1113
- payload = {
1114
- "model": self.model_id,
1115
- "messages": full_messages,
1116
- "max_tokens": 5000,
1117
- "temperature": 0.85,
1118
- "top_p": 0.95,
1119
- "stream": False
1120
- }
1121
-
1122
- logger.debug(f"API ์š”์ฒญ ์ค‘... URL: {self.api_url}")
1123
- start_time = time.time()
1124
-
1125
- response = requests.post(
1126
- self.api_url,
1127
- headers=self.create_headers(),
1128
- json=payload,
1129
- timeout=180
1130
- )
1131
-
1132
- elapsed_time = time.time() - start_time
1133
- logger.info(f"API ์‘๋‹ต ์ˆ˜์‹  - ์†Œ์š”์‹œ๊ฐ„: {elapsed_time:.2f}์ดˆ, ์ƒํƒœ์ฝ”๋“œ: {response.status_code}")
1134
-
1135
- if response.status_code != 200:
1136
- logger.error(f"API ์˜ค๋ฅ˜: {response.status_code} - {response.text}")
1137
- return f"โŒ API Error (Status Code: {response.status_code})"
1138
-
1139
- result = response.json()
1140
- if 'choices' in result and len(result['choices']) > 0:
1141
- content = result['choices'][0]['message']['content'].strip()
1142
- logger.info(f"LLM ์‘๋‹ต ๊ธธ์ด: {len(content)} ๋ฌธ์ž")
1143
- return content
1144
- else:
1145
- logger.error(f"์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‘๋‹ต ํ˜•์‹: {result}")
1146
- return "โŒ No response from API"
1147
-
1148
- except requests.exceptions.Timeout:
1149
- logger.error("API ํ˜ธ์ถœ ํƒ€์ž„์•„์›ƒ")
1150
- return "โŒ API ํ˜ธ์ถœ ์‹œ๊ฐ„ ์ดˆ๊ณผ"
1151
- except Exception as e:
1152
- logger.error(f"LLM ํ˜ธ์ถœ ์˜ค๋ฅ˜: {type(e).__name__}: {str(e)}", exc_info=True)
1153
- return f"โŒ Error: {str(e)}"
1154
-
1155
- def get_system_prompts(self) -> Dict[str, str]:
1156
- """System prompts for different roles"""
1157
- return {
1158
- "planner": """๋‹น์‹ ์€ ํ•œ๊ตญ ์›น์†Œ์„ค ์‹œ์žฅ์„ ์™„๋ฒฝํžˆ ์ดํ•ดํ•˜๋Š” ๊ธฐํš์ž์ž…๋‹ˆ๋‹ค.
1159
- ๋…์ž๋ฅผ ์ค‘๋…์‹œํ‚ค๋Š” ํ”Œ๋กฏ๊ณผ ์ „๊ฐœ๋ฅผ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.
1160
- ์žฅ๋ฅด๋ณ„ ๊ด€์Šต๊ณผ ๋…์ž ๊ธฐ๋Œ€๋ฅผ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•ฉ๋‹ˆ๋‹ค.
1161
- 40ํ™” ์™„๊ฒฐ ๊ตฌ์กฐ๋กœ ์™„๋ฒฝํ•œ ๊ธฐ์Šน์ „๊ฒฐ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.""",
1162
-
1163
- "writer": """๋‹น์‹ ์€ ๋…์ž๋ฅผ ์‚ฌ๋กœ์žก๋Š” ์›น์†Œ์„ค ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค.
1164
- ์ƒ์ƒํ•˜๊ณ  ๋ชฐ์ž…๊ฐ ์žˆ๋Š” ๋ฌธ์ฒด๋ฅผ ๊ตฌ์‚ฌํ•ฉ๋‹ˆ๋‹ค.
1165
- ๊ฐ ํ™”๋ฅผ ์ •ํ™•ํžˆ 500๋‹จ์–ด๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
1166
- ๋Œ€ํ™”, ํ–‰๋™, ๋‚ด๋ฉด ๋ฌ˜์‚ฌ๋ฅผ ๊ท ํ˜•์žˆ๊ฒŒ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
1167
- ๋งค ํ™” ๋์— ๊ฐ•๋ ฅํ•œ ํ›„ํฌ๋กœ ๋‹ค์Œ ํ™”๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค."""
1168
- }
1169
-
1170
- # --- Helper functions from paste.txt ---
1171
- def determine_genre(categories_words: List[Tuple[str, str]]) -> str:
1172
- """์„ ํƒ๋œ ๋‹จ์–ด๋“ค์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์žฅ๋ฅด๋ฅผ ๊ฒฐ์ •"""
1173
- fantasy_words = {"๋งˆ๋ฒ•์‚ฌ", "๊ธฐ์‚ฌ", "์šฉ์‚ฌ", "๋งˆ๋ฒ•", "์ฃผ๋ฌธ์„œ", "์„ฑ", "๋˜์ „", "์ €์ฃผ", "์ถ•๋ณต"}
1174
- mystery_words = {"ํƒ์ •", "ํ˜•์‚ฌ", "์ฆ๊ฑฐ", "๋ฒ”์ฃ„ํ˜„์žฅ", "์‹ค์ข…", "์‚ด์ธ", "์ถ”๋ฆฌ"}
1175
- sf_words = {"์šฐ์ฃผ", "AI", "๋กœ๋ด‡", "ํ™€๋กœ๊ทธ๋žจ", "ํƒ€์ž„๋จธ์‹ ", "์‚ฌ์ด๋ณด๊ทธ"}
1176
- horror_words = {"์œ ๋ น", "์•…๋งˆ", "์ €์ฃผ", "๊ณตํฌ", "๋ฌ˜์ง€", "ํ”ผ"}
1177
- romance_words = {"์‚ฌ๋ž‘", "์—ฐ์ธ", "์ฒซ๋งŒ๋‚จ", "ํ”„๋กœํฌ์ฆˆ", "๋ฐ์ดํŠธ"}
1178
-
1179
- words = [word for _, word in categories_words]
1180
-
1181
- if any(word in fantasy_words for word in words):
1182
- return "ํŒํƒ€์ง€"
1183
- elif any(word in mystery_words for word in words):
1184
- return "๋ฏธ์Šคํ„ฐ๋ฆฌ"
1185
- elif any(word in sf_words for word in words):
1186
- return "SF"
1187
- elif any(word in horror_words for word in words):
1188
- return "ํ˜ธ๋Ÿฌ"
1189
- elif any(word in romance_words for word in words):
1190
- return "๋กœ๋งจ์Šค"
1191
- else:
1192
- return "ํ˜„๋Œ€๊ทน"
1193
-
1194
- def generate_comprehensive_story_plan(selected_logline: str, categories_words: List[Tuple[str, str]], genre: str) -> Dict[str, str]:
1195
- """์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํฌ๊ด„์ ์ธ ์Šคํ† ๋ฆฌ ๊ธฐํš์•ˆ ์ƒ์„ฑ"""
1196
- system = WebNovelSystem()
1197
-
1198
- prompt = f"""๋‹ค์Œ ๋กœ๊ทธ๋ผ์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ {genre} ์žฅ๋ฅด์˜ ์™„์ „ํ•œ ์Šคํ† ๋ฆฌ ๊ธฐํš์•ˆ์„ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.
1199
-
1200
- ์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ: {selected_logline}
1201
-
1202
- ์‚ฌ์šฉ๋œ ๋‹จ์–ด:
1203
- 1. {categories_words[0][0]}: {categories_words[0][1]}
1204
- 2. {categories_words[1][0]}: {categories_words[1][1]}
1205
- 3. {categories_words[2][0]}: {categories_words[2][1]}
1206
-
1207
- ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”:
1208
-
1209
- [3๋‹จ๊ณ„ ๊ฐˆ๋“ฑ๊ตฌ์กฐ]
1210
- โ€ข ์™ธ์  ๊ฐˆ๋“ฑ: (์ฃผ์ธ๊ณต vs ์ ๋Œ€์ž/ํ™˜๊ฒฝ)
1211
- โ€ข ๋‚ด์  ๊ฐˆ๋“ฑ: (์ฃผ์ธ๊ณต์˜ ๋‚ด๋ฉด์  ๋”œ๋ ˆ๋งˆ)
1212
- โ€ข ๊ด€๊ณ„์  ๊ฐˆ๋“ฑ: (์ฃผ์ธ๊ณต๊ณผ ์ฃผ๋ณ€ ์ธ๋ฌผ๋“ค ๊ฐ„์˜ ๊ฐˆ๋“ฑ)
1213
-
1214
- [์Šคํ† ๋ฆฌ ์•„ํฌ - 3๋ง‰ ๊ตฌ์กฐ]
1215
- ์ œ1๋ง‰ (์„ค์ •):
1216
- - ์˜คํ”„๋‹: (์ฒซ ์žฅ๋ฉด ๋ฌ˜์‚ฌ)
1217
- - ์ผ์ƒ์˜ ์„ธ๊ณ„: (์ฃผ์ธ๊ณต์˜ ํ‰๋ฒ”ํ•œ ์ผ์ƒ)
1218
- - ์ด‰๋ฐœ ์‚ฌ๊ฑด: (๋ชจํ—˜์˜ ์‹œ์ž‘)
1219
- - ๊ฑฐ๋ถ€์™€ ๊ณ ๋ฏผ: (์ฃผ์ธ๊ณต์˜ ๋ง์„ค์ž„)
1220
-
1221
- ์ œ2๋ง‰ (๋Œ€๋ฆฝ):
1222
- - ์ฒซ ๋ฒˆ์งธ ๊ด€๋ฌธ: (์ฒซ ๋„์ „)
1223
- - ๋™๋ฃŒ์™€ ์ : (์กฐ๋ ฅ์ž์™€ ์ ๋Œ€์ž ๋“ฑ์žฅ)
1224
- - ์‹œ๋ จ์˜ ์—ฐ์†: (์ ์  ์‹ฌํ™”๋˜๋Š” ๊ฐˆ๋“ฑ)
1225
- - ๊ฐ€์งœ ์Šน๋ฆฌ/ํŒจ๋ฐฐ: (๋ฐ˜์ „ ์ง์ „)
1226
- - ์ ˆ๋ง์˜ ์ˆœ๊ฐ„: (์ตœ์ €์ )
1227
-
1228
- ์ œ3๋ง‰ (ํ•ด๊ฒฐ):
1229
- - ๊ฐ์„ฑ๊ณผ ๊นจ๋‹ฌ์Œ: (์ฃผ์ธ๊ณต์˜ ๋ณ€ํ™”)
1230
- - ์ตœ์ข… ๋Œ€๊ฒฐ: (ํด๋ผ์ด๋งฅ์Šค)
1231
- - ์ƒˆ๋กœ์šด ์„ธ๊ณ„: (๋ณ€ํ™”๋œ ์ผ์ƒ)
1232
-
1233
- [์บ๋ฆญํ„ฐ ์•„ํฌ]
1234
- ์ฃผ์ธ๊ณต:
1235
- โ€ข ์‹œ์ž‘์ : (์บ๋ฆญํ„ฐ์˜ ์ดˆ๊ธฐ ์ƒํƒœ)
1236
- โ€ข ์š•๋ง: (์›ํ•˜๋Š” ๊ฒƒ)
1237
- โ€ข ์•ฝ์ : (๊ทน๋ณตํ•ด์•ผ ํ•  ๋‹จ์ )
1238
- โ€ข ์„ฑ์žฅ: (์–ด๋–ป๊ฒŒ ๋ณ€ํ™”ํ•˜๋Š”๊ฐ€)
1239
- โ€ข ์ตœ์ข… ์ƒํƒœ: (๋ณ€ํ™” ํ›„ ๋ชจ์Šต)
1240
-
1241
- ์•ˆํƒ€๊ณ ๋‹ˆ์ŠคํŠธ:
1242
- โ€ข ์ •์ฒด: (๋ˆ„๊ตฌ/๋ฌด์—‡์ธ๊ฐ€)
1243
- โ€ข ๋™๊ธฐ: (์™œ ์ฃผ์ธ๊ณต๊ณผ ๋Œ€๋ฆฝํ•˜๋Š”๊ฐ€)
1244
- โ€ข ๊ฐ•์ : (๋ฌด์—‡์ด ์œ„ํ˜‘์ ์ธ๊ฐ€)
1245
-
1246
- [ํ•ต์‹ฌ ํ…Œ๋งˆ์™€ ๋ฉ”์‹œ์ง€]
1247
- โ€ข ํ‘œ๋ฉด ํ…Œ๋งˆ: (๊ฒ‰์œผ๋กœ ๋“œ๋Ÿฌ๋‚˜๋Š” ์ฃผ์ œ)
1248
- โ€ข ์‹ฌ์ธต ํ…Œ๋งˆ: (๊นŠ์€ ์˜๋ฏธ์˜ ์ฃผ์ œ)
1249
- โ€ข ์ฒ ํ•™์  ์งˆ๋ฌธ: (๊ด€๊ฐ์—๊ฒŒ ๋˜์ง€๋Š” ์งˆ๋ฌธ)
1250
-
1251
- [3๊ฐ€์ง€ ๋ฐ˜์ „ ํฌ์ธํŠธ]
1252
- 1. 1๋ง‰ ํ›„๋ฐ˜: (์ฒซ ๋ฒˆ์งธ ๋†€๋ผ์›€)
1253
- 2. 2๋ง‰ ์ค‘๋ฐ˜: (ํฐ ๋ฐ˜์ „)
1254
- 3. 3๋ง‰ ์ดˆ๋ฐ˜: (์ตœ์ข… ๋ฐ˜์ „)
1255
-
1256
- [๊ฐ๋™ ํฌ์ธํŠธ]
1257
- โ€ข ํฌ์ƒ์˜ ์ˆœ๊ฐ„: (์ฃผ์ธ๊ณต์ด ํฌ๊ธฐํ•˜๋Š” ๊ฒƒ)
1258
- โ€ข ํ™”ํ•ด์˜ ์ˆœ๊ฐ„: (๊ด€๊ณ„ ํšŒ๋ณต)
1259
- โ€ข ์„ฑ์žฅ์˜ ์ˆœ๊ฐ„: (์ง„์ •ํ•œ ์ž์•„ ๋ฐœ๊ฒฌ)"""
1260
-
1261
- result = system.call_llm_sync([{"role": "user", "content": prompt}], "planner")
1262
- return {"content": result}
1263
-
1264
- def generate_loglines_step(user_prompt="", progress=gr.Progress()):
1265
- """Step 1: 10๊ฐœ ๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ๋ฐ ํ‰๊ฐ€"""
1266
- global current_state
1267
-
1268
- try:
1269
- logger.info(f"=== ๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ์‹œ์ž‘ ===")
1270
- logger.info(f"์‚ฌ์šฉ์ž ์ž…๋ ฅ: {user_prompt[:100]}..." if user_prompt else "๋žœ๋ค ์ƒ์„ฑ ๋ชจ๋“œ")
1271
-
1272
- if user_prompt.strip():
1273
- # ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ
1274
- progress(0.1, desc="๐Ÿ” ์‚ฌ์šฉ์ž ์š”์ฒญ ๋ถ„์„ ์ค‘...")
1275
- logger.info("์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ ๋ถ„์„ ์‹œ์ž‘")
1276
-
1277
- current_state["user_prompt"] = user_prompt
1278
- system = WebNovelSystem()
1279
-
1280
- # ํ”„๋กฌํ”„ํŠธ ํ•ด์„
1281
- interpreted = system.interpret_user_prompt(user_prompt)
1282
- logger.info(f"ํ”„๋กฌํ”„ํŠธ ํ•ด์„ ์™„๋ฃŒ: {interpreted}")
1283
-
1284
- # ๋‹จ์–ด๊ฐ€ 3๊ฐœ ๋ฏธ๋งŒ์ธ ๊ฒฝ์šฐ ๋žœ๋ค์œผ๋กœ ์ฑ„์šฐ๊ธฐ
1285
- selected_words = interpreted["words"]
1286
- while len(selected_words) < 3:
1287
- remaining_categories = [cat for cat in complete_word_list.keys()
1288
- if cat not in [w[0] for w in selected_words]]
1289
- if remaining_categories:
1290
- category = random.choice(remaining_categories)
1291
- word = random.choice(complete_word_list[category])
1292
- selected_words.append((category, word))
1293
- logger.info(f"์ถ”๊ฐ€ ๋‹จ์–ด ์„ ํƒ: {category} - {word}")
1294
-
1295
- genre = interpreted["genre"] or determine_genre(selected_words)
1296
- special_instructions = interpreted["special_instructions"]
1297
-
1298
- # ์ƒํƒœ ์ €์žฅ
1299
- current_state["selected_words"] = selected_words
1300
- current_state["genre"] = genre
1301
-
1302
- # ์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ์™€ ๋‹จ์–ด ํ‘œ์‹œ
1303
- category_display = f"### ๐ŸŽฒ ๋ถ„์„ ๊ฒฐ๊ณผ\n\n**์‚ฌ์šฉ์ž ์š”์ฒญ**: {user_prompt}\n\n**์žฅ๋ฅด**: {genre}\n\n**์ถ”์ถœ๋œ ๋‹จ์–ด**:\n" + "\n".join([f"โ€ข {cat}: **{word}**" for cat, word in selected_words])
1304
- if special_instructions:
1305
- category_display += f"\n\n**ํŠน๋ณ„ ์ง€์‹œ์‚ฌํ•ญ**: {special_instructions}"
1306
-
1307
- progress(0.3, desc="๐Ÿ“ ๋‹จ์–ด ์ถ”์ถœ ์™„๋ฃŒ...")
1308
-
1309
- # ํ”„๋กฌํ”„ํŠธ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ
1310
- progress(0.6, desc="๐Ÿค– 10๊ฐœ์˜ ๋กœ๊ทธ๋ผ์ธ์„ ์ƒ์„ฑํ•˜๊ณ  ํ‰๊ฐ€ํ•˜๋Š” ์ค‘...")
1311
- logger.info("๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ์‹œ์ž‘")
1312
-
1313
- logline_result = system.generate_10_loglines_with_prompt(user_prompt, selected_words, genre, special_instructions)
1314
- logger.info(f"๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ์™„๋ฃŒ: {len(logline_result.get('loglines', []))}๊ฐœ")
1315
-
1316
- else:
1317
- # ๋žœ๋ค ์ƒ์„ฑ
1318
- progress(0.1, desc="๐ŸŽฒ ๋žœ๋ค ๋‹จ์–ด ์„ ํƒ ์ค‘...")
1319
- logger.info("๋žœ๋ค ๋ชจ๋“œ ์‹œ์ž‘")
1320
-
1321
- # 3๊ฐœ์˜ ๋‹ค๋ฅธ ์นดํ…Œ๊ณ ๋ฆฌ ๋žœ๋ค ์„ ํƒ
1322
- selected_categories = random.sample(list(complete_word_list.keys()), 3)
1323
- logger.info(f"์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ: {selected_categories}")
1324
-
1325
- # ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ ๋žœ๋ค ๋‹จ์–ด ์„ ํƒ
1326
- selected_words = []
1327
- for category in selected_categories:
1328
- word = random.choice(complete_word_list[category])
1329
- selected_words.append((category, word))
1330
- logger.info(f"์„ ํƒ๋œ ๋‹จ์–ด: {category} - {word}")
1331
-
1332
- # ์žฅ๋ฅด ๊ฒฐ์ •
1333
- genre = determine_genre(selected_words)
1334
- logger.info(f"๊ฒฐ์ •๋œ ์žฅ๋ฅด: {genre}")
1335
-
1336
- # ์ƒํƒœ ์ €์žฅ
1337
- current_state["selected_words"] = selected_words
1338
- current_state["genre"] = genre
1339
- current_state["user_prompt"] = ""
1340
-
1341
- # ์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ์™€ ๋‹จ์–ด ํ‘œ์‹œ
1342
- category_display = f"### ๐ŸŽฒ ์„ ํƒ ๊ฒฐ๊ณผ\n\n**์žฅ๋ฅด**: {genre}\n\n**์„ ํƒ๋œ ๋‹จ์–ด**:\n" + "\n".join([f"โ€ข {cat}: **{word}**" for cat, word in selected_words])
1343
-
1344
- progress(0.3, desc="๐Ÿ“ ๋‹จ์–ด ์„ ํƒ ์™„๋ฃŒ...")
1345
-
1346
- # 10๊ฐœ ๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ๋ฐ ํ‰๊ฐ€
1347
- progress(0.6, desc="๐Ÿค– 10๊ฐœ์˜ ๋กœ๊ทธ๋ผ์ธ์„ ์ƒ์„ฑํ•˜๊ณ  ํ‰๊ฐ€ํ•˜๋Š” ์ค‘...")
1348
- logger.info("๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ์‹œ์ž‘")
1349
-
1350
- system = WebNovelSystem()
1351
- logline_result = system.generate_loglines_and_evaluate(selected_words)
1352
- logger.info(f"๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ์™„๋ฃŒ: {len(logline_result.get('loglines', []))}๊ฐœ")
1353
-
1354
- # ํ‰๊ฐ€ ์ค‘ ํ‘œ์‹œ
1355
- progress(0.8, desc="โญ ๋กœ๊ทธ๋ผ์ธ ํ‰๊ฐ€ ์ค‘...")
1356
-
1357
- # ์ƒํƒœ ์ €์žฅ
1358
- current_state["loglines"] = logline_result.get('loglines', [])
1359
- current_state["evaluations"] = logline_result.get('evaluations', [])
1360
- current_state["ai_selected"] = logline_result.get('selected', '')
1361
- current_state["ai_reason"] = logline_result.get('reason', '')
1362
-
1363
- # ๋กœ๊ทธ๋ผ์ธ ์ •๋ฆฌ
1364
- loglines_display = "### ๐Ÿ“ ์ƒ์„ฑ๋œ 10๊ฐœ ๋กœ๊ทธ๋ผ์ธ\n\n"
1365
- for i, logline in enumerate(logline_result.get('loglines', [])):
1366
- loglines_display += f"**๋กœ๊ทธ๋ผ์ธ {i+1}**: {logline}\n\n"
1367
-
1368
- # AI ์ถ”์ฒœ ํ‘œ์‹œ
1369
- if logline_result.get('selected'):
1370
- loglines_display += f"### ๐Ÿค– AI ์ถ”์ฒœ\n\n**{logline_result['selected']}**\n\n"
1371
- if logline_result.get('reason'):
1372
- loglines_display += f"**์ถ”์ฒœ ์ด์œ **: {logline_result['reason']}"
1373
-
1374
- # ๋“œ๋กญ๋‹ค์šด ์„ ํƒ์ง€ ์ƒ์„ฑ
1375
- choices = [f"๋กœ๊ทธ๋ผ์ธ {i+1}: {logline[:50]}..." for i, logline in enumerate(logline_result.get('loglines', []))]
1376
-
1377
- progress(1.0, desc="โœ… ์™„๋ฃŒ!")
1378
- logger.info("๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ")
1379
-
1380
- return (
1381
- category_display,
1382
- loglines_display,
1383
- gr.update(visible=True, choices=choices, value=choices[0] if choices else None),
1384
- gr.update(visible=True),
1385
- gr.update(visible=True)
1386
- )
1387
-
1388
- except Exception as e:
1389
- logger.error(f"๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ์˜ค๋ฅ˜: {str(e)}", exc_info=True)
1390
- error_msg = f"### โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ\n\n{str(e)}\n\n์ƒ์„ธ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”."
1391
- return (
1392
- error_msg,
1393
- "",
1394
- gr.update(visible=False, choices=[]),
1395
- gr.update(visible=False),
1396
- gr.update(visible=False)
1397
- )
1398
-
1399
- def generate_story_from_selection(selected_logline_dropdown):
1400
- """Step 2: ์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ"""
1401
- global current_state
1402
-
1403
- logger.info(f"=== ์Šคํ† ๋ฆฌ ์ƒ์„ฑ ์‹œ์ž‘ ===")
1404
- logger.info(f"์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ: {selected_logline_dropdown}")
1405
-
1406
- if not selected_logline_dropdown:
1407
- return "### โš ๏ธ ๋กœ๊ทธ๋ผ์ธ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”", "", "", ""
1408
-
1409
- try:
1410
- # ์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ ๋ฒˆํ˜ธ ์ถ”์ถœ
1411
- logline_num = int(selected_logline_dropdown.split(':')[0].split()[-1]) - 1
1412
- selected_logline = current_state["loglines"][logline_num]
1413
- logger.info(f"๋กœ๊ทธ๋ผ์ธ ๋ฒˆํ˜ธ {logline_num + 1} ์„ ํƒ๋จ")
1414
-
1415
- # ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
1416
- progress_display = f"### โณ ์Šคํ† ๋ฆฌ ๊ตฌ์„ฑ ์ค‘...\n\n**์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ**: {selected_logline}\n\n**์ง„ํ–‰ ์ค‘**: AI๊ฐ€ ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."
1417
-
1418
- # ์Šคํ† ๋ฆฌ ๊ธฐํš์•ˆ ์ƒ์„ฑ
1419
- logger.info("์Šคํ† ๋ฆฌ ๊ธฐํš์•ˆ ์ƒ์„ฑ ์‹œ์ž‘")
1420
- story_plan = generate_comprehensive_story_plan(
1421
- selected_logline,
1422
- current_state["selected_words"],
1423
- current_state["genre"]
1424
- )
1425
-
1426
- if "error" in story_plan:
1427
- logger.error(f"์Šคํ† ๋ฆฌ ์ƒ์„ฑ ์˜ค๋ฅ˜: {story_plan['error']}")
1428
- return f"### โŒ ์˜ค๋ฅ˜\n\n{story_plan['error']}", "", "", ""
1429
-
1430
- # ๊ธฐํš์•ˆ ๋‚ด์šฉ ํŒŒ์‹ฑ ๋ฐ ์ •๋ฆฌ
1431
- full_plan = story_plan.get('content', '')
1432
- logger.info(f"์Šคํ† ๋ฆฌ ๊ธฐํš์•ˆ ์ƒ์„ฑ ์™„๋ฃŒ - ๊ธธ์ด: {len(full_plan)} ๋ฌธ์ž")
1433
-
1434
- # ์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ ํ‘œ์‹œ
1435
- selected_display = f"### ๐ŸŽฌ ์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ\n\n**๋กœ๊ทธ๋ผ์ธ {logline_num + 1}**: {selected_logline}"
1436
-
1437
- # ์„น์…˜๋ณ„๋กœ ๋ถ„๋ฆฌ
1438
- sections = {
1439
- "๊ฐˆ๋“ฑ๊ตฌ์กฐ": "",
1440
- "์Šคํ† ๋ฆฌ์•„ํฌ": "",
1441
- "์บ๋ฆญํ„ฐ์™€ํ…Œ๋งˆ": ""
1442
- }
1443
-
1444
- # ๊ฐ„๋‹จํ•œ ๋ถ„๋ฆฌ ๋กœ์ง
1445
- if "[3๋‹จ๊ณ„ ๊ฐˆ๋“ฑ๊ตฌ์กฐ]" in full_plan:
1446
- parts = full_plan.split("[์Šคํ† ๋ฆฌ ์•„ํฌ")
1447
- sections["๊ฐˆ๋“ฑ๊ตฌ์กฐ"] = parts[0]
1448
-
1449
- if len(parts) > 1:
1450
- parts2 = parts[1].split("[์บ๋ฆญํ„ฐ ์•„ํฌ]")
1451
- sections["์Šคํ† ๋ฆฌ์•„ํฌ"] = "[์Šคํ† ๋ฆฌ ์•„ํฌ" + parts2[0]
1452
-
1453
- if len(parts2) > 1:
1454
- sections["์บ๋ฆญํ„ฐ์™€ํ…Œ๋งˆ"] = "[์บ๋ฆญํ„ฐ ์•„ํฌ]\n" + parts2[1]
1455
-
1456
- logger.info("์Šคํ† ๋ฆฌ ์ƒ์„ฑ ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ")
1457
- return selected_display, sections["๊ฐˆ๋“ฑ๊ตฌ์กฐ"], sections["์Šคํ† ๋ฆฌ์•„ํฌ"], sections["์บ๋ฆญํ„ฐ์™€ํ…Œ๋งˆ"]
1458
-
1459
- except Exception as e:
1460
- logger.error(f"์Šคํ† ๋ฆฌ ์ƒ์„ฑ ์˜ค๋ฅ˜: {str(e)}", exc_info=True)
1461
- return f"### โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ\n\n{str(e)}", "", "", ""
1462
-
1463
- def generate_direct_story(user_prompt="", progress=gr.Progress()):
1464
- """์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ๋กœ ์ง์ ‘ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ"""
1465
- global current_state
1466
-
1467
- if not user_prompt.strip():
1468
- return "### โš ๏ธ ์˜ค๋ฅ˜\n\n์Šคํ† ๋ฆฌ ์ƒ์„ฑ์„ ์œ„ํ•œ ์š”์ฒญ์‚ฌํ•ญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", "", "", ""
1469
-
1470
- try:
1471
- progress(0.5, desc="์‚ฌ์šฉ์ž ์š”์ฒญ์„ ๋ถ„์„ํ•˜์—ฌ ์Šคํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ค‘... (30์ดˆ ์†Œ์š”)")
1472
-
1473
- system = WebNovelSystem()
1474
-
1475
- # ์Šคํ† ๋ฆฌ ์ง์ ‘ ์ƒ์„ฑ
1476
- story_result = system.generate_story_from_prompt(user_prompt)
1477
-
1478
- # ๊ธฐํš์•ˆ ๋‚ด์šฉ ํŒŒ์‹ฑ ๋ฐ ์ •๋ฆฌ
1479
- full_plan = story_result.get('content', '')
1480
-
1481
- # ์š”์ฒญ์‚ฌํ•ญ ๏ฟฝ๏ฟฝ๏ฟฝ์‹œ
1482
- request_display = f"### ๐Ÿ“‹ ์‚ฌ์šฉ์ž ์š”์ฒญ\n\n**์ž…๋ ฅ๋œ ์š”์ฒญ**: {user_prompt}"
1483
-
1484
- # ์„น์…˜๋ณ„๋กœ ๋ถ„๋ฆฌ
1485
- sections = {
1486
- "๊ฐˆ๋“ฑ๊ตฌ์กฐ": "",
1487
- "์Šคํ† ๋ฆฌ์•„ํฌ": "",
1488
- "์บ๋ฆญํ„ฐ์™€ํ…Œ๋งˆ": ""
1489
- }
1490
-
1491
- # ์„น์…˜ ๋ถ„๋ฆฌ
1492
- if "[์Šคํ† ๋ฆฌ ๊ฐœ์š”]" in full_plan:
1493
- parts = full_plan.split("[3๋‹จ๊ณ„ ๊ฐˆ๋“ฑ๊ตฌ์กฐ]")
1494
- overview = parts[0]
1495
-
1496
- if len(parts) > 1:
1497
- parts2 = parts[1].split("[์Šคํ† ๋ฆฌ ์•„ํฌ")
1498
- sections["๊ฐˆ๋“ฑ๊ตฌ์กฐ"] = "[3๋‹จ๊ณ„ ๊ฐˆ๋“ฑ๊ตฌ์กฐ]\n" + parts2[0]
1499
-
1500
- if len(parts2) > 1:
1501
- parts3 = parts2[1].split("[์บ๋ฆญํ„ฐ ์•„ํฌ]")
1502
- sections["์Šคํ† ๋ฆฌ์•„ํฌ"] = "[์Šคํ† ๋ฆฌ ์•„ํฌ" + parts3[0]
1503
-
1504
- if len(parts3) > 1:
1505
- sections["์บ๋ฆญํ„ฐ์™€ํ…Œ๋งˆ"] = "[์บ๋ฆญํ„ฐ ์•„ํฌ]\n" + parts3[1]
1506
-
1507
- progress(1.0, desc="์™„๋ฃŒ!")
1508
-
1509
- return request_display + "\n\n" + overview, sections["๊ฐˆ๋“ฑ๊ตฌ์กฐ"], sections["์Šคํ† ๋ฆฌ์•„ํฌ"], sections["์บ๋ฆญํ„ฐ์™€ํ…Œ๋งˆ"]
1510
-
1511
- except Exception as e:
1512
- return f"### โš ๏ธ ์˜ค๋ฅ˜\n\n์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}", "", "", ""
1513
-
1514
- # --- Gradio interface ---
1515
- def create_interface():
1516
- with gr.Blocks(theme=gr.themes.Soft(), title="K-Story AI Studio") as interface:
1517
- # Header with OAuth login status
1518
- with gr.Row():
1519
- gr.HTML("""
1520
- <div style="text-align: center; margin-bottom: 2rem;">
1521
- <h1 style="font-size: 3rem;">๐Ÿ“š K-Story AI Studio</h1>
1522
- <p style="font-size: 1.2rem;">AI ๊ธฐ๋ฐ˜ ํ•œ๊ตญํ˜• ์›น์†Œ์„ค ์ฐฝ์ž‘ ์‹œ์Šคํ…œ</p>
1523
- </div>
1524
- """)
1525
-
1526
- with gr.Row():
1527
- login_status = gr.Markdown("๐Ÿ”’ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
1528
- gr.LoginButton()
1529
-
1530
- # State management
1531
- user_state = gr.State(None)
1532
- current_session = gr.State(None)
1533
- story_structure_state = gr.State(None)
1534
-
1535
- with gr.Tab("๐Ÿ“ ์ƒˆ ์ž‘ํ’ˆ ์‹œ์ž‘"):
1536
- with gr.Column():
1537
- # Step 1 UI (from paste.txt)
1538
- gr.Markdown("""
1539
- # ๐ŸŽฌ AI ์Šคํ† ๋ฆฌ ๊ธฐํš ์‹œ์Šคํ…œ 3.0
1540
-
1541
- ### ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ฐ˜์˜ํ•˜์—ฌ ๋งž์ถคํ˜• ์Šคํ† ๋ฆฌ๋ฅผ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค!
1542
-
1543
- #### ๐Ÿš€ ์ฃผ์š” ๊ธฐ๋Šฅ
1544
- 1. **์ž์œ ๋กœ์šด ์ž…๋ ฅ**: ์›ํ•˜๋Š” ์Šคํ† ๋ฆฌ์˜ ๋ถ„์œ„๊ธฐ, ์žฅ๋ฅด, ํ‚ค์›Œ๋“œ ๋“ฑ์„ ์ž์œ ๋กญ๊ฒŒ ์ž…๋ ฅ
1545
- 2. **10๊ฐœ ๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ**: ์ž…๋ ฅ๋œ ์š”์ฒญ์„ ๋ฐ˜์˜ํ•œ ๋‹ค์–‘ํ•œ ๋กœ๊ทธ๋ผ์ธ ์ œ์‹œ
1546
- 3. **์ง์ ‘ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ**: ๋กœ๊ทธ๋ผ์ธ ๋‹จ๊ณ„๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋ฐ”๋กœ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ ๊ฐ€๋Šฅ
1547
- 4. **AI ํ‰๊ฐ€ ์‹œ์Šคํ…œ**: ๋…์ฐฝ์„ฑ, ํฅ๋ฏธ๋„, ์„œ์‚ฌ์„ฑ, ๊ฐœ์—ฐ์„ฑ ๊ธฐ์ค€์œผ๋กœ ํ‰๊ฐ€
1548
- """)
1549
-
1550
- with gr.Row():
1551
- with gr.Column(scale=3):
1552
- user_prompt_input = gr.Textbox(
1553
- label="๐Ÿ–Š๏ธ ์›ํ•˜๋Š” ์Šคํ† ๋ฆฌ ์š”์ฒญ์‚ฌํ•ญ์„ ์ž…๋ ฅํ•˜์„ธ์š” (์„ ํƒ์‚ฌํ•ญ)",
1554
- placeholder="์˜ˆ์‹œ: '์šฐ์ฃผ๋ฅผ ๋ฐฐ๊ฒฝ์œผ๋กœ ํ•œ ๊ฐ๋™์ ์ธ ๊ฐ€์กฑ ์ด์•ผ๊ธฐ', 'ํƒ์ •์ด ๋“ฑ์žฅํ•˜๋Š” ์ฝ”๋ฏธ๋”” ๋ฏธ์Šคํ„ฐ๋ฆฌ', '์‹œ๊ฐ„์—ฌํ–‰๊ณผ ์ฒซ์‚ฌ๋ž‘์ด ๊ฒฐํ•ฉ๋œ SF ๋กœ๋งจ์Šค'",
1555
- lines=2
1556
- )
1557
-
1558
- with gr.Row():
1559
- with gr.Column(scale=1):
1560
- random_btn = gr.Button("๐ŸŽฒ ๋žœ๋ค ๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑํ•˜๊ธฐ", variant="primary", size="lg")
1561
- with gr.Column(scale=1):
1562
- generate_btn = gr.Button("โœ๏ธ ์š”์ฒญ์‚ฌํ•ญ ๋ฐ˜์˜ ๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑํ•˜๊ธฐ", variant="primary", size="lg")
1563
- with gr.Column(scale=1):
1564
- direct_story_btn = gr.Button("๐Ÿ“– ๋ฐ”๋กœ ์Šคํ† ๋ฆฌ ์ƒ์„ฑํ•˜๊ธฐ", variant="secondary", size="lg")
1565
-
1566
- gr.Markdown("---")
1567
-
1568
- with gr.Row():
1569
- with gr.Column(scale=1):
1570
- category_output = gr.Markdown(value="", label="์„ ํƒ๋œ ๋‹จ์–ด")
1571
-
1572
- with gr.Row():
1573
- with gr.Column(scale=1):
1574
- loglines_output = gr.Markdown(value="", label="๋กœ๊ทธ๋ผ์ธ ์ƒ์„ฑ ๋ฐ ํ‰๊ฐ€")
1575
-
1576
- with gr.Row():
1577
- with gr.Column(scale=1):
1578
- logline_dropdown = gr.Dropdown(
1579
- label="๐Ÿ“Œ ์›ํ•˜๋Š” ๋กœ๊ทธ๋ผ์ธ์„ ์„ ํƒํ•˜์„ธ์š”",
1580
- choices=[],
1581
- visible=False,
1582
- interactive=True
1583
- )
1584
- generate_story_btn = gr.Button(
1585
- "๐ŸŽฌ ์„ ํƒํ•œ ๋กœ๊ทธ๋ผ์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ ์ƒ์„ฑํ•˜๊ธฐ",
1586
- variant="secondary",
1587
- visible=False
1588
- )
1589
-
1590
- with gr.Row():
1591
- with gr.Column(scale=1):
1592
- story_header = gr.Markdown("### ๐Ÿ“– ์ƒ์„ฑ๋œ ์Šคํ† ๋ฆฌ ๊ธฐํš์•ˆ", visible=False)
1593
-
1594
- with gr.Row():
1595
- with gr.Column(scale=1):
1596
- selected_logline_output = gr.Markdown(value="", label="์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ")
1597
-
1598
- with gr.Row():
1599
- with gr.Column(scale=1):
1600
- conflicts_output = gr.Markdown(value="", label="๊ฐˆ๋“ฑ๊ตฌ์กฐ")
1601
-
1602
- with gr.Row():
1603
- with gr.Column(scale=1):
1604
- story_arc_output = gr.Markdown(value="", label="์Šคํ† ๋ฆฌ ์•„ํฌ")
1605
-
1606
- with gr.Row():
1607
- with gr.Column(scale=1):
1608
- final_details_output = gr.Markdown(value="", label="์บ๋ฆญํ„ฐ, ํ…Œ๋งˆ, ๋ฐ˜์ „")
1609
-
1610
- # Step 2-3: Web novel writing
1611
- gr.Markdown("---")
1612
- gr.Markdown("### ๐Ÿ“š ์›น์†Œ์„ค ์ž‘์„ฑ")
1613
-
1614
- genre_select = gr.Radio(
1615
- choices=list(WEBNOVEL_GENRES.keys()),
1616
- label="์žฅ๋ฅด ์„ ํƒ",
1617
- value="๋กœ๋งจ์Šค",
1618
- visible=False
1619
- )
1620
-
1621
- generate_webnovel_btn = gr.Button("๐Ÿ“– ์›น์†Œ์„ค ๊ตฌ์„ฑ ์ƒ์„ฑ", variant="primary", visible=False)
1622
- webnovel_structure_output = gr.Markdown(visible=False)
1623
-
1624
- write_first_btn = gr.Button("โœ๏ธ 1ํ™” ์ž‘์„ฑ", variant="primary", visible=False)
1625
- episode_output = gr.Markdown(visible=False)
1626
- write_next_btn = gr.Button("โžก๏ธ ๋‹ค์Œํ™” ์ž‘์„ฑ", variant="secondary", visible=False)
1627
- episode_counter = gr.State(1)
1628
-
1629
- with gr.Tab("๐Ÿ“š ๋‚ด ์ž‘ํ’ˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ"):
1630
- refresh_library_btn = gr.Button("๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ")
1631
- library_output = gr.HTML()
1632
-
1633
- with gr.Row():
1634
- selected_novel = gr.Dropdown(label="์ž‘ํ’ˆ ์„ ํƒ", visible=False)
1635
- continue_writing_btn = gr.Button("์ด์–ด์“ฐ๊ธฐ", visible=False)
1636
- preview_btn = gr.Button("๋ฏธ๋ฆฌ๋ณด๊ธฐ", visible=False)
1637
-
1638
- # Event handlers
1639
-
1640
- def check_login(request: gr.Request):
1641
- """Check if user is logged in via OAuth"""
1642
- logger.info("=== ๋กœ๊ทธ์ธ ์ฒดํฌ ===")
1643
-
1644
- # OAuth ์œ ์ € ํ™•์ธ
1645
- user_info = None
1646
- if request:
1647
- user_info = request.username
1648
- logger.info(f"OAuth username: {user_info}")
1649
-
1650
- # Hugging Face Spaces์˜ ๊ฒฝ์šฐ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ํ™•์ธ
1651
- if not user_info and hasattr(request, 'headers'):
1652
- # ํ—ค๋”์—์„œ ์œ ์ € ์ •๋ณด ์ถ”์ถœ ์‹œ๋„
1653
- auth_header = request.headers.get('x-user-info', '')
1654
- if auth_header:
1655
- user_info = auth_header
1656
- logger.info(f"Header user info: {user_info}")
1657
-
1658
- if user_info:
1659
- # OAuth ๋กœ๊ทธ์ธ ์„ฑ๊ณต
1660
- WebNovelDatabase.create_or_update_user(
1661
- user_id=user_info,
1662
- username=user_info
1663
- )
1664
- return f"๐Ÿ‘ค {user_info}๋‹˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!", {"user_id": user_info, "username": user_info}
1665
- else:
1666
- # ๊ฐœ๋ฐœ/๋ฐ๋ชจ ๋ชจ๋“œ - ๊ณ ์œ  ์„ธ์…˜ ID ์ƒ์„ฑ
1667
- session_id = hashlib.md5(f"guest_{datetime.now()}_{random.randint(1000,9999)}".encode()).hexdigest()[:8]
1668
- guest_user = f"guest_{session_id}"
1669
-
1670
- WebNovelDatabase.create_or_update_user(
1671
- user_id=guest_user,
1672
- username=f"๊ฒŒ์ŠคํŠธ_{session_id}"
1673
- )
1674
-
1675
- logger.info(f"๊ฒŒ์ŠคํŠธ ์œ ์ € ์ƒ์„ฑ: {guest_user}")
1676
- return f"๐ŸŽญ ๊ฒŒ์ŠคํŠธ ๋ชจ๋“œ (ID: {session_id})", {"user_id": guest_user, "username": f"๊ฒŒ์ŠคํŠธ_{session_id}"}
1677
-
1678
-
1679
- def show_genre_after_story(story_output, user_state):
1680
- """์Šคํ† ๋ฆฌ ์ƒ์„ฑ ํ›„ ์žฅ๋ฅด ์„ ํƒ ํ‘œ์‹œ - user_state ์œ ์ง€"""
1681
- logger.info(f"์Šคํ† ๋ฆฌ ์ƒ์„ฑ ์™„๋ฃŒ ์ฒดํฌ: {bool(story_output and '###' in story_output)}")
1682
- logger.info(f"user_state ์œ ์ง€: {user_state}")
1683
- if story_output and "###" in story_output:
1684
- return gr.update(visible=True), gr.update(visible=True), user_state
1685
- return gr.update(visible=False), gr.update(visible=False), user_state
1686
-
1687
-
1688
- def generate_webnovel_structure(selected_logline_dropdown, genre, request: gr.Request):
1689
- """Generate web novel structure from logline and genre"""
1690
- logger.info(f"=== ์›น์†Œ์„ค ๊ตฌ์กฐ ์ƒ์„ฑ ์‹œ์ž‘ ===")
1691
- logger.info(f"์žฅ๋ฅด: {genre}")
1692
-
1693
- # ์œ ์ € ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (OAuth ๋˜๋Š” ๊ฒŒ์ŠคํŠธ)
1694
- user_state = get_user_state(request)
1695
-
1696
- if not user_state:
1697
- logger.error("์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Œ")
1698
- return gr.update(value="### โš ๏ธ ์˜ค๋ฅ˜\n\nํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด์ฃผ์„ธ์š”."), gr.update(), None
1699
-
1700
- logger.info(f"ํ˜„์žฌ ์‚ฌ์šฉ์ž: {user_state}")
1701
-
1702
- try:
1703
- # ๋กœ๊ทธ๋ผ์ธ ์„ ํƒ ํ™•์ธ
1704
- if not selected_logline_dropdown:
1705
- selected_logline = current_state.get("loglines", [""])[0] if current_state.get("loglines") else ""
1706
- logger.info("์ง์ ‘ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ ๋ชจ๋“œ")
1707
- else:
1708
- logline_num = int(selected_logline_dropdown.split(':')[0].split()[-1]) - 1
1709
- selected_logline = current_state["loglines"][logline_num]
1710
- logger.info(f"๋“œ๋กญ๋‹ค์šด ์„ ํƒ ๋ชจ๋“œ - ๋กœ๊ทธ๋ผ์ธ ๋ฒˆํ˜ธ: {logline_num + 1}")
1711
-
1712
- if not selected_logline:
1713
- logger.error("์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ์ด ์—†์Œ")
1714
- return gr.update(value="### โš ๏ธ ๋กœ๊ทธ๋ผ์ธ์„ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”"), gr.update(), None
1715
-
1716
- logger.info(f"์„ ํƒ๋œ ๋กœ๊ทธ๋ผ์ธ: {selected_logline[:50]}...")
1717
-
1718
- # ์›น์†Œ์„ค ๊ตฌ์กฐ ์ƒ์„ฑ
1719
- system = WebNovelSystem()
1720
- story = system.generate_story_structure(selected_logline, genre)
1721
- logger.info(f"์Šคํ† ๋ฆฌ ๊ตฌ์กฐ ์ƒ์„ฑ ์™„๋ฃŒ - ์ œ๋ชฉ: {story.title}")
1722
-
1723
- # ์„ธ์…˜ ์ƒ์„ฑ
1724
- session_id = WebNovelDatabase.create_session(
1725
- user_id=user_state['user_id'],
1726
- genre=genre,
1727
- title=story.title,
1728
- logline=selected_logline
1729
- )
1730
- logger.info(f"์„ธ์…˜ ์ƒ์„ฑ ์™„๋ฃŒ: {session_id}")
1731
-
1732
- # ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ ์ €์žฅ
1733
- WebNovelDatabase.save_story_structure(session_id, story)
1734
- logger.info("์Šคํ† ๋ฆฌ ๊ตฌ์กฐ DB ์ €์žฅ ์™„๋ฃŒ")
1735
-
1736
- # ์ถœ๋ ฅ ํฌ๋งท
1737
- output = f"## ๐ŸŽญ {story.title}\n\n"
1738
- output += f"**์ž‘๊ฐ€:** {user_state.get('username', 'Unknown')}\n"
1739
- output += f"**์žฅ๋ฅด:** {genre}\n\n"
1740
- output += f"**์‹œ๋†‰์‹œ์Šค:**\n{story.synopsis}\n\n"
1741
- output += f"### โœ… 40ํ™” ๊ตฌ์„ฑ ์™„๋ฃŒ!\n\n"
1742
- output += "์ด์ œ **1ํ™” ์ž‘์„ฑ** ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์›น์†Œ์„ค์„ ์‹œ์ž‘ํ•˜์„ธ์š”."
1743
-
1744
- # ์„ธ์…˜ ์ •๋ณด์— user_state ํฌํ•จ
1745
- session_data = {
1746
- "session_id": session_id,
1747
- "story": story,
1748
- "user_state": user_state # ์œ ์ € ์ •๋ณด ํฌํ•จ
1749
- }
1750
-
1751
- return (
1752
- gr.update(value=output, visible=True),
1753
- gr.update(visible=True),
1754
- session_data
1755
- )
1756
-
1757
- except Exception as e:
1758
- logger.error(f"์›น์†Œ์„ค ๊ตฌ์กฐ ์ƒ์„ฑ ์˜ค๋ฅ˜: {str(e)}", exc_info=True)
1759
- return gr.update(value=f"### โŒ ์˜ค๋ฅ˜\n\n{str(e)}"), gr.update(), None
1760
-
1761
-
1762
-
1763
- def write_episode_handler(current_session, episode_num=1, request: gr.Request = None):
1764
- """Write an episode"""
1765
- logger.info(f"=== ์—ํ”ผ์†Œ๋“œ ์ž‘์„ฑ ์‹œ์ž‘ - {episode_num}ํ™” ===")
1766
-
1767
- if not current_session:
1768
- logger.error("current_session์ด None")
1769
- return gr.update(value="### โš ๏ธ ์Šคํ† ๋ฆฌ ๊ตฌ์„ฑ์„ ๋จผ์ € ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”"), gr.update(), episode_num
1770
-
1771
- # ์„ธ์…˜์— ์ €์žฅ๋œ user_state ์‚ฌ์šฉ ๋˜๋Š” request์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
1772
- user_state = current_session.get('user_state')
1773
- if not user_state:
1774
- user_state = get_user_state(request)
1775
-
1776
- if not user_state:
1777
- logger.error("์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Œ")
1778
- return gr.update(value="### โš ๏ธ ์˜ค๋ฅ˜\n\nํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด์ฃผ์„ธ์š”."), gr.update(), episode_num
1779
-
1780
- logger.info(f"ํ˜„์žฌ ์‚ฌ์šฉ์ž: {user_state}")
1781
-
1782
- try:
1783
- # ์„ธ์…˜ ์ •๋ณด ํ™•์ธ
1784
- session_id = current_session.get('session_id')
1785
- if not session_id:
1786
- logger.error("session_id๊ฐ€ ์—†์Œ")
1787
- return gr.update(value="### โš ๏ธ ์„ธ์…˜ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"), gr.update(), episode_num
1788
-
1789
- # ์†Œ์œ ๊ถŒ ํ™•์ธ
1790
- logger.info(f"์†Œ์œ ๊ถŒ ํ™•์ธ ์ค‘ - session_id: {session_id}, user_id: {user_state['user_id']}")
1791
- if not WebNovelDatabase.check_ownership(session_id, user_state['user_id']):
1792
- logger.warning("์†Œ์œ ๊ถŒ ์—†์Œ")
1793
- return gr.update(value="### โš ๏ธ ์ž‘ํ’ˆ ์†Œ์œ ๊ถŒ์ด ์—†์Šต๋‹ˆ๋‹ค"), gr.update(), episode_num
1794
-
1795
- logger.info("์†Œ์œ ๊ถŒ ํ™•์ธ ์™„๋ฃŒ")
1796
-
1797
- # ์—ํ”ผ์†Œ๋“œ ์ž‘์„ฑ
1798
- system = WebNovelSystem()
1799
- previous_episodes = WebNovelDatabase.get_episodes(session_id)
1800
- prev_contents = [ep['content'] for ep in previous_episodes]
1801
-
1802
- story = current_session.get('story')
1803
- if not story:
1804
- logger.error("story ์ •๋ณด๊ฐ€ ์—†์Œ")
1805
- return gr.update(value="### โš ๏ธ ์Šคํ† ๋ฆฌ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"), gr.update(), episode_num
1806
-
1807
- episode_data = system.write_episode(story, episode_num, prev_contents)
1808
- logger.info(f"์—ํ”ผ์†Œ๋“œ ์ž‘์„ฑ ์™„๋ฃŒ - {episode_data['word_count']} ๋‹จ์–ด")
1809
-
1810
- # DB ์ €์žฅ
1811
- WebNovelDatabase.save_episode(
1812
- session_id,
1813
- episode_num,
1814
- episode_data['title'],
1815
- episode_data['content'],
1816
- episode_data['hook']
1817
- )
1818
-
1819
- # ์ถœ๋ ฅ ํฌ๋งท
1820
- output = f"## {episode_data['title']}\n\n"
1821
- output += episode_data['content']
1822
- output += f"\n\n---\n"
1823
- output += f"๐Ÿ“Š **ํ†ต๊ณ„**\n"
1824
- output += f"- ๋‹จ์–ด ์ˆ˜: {episode_data['word_count']}\n"
1825
- output += f"- ์ง„ํ–‰๋ฅ : {episode_num}/40ํ™”\n"
1826
- output += f"- ์ž‘๊ฐ€: {user_state.get('username', 'Unknown')}\n"
1827
- output += f"\n๐Ÿช **๋‹ค์Œ ํ™” ์˜ˆ๊ณ **\n> {episode_data['hook']}"
1828
-
1829
- next_visible = episode_num < 40
1830
-
1831
- return (
1832
- gr.update(value=output, visible=True),
1833
- gr.update(visible=next_visible),
1834
- episode_num
1835
- )
1836
-
1837
- except Exception as e:
1838
- logger.error(f"์—ํ”ผ์†Œ๋“œ ์ž‘์„ฑ ์˜ค๋ฅ˜: {str(e)}", exc_info=True)
1839
- return gr.update(value=f"### โŒ ์˜ค๋ฅ˜\n\n{str(e)}"), gr.update(), episode_num
1840
-
1841
-
1842
-
1843
- def get_user_state(request: gr.Request) -> Optional[Dict[str, str]]:
1844
- """Request์—์„œ ์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ—ฌํผ ํ•จ์ˆ˜"""
1845
- if not request:
1846
- # ๊ฐœ๋ฐœ ๋ชจ๋“œ
1847
- session_id = hashlib.md5(f"dev_{datetime.now()}_{random.randint(1000,9999)}".encode()).hexdigest()[:8]
1848
- return {"user_id": f"dev_{session_id}", "username": f"๊ฐœ๋ฐœ์ž_{session_id}"}
1849
-
1850
- # OAuth ํ™•์ธ
1851
- if request.username:
1852
- return {"user_id": request.username, "username": request.username}
1853
-
1854
- # ํ—ค๋” ํ™•์ธ (Hugging Face Spaces)
1855
- if hasattr(request, 'headers'):
1856
- user_info = request.headers.get('x-user-info', '')
1857
- if user_info:
1858
- return {"user_id": user_info, "username": user_info}
1859
-
1860
- # ๊ฒŒ์ŠคํŠธ ๋ชจ๋“œ
1861
- session_hash = getattr(request, 'session_hash', '')
1862
- if session_hash:
1863
- guest_id = f"guest_{session_hash[:8]}"
1864
- return {"user_id": guest_id, "username": f"๊ฒŒ์ŠคํŠธ_{session_hash[:8]}"}
1865
-
1866
- # ์ตœํ›„์˜ ์ˆ˜๋‹จ: ๋žœ๋ค ๊ฒŒ์ŠคํŠธ
1867
- session_id = hashlib.md5(f"guest_{datetime.now()}_{random.randint(1000,9999)}".encode()).hexdigest()[:8]
1868
- return {"user_id": f"guest_{session_id}", "username": f"๊ฒŒ์ŠคํŠธ_{session_id}"}
1869
-
1870
- # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ˆ˜์ •
1871
- def write_next_episode(current_session, counter):
1872
- """๋‹ค์Œ ์—ํ”ผ์†Œ๋“œ ์ž‘์„ฑ"""
1873
- next_num = counter + 1
1874
- result = write_episode_handler(current_session, next_num)
1875
- return result[0], result[1], next_num
1876
-
1877
-
1878
-
1879
- def load_library(user_state):
1880
- """Load user's novel library"""
1881
- if not user_state:
1882
- return "<p>๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค</p>", gr.update(), gr.update(), gr.update()
1883
-
1884
- sessions = WebNovelDatabase.get_user_sessions(user_state['user_id'])
1885
-
1886
- if not sessions:
1887
- return "<p>์ž‘์„ฑํ•œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค</p>", gr.update(), gr.update(), gr.update()
1888
-
1889
- # Create library HTML
1890
- html = "<h3>๋‚ด ์ž‘ํ’ˆ ๋ชฉ๋ก</h3>"
1891
- html += "<table style='width:100%; border-collapse: collapse;'>"
1892
- html += "<tr style='background-color: #f0f0f0;'>"
1893
- html += "<th style='padding: 10px; border: 1px solid #ddd;'>์ œ๋ชฉ</th>"
1894
- html += "<th style='padding: 10px; border: 1px solid #ddd;'>์žฅ๋ฅด</th>"
1895
- html += "<th style='padding: 10px; border: 1px solid #ddd;'>์ง„ํ–‰๋ฅ </th>"
1896
- html += "<th style='padding: 10px; border: 1px solid #ddd;'>์ตœ๊ทผ ์ˆ˜์ •</th>"
1897
- html += "</tr>"
1898
-
1899
- novel_choices = []
1900
- for session in sessions:
1901
- progress = f"{session['current_episode']}/{session['total_episodes']}ํ™”"
1902
- html += f"<tr>"
1903
- html += f"<td style='padding: 10px; border: 1px solid #ddd;'>{session['title'] or '์ œ๋ชฉ ์—†์Œ'}</td>"
1904
- html += f"<td style='padding: 10px; border: 1px solid #ddd;'>{session['genre']}</td>"
1905
- html += f"<td style='padding: 10px; border: 1px solid #ddd;'>{progress}</td>"
1906
- html += f"<td style='padding: 10px; border: 1px solid #ddd;'>{session['updated_at'][:10]}</td>"
1907
- html += f"</tr>"
1908
-
1909
- novel_choices.append({
1910
- "label": f"{session['title'] or '์ œ๋ชฉ ์—†์Œ'} ({session['genre']}) - {progress}",
1911
- "value": session['session_id']
1912
- })
1913
-
1914
- html += "</table>"
1915
-
1916
- return (
1917
- html,
1918
- gr.update(choices=novel_choices, visible=True),
1919
- gr.update(visible=True),
1920
- gr.update(visible=True)
1921
- )
1922
-
1923
- # Connect events
1924
- interface.load(check_login, outputs=[login_status, user_state])
1925
-
1926
- # Step 1: Logline generation (from paste.txt)
1927
- random_btn.click(
1928
- fn=lambda: generate_loglines_step("", gr.Progress()),
1929
- inputs=[],
1930
- outputs=[
1931
- category_output,
1932
- loglines_output,
1933
- logline_dropdown,
1934
- generate_story_btn,
1935
- story_header
1936
- ],
1937
- show_progress=True
1938
- )
1939
-
1940
- generate_btn.click(
1941
- fn=lambda prompt: generate_loglines_step(prompt, gr.Progress()),
1942
- inputs=[user_prompt_input],
1943
- outputs=[
1944
- category_output,
1945
- loglines_output,
1946
- logline_dropdown,
1947
- generate_story_btn,
1948
- story_header
1949
- ],
1950
- show_progress=True
1951
- )
1952
-
1953
- generate_story_btn.click(
1954
- fn=generate_story_from_selection,
1955
- inputs=[logline_dropdown],
1956
- outputs=[
1957
- selected_logline_output,
1958
- conflicts_output,
1959
- story_arc_output,
1960
- final_details_output
1961
- ]
1962
- ).then(
1963
- fn=show_genre_after_story,
1964
- inputs=[selected_logline_output, user_state], # user_state ์ถ”๊ฐ€
1965
- outputs=[genre_select, generate_webnovel_btn, user_state] # user_state ์ถœ๋ ฅ ์ถ”๊ฐ€
1966
- )
1967
-
1968
- # ์ˆ˜์ •๋œ ์ฝ”๋“œ
1969
- direct_story_btn.click(
1970
- fn=lambda prompt: generate_direct_story(prompt, gr.Progress()),
1971
- inputs=[user_prompt_input],
1972
- outputs=[
1973
- selected_logline_output,
1974
- conflicts_output,
1975
- story_arc_output,
1976
- final_details_output
1977
- ],
1978
- show_progress=True
1979
- ).then(
1980
- fn=show_genre_after_story,
1981
- inputs=[selected_logline_output, user_state], # user_state ์ถ”๊ฐ€
1982
- outputs=[genre_select, generate_webnovel_btn, user_state] # user_state ์ถœ๋ ฅ ์ถ”๊ฐ€
1983
- )
1984
-
1985
-
1986
- # Step 2-3: Web novel generation
1987
- generate_webnovel_btn.click(
1988
- generate_webnovel_structure,
1989
- inputs=[logline_dropdown, genre_select],
1990
- outputs=[webnovel_structure_output, write_first_btn, current_session]
1991
- )
1992
- write_first_btn.click(
1993
- lambda cs: write_episode_handler(cs, 1),
1994
- inputs=[current_session],
1995
- outputs=[episode_output, write_next_btn, episode_counter]
1996
- )
1997
-
1998
-
1999
- write_next_btn.click(
2000
- write_next_episode,
2001
- inputs=[current_session, episode_counter],
2002
- outputs=[episode_output, write_next_btn, episode_counter]
2003
- )
2004
-
2005
- # Library
2006
- refresh_library_btn.click(
2007
- load_library,
2008
- inputs=[user_state],
2009
- outputs=[library_output, selected_novel, continue_writing_btn, preview_btn]
2010
- )
2011
-
2012
- return interface
2013
-
2014
- # Main
2015
- if __name__ == "__main__":
2016
- logger.info("K-Story AI Studio Starting...")
2017
- logger.info("=" * 60)
2018
-
2019
- # Environment check
2020
- if not FRIENDLI_TOKEN:
2021
- logger.warning("FRIENDLI_TOKEN not set. Using dummy token for testing.")
2022
-
2023
- # Initialize database
2024
- WebNovelDatabase.init_db()
2025
- logger.info("Database initialized.")
2026
-
2027
- # Launch interface
2028
- interface = create_interface()
2029
- interface.launch(
2030
- server_name="0.0.0.0",
2031
- server_port=7860,
2032
- share=False
2033
- )