Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -52,9 +52,10 @@ selected_language = "English" # ๊ธฐ๋ณธ ์ธ์ด
|
|
| 52 |
DB_PATH = "novel_sessions.db"
|
| 53 |
db_lock = threading.Lock()
|
| 54 |
|
| 55 |
-
# Stage ๋ฒํธ ์์ -
|
| 56 |
-
WRITER_DRAFT_STAGES = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30
|
| 57 |
-
WRITER_REVISION_STAGES = [5, 8, 11, 14, 17, 20, 23, 26, 29, 32
|
|
|
|
| 58 |
|
| 59 |
class WebSearchIntegration:
|
| 60 |
"""Brave Search API integration for research"""
|
|
@@ -149,7 +150,7 @@ Source: {url}
|
|
| 149 |
f"{topic} ๊ตฌ์ฒด์ ์ฅ๋ฉด ๋ฌ์ฌ",
|
| 150 |
f"{topic} ์ ๋ฌธ ์ฉ์ด ์ค๋ช
"
|
| 151 |
]
|
| 152 |
-
elif writer_num <=
|
| 153 |
queries = [
|
| 154 |
f"{topic} ๊ฐ๋ฑ ์ํฉ ์ฌ๋ก",
|
| 155 |
f"{topic} ์ฌ๋ฆฌ์ ์ธก๋ฉด"
|
|
@@ -178,7 +179,7 @@ Source: {url}
|
|
| 178 |
f"{topic} vivid scene descriptions",
|
| 179 |
f"{topic} technical terminology explained"
|
| 180 |
]
|
| 181 |
-
elif writer_num <=
|
| 182 |
queries = [
|
| 183 |
f"{topic} conflict scenarios",
|
| 184 |
f"{topic} psychological aspects"
|
|
@@ -197,7 +198,7 @@ Source: {url}
|
|
| 197 |
return queries
|
| 198 |
|
| 199 |
class NovelDatabase:
|
| 200 |
-
"""Novel session management database with
|
| 201 |
|
| 202 |
@staticmethod
|
| 203 |
def init_db():
|
|
@@ -208,7 +209,7 @@ class NovelDatabase:
|
|
| 208 |
|
| 209 |
cursor = conn.cursor()
|
| 210 |
|
| 211 |
-
# Sessions table
|
| 212 |
cursor.execute('''
|
| 213 |
CREATE TABLE IF NOT EXISTS sessions (
|
| 214 |
session_id TEXT PRIMARY KEY,
|
|
@@ -218,6 +219,8 @@ class NovelDatabase:
|
|
| 218 |
updated_at TEXT DEFAULT (datetime('now')),
|
| 219 |
status TEXT DEFAULT 'active',
|
| 220 |
current_stage INTEGER DEFAULT 0,
|
|
|
|
|
|
|
| 221 |
final_novel TEXT
|
| 222 |
)
|
| 223 |
''')
|
|
@@ -231,6 +234,7 @@ class NovelDatabase:
|
|
| 231 |
stage_name TEXT NOT NULL,
|
| 232 |
role TEXT NOT NULL,
|
| 233 |
content TEXT,
|
|
|
|
| 234 |
status TEXT DEFAULT 'pending',
|
| 235 |
created_at TEXT DEFAULT (datetime('now')),
|
| 236 |
updated_at TEXT DEFAULT (datetime('now')),
|
|
@@ -257,6 +261,7 @@ class NovelDatabase:
|
|
| 257 |
cursor.execute('CREATE INDEX IF NOT EXISTS idx_session_id ON stages(session_id)')
|
| 258 |
cursor.execute('CREATE INDEX IF NOT EXISTS idx_stage_number ON stages(stage_number)')
|
| 259 |
cursor.execute('CREATE INDEX IF NOT EXISTS idx_search_session ON search_history(session_id)')
|
|
|
|
| 260 |
|
| 261 |
conn.commit()
|
| 262 |
|
|
@@ -271,7 +276,6 @@ class NovelDatabase:
|
|
| 271 |
''', (session_id, stage_number, role, query, results))
|
| 272 |
conn.commit()
|
| 273 |
|
| 274 |
-
# ... (์ด์ ์ ๋ชจ๋ NovelDatabase ๋ฉ์๋๋ค์ ๋์ผ)
|
| 275 |
@staticmethod
|
| 276 |
@contextmanager
|
| 277 |
def get_db():
|
|
@@ -302,28 +306,32 @@ class NovelDatabase:
|
|
| 302 |
@staticmethod
|
| 303 |
def save_stage(session_id: str, stage_number: int, stage_name: str,
|
| 304 |
role: str, content: str, status: str = 'complete'):
|
| 305 |
-
"""Save stage content
|
|
|
|
|
|
|
| 306 |
with NovelDatabase.get_db() as conn:
|
| 307 |
cursor = conn.cursor()
|
| 308 |
|
| 309 |
# UPSERT operation
|
| 310 |
cursor.execute('''
|
| 311 |
-
INSERT INTO stages (session_id, stage_number, stage_name, role, content, status)
|
| 312 |
-
VALUES (?, ?, ?, ?, ?, ?)
|
| 313 |
ON CONFLICT(session_id, stage_number)
|
| 314 |
-
DO UPDATE SET content=?, status=?, stage_name=?, updated_at=datetime('now')
|
| 315 |
-
''', (session_id, stage_number, stage_name, role, content, status,
|
| 316 |
-
content, status, stage_name))
|
| 317 |
|
| 318 |
# Update session
|
| 319 |
cursor.execute('''
|
| 320 |
UPDATE sessions
|
| 321 |
-
SET updated_at = datetime('now'),
|
|
|
|
|
|
|
| 322 |
WHERE session_id = ?
|
| 323 |
-
''', (stage_number, session_id))
|
| 324 |
|
| 325 |
conn.commit()
|
| 326 |
-
logger.info(f"Saved stage {stage_number} for session {session_id}, content length: {len(content)}")
|
| 327 |
|
| 328 |
@staticmethod
|
| 329 |
def get_session(session_id: str) -> Optional[Dict]:
|
|
@@ -333,6 +341,19 @@ class NovelDatabase:
|
|
| 333 |
cursor.execute('SELECT * FROM sessions WHERE session_id = ?', (session_id,))
|
| 334 |
return cursor.fetchone()
|
| 335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
@staticmethod
|
| 337 |
def get_stages(session_id: str) -> List[Dict]:
|
| 338 |
"""Get all stages for a session"""
|
|
@@ -347,7 +368,7 @@ class NovelDatabase:
|
|
| 347 |
|
| 348 |
@staticmethod
|
| 349 |
def get_all_writer_content(session_id: str) -> str:
|
| 350 |
-
"""๋ชจ๋ ์๊ฐ์ ์์ ๋ณธ ๋ด์ฉ์ ๊ฐ์ ธ์์ ํฉ์น๊ธฐ -
|
| 351 |
with NovelDatabase.get_db() as conn:
|
| 352 |
cursor = conn.cursor()
|
| 353 |
|
|
@@ -357,7 +378,7 @@ class NovelDatabase:
|
|
| 357 |
|
| 358 |
for stage_num in WRITER_REVISION_STAGES:
|
| 359 |
cursor.execute('''
|
| 360 |
-
SELECT content, stage_name FROM stages
|
| 361 |
WHERE session_id = ? AND stage_number = ?
|
| 362 |
''', (session_id, stage_num))
|
| 363 |
|
|
@@ -370,17 +391,17 @@ class NovelDatabase:
|
|
| 370 |
|
| 371 |
if clean_content:
|
| 372 |
writer_count += 1
|
| 373 |
-
word_count = len(clean_content.split())
|
| 374 |
total_word_count += word_count
|
| 375 |
all_content.append(clean_content)
|
| 376 |
logger.info(f"Writer {writer_count} (stage {stage_num}): {word_count} words")
|
| 377 |
|
| 378 |
full_content = '\n\n'.join(all_content)
|
| 379 |
-
logger.info(f"Total: {writer_count} writers, {total_word_count} words
|
| 380 |
|
| 381 |
-
#
|
| 382 |
-
if total_word_count <
|
| 383 |
-
logger.warning(f"Content too short! Only {total_word_count} words instead of ~
|
| 384 |
|
| 385 |
return full_content
|
| 386 |
|
|
@@ -429,7 +450,7 @@ class NovelDatabase:
|
|
| 429 |
|
| 430 |
# ๋ชจ๋ ์๊ฐ ์์ ๋ณธ ํ์ธ
|
| 431 |
cursor.execute(f'''
|
| 432 |
-
SELECT stage_number, stage_name, LENGTH(content) as content_length
|
| 433 |
FROM stages
|
| 434 |
WHERE session_id = ? AND stage_number IN ({','.join(map(str, WRITER_REVISION_STAGES))})
|
| 435 |
ORDER BY stage_number
|
|
@@ -437,14 +458,17 @@ class NovelDatabase:
|
|
| 437 |
|
| 438 |
results = []
|
| 439 |
total_length = 0
|
|
|
|
| 440 |
|
| 441 |
for row in cursor.fetchall():
|
| 442 |
results.append({
|
| 443 |
'stage': row['stage_number'],
|
| 444 |
'name': row['stage_name'],
|
| 445 |
-
'length': row['content_length'] or 0
|
|
|
|
| 446 |
})
|
| 447 |
total_length += row['content_length'] or 0
|
|
|
|
| 448 |
|
| 449 |
# ์ต์ข
์์ค ํ์ธ
|
| 450 |
cursor.execute('''
|
|
@@ -459,8 +483,9 @@ class NovelDatabase:
|
|
| 459 |
return {
|
| 460 |
'writer_stages': results,
|
| 461 |
'total_writer_content': total_length,
|
|
|
|
| 462 |
'final_novel_length': final_length,
|
| 463 |
-
'
|
| 464 |
}
|
| 465 |
|
| 466 |
class NovelWritingSystem:
|
|
@@ -550,15 +575,14 @@ Balance real facts with creative fiction to create an immersive story for reader
|
|
| 550 |
|
| 551 |
return original_prompt + "\n\n" + research_section
|
| 552 |
|
| 553 |
-
# ... (์ด์ ์ ๋ชจ๋ ํ๋กฌํํธ ์์ฑ ๋ฉ์๋๋ค์ ๋์ผ)
|
| 554 |
def create_director_initial_prompt(self, user_query: str, language: str = "English") -> str:
|
| 555 |
-
"""Director AI initial prompt - Novel planning for
|
| 556 |
if language == "Korean":
|
| 557 |
-
return f"""๋น์ ์
|
| 558 |
|
| 559 |
์ฌ์ฉ์ ์์ฒญ: {user_query}
|
| 560 |
|
| 561 |
-
๋ค์ ์์๋ค์ ์ฒด๊ณ์ ์ผ๋ก ๊ตฌ์ฑํ์ฌ
|
| 562 |
|
| 563 |
1. **์ฃผ์ ์ ์ฅ๋ฅด**
|
| 564 |
- ํต์ฌ ์ฃผ์ ์ ๋ฉ์์ง
|
|
@@ -574,13 +598,13 @@ Balance real facts with creative fiction to create an immersive story for reader
|
|
| 574 |
- ๊ฐ๋ฑ ๊ตฌ์กฐ
|
| 575 |
- ๊ฐ์ ์ ์ฐ๊ฒฐ๊ณ ๋ฆฌ
|
| 576 |
|
| 577 |
-
4. **์์ฌ ๊ตฌ์กฐ** (
|
| 578 |
| ํํธ | ํ์ด์ง | ์ฃผ์ ์ฌ๊ฑด | ๊ธด์ฅ๋ | ์ธ๋ฌผ ๋ฐ์ |
|
| 579 |
|------|--------|-----------|---------|-----------|
|
| 580 |
| 1 | 1-3 | | | |
|
| 581 |
| 2 | 4-6 | | | |
|
| 582 |
| ... | ... | | | |
|
| 583 |
-
|
|
| 584 |
|
| 585 |
5. **์ธ๊ณ๊ด ์ค์ **
|
| 586 |
- ์๊ณต๊ฐ์ ๋ฐฐ๊ฒฝ
|
|
@@ -589,11 +613,11 @@ Balance real facts with creative fiction to create an immersive story for reader
|
|
| 589 |
|
| 590 |
๊ฐ ์์ฑ์๊ฐ 3ํ์ด์ง์ฉ ์์ฑํ ์ ์๋๋ก ๋ช
ํํ ๊ฐ์ด๋๋ผ์ธ์ ์ ์ํ์ธ์."""
|
| 591 |
else:
|
| 592 |
-
return f"""You are a literary director planning a
|
| 593 |
|
| 594 |
User Request: {user_query}
|
| 595 |
|
| 596 |
-
Systematically compose the following elements to create the foundation for a
|
| 597 |
|
| 598 |
1. **Theme and Genre**
|
| 599 |
- Core theme and message
|
|
@@ -609,13 +633,13 @@ Systematically compose the following elements to create the foundation for a 48-
|
|
| 609 |
- Conflict structure
|
| 610 |
- Emotional connections
|
| 611 |
|
| 612 |
-
4. **Narrative Structure** (divide
|
| 613 |
| Part | Pages | Main Events | Tension | Character Development |
|
| 614 |
|------|-------|-------------|---------|---------------------|
|
| 615 |
| 1 | 1-3 | | | |
|
| 616 |
| 2 | 4-6 | | | |
|
| 617 |
| ... | ... | | | |
|
| 618 |
-
|
|
| 619 |
|
| 620 |
5. **World Building**
|
| 621 |
- Temporal and spatial setting
|
|
@@ -644,7 +668,7 @@ Provide clear guidelines for each writer to compose 3 pages."""
|
|
| 644 |
|------|------|------|-----------|
|
| 645 |
|
| 646 |
3. **๊ตฌ์กฐ์ ๊ท ํ**
|
| 647 |
-
-
|
| 648 |
- ๊ธด์ฅ๊ณผ ์ด์์ ๋ฆฌ๋ฌ
|
| 649 |
- ์ ์ฒด์ ์ธ ํ๋ฆ
|
| 650 |
|
|
@@ -654,7 +678,7 @@ Provide clear guidelines for each writer to compose 3 pages."""
|
|
| 654 |
- ๊ธฐ๋์น ์ถฉ์กฑ๋
|
| 655 |
|
| 656 |
5. **์คํ ๊ฐ๋ฅ์ฑ**
|
| 657 |
-
-
|
| 658 |
- ์ผ๊ด์ฑ ์ ์ง ๋ฐฉ์
|
| 659 |
- ์ ์ฌ์ ๋ฌธ์ ์
|
| 660 |
|
|
@@ -677,7 +701,7 @@ Critique from the following perspectives and provide specific improvements:
|
|
| 677 |
|-----------|-----------|------------|-------------|
|
| 678 |
|
| 679 |
3. **Structural Balance**
|
| 680 |
-
- Distribution across
|
| 681 |
- Rhythm of tension and relief
|
| 682 |
- Overall flow
|
| 683 |
|
|
@@ -687,7 +711,7 @@ Critique from the following perspectives and provide specific improvements:
|
|
| 687 |
- Expectation fulfillment
|
| 688 |
|
| 689 |
5. **Feasibility**
|
| 690 |
-
- Clarity of guidelines for
|
| 691 |
- Consistency maintenance
|
| 692 |
- Potential issues
|
| 693 |
|
|
@@ -706,7 +730,7 @@ Provide specific and constructive feedback."""
|
|
| 706 |
|
| 707 |
๋ค์์ ํฌํจํ ์์ ๋ ์ต์ข
๊ธฐํ์ ์ ์ํ์ธ์:
|
| 708 |
|
| 709 |
-
1. **์์ ๋ ์์ฌ ๊ตฌ์กฐ** (
|
| 710 |
| ํํธ | ํ์ด์ง | ์ฃผ์ ์ฌ๊ฑด | ์์ฑ ์ง์นจ | ์ฃผ์์ฌํญ |
|
| 711 |
|------|--------|-----------|-----------|----------|
|
| 712 |
|
|
@@ -715,7 +739,7 @@ Provide specific and constructive feedback."""
|
|
| 715 |
- ์ธ๋ฌผ ๊ฐ ๊ฐ๋ฑ์ ๊ตฌ์ฒดํ
|
| 716 |
- ๊ฐ์ ์ ์ ๋ณํ ์ถ์ด
|
| 717 |
|
| 718 |
-
3. **๊ฐ ์์ฑ์๋ฅผ ์ํ ์์ธ ๊ฐ์ด๋** (
|
| 719 |
- ํํธ๋ณ ์์๊ณผ ๋ ์ง์
|
| 720 |
- ํ์ ํฌํจ ์์
|
| 721 |
- ๋ฌธ์ฒด์ ํค ์ง์นจ
|
|
@@ -732,7 +756,7 @@ Provide specific and constructive feedback."""
|
|
| 732 |
- ์ ์ฒด์ ํต์ผ์ฑ
|
| 733 |
- ๋
์ ๋ชฐ์
์ ์ง ๋ฐฉ์
|
| 734 |
|
| 735 |
-
|
| 736 |
else:
|
| 737 |
return f"""As director, revise the novel plan reflecting the critic's feedback.
|
| 738 |
|
|
@@ -744,7 +768,7 @@ Critic Feedback:
|
|
| 744 |
|
| 745 |
Present the revised final plan including:
|
| 746 |
|
| 747 |
-
1. **Revised Narrative Structure** (
|
| 748 |
| Part | Pages | Main Events | Writing Guidelines | Cautions |
|
| 749 |
|------|-------|-------------|-------------------|----------|
|
| 750 |
|
|
@@ -753,7 +777,7 @@ Present the revised final plan including:
|
|
| 753 |
- Concrete conflicts between characters
|
| 754 |
- Emotional arc progression
|
| 755 |
|
| 756 |
-
3. **Detailed Guide for Each Writer** (
|
| 757 |
- Start and end points for each part
|
| 758 |
- Essential elements to include
|
| 759 |
- Style and tone guidelines
|
|
@@ -770,15 +794,15 @@ Present the revised final plan including:
|
|
| 770 |
- Overall unity
|
| 771 |
- Reader engagement maintenance
|
| 772 |
|
| 773 |
-
Create a final masterplan that all
|
| 774 |
|
| 775 |
def create_writer_prompt(self, writer_number: int, director_plan: str, previous_content: str, language: str = "English") -> str:
|
| 776 |
-
"""Individual writer prompt -
|
| 777 |
pages_start = (writer_number - 1) * 3 + 1
|
| 778 |
pages_end = writer_number * 3
|
| 779 |
|
| 780 |
if language == "Korean":
|
| 781 |
-
return f"""๋น์ ์ ์์ฑ์ {writer_number}๋ฒ์
๋๋ค.
|
| 782 |
|
| 783 |
๊ฐ๋
์์ ๋ง์คํฐํ๋:
|
| 784 |
{director_plan}
|
|
@@ -789,9 +813,9 @@ Create a final masterplan that all 16 writers can clearly understand."""
|
|
| 789 |
**์ค์ ์ง์นจ:**
|
| 790 |
|
| 791 |
1. **ํ์ ๋ถ๋**:
|
| 792 |
-
- ์ต์
|
| 793 |
-
- ๋๋ต 7500
|
| 794 |
-
- 3ํ์ด์ง ๋ถ๋์
|
| 795 |
|
| 796 |
2. **๋ถ๋ ํ๋ณด ์ ๋ต**:
|
| 797 |
- ์์ธํ ์ฅ๋ฉด ๋ฌ์ฌ ํฌํจ
|
|
@@ -811,9 +835,9 @@ Create a final masterplan that all 16 writers can clearly understand."""
|
|
| 811 |
- ๋
์์ ๊ด์ฌ ์ ์ง
|
| 812 |
|
| 813 |
**์์ฑ ์์:**
|
| 814 |
-
์ด์
|
| 815 |
else:
|
| 816 |
-
return f"""You are Writer #{writer_number}. Write pages {pages_start}-{pages_end} (3 pages) of the
|
| 817 |
|
| 818 |
Director's Masterplan:
|
| 819 |
{director_plan}
|
|
@@ -824,9 +848,9 @@ Director's Masterplan:
|
|
| 824 |
**CRITICAL INSTRUCTIONS:**
|
| 825 |
|
| 826 |
1. **MANDATORY LENGTH**:
|
| 827 |
-
- Minimum
|
| 828 |
-
- Approximately 7500
|
| 829 |
-
- You MUST fill 3
|
| 830 |
|
| 831 |
2. **LENGTH STRATEGIES**:
|
| 832 |
- Include detailed scene descriptions
|
|
@@ -846,7 +870,7 @@ Director's Masterplan:
|
|
| 846 |
- Maintain reader interest
|
| 847 |
|
| 848 |
**BEGIN WRITING:**
|
| 849 |
-
Now write your
|
| 850 |
|
| 851 |
def create_critic_writer_prompt(self, writer_number: int, writer_content: str, director_plan: str, all_previous_content: str, language: str = "English") -> str:
|
| 852 |
"""Critic's review of individual writer's work"""
|
|
@@ -959,7 +983,7 @@ Clearly distinguish between mandatory revisions and optional improvements."""
|
|
| 959 |
- ๋ฌ์ฌ์ ๋ํ ๊ฐ์
|
| 960 |
|
| 961 |
3. **๋ถ๋ ์ ์ง**
|
| 962 |
-
- ์ฌ์ ํ ์ ํํ 3ํ์ด์ง (
|
| 963 |
- ํ์ด์ง ๊ตฌ๋ถ ํ์ ์ ๋ ๊ธ์ง
|
| 964 |
|
| 965 |
4. **์ฐ์์ฑ ํ๋ณด**
|
|
@@ -967,7 +991,7 @@ Clearly distinguish between mandatory revisions and optional improvements."""
|
|
| 967 |
- ์์ ์ผ๋ก ์ธํ ์๋ก์ด ๋ชจ์ ๋ฐฉ์ง
|
| 968 |
|
| 969 |
์์ ๋ ์ต์ข
๋ณธ์ ์ ์ํ์ธ์. ํ์ด์ง ๋งํฌ๋ ์ ๋ ์ฌ์ฉํ์ง ๋ง์ธ์.
|
| 970 |
-
๋ฐ๋์
|
| 971 |
else:
|
| 972 |
return f"""As Writer #{writer_number}, revise based on critic's feedback.
|
| 973 |
|
|
@@ -990,7 +1014,7 @@ Write a revision reflecting:
|
|
| 990 |
- Improve descriptions and dialogue
|
| 991 |
|
| 992 |
3. **Maintain Length**
|
| 993 |
-
- Still exactly 3 pages (
|
| 994 |
- Absolutely no page markers
|
| 995 |
|
| 996 |
4. **Ensure Continuity**
|
|
@@ -998,14 +1022,14 @@ Write a revision reflecting:
|
|
| 998 |
- Prevent new contradictions from revisions
|
| 999 |
|
| 1000 |
Present the revised final version. Never use page markers.
|
| 1001 |
-
You MUST maintain
|
| 1002 |
|
| 1003 |
def create_critic_final_prompt(self, all_content: str, director_plan: str, language: str = "English") -> str:
|
| 1004 |
"""Final critic evaluation of complete novel"""
|
| 1005 |
content_preview = all_content[:3000] + "\n...\n" + all_content[-3000:] if len(all_content) > 6000 else all_content
|
| 1006 |
|
| 1007 |
if language == "Korean":
|
| 1008 |
-
return f"""์ ์ฒด
|
| 1009 |
|
| 1010 |
๊ฐ๋
์์ ๋ง์คํฐํ๋:
|
| 1011 |
{director_plan}
|
|
@@ -1048,7 +1072,7 @@ You MUST maintain 1500-1800 words."""
|
|
| 1048 |
|
| 1049 |
๊ฐ๋
์๊ฐ ์ต์ข
์์ ํ ์ ์๋๋ก ๊ตฌ์ฒด์ ์ด๊ณ ์คํ ๊ฐ๋ฅํ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ธ์."""
|
| 1050 |
else:
|
| 1051 |
-
return f"""Final evaluation of the complete
|
| 1052 |
|
| 1053 |
Director's Masterplan:
|
| 1054 |
{director_plan}
|
|
@@ -1094,12 +1118,12 @@ Provide specific and actionable feedback for the director's final revision."""
|
|
| 1094 |
def create_director_final_prompt(self, all_content: str, critic_final_feedback: str, language: str = "English") -> str:
|
| 1095 |
"""Director's final compilation and polish - ๋ชจ๋ ์๊ฐ ๋ด์ฉ ํฌํจ"""
|
| 1096 |
word_count = len(all_content.split())
|
| 1097 |
-
expected_words =
|
| 1098 |
|
| 1099 |
if language == "Korean":
|
| 1100 |
return f"""๊ฐ๋
์๋ก์ ๋นํ๊ฐ์ ์ต์ข
ํ๊ฐ๋ฅผ ๋ฐ์ํ์ฌ ์์ฑ๋ณธ์ ์ ์ํฉ๋๋ค.
|
| 1101 |
|
| 1102 |
-
์ ์ฒด ์๊ฐ๋ค์ ์ํ (
|
| 1103 |
{all_content}
|
| 1104 |
|
| 1105 |
๋นํ๊ฐ ์ต์ข
ํ๊ฐ:
|
|
@@ -1111,7 +1135,7 @@ Provide specific and actionable feedback for the director's final revision."""
|
|
| 1111 |
|
| 1112 |
## ์ํ ์ ๋ณด
|
| 1113 |
- ์ฅ๋ฅด:
|
| 1114 |
-
- ๋ถ๋:
|
| 1115 |
- ์ฃผ์ :
|
| 1116 |
- ํ ์ค ์์ฝ:
|
| 1117 |
|
|
@@ -1122,7 +1146,7 @@ Provide specific and actionable feedback for the director's final revision."""
|
|
| 1122 |
|
| 1123 |
## ๋ณธ๋ฌธ
|
| 1124 |
|
| 1125 |
-
[
|
| 1126 |
1. ์ค๋ ์ค๋ฅ ์์ ์๋ฃ
|
| 1127 |
2. ํํธ ๊ฐ ์ฐ๊ฒฐ ๋งค๋๋ฝ๊ฒ ์กฐ์
|
| 1128 |
3. ๋ฌธ์ฒด์ ํค ํต์ผ
|
|
@@ -1130,18 +1154,18 @@ Provide specific and actionable feedback for the director's final revision."""
|
|
| 1130 |
5. ํ์ด์ง ๊ตฌ๋ถ ํ์ ์์ ์ ๊ฑฐ
|
| 1131 |
6. ์์ฐ์ค๋ฌ์ด ํ๋ฆ์ผ๋ก ์ฌ๊ตฌ์ฑ
|
| 1132 |
|
| 1133 |
-
[์ ์ฒด
|
| 1134 |
|
| 1135 |
---
|
| 1136 |
|
| 1137 |
## ์๊ฐ์ ๋ง
|
| 1138 |
[์ํ์ ๋ํ ๊ฐ๋จํ ํด์ค์ด๋ ์๋]
|
| 1139 |
|
| 1140 |
-
๋ชจ๋ ์๊ฐ์ ๊ธฐ์ฌ๋ฅผ ํตํฉํ ์์ ํ
|
| 1141 |
else:
|
| 1142 |
return f"""As director, create the final version reflecting the critic's final evaluation.
|
| 1143 |
|
| 1144 |
-
Complete Writers' Work (Full
|
| 1145 |
{all_content}
|
| 1146 |
|
| 1147 |
Critic's Final Evaluation:
|
|
@@ -1153,7 +1177,7 @@ Present the final version including:
|
|
| 1153 |
|
| 1154 |
## Work Information
|
| 1155 |
- Genre:
|
| 1156 |
-
- Length:
|
| 1157 |
- Theme:
|
| 1158 |
- One-line summary:
|
| 1159 |
|
|
@@ -1164,7 +1188,7 @@ Present the final version including:
|
|
| 1164 |
|
| 1165 |
## Main Text
|
| 1166 |
|
| 1167 |
-
[Integrate all
|
| 1168 |
1. Critical errors corrected
|
| 1169 |
2. Smooth transitions between parts
|
| 1170 |
3. Unified style and tone
|
|
@@ -1172,14 +1196,14 @@ Present the final version including:
|
|
| 1172 |
5. Complete removal of page markers
|
| 1173 |
6. Reorganized for natural flow
|
| 1174 |
|
| 1175 |
-
[Complete
|
| 1176 |
|
| 1177 |
---
|
| 1178 |
|
| 1179 |
## Author's Note
|
| 1180 |
[Brief commentary or intention about the work]
|
| 1181 |
|
| 1182 |
-
Present a complete
|
| 1183 |
|
| 1184 |
def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
|
| 1185 |
"""Simulate streaming in test mode"""
|
|
@@ -1223,26 +1247,26 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1223 |
# ์๊ฐ์๊ฒ ๋ ๊ฐํ ์์คํ
ํ๋กฌํํธ ์ถ๊ฐ
|
| 1224 |
if role.startswith("writer"):
|
| 1225 |
if language == "Korean":
|
| 1226 |
-
system_prompts[role] += "\n\n**์ ๋์ ์๊ตฌ์ฌํญ**: ๋น์ ์ ๋ฐ๋์
|
| 1227 |
else:
|
| 1228 |
-
system_prompts[role] += "\n\n**ABSOLUTE REQUIREMENT**: You MUST write
|
| 1229 |
|
| 1230 |
full_messages = [
|
| 1231 |
{"role": "system", "content": system_prompts.get(role, "")},
|
| 1232 |
*messages
|
| 1233 |
]
|
| 1234 |
|
| 1235 |
-
# ์์ฑ์๋ค์๊ฒ๋
|
| 1236 |
if role.startswith("writer"):
|
| 1237 |
-
max_tokens =
|
| 1238 |
-
temperature = 0.8
|
| 1239 |
-
top_p = 0.95
|
| 1240 |
elif role == "director" and ("์ต์ข
" in str(messages) or "final" in str(messages)):
|
| 1241 |
-
max_tokens =
|
| 1242 |
temperature = 0.6
|
| 1243 |
top_p = 0.9
|
| 1244 |
else:
|
| 1245 |
-
max_tokens =
|
| 1246 |
temperature = 0.6
|
| 1247 |
top_p = 0.9
|
| 1248 |
|
|
@@ -1263,7 +1287,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1263 |
headers=self.create_headers(),
|
| 1264 |
json=payload,
|
| 1265 |
stream=True,
|
| 1266 |
-
timeout=60
|
| 1267 |
)
|
| 1268 |
|
| 1269 |
if response.status_code != 200:
|
|
@@ -1307,14 +1331,14 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1307 |
# ์๊ฐ์ ๊ฒฝ์ฐ ๋ด์ฉ ๊ธธ์ด ํ์ธ ๋ฐ ์ฌ์๋
|
| 1308 |
if role.startswith("writer"):
|
| 1309 |
word_count = len(total_content.split())
|
| 1310 |
-
if word_count <
|
| 1311 |
logger.warning(f"Writer {role} produced only {word_count} words! Requesting continuation...")
|
| 1312 |
|
| 1313 |
# ์ถ๊ฐ ์์ฒญ
|
| 1314 |
-
continuation_prompt = f"Continue writing to reach the required
|
| 1315 |
|
| 1316 |
if language == "Korean":
|
| 1317 |
-
continuation_prompt = f"ํ์ ๋ถ๋
|
| 1318 |
|
| 1319 |
full_messages.append({"role": "assistant", "content": total_content})
|
| 1320 |
full_messages.append({"role": "user", "content": continuation_prompt})
|
|
@@ -1323,7 +1347,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1323 |
continuation_payload = {
|
| 1324 |
"model": self.model_id,
|
| 1325 |
"messages": full_messages,
|
| 1326 |
-
"max_tokens":
|
| 1327 |
"temperature": temperature,
|
| 1328 |
"top_p": top_p,
|
| 1329 |
"stream": True
|
|
@@ -1368,67 +1392,54 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1368 |
logger.error(f"Error during streaming: {str(e)}")
|
| 1369 |
yield f"โ Error occurred: {str(e)}"
|
| 1370 |
|
| 1371 |
-
# ... (๋๋จธ์ง ๋ฉ์๋๋ค์ ๋์ผ)
|
| 1372 |
def get_system_prompts(self, language: str) -> Dict[str, str]:
|
| 1373 |
-
"""Get system prompts for all
|
| 1374 |
if language == "Korean":
|
| 1375 |
prompts = {
|
| 1376 |
-
"director": "๋น์ ์
|
| 1377 |
"critic": "๋น์ ์ ๋ ์นด๋ก์ด ํต์ฐฐ๋ ฅ์ ๊ฐ์ง ๋ฌธํ ๋นํ๊ฐ์
๋๋ค. ๊ฑด์ค์ ์ด๊ณ ๊ตฌ์ฒด์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค."
|
| 1378 |
}
|
| 1379 |
|
| 1380 |
-
#
|
| 1381 |
writer_roles = [
|
| 1382 |
"์์ค์ ๋์
๋ถ๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋
์๋ฅผ ์ฌ๋ก์ก๋ ์์์ ๋ง๋ญ๋๋ค.",
|
| 1383 |
"์ด๋ฐ ์ ๊ฐ๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ธ๋ฌผ๊ณผ ์ํฉ์ ๊น์ด ์๊ฒ ๋ฐ์ ์ํต๋๋ค.",
|
| 1384 |
"๊ฐ๋ฑ ๋์
์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ด์ผ๊ธฐ์ ํต์ฌ ๊ฐ๋ฑ์ ์ ์ํฉ๋๋ค.",
|
| 1385 |
"๊ฐ๋ฑ ์์น์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๊ธด์ฅ๊ฐ์ ๋์ด๊ณ ๋ณต์ก์ฑ์ ๋ํฉ๋๋ค.",
|
| 1386 |
-
"
|
| 1387 |
-
"์ค๋ฐ๋ถ
|
| 1388 |
-
"๊น์ด ํ๊ตฌ๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ธ๋ฌผ๊ณผ ์ฃผ์ ๋ฅผ ๊น์ด ํ์ํฉ๋๋ค.",
|
| 1389 |
-
"์ค๋ฐ ์ ํ์ ์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์์์น ๋ชปํ ๋ณํ๋ฅผ ๋ง๋ค์ด๋
๋๋ค.",
|
| 1390 |
-
"๊ฐ๋ฑ ์ฌํ๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์๊ธฐ๋ฅผ ๊ทน๋ํํฉ๋๋ค.",
|
| 1391 |
"ํด๋ผ์ด๋งฅ์ค ์ค๋น๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ต๊ณ ์กฐ๋ฅผ ํฅํด ๋์๊ฐ๋๋ค.",
|
| 1392 |
"ํด๋ผ์ด๋งฅ์ค๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋ชจ๋ ๊ฐ๋ฑ์ด ํญ๋ฐํ๋ ์๊ฐ์ ๊ทธ๋ฆฝ๋๋ค.",
|
| 1393 |
-
"ํด๋ผ์ด๋งฅ์ค ํ๋ฐ์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๊ธด์ฅ์ ์ ์ ์ ๋ง๋ฌด๋ฆฌํฉ๋๋ค.",
|
| 1394 |
"ํด๊ฒฐ ์์์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋งค๋ญ์ ํ์ด๋๊ฐ๊ธฐ ์์ํฉ๋๋ค.",
|
| 1395 |
-
"ํด๊ฒฐ ์งํ์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋ชจ๋ ๊ฐ๋ฑ์ ์ ๋ฆฌํฉ๋๋ค.",
|
| 1396 |
-
"๊ฒฐ๋ง ์ค๋น๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋ง์ง๋ง์ ํฅํด ๋์๊ฐ๋๋ค.",
|
| 1397 |
"์ต์ข
๊ฒฐ๋ง์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ฌ์ด์ด ๋จ๋ ๋ง๋ฌด๋ฆฌ๋ฅผ ๋ง๋ญ๋๋ค."
|
| 1398 |
]
|
| 1399 |
|
| 1400 |
for i, role_desc in enumerate(writer_roles, 1):
|
| 1401 |
-
prompts[f"writer{i}"] = f"๋น์ ์ {role_desc} ๋ฐ๋์
|
| 1402 |
|
| 1403 |
return prompts
|
| 1404 |
else:
|
| 1405 |
prompts = {
|
| 1406 |
-
"director": "You are a literary director planning and supervising a
|
| 1407 |
"critic": "You are a literary critic with sharp insights. You provide constructive and specific feedback."
|
| 1408 |
}
|
| 1409 |
|
| 1410 |
-
#
|
| 1411 |
writer_roles = [
|
| 1412 |
"the writer responsible for the introduction. You create a captivating beginning.",
|
| 1413 |
"the writer responsible for early development. You deepen characters and situations.",
|
| 1414 |
"the writer responsible for conflict introduction. You present the core conflict.",
|
| 1415 |
"the writer responsible for rising conflict. You increase tension and add complexity.",
|
| 1416 |
-
"the writer responsible for the
|
| 1417 |
-
"the writer responsible for
|
| 1418 |
-
"the writer responsible for depth exploration. You deeply explore characters and themes.",
|
| 1419 |
-
"the writer responsible for the mid-point turn. You create unexpected changes.",
|
| 1420 |
-
"the writer responsible for deepening conflict. You maximize the crisis.",
|
| 1421 |
"the writer responsible for climax preparation. You move toward the peak.",
|
| 1422 |
"the writer responsible for the climax. You depict the moment when all conflicts explode.",
|
| 1423 |
-
"the writer responsible for climax conclusion. You complete the tension's peak.",
|
| 1424 |
"the writer responsible for resolution beginning. You start untangling the knots.",
|
| 1425 |
-
"the writer responsible for resolution progress. You resolve all conflicts.",
|
| 1426 |
-
"the writer responsible for ending preparation. You move toward the final moments.",
|
| 1427 |
"the writer responsible for the final ending. You create a lingering conclusion."
|
| 1428 |
]
|
| 1429 |
|
| 1430 |
for i, role_desc in enumerate(writer_roles, 1):
|
| 1431 |
-
prompts[f"writer{i}"] = f"You are {role_desc} You MUST write
|
| 1432 |
|
| 1433 |
return prompts
|
| 1434 |
|
|
@@ -1442,7 +1453,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1442 |
def get_korean_test_response(self, role: str) -> str:
|
| 1443 |
"""Korean test responses with appropriate length"""
|
| 1444 |
test_responses = {
|
| 1445 |
-
"director": """
|
| 1446 |
|
| 1447 |
## 1. ์ฃผ์ ์ ์ฅ๋ฅด
|
| 1448 |
- **ํต์ฌ ์ฃผ์ **: ์ธ๊ฐ ๋ณธ์ฑ๊ณผ ๊ธฐ์ ์ ์ถฉ๋ ์์์ ์ฐพ๋ ์ง์ ํ ์ฐ๊ฒฐ
|
|
@@ -1458,7 +1469,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1458 |
| ๋ฏผ์ค | ์กฐ๋ ฅ์ | ๋ฐ๋ปํจ, ์ง๊ด์ | ์ฌ๋ฆฌ์๋ด์ฌ | ์์ฐ์ ๋์ ๊ท ํ ์ฐพ๊ธฐ | ๊ธฐ์ ์์ฉ๊ณผ ์กฐํ |
|
| 1459 |
| ARIA | ๋๋ฆฝ์โ๋๋ฐ์ | ๋
ผ๋ฆฌ์ โ๊ฐ์ฑ ํ์ต | AI ํ๋กํ ํ์
| ์ง์ ํ ์กด์ฌ ๋๊ธฐ | ์์ ์ ์ฒด์ฑ ํ๋ฆฝ |
|
| 1460 |
|
| 1461 |
-
## 3. ์์ฌ ๊ตฌ์กฐ (
|
| 1462 |
|
| 1463 |
| ํํธ | ํ์ด์ง | ์ฃผ์ ์ฌ๊ฑด | ๊ธด์ฅ๋ | ์ธ๋ฌผ ๋ฐ์ |
|
| 1464 |
|------|--------|-----------|---------|-----------|
|
|
@@ -1468,16 +1479,10 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1468 |
| 4 | 10-12 | ARIA์ ์์ ์ธ์ ์งํ | 5/10 | ๊ฐ๋ฑ์ ์จ์ |
|
| 1469 |
| 5 | 13-15 | ์ฒซ ๋ฒ์งธ ์๊ธฐ | 6/10 | ์ ํ์ ์๊ฐ |
|
| 1470 |
| 6 | 16-18 | ์ค๋ฆฌ์์ํ ๊ฐ์
| 7/10 | ์ธ๋ถ ์๋ ฅ |
|
| 1471 |
-
| 7 | 19-21 |
|
| 1472 |
-
| 8 | 22-24 |
|
| 1473 |
-
| 9 | 25-27 |
|
| 1474 |
-
| 10 | 28-30 |
|
| 1475 |
-
| 11 | 31-33 | ์ธ๋ถ ์ํ ๋ฑ์ฅ | 9/10 | ์ฐ๋์ ํ์ |
|
| 1476 |
-
| 12 | 34-36 | ์ค๋น์ ๊ฒฐ์ | 8/10 | ํ์ ๋ชจ์ |
|
| 1477 |
-
| 13 | 37-39 | ์ตํ์ ๋๊ฒฐ | 10/10 | ํด๋ผ์ด๋งฅ์ค |
|
| 1478 |
-
| 14 | 40-42 | ์ ํ์ ๊ฒฐ๊ณผ | 7/10 | ๋ณํ ์์ฉ |
|
| 1479 |
-
| 15 | 43-45 | ์๋ก์ด ๊ธธ | 5/10 | ํํด์ ์ฑ์ฅ |
|
| 1480 |
-
| 16 | 46-48 | ๊ณต์กด์ ์์ | 4/10 | ์๋ก์ด ๊ด๊ณ |""",
|
| 1481 |
|
| 1482 |
"critic": """๊ฐ๋
์์ ๊ธฐํ์ ๊ฒํ ํ์ต๋๋ค.
|
| 1483 |
|
|
@@ -1485,7 +1490,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1485 |
|
| 1486 |
### 1. ์์ฌ์ ์์ฑ๋
|
| 1487 |
- **๊ฐ์ **: AI์ ์ธ๊ฐ์ ๊ด๊ณ๋ผ๋ ์์์ ์ ํ ์ฃผ์
|
| 1488 |
-
- **๊ฐ์ ์ **:
|
| 1489 |
|
| 1490 |
### 2. ์ธ๋ฌผ ์ค์ ๊ฒํ
|
| 1491 |
|
|
@@ -1500,18 +1505,18 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1500 |
- ํํธ ๊ฐ ์ฐ๊ฒฐ์ฑ ๊ฐ์ด๋๋ผ์ธ ๋ณด๊ฐ ํ์""",
|
| 1501 |
}
|
| 1502 |
|
| 1503 |
-
# ์๊ฐ ์๋ต -
|
| 1504 |
sample_story = """์์ฐ์ ์ฐ๊ตฌ์ค์ ์ฐจ๊ฐ์ด ํ๊ด๋ฑ ์๋์์ ๋ ๋ค๋ฅธ ๋ฐค์ ๋ณด๋ด๊ณ ์์๋ค. ๋ชจ๋ํฐ์ ํธ๋ฅธ ๋น์ด ๊ทธ๋
์ ์ฐฝ๋ฐฑํ ์ผ๊ตด์ ๋น์ถ๊ณ ์์๊ณ , ์์ญ ๊ฐ์ ์ฝ๋ ๋ผ์ธ์ด ๋์์์ด ์คํฌ๋กค๋๊ณ ์์๋ค. ARIA ํ๋ก์ ํธ๋ ๊ทธ๋
์ ์ถ ์ ๋ถ์๋ค. 3๋
์ด๋ผ๋ ์๊ฐ ๋์ ๊ทธ๋
๋ ์ด ์ธ๊ณต์ง๋ฅ์ ๋ชจ๋ ๊ฒ์ ์์๋ถ์๋ค.
|
| 1505 |
|
| 1506 |
"์์คํ
์ฒดํฌ ์๋ฃ. ๋ชจ๋ ํ๋ผ๋ฏธํฐ ์ ์." ๊ธฐ๊ณ์ ์ธ ์์ฑ์ด ์คํผ์ปค๋ฅผ ํตํด ํ๋ฌ๋์๋ค.
|
| 1507 |
|
| 1508 |
์์ฐ์ ์ ์ ์์์ ๊ธฐ๋์ด ๋์ ๊ฐ์๋ค. ํผ๋ก๊ฐ ๋ผ ์๊น์ง ํ๊ณ ๋ค์์ง๋ง, ๋ฉ์ถ ์ ์์๋ค. ARIA๋ ๋จ์ํ ํ๋ก์ ํธ๊ฐ ์๋์๋ค. ๊ทธ๊ฒ์ ๊ทธ๋
๊ฐ ์์ด๋ฒ๋ฆฐ ๊ฒ๋ค์ ๋์ฐพ์ ์ ์๋ ์ ์ผํ ํฌ๋ง์ด์๋ค."""
|
| 1509 |
|
| 1510 |
-
for i in range(1,
|
| 1511 |
-
# ๊ฐ ์๊ฐ๋ง๋ค
|
| 1512 |
writer_content = f"์์ฑ์ {i}๋ฒ์ ํํธ์
๋๋ค.\n\n"
|
| 1513 |
-
# ์ฝ 300๋จ์ด์ฉ 5
|
| 1514 |
-
for j in range(
|
| 1515 |
writer_content += sample_story + f"\n\n๊ทธ๊ฒ์ ์๊ฐ {i}์ {j+1}๋ฒ์งธ ๋จ๋ฝ์ด์๋ค. "
|
| 1516 |
writer_content += "์ด๏ฟฝ๏ฟฝ๏ฟฝ๊ธฐ๋ ๊ณ์ ์ ๊ฐ๋์๊ณ , ์ธ๋ฌผ๋ค์ ๊ฐ์ ์ ์ ์ ๋ ๋ณต์กํด์ก๋ค. " * 15
|
| 1517 |
writer_content += "\n\n"
|
|
@@ -1523,7 +1528,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1523 |
def get_english_test_response(self, role: str) -> str:
|
| 1524 |
"""English test responses with appropriate length"""
|
| 1525 |
test_responses = {
|
| 1526 |
-
"director": """I present the
|
| 1527 |
|
| 1528 |
## 1. Theme and Genre
|
| 1529 |
- **Core Theme**: Finding true connection in the collision of human nature and technology
|
|
@@ -1539,13 +1544,13 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1539 |
| Minjun | Helper | Warm, intuitive | Psychologist | Help Seoyeon find balance | Accept and harmonize with technology |
|
| 1540 |
| ARIA | AntagonistโCompanion | LogicalโLearning emotion | AI prototype | Become truly existent | Establish self-identity |
|
| 1541 |
|
| 1542 |
-
## 3. Narrative Structure (
|
| 1543 |
|
| 1544 |
| Part | Pages | Main Events | Tension | Character Development |
|
| 1545 |
|------|-------|-------------|---------|---------------------|
|
| 1546 |
| 1 | 1-3 | Seoyeon's lonely lab, ARIA's first awakening | 3/10 | Seoyeon's obsession revealed |
|
| 1547 |
| 2 | 4-6 | ARIA's anomalies begin | 4/10 | Questions arise |
|
| 1548 |
-
[... continues for all
|
| 1549 |
|
| 1550 |
"critic": """I have reviewed the director's plan.
|
| 1551 |
|
|
@@ -1553,7 +1558,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1553 |
|
| 1554 |
### 1. Narrative Completeness
|
| 1555 |
- **Strength**: Timely theme of AI-human relationships
|
| 1556 |
-
- **Improvement**:
|
| 1557 |
|
| 1558 |
### 2. Character Review
|
| 1559 |
|
|
@@ -1568,18 +1573,18 @@ Present a complete 48-page novel integrating all writers' contributions."""
|
|
| 1568 |
- Need to strengthen inter-part connectivity guidelines""",
|
| 1569 |
}
|
| 1570 |
|
| 1571 |
-
# Writer responses -
|
| 1572 |
sample_story = """Seoyeon spent another night under the cold fluorescent lights of her laboratory. The blue glow from the monitor illuminated her pale face, and dozens of lines of code scrolled endlessly. The ARIA project was her entire life. For three years, she had poured everything into this artificial intelligence.
|
| 1573 |
|
| 1574 |
"System check complete. All parameters normal." The mechanical voice flowed through the speakers.
|
| 1575 |
|
| 1576 |
Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penetrated to her bones, but she couldn't stop. ARIA wasn't just a project. It was her only hope to reclaim what she had lost."""
|
| 1577 |
|
| 1578 |
-
for i in range(1,
|
| 1579 |
-
# Each writer produces
|
| 1580 |
writer_content = f"Writer {i} begins their section here.\n\n"
|
| 1581 |
-
# About 300 words repeated 5
|
| 1582 |
-
for j in range(
|
| 1583 |
writer_content += sample_story + f"\n\nThis was writer {i}'s paragraph {j+1}. "
|
| 1584 |
writer_content += "The story continued to unfold, and the characters' emotions grew increasingly complex. " * 15
|
| 1585 |
writer_content += "\n\n"
|
|
@@ -1627,15 +1632,15 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
|
|
| 1627 |
"content": stage_data['content'] or ""
|
| 1628 |
})
|
| 1629 |
|
| 1630 |
-
# Define all stages
|
| 1631 |
stage_definitions = [
|
| 1632 |
("director", f"๐ฌ {'๊ฐ๋
์: ์ด๊ธฐ ๊ธฐํ' if language == 'Korean' else 'Director: Initial Planning'}"),
|
| 1633 |
("critic", f"๐ {'๋นํ๊ฐ: ๊ธฐํ ๊ฒํ ' if language == 'Korean' else 'Critic: Plan Review'}"),
|
| 1634 |
("director", f"๐ฌ {'๊ฐ๋
์: ์์ ๋ ๋ง์คํฐํ๋' if language == 'Korean' else 'Director: Revised Masterplan'}"),
|
| 1635 |
]
|
| 1636 |
|
| 1637 |
-
# Add writer stages for
|
| 1638 |
-
for writer_num in range(1,
|
| 1639 |
stage_definitions.extend([
|
| 1640 |
(f"writer{writer_num}", f"โ๏ธ {'์์ฑ์' if language == 'Korean' else 'Writer'} {writer_num}: {'์ด์' if language == 'Korean' else 'Draft'}"),
|
| 1641 |
("critic", f"๐ {'๋นํ๊ฐ: ์์ฑ์' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒํ ' if language == 'Korean' else 'Review'}"),
|
|
@@ -1704,6 +1709,11 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
|
|
| 1704 |
"complete"
|
| 1705 |
)
|
| 1706 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1707 |
yield "", stages
|
| 1708 |
|
| 1709 |
# Verify content after completion
|
|
@@ -1711,8 +1721,8 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
|
|
| 1711 |
verification = NovelDatabase.verify_novel_content(self.current_session_id)
|
| 1712 |
logger.info(f"Content verification: {verification}")
|
| 1713 |
|
| 1714 |
-
if verification['
|
| 1715 |
-
logger.error(f"Final novel too short! Only {verification['
|
| 1716 |
|
| 1717 |
# Get final novel from last stage
|
| 1718 |
final_novel = stages[-1]["content"] if stages else ""
|
|
@@ -1796,7 +1806,7 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
|
|
| 1796 |
# Writer review
|
| 1797 |
else:
|
| 1798 |
# Find which writer we're reviewing
|
| 1799 |
-
for i in range(1,
|
| 1800 |
if f"์์ฑ์ {i}" in stages[stage_idx]["name"] or f"Writer {i}" in stages[stage_idx]["name"]:
|
| 1801 |
writer_content_idx = stage_idx - 1
|
| 1802 |
# Get previous writers' content from DB
|
|
@@ -1822,47 +1832,71 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
|
|
| 1822 |
|
| 1823 |
return ""
|
| 1824 |
|
| 1825 |
-
# Gradio Interface Functions
|
| 1826 |
-
def process_query(query: str, language: str, session_id: str = None) -> Generator[Tuple[str, str, str], None, None]:
|
| 1827 |
-
"""Process query and yield updates"""
|
| 1828 |
if not query.strip() and not session_id:
|
| 1829 |
if language == "Korean":
|
| 1830 |
-
yield "", "", "โ ์์ค ์ฃผ์ ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์."
|
| 1831 |
else:
|
| 1832 |
-
yield "", "", "โ Please enter a novel theme."
|
| 1833 |
return
|
| 1834 |
|
| 1835 |
system = NovelWritingSystem()
|
| 1836 |
|
| 1837 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1838 |
for final_novel, stages in system.process_novel_stream(query, language, session_id):
|
| 1839 |
# Format stages for display
|
| 1840 |
stages_display = format_stages_display(stages, language)
|
| 1841 |
|
| 1842 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1843 |
|
| 1844 |
-
yield stages_display, final_novel, status
|
| 1845 |
|
| 1846 |
except Exception as e:
|
| 1847 |
logger.error(f"Error in process_query: {str(e)}", exc_info=True)
|
| 1848 |
if language == "Korean":
|
| 1849 |
-
yield "", "", f"โ ์ค๋ฅ ๋ฐ์: {str(e)}"
|
| 1850 |
else:
|
| 1851 |
-
yield "", "", f"โ Error occurred: {str(e)}"
|
| 1852 |
|
| 1853 |
def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
|
| 1854 |
-
"""Format stages into simple display
|
| 1855 |
display = ""
|
| 1856 |
|
| 1857 |
for idx, stage in enumerate(stages):
|
| 1858 |
status_icon = "โ
" if stage.get("status") == "complete" else ("โณ" if stage.get("status") == "active" else "โ")
|
| 1859 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1860 |
# Show only active stage content in detail, others just show status
|
| 1861 |
if stage.get("status") == "active":
|
| 1862 |
display += f"\n\n{status_icon} **{stage['name']}**\n"
|
| 1863 |
-
display += f"```\n{stage.get('content', '')[-1000:]}\n```"
|
| 1864 |
else:
|
| 1865 |
-
display += f"\n{status_icon} {stage['name']}"
|
| 1866 |
|
| 1867 |
return display
|
| 1868 |
|
|
@@ -1884,7 +1918,7 @@ def get_active_sessions(language: str) -> List[Tuple[str, str]]:
|
|
| 1884 |
logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
|
| 1885 |
return []
|
| 1886 |
|
| 1887 |
-
def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str], None, None]:
|
| 1888 |
"""Resume an existing session"""
|
| 1889 |
if not session_id:
|
| 1890 |
return
|
|
@@ -1892,8 +1926,33 @@ def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str,
|
|
| 1892 |
# Process with existing session ID
|
| 1893 |
yield from process_query("", language, session_id)
|
| 1894 |
|
| 1895 |
-
def
|
| 1896 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1897 |
if not novel_text:
|
| 1898 |
return None
|
| 1899 |
|
|
@@ -1911,29 +1970,76 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
|
|
| 1911 |
# Create DOCX
|
| 1912 |
doc = Document()
|
| 1913 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1914 |
# ๋ฌธ์ ์ ๋ณด ์ถ๊ฐ
|
| 1915 |
-
doc.
|
| 1916 |
-
|
| 1917 |
-
|
| 1918 |
-
|
| 1919 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1920 |
doc.add_page_break()
|
| 1921 |
|
| 1922 |
-
#
|
| 1923 |
-
|
| 1924 |
-
for
|
| 1925 |
-
|
| 1926 |
-
|
| 1927 |
-
|
| 1928 |
-
|
| 1929 |
-
|
| 1930 |
-
|
| 1931 |
-
|
| 1932 |
-
|
| 1933 |
-
|
| 1934 |
-
|
| 1935 |
-
|
| 1936 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1937 |
|
| 1938 |
# ํ์ด์ง ์ค์
|
| 1939 |
section = doc.sections[0]
|
|
@@ -1946,7 +2052,7 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
|
|
| 1946 |
|
| 1947 |
# Save
|
| 1948 |
temp_dir = tempfile.gettempdir()
|
| 1949 |
-
filename = f"
|
| 1950 |
filepath = os.path.join(temp_dir, filename)
|
| 1951 |
doc.save(filepath)
|
| 1952 |
|
|
@@ -1955,23 +2061,40 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
|
|
| 1955 |
else:
|
| 1956 |
# TXT format
|
| 1957 |
temp_dir = tempfile.gettempdir()
|
| 1958 |
-
filename = f"
|
| 1959 |
filepath = os.path.join(temp_dir, filename)
|
| 1960 |
|
| 1961 |
# ํ์ผ ์์ ๋ถ๋ถ์ ์ ๋ณด ์ถ๊ฐ
|
| 1962 |
with open(filepath, 'w', encoding='utf-8') as f:
|
| 1963 |
-
f.write(
|
|
|
|
|
|
|
| 1964 |
f.write(f"Total words: {word_count:,}\n")
|
| 1965 |
f.write(f"Total characters: {char_count:,}\n")
|
| 1966 |
f.write(f"Estimated pages: {word_count/500:.1f}\n")
|
| 1967 |
f.write(f"Export date: {datetime.now()}\n")
|
| 1968 |
-
f.write("="*
|
| 1969 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1970 |
|
| 1971 |
logger.info(f"TXT saved: {filepath}")
|
| 1972 |
return filepath
|
| 1973 |
|
| 1974 |
-
# Custom CSS
|
| 1975 |
custom_css = """
|
| 1976 |
.gradio-container {
|
| 1977 |
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
|
@@ -2005,6 +2128,15 @@ custom_css = """
|
|
| 2005 |
color: white;
|
| 2006 |
}
|
| 2007 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2008 |
#stages-display {
|
| 2009 |
background-color: rgba(255, 255, 255, 0.95);
|
| 2010 |
padding: 20px;
|
|
@@ -2033,6 +2165,11 @@ custom_css = """
|
|
| 2033 |
color: #4CAF50;
|
| 2034 |
font-weight: bold;
|
| 2035 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2036 |
"""
|
| 2037 |
|
| 2038 |
# Create Gradio Interface
|
|
@@ -2044,13 +2181,15 @@ def create_interface():
|
|
| 2044 |
๐ SOMA Novel Writing System
|
| 2045 |
</h1>
|
| 2046 |
<h3 style="color: #ccc; margin-bottom: 20px;">
|
| 2047 |
-
AI Collaborative Novel Generation
|
| 2048 |
</h3>
|
| 2049 |
<p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
|
| 2050 |
-
Enter a theme or prompt, and watch as
|
| 2051 |
-
The system includes 1 Director, 1 Critic, and
|
| 2052 |
-
<
|
| 2053 |
-
|
|
|
|
|
|
|
| 2054 |
</p>
|
| 2055 |
</div>
|
| 2056 |
""")
|
|
@@ -2058,6 +2197,13 @@ def create_interface():
|
|
| 2058 |
# State management
|
| 2059 |
current_session_id = gr.State(None)
|
| 2060 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2061 |
with gr.Row():
|
| 2062 |
with gr.Column(scale=1):
|
| 2063 |
with gr.Group(elem_classes=["input-section"]):
|
|
@@ -2097,8 +2243,9 @@ def create_interface():
|
|
| 2097 |
interactive=True
|
| 2098 |
)
|
| 2099 |
with gr.Row():
|
| 2100 |
-
refresh_btn = gr.Button("๐ Refresh / ์๋ก๊ณ ์นจ", scale=1)
|
| 2101 |
-
resume_btn = gr.Button("โถ๏ธ Resume / ์ฌ๊ฐ", variant="secondary", scale=1)
|
|
|
|
| 2102 |
|
| 2103 |
with gr.Column(scale=2):
|
| 2104 |
with gr.Tab("๐ Writing Process / ์์ฑ ๊ณผ์ "):
|
|
@@ -2114,14 +2261,14 @@ def create_interface():
|
|
| 2114 |
)
|
| 2115 |
|
| 2116 |
with gr.Group(elem_classes=["download-section"]):
|
| 2117 |
-
gr.Markdown("### ๐ฅ Download Novel / ์์ค ๋ค์ด๋ก๋")
|
| 2118 |
with gr.Row():
|
| 2119 |
format_select = gr.Radio(
|
| 2120 |
choices=["DOCX", "TXT"],
|
| 2121 |
value="DOCX" if DOCX_AVAILABLE else "TXT",
|
| 2122 |
label="Format / ํ์"
|
| 2123 |
)
|
| 2124 |
-
download_btn = gr.Button("โฌ๏ธ Download / ๋ค์ด๋ก๋", variant="secondary")
|
| 2125 |
|
| 2126 |
download_file = gr.File(
|
| 2127 |
label="Downloaded File / ๋ค์ด๋ก๋๋ ํ์ผ",
|
|
@@ -2147,8 +2294,8 @@ def create_interface():
|
|
| 2147 |
)
|
| 2148 |
|
| 2149 |
# Event handlers
|
| 2150 |
-
def update_novel_state(stages, novel, status):
|
| 2151 |
-
return stages, novel, status, novel
|
| 2152 |
|
| 2153 |
def refresh_sessions():
|
| 2154 |
try:
|
|
@@ -2158,14 +2305,25 @@ def create_interface():
|
|
| 2158 |
logger.error(f"Error refreshing sessions: {str(e)}", exc_info=True)
|
| 2159 |
return gr.update(choices=[])
|
| 2160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2161 |
submit_btn.click(
|
| 2162 |
fn=process_query,
|
| 2163 |
inputs=[query_input, language_select, current_session_id],
|
| 2164 |
-
outputs=[stages_display, novel_output, status_text]
|
| 2165 |
).then(
|
| 2166 |
fn=update_novel_state,
|
| 2167 |
-
inputs=[stages_display, novel_output, status_text],
|
| 2168 |
-
outputs=[stages_display, novel_output, status_text, novel_text_state]
|
| 2169 |
)
|
| 2170 |
|
| 2171 |
resume_btn.click(
|
|
@@ -2175,7 +2333,17 @@ def create_interface():
|
|
| 2175 |
).then(
|
| 2176 |
fn=resume_session,
|
| 2177 |
inputs=[current_session_id, language_select],
|
| 2178 |
-
outputs=[stages_display, novel_output, status_text]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2179 |
)
|
| 2180 |
|
| 2181 |
refresh_btn.click(
|
|
@@ -2184,15 +2352,15 @@ def create_interface():
|
|
| 2184 |
)
|
| 2185 |
|
| 2186 |
clear_btn.click(
|
| 2187 |
-
fn=lambda: ("", "", "๐ Ready", "", None),
|
| 2188 |
-
outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id]
|
| 2189 |
)
|
| 2190 |
|
| 2191 |
-
def handle_download(novel_text, format_type, language):
|
| 2192 |
-
if not novel_text:
|
| 2193 |
return gr.update(visible=False)
|
| 2194 |
|
| 2195 |
-
file_path = download_novel(novel_text, format_type, language)
|
| 2196 |
if file_path:
|
| 2197 |
return gr.update(value=file_path, visible=True)
|
| 2198 |
else:
|
|
@@ -2200,52 +2368,33 @@ def create_interface():
|
|
| 2200 |
|
| 2201 |
download_btn.click(
|
| 2202 |
fn=handle_download,
|
| 2203 |
-
inputs=[novel_text_state, format_select, language_select],
|
| 2204 |
outputs=[download_file]
|
| 2205 |
)
|
| 2206 |
|
| 2207 |
-
# Load sessions on startup
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2208 |
interface.load(
|
| 2209 |
-
fn=
|
| 2210 |
-
outputs=[session_dropdown]
|
| 2211 |
)
|
| 2212 |
|
| 2213 |
return interface
|
| 2214 |
|
| 2215 |
-
# ๋น ๋ฅธ ํ
์คํธ๋ฅผ ์ํ ์ถ์ ๋ฒ์ ์ถ๊ฐ
|
| 2216 |
-
def create_quick_test_interface():
|
| 2217 |
-
"""๋น ๋ฅธ ํ
์คํธ๋ฅผ ์ํ ๊ฐ๋จํ ์ธํฐํ์ด์ค"""
|
| 2218 |
-
with gr.Blocks(title="Novel Writing System - Quick Test") as interface:
|
| 2219 |
-
gr.Markdown("# ๐ Quick Test Mode - 3 Writers Only")
|
| 2220 |
-
|
| 2221 |
-
with gr.Row():
|
| 2222 |
-
query_input = gr.Textbox(label="Novel Theme", placeholder="Enter theme...")
|
| 2223 |
-
language = gr.Radio(["English", "Korean"], value="English", label="Language")
|
| 2224 |
-
submit_btn = gr.Button("Start Quick Test")
|
| 2225 |
-
|
| 2226 |
-
output = gr.Textbox(label="Output", lines=20)
|
| 2227 |
-
|
| 2228 |
-
def quick_test(query, lang):
|
| 2229 |
-
system = NovelWritingSystem()
|
| 2230 |
-
# 3๋ช
์ ์๊ฐ๋ง ํ
์คํธ
|
| 2231 |
-
stages = ["Director Plan", "Writer 1", "Writer 2", "Writer 3", "Final"]
|
| 2232 |
-
result = f"Testing with theme: {query}\n\n"
|
| 2233 |
-
|
| 2234 |
-
for stage in stages:
|
| 2235 |
-
result += f"Processing {stage}...\n"
|
| 2236 |
-
time.sleep(1) # ์๋ฎฌ๋ ์ด์
|
| 2237 |
-
|
| 2238 |
-
return result + "\nQuick test complete!"
|
| 2239 |
-
|
| 2240 |
-
submit_btn.click(quick_test, inputs=[query_input, language], outputs=output)
|
| 2241 |
-
|
| 2242 |
-
return interface
|
| 2243 |
-
|
| 2244 |
# Main execution
|
| 2245 |
if __name__ == "__main__":
|
| 2246 |
import sys
|
| 2247 |
|
| 2248 |
-
logger.info("Starting SOMA Novel Writing System...")
|
| 2249 |
|
| 2250 |
# Check environment
|
| 2251 |
if TEST_MODE:
|
|
@@ -2264,13 +2413,7 @@ if __name__ == "__main__":
|
|
| 2264 |
NovelDatabase.init_db()
|
| 2265 |
logger.info("Database initialized successfully.")
|
| 2266 |
|
| 2267 |
-
|
| 2268 |
-
if "--quick-test" in sys.argv:
|
| 2269 |
-
logger.info("Running in QUICK TEST mode")
|
| 2270 |
-
interface = create_quick_test_interface()
|
| 2271 |
-
else:
|
| 2272 |
-
logger.info("Running in FULL mode")
|
| 2273 |
-
interface = create_interface()
|
| 2274 |
|
| 2275 |
interface.launch(
|
| 2276 |
server_name="0.0.0.0",
|
|
|
|
| 52 |
DB_PATH = "novel_sessions.db"
|
| 53 |
db_lock = threading.Lock()
|
| 54 |
|
| 55 |
+
# Stage ๋ฒํธ ์์ - 10๋ช
์ ์๊ฐ๋ก ๋ณ๊ฒฝ
|
| 56 |
+
WRITER_DRAFT_STAGES = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30] # ์๊ฐ ์ด์
|
| 57 |
+
WRITER_REVISION_STAGES = [5, 8, 11, 14, 17, 20, 23, 26, 29, 32] # ์๊ฐ ์์ ๋ณธ
|
| 58 |
+
TOTAL_WRITERS = 10 # ์ด ์๊ฐ ์
|
| 59 |
|
| 60 |
class WebSearchIntegration:
|
| 61 |
"""Brave Search API integration for research"""
|
|
|
|
| 150 |
f"{topic} ๊ตฌ์ฒด์ ์ฅ๋ฉด ๋ฌ์ฌ",
|
| 151 |
f"{topic} ์ ๋ฌธ ์ฉ์ด ์ค๋ช
"
|
| 152 |
]
|
| 153 |
+
elif writer_num <= 6: # ์ค๋ฐ๋ถ ์๊ฐ
|
| 154 |
queries = [
|
| 155 |
f"{topic} ๊ฐ๋ฑ ์ํฉ ์ฌ๋ก",
|
| 156 |
f"{topic} ์ฌ๋ฆฌ์ ์ธก๋ฉด"
|
|
|
|
| 179 |
f"{topic} vivid scene descriptions",
|
| 180 |
f"{topic} technical terminology explained"
|
| 181 |
]
|
| 182 |
+
elif writer_num <= 6: # Middle writers
|
| 183 |
queries = [
|
| 184 |
f"{topic} conflict scenarios",
|
| 185 |
f"{topic} psychological aspects"
|
|
|
|
| 198 |
return queries
|
| 199 |
|
| 200 |
class NovelDatabase:
|
| 201 |
+
"""Novel session management database with enhanced recovery features"""
|
| 202 |
|
| 203 |
@staticmethod
|
| 204 |
def init_db():
|
|
|
|
| 209 |
|
| 210 |
cursor = conn.cursor()
|
| 211 |
|
| 212 |
+
# Sessions table with enhanced recovery fields
|
| 213 |
cursor.execute('''
|
| 214 |
CREATE TABLE IF NOT EXISTS sessions (
|
| 215 |
session_id TEXT PRIMARY KEY,
|
|
|
|
| 219 |
updated_at TEXT DEFAULT (datetime('now')),
|
| 220 |
status TEXT DEFAULT 'active',
|
| 221 |
current_stage INTEGER DEFAULT 0,
|
| 222 |
+
last_saved_stage INTEGER DEFAULT -1,
|
| 223 |
+
recovery_data TEXT,
|
| 224 |
final_novel TEXT
|
| 225 |
)
|
| 226 |
''')
|
|
|
|
| 234 |
stage_name TEXT NOT NULL,
|
| 235 |
role TEXT NOT NULL,
|
| 236 |
content TEXT,
|
| 237 |
+
word_count INTEGER DEFAULT 0,
|
| 238 |
status TEXT DEFAULT 'pending',
|
| 239 |
created_at TEXT DEFAULT (datetime('now')),
|
| 240 |
updated_at TEXT DEFAULT (datetime('now')),
|
|
|
|
| 261 |
cursor.execute('CREATE INDEX IF NOT EXISTS idx_session_id ON stages(session_id)')
|
| 262 |
cursor.execute('CREATE INDEX IF NOT EXISTS idx_stage_number ON stages(stage_number)')
|
| 263 |
cursor.execute('CREATE INDEX IF NOT EXISTS idx_search_session ON search_history(session_id)')
|
| 264 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_session_status ON sessions(status)')
|
| 265 |
|
| 266 |
conn.commit()
|
| 267 |
|
|
|
|
| 276 |
''', (session_id, stage_number, role, query, results))
|
| 277 |
conn.commit()
|
| 278 |
|
|
|
|
| 279 |
@staticmethod
|
| 280 |
@contextmanager
|
| 281 |
def get_db():
|
|
|
|
| 306 |
@staticmethod
|
| 307 |
def save_stage(session_id: str, stage_number: int, stage_name: str,
|
| 308 |
role: str, content: str, status: str = 'complete'):
|
| 309 |
+
"""Save stage content with word count"""
|
| 310 |
+
word_count = len(content.split()) if content else 0
|
| 311 |
+
|
| 312 |
with NovelDatabase.get_db() as conn:
|
| 313 |
cursor = conn.cursor()
|
| 314 |
|
| 315 |
# UPSERT operation
|
| 316 |
cursor.execute('''
|
| 317 |
+
INSERT INTO stages (session_id, stage_number, stage_name, role, content, word_count, status)
|
| 318 |
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
| 319 |
ON CONFLICT(session_id, stage_number)
|
| 320 |
+
DO UPDATE SET content=?, word_count=?, status=?, stage_name=?, updated_at=datetime('now')
|
| 321 |
+
''', (session_id, stage_number, stage_name, role, content, word_count, status,
|
| 322 |
+
content, word_count, status, stage_name))
|
| 323 |
|
| 324 |
# Update session
|
| 325 |
cursor.execute('''
|
| 326 |
UPDATE sessions
|
| 327 |
+
SET updated_at = datetime('now'),
|
| 328 |
+
current_stage = ?,
|
| 329 |
+
last_saved_stage = ?
|
| 330 |
WHERE session_id = ?
|
| 331 |
+
''', (stage_number, stage_number, session_id))
|
| 332 |
|
| 333 |
conn.commit()
|
| 334 |
+
logger.info(f"Saved stage {stage_number} for session {session_id}, content length: {len(content)}, words: {word_count}")
|
| 335 |
|
| 336 |
@staticmethod
|
| 337 |
def get_session(session_id: str) -> Optional[Dict]:
|
|
|
|
| 341 |
cursor.execute('SELECT * FROM sessions WHERE session_id = ?', (session_id,))
|
| 342 |
return cursor.fetchone()
|
| 343 |
|
| 344 |
+
@staticmethod
|
| 345 |
+
def get_latest_active_session() -> Optional[Dict]:
|
| 346 |
+
"""Get the most recent active session"""
|
| 347 |
+
with NovelDatabase.get_db() as conn:
|
| 348 |
+
cursor = conn.cursor()
|
| 349 |
+
cursor.execute('''
|
| 350 |
+
SELECT * FROM sessions
|
| 351 |
+
WHERE status = 'active'
|
| 352 |
+
ORDER BY updated_at DESC
|
| 353 |
+
LIMIT 1
|
| 354 |
+
''')
|
| 355 |
+
return cursor.fetchone()
|
| 356 |
+
|
| 357 |
@staticmethod
|
| 358 |
def get_stages(session_id: str) -> List[Dict]:
|
| 359 |
"""Get all stages for a session"""
|
|
|
|
| 368 |
|
| 369 |
@staticmethod
|
| 370 |
def get_all_writer_content(session_id: str) -> str:
|
| 371 |
+
"""๋ชจ๋ ์๊ฐ์ ์์ ๋ณธ ๋ด์ฉ์ ๊ฐ์ ธ์์ ํฉ์น๊ธฐ - 10๋ช
์๊ฐ"""
|
| 372 |
with NovelDatabase.get_db() as conn:
|
| 373 |
cursor = conn.cursor()
|
| 374 |
|
|
|
|
| 378 |
|
| 379 |
for stage_num in WRITER_REVISION_STAGES:
|
| 380 |
cursor.execute('''
|
| 381 |
+
SELECT content, stage_name, word_count FROM stages
|
| 382 |
WHERE session_id = ? AND stage_number = ?
|
| 383 |
''', (session_id, stage_num))
|
| 384 |
|
|
|
|
| 391 |
|
| 392 |
if clean_content:
|
| 393 |
writer_count += 1
|
| 394 |
+
word_count = row['word_count'] or len(clean_content.split())
|
| 395 |
total_word_count += word_count
|
| 396 |
all_content.append(clean_content)
|
| 397 |
logger.info(f"Writer {writer_count} (stage {stage_num}): {word_count} words")
|
| 398 |
|
| 399 |
full_content = '\n\n'.join(all_content)
|
| 400 |
+
logger.info(f"Total: {writer_count} writers, {total_word_count} words")
|
| 401 |
|
| 402 |
+
# 10๋ช
์๊ฐ * 1,450 ํ๊ท = 14,500 ๋จ์ด ๋ชฉํ
|
| 403 |
+
if total_word_count < 12000:
|
| 404 |
+
logger.warning(f"Content too short! Only {total_word_count} words instead of ~14,500")
|
| 405 |
|
| 406 |
return full_content
|
| 407 |
|
|
|
|
| 450 |
|
| 451 |
# ๋ชจ๋ ์๊ฐ ์์ ๋ณธ ํ์ธ
|
| 452 |
cursor.execute(f'''
|
| 453 |
+
SELECT stage_number, stage_name, LENGTH(content) as content_length, word_count
|
| 454 |
FROM stages
|
| 455 |
WHERE session_id = ? AND stage_number IN ({','.join(map(str, WRITER_REVISION_STAGES))})
|
| 456 |
ORDER BY stage_number
|
|
|
|
| 458 |
|
| 459 |
results = []
|
| 460 |
total_length = 0
|
| 461 |
+
total_words = 0
|
| 462 |
|
| 463 |
for row in cursor.fetchall():
|
| 464 |
results.append({
|
| 465 |
'stage': row['stage_number'],
|
| 466 |
'name': row['stage_name'],
|
| 467 |
+
'length': row['content_length'] or 0,
|
| 468 |
+
'words': row['word_count'] or 0
|
| 469 |
})
|
| 470 |
total_length += row['content_length'] or 0
|
| 471 |
+
total_words += row['word_count'] or 0
|
| 472 |
|
| 473 |
# ์ต์ข
์์ค ํ์ธ
|
| 474 |
cursor.execute('''
|
|
|
|
| 483 |
return {
|
| 484 |
'writer_stages': results,
|
| 485 |
'total_writer_content': total_length,
|
| 486 |
+
'total_words': total_words,
|
| 487 |
'final_novel_length': final_length,
|
| 488 |
+
'expected_words': 14500 # 10 ์๊ฐ * 1450 ํ๊ท
|
| 489 |
}
|
| 490 |
|
| 491 |
class NovelWritingSystem:
|
|
|
|
| 575 |
|
| 576 |
return original_prompt + "\n\n" + research_section
|
| 577 |
|
|
|
|
| 578 |
def create_director_initial_prompt(self, user_query: str, language: str = "English") -> str:
|
| 579 |
+
"""Director AI initial prompt - Novel planning for 10 writers"""
|
| 580 |
if language == "Korean":
|
| 581 |
+
return f"""๋น์ ์ 30ํ์ด์ง ๋ถ๋์ ์คํธ ์์ค์ ๊ธฐํํ๋ ๋ฌธํ ๊ฐ๋
์์
๋๋ค.
|
| 582 |
|
| 583 |
์ฌ์ฉ์ ์์ฒญ: {user_query}
|
| 584 |
|
| 585 |
+
๋ค์ ์์๋ค์ ์ฒด๊ณ์ ์ผ๋ก ๊ตฌ์ฑํ์ฌ 30ํ์ด์ง ์คํธ ์์ค์ ๊ธฐ์ด๋ฅผ ๋ง๋์ธ์:
|
| 586 |
|
| 587 |
1. **์ฃผ์ ์ ์ฅ๋ฅด**
|
| 588 |
- ํต์ฌ ์ฃผ์ ์ ๋ฉ์์ง
|
|
|
|
| 598 |
- ๊ฐ๋ฑ ๊ตฌ์กฐ
|
| 599 |
- ๊ฐ์ ์ ์ฐ๊ฒฐ๊ณ ๋ฆฌ
|
| 600 |
|
| 601 |
+
4. **์์ฌ ๊ตฌ์กฐ** (30ํ์ด์ง๋ฅผ 10๊ฐ ํํธ๋ก ๋๋์ด ๊ฐ 3ํ์ด์ง)
|
| 602 |
| ํํธ | ํ์ด์ง | ์ฃผ์ ์ฌ๊ฑด | ๊ธด์ฅ๋ | ์ธ๋ฌผ ๋ฐ์ |
|
| 603 |
|------|--------|-----------|---------|-----------|
|
| 604 |
| 1 | 1-3 | | | |
|
| 605 |
| 2 | 4-6 | | | |
|
| 606 |
| ... | ... | | | |
|
| 607 |
+
| 10 | 28-30 | | | |
|
| 608 |
|
| 609 |
5. **์ธ๊ณ๊ด ์ค์ **
|
| 610 |
- ์๊ณต๊ฐ์ ๋ฐฐ๊ฒฝ
|
|
|
|
| 613 |
|
| 614 |
๊ฐ ์์ฑ์๊ฐ 3ํ์ด์ง์ฉ ์์ฑํ ์ ์๋๋ก ๋ช
ํํ ๊ฐ์ด๋๋ผ์ธ์ ์ ์ํ์ธ์."""
|
| 615 |
else:
|
| 616 |
+
return f"""You are a literary director planning a 30-page novella.
|
| 617 |
|
| 618 |
User Request: {user_query}
|
| 619 |
|
| 620 |
+
Systematically compose the following elements to create the foundation for a 30-page novella:
|
| 621 |
|
| 622 |
1. **Theme and Genre**
|
| 623 |
- Core theme and message
|
|
|
|
| 633 |
- Conflict structure
|
| 634 |
- Emotional connections
|
| 635 |
|
| 636 |
+
4. **Narrative Structure** (divide 30 pages into 10 parts, 3 pages each)
|
| 637 |
| Part | Pages | Main Events | Tension | Character Development |
|
| 638 |
|------|-------|-------------|---------|---------------------|
|
| 639 |
| 1 | 1-3 | | | |
|
| 640 |
| 2 | 4-6 | | | |
|
| 641 |
| ... | ... | | | |
|
| 642 |
+
| 10 | 28-30 | | | |
|
| 643 |
|
| 644 |
5. **World Building**
|
| 645 |
- Temporal and spatial setting
|
|
|
|
| 668 |
|------|------|------|-----------|
|
| 669 |
|
| 670 |
3. **๊ตฌ์กฐ์ ๊ท ํ**
|
| 671 |
+
- 10๊ฐ ํํธ๋ณ ๋ถ๋ ๋ฐฐ๋ถ
|
| 672 |
- ๊ธด์ฅ๊ณผ ์ด์์ ๋ฆฌ๋ฌ
|
| 673 |
- ์ ์ฒด์ ์ธ ํ๋ฆ
|
| 674 |
|
|
|
|
| 678 |
- ๊ธฐ๋์น ์ถฉ์กฑ๋
|
| 679 |
|
| 680 |
5. **์คํ ๊ฐ๋ฅ์ฑ**
|
| 681 |
+
- 10๋ช
์ ์์ฑ์๋ฅผ ์ํ ๊ฐ์ด๋๋ผ์ธ์ ๋ช
ํ์ฑ
|
| 682 |
- ์ผ๊ด์ฑ ์ ์ง ๋ฐฉ์
|
| 683 |
- ์ ์ฌ์ ๋ฌธ์ ์
|
| 684 |
|
|
|
|
| 701 |
|-----------|-----------|------------|-------------|
|
| 702 |
|
| 703 |
3. **Structural Balance**
|
| 704 |
+
- Distribution across 10 parts
|
| 705 |
- Rhythm of tension and relief
|
| 706 |
- Overall flow
|
| 707 |
|
|
|
|
| 711 |
- Expectation fulfillment
|
| 712 |
|
| 713 |
5. **Feasibility**
|
| 714 |
+
- Clarity of guidelines for 10 writers
|
| 715 |
- Consistency maintenance
|
| 716 |
- Potential issues
|
| 717 |
|
|
|
|
| 730 |
|
| 731 |
๋ค์์ ํฌํจํ ์์ ๋ ์ต์ข
๊ธฐํ์ ์ ์ํ์ธ์:
|
| 732 |
|
| 733 |
+
1. **์์ ๋ ์์ฌ ๊ตฌ์กฐ** (10๊ฐ ํํธ)
|
| 734 |
| ํํธ | ํ์ด์ง | ์ฃผ์ ์ฌ๊ฑด | ์์ฑ ์ง์นจ | ์ฃผ์์ฌํญ |
|
| 735 |
|------|--------|-----------|-----------|----------|
|
| 736 |
|
|
|
|
| 739 |
- ์ธ๋ฌผ ๊ฐ ๊ฐ๋ฑ์ ๊ตฌ์ฒดํ
|
| 740 |
- ๊ฐ์ ์ ์ ๋ณํ ์ถ์ด
|
| 741 |
|
| 742 |
+
3. **๊ฐ ์์ฑ์๋ฅผ ์ํ ์์ธ ๊ฐ์ด๋** (10๋ช
)
|
| 743 |
- ํํธ๋ณ ์์๊ณผ ๋ ์ง์
|
| 744 |
- ํ์ ํฌํจ ์์
|
| 745 |
- ๋ฌธ์ฒด์ ํค ์ง์นจ
|
|
|
|
| 756 |
- ์ ์ฒด์ ํต์ผ์ฑ
|
| 757 |
- ๋
์ ๋ชฐ์
์ ์ง ๋ฐฉ์
|
| 758 |
|
| 759 |
+
10๋ช
์ ์์ฑ์๊ฐ ๋ช
ํํ ์ดํดํ ์ ์๋ ์ต์ข
๋ง์คํฐํ๋์ ์์ฑํ์ธ์."""
|
| 760 |
else:
|
| 761 |
return f"""As director, revise the novel plan reflecting the critic's feedback.
|
| 762 |
|
|
|
|
| 768 |
|
| 769 |
Present the revised final plan including:
|
| 770 |
|
| 771 |
+
1. **Revised Narrative Structure** (10 parts)
|
| 772 |
| Part | Pages | Main Events | Writing Guidelines | Cautions |
|
| 773 |
|------|-------|-------------|-------------------|----------|
|
| 774 |
|
|
|
|
| 777 |
- Concrete conflicts between characters
|
| 778 |
- Emotional arc progression
|
| 779 |
|
| 780 |
+
3. **Detailed Guide for Each Writer** (10 writers)
|
| 781 |
- Start and end points for each part
|
| 782 |
- Essential elements to include
|
| 783 |
- Style and tone guidelines
|
|
|
|
| 794 |
- Overall unity
|
| 795 |
- Reader engagement maintenance
|
| 796 |
|
| 797 |
+
Create a final masterplan that all 10 writers can clearly understand."""
|
| 798 |
|
| 799 |
def create_writer_prompt(self, writer_number: int, director_plan: str, previous_content: str, language: str = "English") -> str:
|
| 800 |
+
"""Individual writer prompt - 1,400-1,500 ๋จ์ด"""
|
| 801 |
pages_start = (writer_number - 1) * 3 + 1
|
| 802 |
pages_end = writer_number * 3
|
| 803 |
|
| 804 |
if language == "Korean":
|
| 805 |
+
return f"""๋น์ ์ ์์ฑ์ {writer_number}๋ฒ์
๋๋ค. 30ํ์ด์ง ์คํธ ์์ค์ {pages_start}-{pages_end}ํ์ด์ง(3ํ์ด์ง)๋ฅผ ์์ฑํ์ธ์.
|
| 806 |
|
| 807 |
๊ฐ๋
์์ ๋ง์คํฐํ๋:
|
| 808 |
{director_plan}
|
|
|
|
| 813 |
**์ค์ ์ง์นจ:**
|
| 814 |
|
| 815 |
1. **ํ์ ๋ถ๋**:
|
| 816 |
+
- ์ต์ 1,400๋จ์ด, ์ต๋ 1,500๋จ์ด
|
| 817 |
+
- ๋๋ต 7000-7500์ (๊ณต๋ฐฑ ํฌํจ)
|
| 818 |
+
- 3ํ์ด์ง ๋ถ๋์ ์ ํํ ์ฑ์์ผ ํฉ๋๋ค
|
| 819 |
|
| 820 |
2. **๋ถ๋ ํ๋ณด ์ ๋ต**:
|
| 821 |
- ์์ธํ ์ฅ๋ฉด ๋ฌ์ฌ ํฌํจ
|
|
|
|
| 835 |
- ๋
์์ ๊ด์ฌ ์ ์ง
|
| 836 |
|
| 837 |
**์์ฑ ์์:**
|
| 838 |
+
์ด์ 1,400-1,500๋จ์ด ๋ถ๋์ ์์ค์ ์์ฑํ์ธ์. ํ์ด์ง ๊ตฌ๋ถ ํ์๋ ํ์ง ๋ง์ธ์."""
|
| 839 |
else:
|
| 840 |
+
return f"""You are Writer #{writer_number}. Write pages {pages_start}-{pages_end} (3 pages) of the 30-page novella.
|
| 841 |
|
| 842 |
Director's Masterplan:
|
| 843 |
{director_plan}
|
|
|
|
| 848 |
**CRITICAL INSTRUCTIONS:**
|
| 849 |
|
| 850 |
1. **MANDATORY LENGTH**:
|
| 851 |
+
- Minimum 1,400 words, Maximum 1,500 words
|
| 852 |
+
- Approximately 7000-7500 characters
|
| 853 |
+
- You MUST fill exactly 3 pages
|
| 854 |
|
| 855 |
2. **LENGTH STRATEGIES**:
|
| 856 |
- Include detailed scene descriptions
|
|
|
|
| 870 |
- Maintain reader interest
|
| 871 |
|
| 872 |
**BEGIN WRITING:**
|
| 873 |
+
Now write your 1,400-1,500 word section. Do not use any page markers."""
|
| 874 |
|
| 875 |
def create_critic_writer_prompt(self, writer_number: int, writer_content: str, director_plan: str, all_previous_content: str, language: str = "English") -> str:
|
| 876 |
"""Critic's review of individual writer's work"""
|
|
|
|
| 983 |
- ๋ฌ์ฌ์ ๋ํ ๊ฐ์
|
| 984 |
|
| 985 |
3. **๋ถ๋ ์ ์ง**
|
| 986 |
+
- ์ฌ์ ํ ์ ํํ 3ํ์ด์ง (1,400-1,500๋จ์ด)
|
| 987 |
- ํ์ด์ง ๊ตฌ๋ถ ํ์ ์ ๋ ๊ธ์ง
|
| 988 |
|
| 989 |
4. **์ฐ์์ฑ ํ๋ณด**
|
|
|
|
| 991 |
- ์์ ์ผ๋ก ์ธํ ์๋ก์ด ๋ชจ์ ๋ฐฉ์ง
|
| 992 |
|
| 993 |
์์ ๋ ์ต์ข
๋ณธ์ ์ ์ํ์ธ์. ํ์ด์ง ๋งํฌ๋ ์ ๋ ์ฌ์ฉํ์ง ๋ง์ธ์.
|
| 994 |
+
๋ฐ๋์ 1,400-1,500๋จ์ด ๋ถ๋์ ์ ์งํ์ธ์."""
|
| 995 |
else:
|
| 996 |
return f"""As Writer #{writer_number}, revise based on critic's feedback.
|
| 997 |
|
|
|
|
| 1014 |
- Improve descriptions and dialogue
|
| 1015 |
|
| 1016 |
3. **Maintain Length**
|
| 1017 |
+
- Still exactly 3 pages (1,400-1,500 words)
|
| 1018 |
- Absolutely no page markers
|
| 1019 |
|
| 1020 |
4. **Ensure Continuity**
|
|
|
|
| 1022 |
- Prevent new contradictions from revisions
|
| 1023 |
|
| 1024 |
Present the revised final version. Never use page markers.
|
| 1025 |
+
You MUST maintain 1,400-1,500 words."""
|
| 1026 |
|
| 1027 |
def create_critic_final_prompt(self, all_content: str, director_plan: str, language: str = "English") -> str:
|
| 1028 |
"""Final critic evaluation of complete novel"""
|
| 1029 |
content_preview = all_content[:3000] + "\n...\n" + all_content[-3000:] if len(all_content) > 6000 else all_content
|
| 1030 |
|
| 1031 |
if language == "Korean":
|
| 1032 |
+
return f"""์ ์ฒด 30ํ์ด์ง ์์ค์ ์ต์ข
ํ๊ฐํฉ๋๋ค.
|
| 1033 |
|
| 1034 |
๊ฐ๋
์์ ๋ง์คํฐํ๋:
|
| 1035 |
{director_plan}
|
|
|
|
| 1072 |
|
| 1073 |
๊ฐ๋
์๊ฐ ์ต์ข
์์ ํ ์ ์๋๋ก ๊ตฌ์ฒด์ ์ด๊ณ ์คํ ๊ฐ๋ฅํ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ธ์."""
|
| 1074 |
else:
|
| 1075 |
+
return f"""Final evaluation of the complete 30-page novel.
|
| 1076 |
|
| 1077 |
Director's Masterplan:
|
| 1078 |
{director_plan}
|
|
|
|
| 1118 |
def create_director_final_prompt(self, all_content: str, critic_final_feedback: str, language: str = "English") -> str:
|
| 1119 |
"""Director's final compilation and polish - ๋ชจ๋ ์๊ฐ ๋ด์ฉ ํฌํจ"""
|
| 1120 |
word_count = len(all_content.split())
|
| 1121 |
+
expected_words = 14500 # 30ํ์ด์ง * 500๋จ์ด
|
| 1122 |
|
| 1123 |
if language == "Korean":
|
| 1124 |
return f"""๊ฐ๋
์๋ก์ ๋นํ๊ฐ์ ์ต์ข
ํ๊ฐ๋ฅผ ๋ฐ์ํ์ฌ ์์ฑ๋ณธ์ ์ ์ํฉ๋๋ค.
|
| 1125 |
|
| 1126 |
+
์ ์ฒด ์๊ฐ๋ค์ ์ํ (30ํ์ด์ง ์ ์ฒด, {word_count}๋จ์ด):
|
| 1127 |
{all_content}
|
| 1128 |
|
| 1129 |
๋นํ๊ฐ ์ต์ข
ํ๊ฐ:
|
|
|
|
| 1135 |
|
| 1136 |
## ์ํ ์ ๋ณด
|
| 1137 |
- ์ฅ๋ฅด:
|
| 1138 |
+
- ๋ถ๋: 30ํ์ด์ง ({word_count}๋จ์ด)
|
| 1139 |
- ์ฃผ์ :
|
| 1140 |
- ํ ์ค ์์ฝ:
|
| 1141 |
|
|
|
|
| 1146 |
|
| 1147 |
## ๋ณธ๋ฌธ
|
| 1148 |
|
| 1149 |
+
[10๋ช
์ ์๊ฐ๊ฐ ์์ฑํ ์ ์ฒด 30ํ์ด์ง ๋ด์ฉ์ ๋ค์ ๊ธฐ์ค์ผ๋ก ํตํฉ]
|
| 1150 |
1. ์ค๋ ์ค๋ฅ ์์ ์๋ฃ
|
| 1151 |
2. ํํธ ๊ฐ ์ฐ๊ฒฐ ๋งค๋๋ฝ๊ฒ ์กฐ์
|
| 1152 |
3. ๋ฌธ์ฒด์ ํค ํต์ผ
|
|
|
|
| 1154 |
5. ํ์ด์ง ๊ตฌ๋ถ ํ์ ์์ ์ ๊ฑฐ
|
| 1155 |
6. ์์ฐ์ค๋ฌ์ด ํ๋ฆ์ผ๋ก ์ฌ๊ตฌ์ฑ
|
| 1156 |
|
| 1157 |
+
[์ ์ฒด 30ํ์ด์ง ๋ถ๋์ ์์ฑ๋ ์์ค ๋ณธ๋ฌธ]
|
| 1158 |
|
| 1159 |
---
|
| 1160 |
|
| 1161 |
## ์๊ฐ์ ๋ง
|
| 1162 |
[์ํ์ ๋ํ ๊ฐ๋จํ ํด์ค์ด๋ ์๋]
|
| 1163 |
|
| 1164 |
+
๋ชจ๋ ์๊ฐ์ ๊ธฐ์ฌ๋ฅผ ํตํฉํ ์์ ํ 30ํ์ด์ง ์์ค์ ์ ์ํ์ธ์."""
|
| 1165 |
else:
|
| 1166 |
return f"""As director, create the final version reflecting the critic's final evaluation.
|
| 1167 |
|
| 1168 |
+
Complete Writers' Work (Full 30 pages, {word_count} words):
|
| 1169 |
{all_content}
|
| 1170 |
|
| 1171 |
Critic's Final Evaluation:
|
|
|
|
| 1177 |
|
| 1178 |
## Work Information
|
| 1179 |
- Genre:
|
| 1180 |
+
- Length: 30 pages ({word_count} words)
|
| 1181 |
- Theme:
|
| 1182 |
- One-line summary:
|
| 1183 |
|
|
|
|
| 1188 |
|
| 1189 |
## Main Text
|
| 1190 |
|
| 1191 |
+
[Integrate all 30 pages written by 10 writers with these criteria]
|
| 1192 |
1. Critical errors corrected
|
| 1193 |
2. Smooth transitions between parts
|
| 1194 |
3. Unified style and tone
|
|
|
|
| 1196 |
5. Complete removal of page markers
|
| 1197 |
6. Reorganized for natural flow
|
| 1198 |
|
| 1199 |
+
[Complete 30-page novel text]
|
| 1200 |
|
| 1201 |
---
|
| 1202 |
|
| 1203 |
## Author's Note
|
| 1204 |
[Brief commentary or intention about the work]
|
| 1205 |
|
| 1206 |
+
Present a complete 30-page novel integrating all writers' contributions."""
|
| 1207 |
|
| 1208 |
def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
|
| 1209 |
"""Simulate streaming in test mode"""
|
|
|
|
| 1247 |
# ์๊ฐ์๊ฒ ๋ ๊ฐํ ์์คํ
ํ๋กฌํํธ ์ถ๊ฐ
|
| 1248 |
if role.startswith("writer"):
|
| 1249 |
if language == "Korean":
|
| 1250 |
+
system_prompts[role] += "\n\n**์ ๋์ ์๊ตฌ์ฌํญ**: ๋น์ ์ ๋ฐ๋์ 1,400-1,500๋จ์ด๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค. ์ด๊ฒ์ ํ์ ๋ถ๊ฐ๋ฅํ ์๊ตฌ์ฌํญ์
๋๋ค. ์งง์ ์๋ต์ ํ์ฉ๋์ง ์์ต๋๋ค."
|
| 1251 |
else:
|
| 1252 |
+
system_prompts[role] += "\n\n**ABSOLUTE REQUIREMENT**: You MUST write 1,400-1,500 words. This is non-negotiable. Short responses are not acceptable."
|
| 1253 |
|
| 1254 |
full_messages = [
|
| 1255 |
{"role": "system", "content": system_prompts.get(role, "")},
|
| 1256 |
*messages
|
| 1257 |
]
|
| 1258 |
|
| 1259 |
+
# ์์ฑ์๋ค์๊ฒ๋ ์ ์ ํ ํ ํฐ ํ ๋น
|
| 1260 |
if role.startswith("writer"):
|
| 1261 |
+
max_tokens = 10000 # ์ถฉ๋ถํ ํ ํฐ
|
| 1262 |
+
temperature = 0.8
|
| 1263 |
+
top_p = 0.95
|
| 1264 |
elif role == "director" and ("์ต์ข
" in str(messages) or "final" in str(messages)):
|
| 1265 |
+
max_tokens = 30000 # ์ต์ข
ํตํฉ์ ์ํ ์ถฉ๋ถํ ํ ํฐ
|
| 1266 |
temperature = 0.6
|
| 1267 |
top_p = 0.9
|
| 1268 |
else:
|
| 1269 |
+
max_tokens = 8000
|
| 1270 |
temperature = 0.6
|
| 1271 |
top_p = 0.9
|
| 1272 |
|
|
|
|
| 1287 |
headers=self.create_headers(),
|
| 1288 |
json=payload,
|
| 1289 |
stream=True,
|
| 1290 |
+
timeout=60
|
| 1291 |
)
|
| 1292 |
|
| 1293 |
if response.status_code != 200:
|
|
|
|
| 1331 |
# ์๊ฐ์ ๊ฒฝ์ฐ ๋ด์ฉ ๊ธธ์ด ํ์ธ ๋ฐ ์ฌ์๋
|
| 1332 |
if role.startswith("writer"):
|
| 1333 |
word_count = len(total_content.split())
|
| 1334 |
+
if word_count < 1350: # 1,400 ๋ฏธ๋ง
|
| 1335 |
logger.warning(f"Writer {role} produced only {word_count} words! Requesting continuation...")
|
| 1336 |
|
| 1337 |
# ์ถ๊ฐ ์์ฒญ
|
| 1338 |
+
continuation_prompt = f"Continue writing to reach the required 1,400-1,500 words. You have written {word_count} words so far. Write {1400 - word_count} more words to complete your section."
|
| 1339 |
|
| 1340 |
if language == "Korean":
|
| 1341 |
+
continuation_prompt = f"ํ์ ๋ถ๋ 1,400-1,500๋จ์ด๋ฅผ ์ฑ์ฐ๊ธฐ ์ํด ๊ณ์ ์์ฑํ์ธ์. ์ง๊ธ๊น์ง {word_count}๋จ์ด๋ฅผ ์์ฑํ์ต๋๋ค. {1400 - word_count}๋จ์ด๋ฅผ ๋ ์์ฑํ์ฌ ์น์
์ ์์ฑํ์ธ์."
|
| 1342 |
|
| 1343 |
full_messages.append({"role": "assistant", "content": total_content})
|
| 1344 |
full_messages.append({"role": "user", "content": continuation_prompt})
|
|
|
|
| 1347 |
continuation_payload = {
|
| 1348 |
"model": self.model_id,
|
| 1349 |
"messages": full_messages,
|
| 1350 |
+
"max_tokens": 5000,
|
| 1351 |
"temperature": temperature,
|
| 1352 |
"top_p": top_p,
|
| 1353 |
"stream": True
|
|
|
|
| 1392 |
logger.error(f"Error during streaming: {str(e)}")
|
| 1393 |
yield f"โ Error occurred: {str(e)}"
|
| 1394 |
|
|
|
|
| 1395 |
def get_system_prompts(self, language: str) -> Dict[str, str]:
|
| 1396 |
+
"""Get system prompts for all 10 writers"""
|
| 1397 |
if language == "Korean":
|
| 1398 |
prompts = {
|
| 1399 |
+
"director": "๋น์ ์ 30ํ์ด์ง ์คํธ ์์ค์ ๊ธฐ๏ฟฝ๏ฟฝํ๊ณ ๊ฐ๋
ํ๋ ๋ฌธํ ๊ฐ๋
์์
๋๋ค. ์ฒด๊ณ์ ์ด๊ณ ์ฐฝ์์ ์ธ ์คํ ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ด๋
๋๋ค.",
|
| 1400 |
"critic": "๋น์ ์ ๋ ์นด๋ก์ด ํต์ฐฐ๋ ฅ์ ๊ฐ์ง ๋ฌธํ ๋นํ๊ฐ์
๋๋ค. ๊ฑด์ค์ ์ด๊ณ ๊ตฌ์ฒด์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค."
|
| 1401 |
}
|
| 1402 |
|
| 1403 |
+
# 10๋ช
์ ์๊ฐ ํ๋กฌํํธ
|
| 1404 |
writer_roles = [
|
| 1405 |
"์์ค์ ๋์
๋ถ๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋
์๋ฅผ ์ฌ๋ก์ก๋ ์์์ ๋ง๋ญ๋๋ค.",
|
| 1406 |
"์ด๋ฐ ์ ๊ฐ๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ธ๋ฌผ๊ณผ ์ํฉ์ ๊น์ด ์๊ฒ ๋ฐ์ ์ํต๋๋ค.",
|
| 1407 |
"๊ฐ๋ฑ ๋์
์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ด์ผ๊ธฐ์ ํต์ฌ ๊ฐ๋ฑ์ ์ ์ํฉ๋๋ค.",
|
| 1408 |
"๊ฐ๋ฑ ์์น์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๊ธด์ฅ๊ฐ์ ๋์ด๊ณ ๋ณต์ก์ฑ์ ๋ํฉ๋๋ค.",
|
| 1409 |
+
"์ค๋ฐ๋ถ ์ ํ์ ์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ค์ํ ๋ณํ๋ฅผ ๋ง๋ญ๋๋ค.",
|
| 1410 |
+
"์ค๋ฐ๋ถ ์ฌํ๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ด์ผ๊ธฐ์ ์ค์ฌ์ถ์ ๊ฒฌ๊ณ ํ๊ฒ ๋ง๋ญ๋๋ค.",
|
|
|
|
|
|
|
|
|
|
| 1411 |
"ํด๋ผ์ด๋งฅ์ค ์ค๋น๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ต๊ณ ์กฐ๋ฅผ ํฅํด ๋์๊ฐ๋๋ค.",
|
| 1412 |
"ํด๋ผ์ด๋งฅ์ค๋ฅผ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋ชจ๋ ๊ฐ๋ฑ์ด ํญ๋ฐํ๋ ์๊ฐ์ ๊ทธ๋ฆฝ๋๋ค.",
|
|
|
|
| 1413 |
"ํด๊ฒฐ ์์์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ๋งค๋ญ์ ํ์ด๋๊ฐ๊ธฐ ์์ํฉ๋๋ค.",
|
|
|
|
|
|
|
| 1414 |
"์ต์ข
๊ฒฐ๋ง์ ๋ด๋นํ๋ ์๊ฐ์
๋๋ค. ์ฌ์ด์ด ๋จ๋ ๋ง๋ฌด๋ฆฌ๋ฅผ ๋ง๋ญ๋๋ค."
|
| 1415 |
]
|
| 1416 |
|
| 1417 |
for i, role_desc in enumerate(writer_roles, 1):
|
| 1418 |
+
prompts[f"writer{i}"] = f"๋น์ ์ {role_desc} ๋ฐ๋์ 1,400-1,500๋จ์ด๋ฅผ ์์ฑํ์ธ์. ์ด๋ ์ ๋์ ์ธ ์๊ตฌ์ฌํญ์
๋๋ค."
|
| 1419 |
|
| 1420 |
return prompts
|
| 1421 |
else:
|
| 1422 |
prompts = {
|
| 1423 |
+
"director": "You are a literary director planning and supervising a 30-page novella. You create systematic and creative story structures.",
|
| 1424 |
"critic": "You are a literary critic with sharp insights. You provide constructive and specific feedback."
|
| 1425 |
}
|
| 1426 |
|
| 1427 |
+
# 10 writer prompts
|
| 1428 |
writer_roles = [
|
| 1429 |
"the writer responsible for the introduction. You create a captivating beginning.",
|
| 1430 |
"the writer responsible for early development. You deepen characters and situations.",
|
| 1431 |
"the writer responsible for conflict introduction. You present the core conflict.",
|
| 1432 |
"the writer responsible for rising conflict. You increase tension and add complexity.",
|
| 1433 |
+
"the writer responsible for the midpoint turn. You create important changes.",
|
| 1434 |
+
"the writer responsible for deepening the middle. You solidify the story's central axis.",
|
|
|
|
|
|
|
|
|
|
| 1435 |
"the writer responsible for climax preparation. You move toward the peak.",
|
| 1436 |
"the writer responsible for the climax. You depict the moment when all conflicts explode.",
|
|
|
|
| 1437 |
"the writer responsible for resolution beginning. You start untangling the knots.",
|
|
|
|
|
|
|
| 1438 |
"the writer responsible for the final ending. You create a lingering conclusion."
|
| 1439 |
]
|
| 1440 |
|
| 1441 |
for i, role_desc in enumerate(writer_roles, 1):
|
| 1442 |
+
prompts[f"writer{i}"] = f"You are {role_desc} You MUST write 1,400-1,500 words. This is an absolute requirement."
|
| 1443 |
|
| 1444 |
return prompts
|
| 1445 |
|
|
|
|
| 1453 |
def get_korean_test_response(self, role: str) -> str:
|
| 1454 |
"""Korean test responses with appropriate length"""
|
| 1455 |
test_responses = {
|
| 1456 |
+
"director": """30ํ์ด์ง ์คํธ ์์ค ๊ธฐํ์์ ์ ์ํฉ๋๋ค.
|
| 1457 |
|
| 1458 |
## 1. ์ฃผ์ ์ ์ฅ๋ฅด
|
| 1459 |
- **ํต์ฌ ์ฃผ์ **: ์ธ๊ฐ ๋ณธ์ฑ๊ณผ ๊ธฐ์ ์ ์ถฉ๋ ์์์ ์ฐพ๋ ์ง์ ํ ์ฐ๊ฒฐ
|
|
|
|
| 1469 |
| ๋ฏผ์ค | ์กฐ๋ ฅ์ | ๋ฐ๋ปํจ, ์ง๊ด์ | ์ฌ๋ฆฌ์๋ด์ฌ | ์์ฐ์ ๋์ ๊ท ํ ์ฐพ๊ธฐ | ๊ธฐ์ ์์ฉ๊ณผ ์กฐํ |
|
| 1470 |
| ARIA | ๋๋ฆฝ์โ๋๋ฐ์ | ๋
ผ๋ฆฌ์ โ๊ฐ์ฑ ํ์ต | AI ํ๋กํ ํ์
| ์ง์ ํ ์กด์ฌ ๋๊ธฐ | ์์ ์ ์ฒด์ฑ ํ๋ฆฝ |
|
| 1471 |
|
| 1472 |
+
## 3. ์์ฌ ๊ตฌ์กฐ (10๊ฐ ํํธ, ๊ฐ 3ํ์ด์ง)
|
| 1473 |
|
| 1474 |
| ํํธ | ํ์ด์ง | ์ฃผ์ ์ฌ๊ฑด | ๊ธด์ฅ๋ | ์ธ๋ฌผ ๋ฐ์ |
|
| 1475 |
|------|--------|-----------|---------|-----------|
|
|
|
|
| 1479 |
| 4 | 10-12 | ARIA์ ์์ ์ธ์ ์งํ | 5/10 | ๊ฐ๋ฑ์ ์จ์ |
|
| 1480 |
| 5 | 13-15 | ์ฒซ ๋ฒ์งธ ์๊ธฐ | 6/10 | ์ ํ์ ์๊ฐ |
|
| 1481 |
| 6 | 16-18 | ์ค๋ฆฌ์์ํ ๊ฐ์
| 7/10 | ์ธ๋ถ ์๋ ฅ |
|
| 1482 |
+
| 7 | 19-21 | ๋ํ์ ์ดํด | 6/10 | ์ํธ ์ธ์ |
|
| 1483 |
+
| 8 | 22-24 | ์ตํ์ ๋๊ฒฐ ์ค๋น | 8/10 | ์ฐ๋์ ํ์ |
|
| 1484 |
+
| 9 | 25-27 | ํด๋ผ์ด๋งฅ์ค | 10/10 | ๋ชจ๋ ๊ฐ๋ฑ ํญ๋ฐ |
|
| 1485 |
+
| 10 | 28-30 | ์๋ก์ด ์์ | 4/10 | ๊ณต์กด๊ณผ ํํด |""",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1486 |
|
| 1487 |
"critic": """๊ฐ๋
์์ ๊ธฐํ์ ๊ฒํ ํ์ต๋๋ค.
|
| 1488 |
|
|
|
|
| 1490 |
|
| 1491 |
### 1. ์์ฌ์ ์์ฑ๋
|
| 1492 |
- **๊ฐ์ **: AI์ ์ธ๊ฐ์ ๊ด๊ณ๋ผ๋ ์์์ ์ ํ ์ฃผ์
|
| 1493 |
+
- **๊ฐ์ ์ **: 10๊ฐ ํํธ ๊ตฌ์ฑ์ด ์ ์ ํ๋ฉฐ ๊ฐ ํํธ์ ๋
๋ฆฝ์ฑ๊ณผ ์ฐ๊ฒฐ์ฑ์ด ์ ๊ท ํ์กํ ์์
|
| 1494 |
|
| 1495 |
### 2. ์ธ๋ฌผ ์ค์ ๊ฒํ
|
| 1496 |
|
|
|
|
| 1505 |
- ํํธ ๊ฐ ์ฐ๊ฒฐ์ฑ ๊ฐ์ด๋๋ผ์ธ ๋ณด๊ฐ ํ์""",
|
| 1506 |
}
|
| 1507 |
|
| 1508 |
+
# ์๊ฐ ์๋ต - 1,400-1,500 ๋จ์ด
|
| 1509 |
sample_story = """์์ฐ์ ์ฐ๊ตฌ์ค์ ์ฐจ๊ฐ์ด ํ๊ด๋ฑ ์๋์์ ๋ ๋ค๋ฅธ ๋ฐค์ ๋ณด๋ด๊ณ ์์๋ค. ๋ชจ๋ํฐ์ ํธ๋ฅธ ๋น์ด ๊ทธ๋
์ ์ฐฝ๋ฐฑํ ์ผ๊ตด์ ๋น์ถ๊ณ ์์๊ณ , ์์ญ ๊ฐ์ ์ฝ๋ ๋ผ์ธ์ด ๋์์์ด ์คํฌ๋กค๋๊ณ ์์๋ค. ARIA ํ๋ก์ ํธ๋ ๊ทธ๋
์ ์ถ ์ ๋ถ์๋ค. 3๋
์ด๋ผ๋ ์๊ฐ ๋์ ๊ทธ๋
๋ ์ด ์ธ๊ณต์ง๋ฅ์ ๋ชจ๋ ๊ฒ์ ์์๋ถ์๋ค.
|
| 1510 |
|
| 1511 |
"์์คํ
์ฒดํฌ ์๋ฃ. ๋ชจ๋ ํ๋ผ๋ฏธํฐ ์ ์." ๊ธฐ๊ณ์ ์ธ ์์ฑ์ด ์คํผ์ปค๋ฅผ ํตํด ํ๋ฌ๋์๋ค.
|
| 1512 |
|
| 1513 |
์์ฐ์ ์ ์ ์์์ ๊ธฐ๋์ด ๋์ ๊ฐ์๋ค. ํผ๋ก๊ฐ ๋ผ ์๊น์ง ํ๊ณ ๋ค์์ง๋ง, ๋ฉ์ถ ์ ์์๋ค. ARIA๋ ๋จ์ํ ํ๋ก์ ํธ๊ฐ ์๋์๋ค. ๊ทธ๊ฒ์ ๊ทธ๋
๊ฐ ์์ด๋ฒ๋ฆฐ ๊ฒ๋ค์ ๋์ฐพ์ ์ ์๋ ์ ์ผํ ํฌ๋ง์ด์๋ค."""
|
| 1514 |
|
| 1515 |
+
for i in range(1, 11):
|
| 1516 |
+
# ๊ฐ ์๊ฐ๋ง๋ค 1,400-1,500๋จ์ด ์์ฑ
|
| 1517 |
writer_content = f"์์ฑ์ {i}๋ฒ์ ํํธ์
๋๋ค.\n\n"
|
| 1518 |
+
# ์ฝ 300๋จ์ด์ฉ 5๋ฒ ๋ฐ๋ณตํ์ฌ 1,400-1,500๋จ์ด ๋ฌ์ฑ
|
| 1519 |
+
for j in range(5):
|
| 1520 |
writer_content += sample_story + f"\n\n๊ทธ๊ฒ์ ์๊ฐ {i}์ {j+1}๋ฒ์งธ ๋จ๋ฝ์ด์๋ค. "
|
| 1521 |
writer_content += "์ด๏ฟฝ๏ฟฝ๏ฟฝ๊ธฐ๋ ๊ณ์ ์ ๊ฐ๋์๊ณ , ์ธ๋ฌผ๋ค์ ๊ฐ์ ์ ์ ์ ๋ ๋ณต์กํด์ก๋ค. " * 15
|
| 1522 |
writer_content += "\n\n"
|
|
|
|
| 1528 |
def get_english_test_response(self, role: str) -> str:
|
| 1529 |
"""English test responses with appropriate length"""
|
| 1530 |
test_responses = {
|
| 1531 |
+
"director": """I present the 30-page novella plan.
|
| 1532 |
|
| 1533 |
## 1. Theme and Genre
|
| 1534 |
- **Core Theme**: Finding true connection in the collision of human nature and technology
|
|
|
|
| 1544 |
| Minjun | Helper | Warm, intuitive | Psychologist | Help Seoyeon find balance | Accept and harmonize with technology |
|
| 1545 |
| ARIA | AntagonistโCompanion | LogicalโLearning emotion | AI prototype | Become truly existent | Establish self-identity |
|
| 1546 |
|
| 1547 |
+
## 3. Narrative Structure (10 parts, 3 pages each)
|
| 1548 |
|
| 1549 |
| Part | Pages | Main Events | Tension | Character Development |
|
| 1550 |
|------|-------|-------------|---------|---------------------|
|
| 1551 |
| 1 | 1-3 | Seoyeon's lonely lab, ARIA's first awakening | 3/10 | Seoyeon's obsession revealed |
|
| 1552 |
| 2 | 4-6 | ARIA's anomalies begin | 4/10 | Questions arise |
|
| 1553 |
+
[... continues for all 10 parts ...]""",
|
| 1554 |
|
| 1555 |
"critic": """I have reviewed the director's plan.
|
| 1556 |
|
|
|
|
| 1558 |
|
| 1559 |
### 1. Narrative Completeness
|
| 1560 |
- **Strength**: Timely theme of AI-human relationships
|
| 1561 |
+
- **Improvement**: 10-part structure is well-balanced. Each part maintains independence while connecting seamlessly.
|
| 1562 |
|
| 1563 |
### 2. Character Review
|
| 1564 |
|
|
|
|
| 1573 |
- Need to strengthen inter-part connectivity guidelines""",
|
| 1574 |
}
|
| 1575 |
|
| 1576 |
+
# Writer responses - 1,400-1,500 words each
|
| 1577 |
sample_story = """Seoyeon spent another night under the cold fluorescent lights of her laboratory. The blue glow from the monitor illuminated her pale face, and dozens of lines of code scrolled endlessly. The ARIA project was her entire life. For three years, she had poured everything into this artificial intelligence.
|
| 1578 |
|
| 1579 |
"System check complete. All parameters normal." The mechanical voice flowed through the speakers.
|
| 1580 |
|
| 1581 |
Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penetrated to her bones, but she couldn't stop. ARIA wasn't just a project. It was her only hope to reclaim what she had lost."""
|
| 1582 |
|
| 1583 |
+
for i in range(1, 11):
|
| 1584 |
+
# Each writer produces 1,400-1,500 words
|
| 1585 |
writer_content = f"Writer {i} begins their section here.\n\n"
|
| 1586 |
+
# About 300 words repeated 5 times to achieve 1,400-1,500 words
|
| 1587 |
+
for j in range(5):
|
| 1588 |
writer_content += sample_story + f"\n\nThis was writer {i}'s paragraph {j+1}. "
|
| 1589 |
writer_content += "The story continued to unfold, and the characters' emotions grew increasingly complex. " * 15
|
| 1590 |
writer_content += "\n\n"
|
|
|
|
| 1632 |
"content": stage_data['content'] or ""
|
| 1633 |
})
|
| 1634 |
|
| 1635 |
+
# Define all stages for 10 writers
|
| 1636 |
stage_definitions = [
|
| 1637 |
("director", f"๐ฌ {'๊ฐ๋
์: ์ด๊ธฐ ๊ธฐํ' if language == 'Korean' else 'Director: Initial Planning'}"),
|
| 1638 |
("critic", f"๐ {'๋นํ๊ฐ: ๊ธฐํ ๊ฒํ ' if language == 'Korean' else 'Critic: Plan Review'}"),
|
| 1639 |
("director", f"๐ฌ {'๊ฐ๋
์: ์์ ๋ ๋ง์คํฐํ๋' if language == 'Korean' else 'Director: Revised Masterplan'}"),
|
| 1640 |
]
|
| 1641 |
|
| 1642 |
+
# Add writer stages for 10 writers
|
| 1643 |
+
for writer_num in range(1, 11):
|
| 1644 |
stage_definitions.extend([
|
| 1645 |
(f"writer{writer_num}", f"โ๏ธ {'์์ฑ์' if language == 'Korean' else 'Writer'} {writer_num}: {'์ด์' if language == 'Korean' else 'Draft'}"),
|
| 1646 |
("critic", f"๐ {'๋นํ๊ฐ: ์์ฑ์' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒํ ' if language == 'Korean' else 'Review'}"),
|
|
|
|
| 1709 |
"complete"
|
| 1710 |
)
|
| 1711 |
|
| 1712 |
+
# Auto-save notification
|
| 1713 |
+
if role.startswith("writer"):
|
| 1714 |
+
writer_num = int(role.replace("writer", ""))
|
| 1715 |
+
logger.info(f"โ
Writer {writer_num} content auto-saved to database")
|
| 1716 |
+
|
| 1717 |
yield "", stages
|
| 1718 |
|
| 1719 |
# Verify content after completion
|
|
|
|
| 1721 |
verification = NovelDatabase.verify_novel_content(self.current_session_id)
|
| 1722 |
logger.info(f"Content verification: {verification}")
|
| 1723 |
|
| 1724 |
+
if verification['total_words'] < 12000:
|
| 1725 |
+
logger.error(f"Final novel too short! Only {verification['total_words']} words")
|
| 1726 |
|
| 1727 |
# Get final novel from last stage
|
| 1728 |
final_novel = stages[-1]["content"] if stages else ""
|
|
|
|
| 1806 |
# Writer review
|
| 1807 |
else:
|
| 1808 |
# Find which writer we're reviewing
|
| 1809 |
+
for i in range(1, 11):
|
| 1810 |
if f"์์ฑ์ {i}" in stages[stage_idx]["name"] or f"Writer {i}" in stages[stage_idx]["name"]:
|
| 1811 |
writer_content_idx = stage_idx - 1
|
| 1812 |
# Get previous writers' content from DB
|
|
|
|
| 1832 |
|
| 1833 |
return ""
|
| 1834 |
|
| 1835 |
+
# Gradio Interface Functions
|
| 1836 |
+
def process_query(query: str, language: str, session_id: str = None) -> Generator[Tuple[str, str, str, str], None, None]:
|
| 1837 |
+
"""Process query and yield updates with recovery status"""
|
| 1838 |
if not query.strip() and not session_id:
|
| 1839 |
if language == "Korean":
|
| 1840 |
+
yield "", "", "โ ์์ค ์ฃผ์ ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.", ""
|
| 1841 |
else:
|
| 1842 |
+
yield "", "", "โ Please enter a novel theme.", ""
|
| 1843 |
return
|
| 1844 |
|
| 1845 |
system = NovelWritingSystem()
|
| 1846 |
|
| 1847 |
try:
|
| 1848 |
+
# Recovery status message
|
| 1849 |
+
recovery_status = ""
|
| 1850 |
+
if session_id:
|
| 1851 |
+
if language == "Korean":
|
| 1852 |
+
recovery_status = "โป๏ธ ์ด์ ์ธ์
์ ๋ณต๊ตฌํ์ฌ ๊ณ์ ์งํํฉ๋๋ค..."
|
| 1853 |
+
else:
|
| 1854 |
+
recovery_status = "โป๏ธ Recovering previous session and continuing..."
|
| 1855 |
+
|
| 1856 |
for final_novel, stages in system.process_novel_stream(query, language, session_id):
|
| 1857 |
# Format stages for display
|
| 1858 |
stages_display = format_stages_display(stages, language)
|
| 1859 |
|
| 1860 |
+
# Progress calculation
|
| 1861 |
+
completed = sum(1 for s in stages if s.get("status") == "complete")
|
| 1862 |
+
total = len(stages)
|
| 1863 |
+
progress_percent = (completed / total * 100) if total > 0 else 0
|
| 1864 |
+
|
| 1865 |
+
if not final_novel:
|
| 1866 |
+
if language == "Korean":
|
| 1867 |
+
status = f"๐ ์งํ์ค... ({completed}/{total} - {progress_percent:.1f}%)"
|
| 1868 |
+
else:
|
| 1869 |
+
status = f"๐ Processing... ({completed}/{total} - {progress_percent:.1f}%)"
|
| 1870 |
+
else:
|
| 1871 |
+
status = "โ
Complete!"
|
| 1872 |
|
| 1873 |
+
yield stages_display, final_novel, status, recovery_status
|
| 1874 |
|
| 1875 |
except Exception as e:
|
| 1876 |
logger.error(f"Error in process_query: {str(e)}", exc_info=True)
|
| 1877 |
if language == "Korean":
|
| 1878 |
+
yield "", "", f"โ ์ค๋ฅ ๋ฐ์: {str(e)}", ""
|
| 1879 |
else:
|
| 1880 |
+
yield "", "", f"โ Error occurred: {str(e)}", ""
|
| 1881 |
|
| 1882 |
def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
|
| 1883 |
+
"""Format stages into simple display with writer save status"""
|
| 1884 |
display = ""
|
| 1885 |
|
| 1886 |
for idx, stage in enumerate(stages):
|
| 1887 |
status_icon = "โ
" if stage.get("status") == "complete" else ("โณ" if stage.get("status") == "active" else "โ")
|
| 1888 |
|
| 1889 |
+
# Add save indicator for completed writers
|
| 1890 |
+
save_indicator = ""
|
| 1891 |
+
if "Writer" in stage['name'] and "Revision" in stage['name'] and stage.get("status") == "complete":
|
| 1892 |
+
save_indicator = " ๐พ"
|
| 1893 |
+
|
| 1894 |
# Show only active stage content in detail, others just show status
|
| 1895 |
if stage.get("status") == "active":
|
| 1896 |
display += f"\n\n{status_icon} **{stage['name']}**\n"
|
| 1897 |
+
display += f"```\n{stage.get('content', '')[-1000:]}\n```"
|
| 1898 |
else:
|
| 1899 |
+
display += f"\n{status_icon} {stage['name']}{save_indicator}"
|
| 1900 |
|
| 1901 |
return display
|
| 1902 |
|
|
|
|
| 1918 |
logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
|
| 1919 |
return []
|
| 1920 |
|
| 1921 |
+
def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str, str], None, None]:
|
| 1922 |
"""Resume an existing session"""
|
| 1923 |
if not session_id:
|
| 1924 |
return
|
|
|
|
| 1926 |
# Process with existing session ID
|
| 1927 |
yield from process_query("", language, session_id)
|
| 1928 |
|
| 1929 |
+
def auto_recover_session(language: str) -> Tuple[str, str]:
|
| 1930 |
+
"""Auto recover the latest active session"""
|
| 1931 |
+
try:
|
| 1932 |
+
latest_session = NovelDatabase.get_latest_active_session()
|
| 1933 |
+
if latest_session:
|
| 1934 |
+
if language == "Korean":
|
| 1935 |
+
message = f"โ
์๋ ๋ณต๊ตฌ: '{latest_session['user_query'][:30]}...' (Stage {latest_session['current_stage']})"
|
| 1936 |
+
else:
|
| 1937 |
+
message = f"โ
Auto recovered: '{latest_session['user_query'][:30]}...' (Stage {latest_session['current_stage']})"
|
| 1938 |
+
return latest_session['session_id'], message
|
| 1939 |
+
else:
|
| 1940 |
+
return None, ""
|
| 1941 |
+
except Exception as e:
|
| 1942 |
+
logger.error(f"Error in auto recovery: {str(e)}")
|
| 1943 |
+
return None, ""
|
| 1944 |
+
|
| 1945 |
+
def download_novel(novel_text: str, format: str, language: str, session_id: str = None) -> str:
|
| 1946 |
+
"""Download novel in specified format - enhanced for 10 writers"""
|
| 1947 |
+
if not novel_text and not session_id:
|
| 1948 |
+
return None
|
| 1949 |
+
|
| 1950 |
+
# If session_id provided, get all writer content
|
| 1951 |
+
if session_id:
|
| 1952 |
+
all_writer_content = NovelDatabase.get_all_writer_content(session_id)
|
| 1953 |
+
if all_writer_content:
|
| 1954 |
+
novel_text = all_writer_content
|
| 1955 |
+
|
| 1956 |
if not novel_text:
|
| 1957 |
return None
|
| 1958 |
|
|
|
|
| 1970 |
# Create DOCX
|
| 1971 |
doc = Document()
|
| 1972 |
|
| 1973 |
+
# Title page
|
| 1974 |
+
title_para = doc.add_paragraph()
|
| 1975 |
+
title_run = title_para.add_run('AI Collaborative Novel')
|
| 1976 |
+
title_run.font.size = Pt(24)
|
| 1977 |
+
title_run.font.bold = True
|
| 1978 |
+
title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 1979 |
+
|
| 1980 |
+
doc.add_paragraph()
|
| 1981 |
+
|
| 1982 |
# ๋ฌธ์ ์ ๋ณด ์ถ๊ฐ
|
| 1983 |
+
info_para = doc.add_paragraph()
|
| 1984 |
+
info_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 1985 |
+
info_run = info_para.add_run(f'10 Writers Collaboration\n')
|
| 1986 |
+
info_run.font.size = Pt(14)
|
| 1987 |
+
|
| 1988 |
+
stats_para = doc.add_paragraph()
|
| 1989 |
+
stats_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 1990 |
+
stats_run = stats_para.add_run(
|
| 1991 |
+
f'Total words: {word_count:,}\n'
|
| 1992 |
+
f'Total pages: ~{word_count/500:.0f}\n'
|
| 1993 |
+
f'Export date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
|
| 1994 |
+
)
|
| 1995 |
+
stats_run.font.size = Pt(12)
|
| 1996 |
+
|
| 1997 |
doc.add_page_break()
|
| 1998 |
|
| 1999 |
+
# Table of Contents
|
| 2000 |
+
doc.add_heading('Table of Contents', 1)
|
| 2001 |
+
for i in range(1, 11):
|
| 2002 |
+
doc.add_paragraph(f'Writer {i}: Pages {(i-1)*3+1}-{i*3}')
|
| 2003 |
+
|
| 2004 |
+
doc.add_page_break()
|
| 2005 |
+
|
| 2006 |
+
# Parse and add content with writer sections
|
| 2007 |
+
if session_id:
|
| 2008 |
+
# Get individual writer contents from DB
|
| 2009 |
+
stages = NovelDatabase.get_stages(session_id)
|
| 2010 |
+
writer_num = 0
|
| 2011 |
+
|
| 2012 |
+
for stage in stages:
|
| 2013 |
+
if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
|
| 2014 |
+
writer_num += 1
|
| 2015 |
+
# Add writer header
|
| 2016 |
+
doc.add_heading(f'Writer {writer_num}', 1)
|
| 2017 |
+
doc.add_paragraph(f'Pages {(writer_num-1)*3+1}-{writer_num*3}')
|
| 2018 |
+
doc.add_paragraph()
|
| 2019 |
+
|
| 2020 |
+
# Add writer content
|
| 2021 |
+
content = stage['content'] or ""
|
| 2022 |
+
paragraphs = content.split('\n\n')
|
| 2023 |
+
for para_text in paragraphs:
|
| 2024 |
+
if para_text.strip():
|
| 2025 |
+
para = doc.add_paragraph(para_text)
|
| 2026 |
+
para.style.font.size = Pt(11)
|
| 2027 |
+
|
| 2028 |
+
if writer_num < 10: # Don't add page break after last writer
|
| 2029 |
+
doc.add_page_break()
|
| 2030 |
+
else:
|
| 2031 |
+
# If no session_id, add content as is
|
| 2032 |
+
lines = novel_text.split('\n')
|
| 2033 |
+
for line in lines:
|
| 2034 |
+
if line.startswith('#'):
|
| 2035 |
+
level = min(len(line.split()[0].strip('#')), 9)
|
| 2036 |
+
text = line.lstrip('#').strip()
|
| 2037 |
+
if text:
|
| 2038 |
+
doc.add_heading(text, level)
|
| 2039 |
+
elif line.strip():
|
| 2040 |
+
para = doc.add_paragraph(line)
|
| 2041 |
+
if hasattr(para.style, 'font'):
|
| 2042 |
+
para.style.font.size = Pt(11)
|
| 2043 |
|
| 2044 |
# ํ์ด์ง ์ค์
|
| 2045 |
section = doc.sections[0]
|
|
|
|
| 2052 |
|
| 2053 |
# Save
|
| 2054 |
temp_dir = tempfile.gettempdir()
|
| 2055 |
+
filename = f"Novel_10Writers_{timestamp}_{word_count}words.docx"
|
| 2056 |
filepath = os.path.join(temp_dir, filename)
|
| 2057 |
doc.save(filepath)
|
| 2058 |
|
|
|
|
| 2061 |
else:
|
| 2062 |
# TXT format
|
| 2063 |
temp_dir = tempfile.gettempdir()
|
| 2064 |
+
filename = f"Novel_10Writers_{timestamp}_{word_count}words.txt"
|
| 2065 |
filepath = os.path.join(temp_dir, filename)
|
| 2066 |
|
| 2067 |
# ํ์ผ ์์ ๋ถ๋ถ์ ์ ๋ณด ์ถ๊ฐ
|
| 2068 |
with open(filepath, 'w', encoding='utf-8') as f:
|
| 2069 |
+
f.write("="*60 + "\n")
|
| 2070 |
+
f.write("AI COLLABORATIVE NOVEL - 10 WRITERS\n")
|
| 2071 |
+
f.write("="*60 + "\n")
|
| 2072 |
f.write(f"Total words: {word_count:,}\n")
|
| 2073 |
f.write(f"Total characters: {char_count:,}\n")
|
| 2074 |
f.write(f"Estimated pages: {word_count/500:.1f}\n")
|
| 2075 |
f.write(f"Export date: {datetime.now()}\n")
|
| 2076 |
+
f.write("="*60 + "\n\n")
|
| 2077 |
+
|
| 2078 |
+
if session_id:
|
| 2079 |
+
# Get individual writer contents
|
| 2080 |
+
stages = NovelDatabase.get_stages(session_id)
|
| 2081 |
+
writer_num = 0
|
| 2082 |
+
|
| 2083 |
+
for stage in stages:
|
| 2084 |
+
if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
|
| 2085 |
+
writer_num += 1
|
| 2086 |
+
f.write(f"\n{'='*40}\n")
|
| 2087 |
+
f.write(f"WRITER {writer_num} (Pages {(writer_num-1)*3+1}-{writer_num*3})\n")
|
| 2088 |
+
f.write(f"{'='*40}\n\n")
|
| 2089 |
+
f.write(stage['content'] or "")
|
| 2090 |
+
f.write("\n\n")
|
| 2091 |
+
else:
|
| 2092 |
+
f.write(novel_text)
|
| 2093 |
|
| 2094 |
logger.info(f"TXT saved: {filepath}")
|
| 2095 |
return filepath
|
| 2096 |
|
| 2097 |
+
# Custom CSS with enhanced recovery status
|
| 2098 |
custom_css = """
|
| 2099 |
.gradio-container {
|
| 2100 |
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
|
|
|
| 2128 |
color: white;
|
| 2129 |
}
|
| 2130 |
|
| 2131 |
+
.recovery-status {
|
| 2132 |
+
background-color: rgba(76, 175, 80, 0.2);
|
| 2133 |
+
border: 1px solid #4CAF50;
|
| 2134 |
+
padding: 10px;
|
| 2135 |
+
border-radius: 5px;
|
| 2136 |
+
margin: 10px 0;
|
| 2137 |
+
color: #4CAF50;
|
| 2138 |
+
}
|
| 2139 |
+
|
| 2140 |
#stages-display {
|
| 2141 |
background-color: rgba(255, 255, 255, 0.95);
|
| 2142 |
padding: 20px;
|
|
|
|
| 2165 |
color: #4CAF50;
|
| 2166 |
font-weight: bold;
|
| 2167 |
}
|
| 2168 |
+
|
| 2169 |
+
.auto-save-indicator {
|
| 2170 |
+
color: #2196F3;
|
| 2171 |
+
font-weight: bold;
|
| 2172 |
+
}
|
| 2173 |
"""
|
| 2174 |
|
| 2175 |
# Create Gradio Interface
|
|
|
|
| 2181 |
๐ SOMA Novel Writing System
|
| 2182 |
</h1>
|
| 2183 |
<h3 style="color: #ccc; margin-bottom: 20px;">
|
| 2184 |
+
AI Collaborative Novel Generation - 10 Writers Edition
|
| 2185 |
</h3>
|
| 2186 |
<p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
|
| 2187 |
+
Enter a theme or prompt, and watch as 13 AI agents collaborate to create a complete 30-page novella.
|
| 2188 |
+
The system includes 1 Director, 1 Critic, and 10 Writers (each writing 3 pages) working in harmony.
|
| 2189 |
+
<br><br>
|
| 2190 |
+
<span class="search-indicator">๐ Web search enabled</span> |
|
| 2191 |
+
<span class="auto-save-indicator">๐พ Auto-save enabled</span> |
|
| 2192 |
+
<span style="color: #FF9800;">โป๏ธ Auto-recovery on refresh</span>
|
| 2193 |
</p>
|
| 2194 |
</div>
|
| 2195 |
""")
|
|
|
|
| 2197 |
# State management
|
| 2198 |
current_session_id = gr.State(None)
|
| 2199 |
|
| 2200 |
+
# Recovery status display
|
| 2201 |
+
recovery_status = gr.Markdown(
|
| 2202 |
+
value="",
|
| 2203 |
+
elem_classes=["recovery-status"],
|
| 2204 |
+
visible=False
|
| 2205 |
+
)
|
| 2206 |
+
|
| 2207 |
with gr.Row():
|
| 2208 |
with gr.Column(scale=1):
|
| 2209 |
with gr.Group(elem_classes=["input-section"]):
|
|
|
|
| 2243 |
interactive=True
|
| 2244 |
)
|
| 2245 |
with gr.Row():
|
| 2246 |
+
refresh_btn = gr.Button("๐ Refresh List / ๋ชฉ๋ก ์๋ก๊ณ ์นจ", scale=1)
|
| 2247 |
+
resume_btn = gr.Button("โถ๏ธ Resume Selected / ์ ํ ์ฌ๊ฐ", variant="secondary", scale=1)
|
| 2248 |
+
auto_recover_btn = gr.Button("โป๏ธ Auto Recover Latest / ์ต์ ์๋๋ณต๊ตฌ", scale=1)
|
| 2249 |
|
| 2250 |
with gr.Column(scale=2):
|
| 2251 |
with gr.Tab("๐ Writing Process / ์์ฑ ๊ณผ์ "):
|
|
|
|
| 2261 |
)
|
| 2262 |
|
| 2263 |
with gr.Group(elem_classes=["download-section"]):
|
| 2264 |
+
gr.Markdown("### ๐ฅ Download Complete Novel / ์ ์ฒด ์์ค ๋ค์ด๋ก๋")
|
| 2265 |
with gr.Row():
|
| 2266 |
format_select = gr.Radio(
|
| 2267 |
choices=["DOCX", "TXT"],
|
| 2268 |
value="DOCX" if DOCX_AVAILABLE else "TXT",
|
| 2269 |
label="Format / ํ์"
|
| 2270 |
)
|
| 2271 |
+
download_btn = gr.Button("โฌ๏ธ Download All 10 Writers / 10๋ช
์๊ฐ ์ ์ฒด ๋ค์ด๋ก๋", variant="secondary")
|
| 2272 |
|
| 2273 |
download_file = gr.File(
|
| 2274 |
label="Downloaded File / ๋ค์ด๋ก๋๋ ํ์ผ",
|
|
|
|
| 2294 |
)
|
| 2295 |
|
| 2296 |
# Event handlers
|
| 2297 |
+
def update_novel_state(stages, novel, status, recovery_msg):
|
| 2298 |
+
return stages, novel, status, novel, recovery_msg
|
| 2299 |
|
| 2300 |
def refresh_sessions():
|
| 2301 |
try:
|
|
|
|
| 2305 |
logger.error(f"Error refreshing sessions: {str(e)}", exc_info=True)
|
| 2306 |
return gr.update(choices=[])
|
| 2307 |
|
| 2308 |
+
def handle_auto_recover(language):
|
| 2309 |
+
session_id, message = auto_recover_session(language)
|
| 2310 |
+
if session_id:
|
| 2311 |
+
return session_id, gr.update(value=message, visible=True)
|
| 2312 |
+
else:
|
| 2313 |
+
if language == "Korean":
|
| 2314 |
+
message = "โ ๋ณต๊ตฌํ ์ธ์
์ด ์์ต๋๋ค."
|
| 2315 |
+
else:
|
| 2316 |
+
message = "โ No session to recover."
|
| 2317 |
+
return None, gr.update(value=message, visible=True)
|
| 2318 |
+
|
| 2319 |
submit_btn.click(
|
| 2320 |
fn=process_query,
|
| 2321 |
inputs=[query_input, language_select, current_session_id],
|
| 2322 |
+
outputs=[stages_display, novel_output, status_text, recovery_status.value]
|
| 2323 |
).then(
|
| 2324 |
fn=update_novel_state,
|
| 2325 |
+
inputs=[stages_display, novel_output, status_text, recovery_status.value],
|
| 2326 |
+
outputs=[stages_display, novel_output, status_text, novel_text_state, recovery_status]
|
| 2327 |
)
|
| 2328 |
|
| 2329 |
resume_btn.click(
|
|
|
|
| 2333 |
).then(
|
| 2334 |
fn=resume_session,
|
| 2335 |
inputs=[current_session_id, language_select],
|
| 2336 |
+
outputs=[stages_display, novel_output, status_text, recovery_status.value]
|
| 2337 |
+
)
|
| 2338 |
+
|
| 2339 |
+
auto_recover_btn.click(
|
| 2340 |
+
fn=handle_auto_recover,
|
| 2341 |
+
inputs=[language_select],
|
| 2342 |
+
outputs=[current_session_id, recovery_status]
|
| 2343 |
+
).then(
|
| 2344 |
+
fn=resume_session,
|
| 2345 |
+
inputs=[current_session_id, language_select],
|
| 2346 |
+
outputs=[stages_display, novel_output, status_text, recovery_status.value]
|
| 2347 |
)
|
| 2348 |
|
| 2349 |
refresh_btn.click(
|
|
|
|
| 2352 |
)
|
| 2353 |
|
| 2354 |
clear_btn.click(
|
| 2355 |
+
fn=lambda: ("", "", "๐ Ready", "", None, gr.update(visible=False)),
|
| 2356 |
+
outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id, recovery_status]
|
| 2357 |
)
|
| 2358 |
|
| 2359 |
+
def handle_download(novel_text, format_type, language, session_id):
|
| 2360 |
+
if not novel_text and not session_id:
|
| 2361 |
return gr.update(visible=False)
|
| 2362 |
|
| 2363 |
+
file_path = download_novel(novel_text, format_type, language, session_id)
|
| 2364 |
if file_path:
|
| 2365 |
return gr.update(value=file_path, visible=True)
|
| 2366 |
else:
|
|
|
|
| 2368 |
|
| 2369 |
download_btn.click(
|
| 2370 |
fn=handle_download,
|
| 2371 |
+
inputs=[novel_text_state, format_select, language_select, current_session_id],
|
| 2372 |
outputs=[download_file]
|
| 2373 |
)
|
| 2374 |
|
| 2375 |
+
# Load sessions and check for auto-recovery on startup
|
| 2376 |
+
def on_load():
|
| 2377 |
+
sessions = get_active_sessions("English")
|
| 2378 |
+
# Check for latest session
|
| 2379 |
+
latest_session = NovelDatabase.get_latest_active_session()
|
| 2380 |
+
if latest_session:
|
| 2381 |
+
recovery_msg = f"๐ก Found active session: '{latest_session['user_query'][:30]}...' - Click 'Auto Recover Latest' to continue"
|
| 2382 |
+
return gr.update(choices=sessions), gr.update(value=recovery_msg, visible=True)
|
| 2383 |
+
else:
|
| 2384 |
+
return gr.update(choices=sessions), gr.update(visible=False)
|
| 2385 |
+
|
| 2386 |
interface.load(
|
| 2387 |
+
fn=on_load,
|
| 2388 |
+
outputs=[session_dropdown, recovery_status]
|
| 2389 |
)
|
| 2390 |
|
| 2391 |
return interface
|
| 2392 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2393 |
# Main execution
|
| 2394 |
if __name__ == "__main__":
|
| 2395 |
import sys
|
| 2396 |
|
| 2397 |
+
logger.info("Starting SOMA Novel Writing System - 10 Writers Edition...")
|
| 2398 |
|
| 2399 |
# Check environment
|
| 2400 |
if TEST_MODE:
|
|
|
|
| 2413 |
NovelDatabase.init_db()
|
| 2414 |
logger.info("Database initialized successfully.")
|
| 2415 |
|
| 2416 |
+
interface = create_interface()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2417 |
|
| 2418 |
interface.launch(
|
| 2419 |
server_name="0.0.0.0",
|