openfree commited on
Commit
75c3035
ยท
verified ยท
1 Parent(s): 9632c30

Update app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +608 -138
app-backup.py CHANGED
@@ -309,6 +309,8 @@ class NovelDatabase:
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
 
@@ -331,7 +333,7 @@ class NovelDatabase:
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]:
@@ -367,8 +369,8 @@ class NovelDatabase:
367
  return cursor.fetchall()
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
 
@@ -376,15 +378,18 @@ class NovelDatabase:
376
  writer_count = 0
377
  total_word_count = 0
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
 
385
  row = cursor.fetchone()
386
  if row and row['content']:
387
- # ํŽ˜์ด์ง€ ๋งˆํฌ ์™„์ „ ์ œ๊ฑฐ
388
  clean_content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', row['content'])
389
  clean_content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', clean_content)
390
  clean_content = clean_content.strip()
@@ -394,14 +399,58 @@ class NovelDatabase:
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
 
@@ -418,6 +467,52 @@ class NovelDatabase:
418
  conn.commit()
419
  logger.info(f"Updated final novel for session {session_id}, length: {len(final_novel)}")
420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  @staticmethod
422
  def get_active_sessions() -> List[Dict]:
423
  """Get all active sessions"""
@@ -978,6 +1073,69 @@ Write a revision reflecting:
978
  Present the revised final version. Never use page markers.
979
  You MUST maintain 1,400-1,500 words."""
980
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
981
  def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
982
  """Simulate streaming in test mode"""
983
  words = text.split()
@@ -1030,7 +1188,11 @@ You MUST maintain 1,400-1,500 words."""
1030
  ]
1031
 
1032
  # ์ž‘์„ฑ์ž๋“ค์—๊ฒŒ๋Š” ์ ์ ˆํ•œ ํ† ํฐ ํ• ๋‹น
1033
- if role.startswith("writer"):
 
 
 
 
1034
  max_tokens = 10000 # ์ถฉ๋ถ„ํ•œ ํ† ํฐ
1035
  temperature = 0.8
1036
  top_p = 0.95
@@ -1162,11 +1324,12 @@ You MUST maintain 1,400-1,500 words."""
1162
  yield f"โŒ Error occurred: {str(e)}"
1163
 
1164
  def get_system_prompts(self, language: str) -> Dict[str, str]:
1165
- """Get system prompts for all 10 writers"""
1166
  if language == "Korean":
1167
  prompts = {
1168
  "director": "๋‹น์‹ ์€ 30ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์„ ๊ธฐํšํ•˜๊ณ  ๊ฐ๋…ํ•˜๋Š” ๋ฌธํ•™ ๊ฐ๋…์ž์ž…๋‹ˆ๋‹ค. ์ฒด๊ณ„์ ์ด๊ณ  ์ฐฝ์˜์ ์ธ ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.",
1169
- "critic": "๋‹น์‹ ์€ ๋‚ ์นด๋กœ์šด ํ†ต์ฐฐ๋ ฅ์„ ๊ฐ€์ง„ ๋ฌธํ•™ ๋น„ํ‰๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ฑด์„ค์ ์ด๊ณ  ๊ตฌ์ฒด์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค."
 
1170
  }
1171
 
1172
  # 10๋ช…์˜ ์ž‘๊ฐ€ ํ”„๋กฌํ”„ํŠธ
@@ -1190,7 +1353,8 @@ You MUST maintain 1,400-1,500 words."""
1190
  else:
1191
  prompts = {
1192
  "director": "You are a literary director planning and supervising a 30-page novella. You create systematic and creative story structures.",
1193
- "critic": "You are a literary critic with sharp insights. You provide constructive and specific feedback."
 
1194
  }
1195
 
1196
  # 10 writer prompts
@@ -1213,7 +1377,7 @@ You MUST maintain 1,400-1,500 words."""
1213
  return prompts
1214
 
1215
  def get_test_response(self, role: str, language: str) -> str:
1216
- """Get test response based on role"""
1217
  if language == "Korean":
1218
  return self.get_korean_test_response(role)
1219
  else:
@@ -1291,6 +1455,19 @@ You MUST maintain 1,400-1,500 words."""
1291
  writer_content += "\n\n"
1292
 
1293
  test_responses[f"writer{i}"] = writer_content
 
 
 
 
 
 
 
 
 
 
 
 
 
1294
 
1295
  return test_responses.get(role, "ํ…Œ์ŠคํŠธ ์‘๋‹ต์ž…๋‹ˆ๋‹ค.")
1296
 
@@ -1359,12 +1536,26 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1359
  writer_content += "\n\n"
1360
 
1361
  test_responses[f"writer{i}"] = writer_content
 
 
 
 
 
 
 
 
 
 
 
 
 
1362
 
1363
  return test_responses.get(role, "Test response.")
1364
 
1365
  def process_novel_stream(self, query: str, language: str = "English",
1366
  session_id: Optional[str] = None,
1367
- resume_from_stage: int = 0) -> Generator[Tuple[str, List[Dict[str, str]]], None, None]:
 
1368
  """Process novel writing with streaming updates - ์ตœ์ข… Director/Critic ์ œ๊ฑฐ"""
1369
  try:
1370
  global conversation_history
@@ -1377,11 +1568,13 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1377
  query = session['user_query']
1378
  language = session['language']
1379
  resume_from_stage = session['current_stage'] + 1
 
1380
  else:
1381
  self.current_session_id = NovelDatabase.create_session(query, language)
1382
  resume_from_stage = 0
 
1383
 
1384
- logger.info(f"Processing novel for session {self.current_session_id}, starting from stage {resume_from_stage}")
1385
 
1386
  # Initialize conversation
1387
  conversation_history = [{
@@ -1402,19 +1595,31 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1402
  })
1403
 
1404
  # Define all stages for 10 writers (์ตœ์ข… ํ‰๊ฐ€ ์ œ๊ฑฐ)
1405
- stage_definitions = [
1406
- ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ดˆ๊ธฐ ๊ธฐํš' if language == 'Korean' else 'Director: Initial Planning'}"),
1407
- ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ๊ธฐํš ๊ฒ€ํ† ' if language == 'Korean' else 'Critic: Plan Review'}"),
1408
- ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ˆ˜์ •๋œ ๋งˆ์Šคํ„ฐํ”Œ๋žœ' if language == 'Korean' else 'Director: Revised Masterplan'}"),
1409
- ]
1410
-
1411
- # Add writer stages for 10 writers
1412
- for writer_num in range(1, 11):
1413
- stage_definitions.extend([
1414
- (f"writer{writer_num}", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} {writer_num}: {'์ดˆ์•ˆ' if language == 'Korean' else 'Draft'}"),
1415
- ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ์ž‘์„ฑ์ž' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒ€ํ† ' if language == 'Korean' else 'Review'}"),
1416
- (f"writer{writer_num}", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} {writer_num}: {'์ˆ˜์ •๋ณธ' if language == 'Korean' else 'Revision'}")
1417
- ])
 
 
 
 
 
 
 
 
 
 
 
 
1418
 
1419
  # ์ตœ์ข… Director์™€ Critic ๋‹จ๊ณ„ ์ œ๊ฑฐ
1420
 
@@ -1442,13 +1647,14 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1442
  yield "", stages
1443
 
1444
  # Get appropriate prompt based on stage
1445
- prompt = self.get_stage_prompt(stage_idx, role, query, language, stages)
1446
 
1447
  # Create stage info for web search
1448
  stage_info = {
1449
  'stage_idx': stage_idx,
1450
  'query': query,
1451
- 'stage_name': stage_name
 
1452
  }
1453
 
1454
  stage_content = ""
@@ -1466,6 +1672,9 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1466
 
1467
  # Mark stage complete and save to DB
1468
  stages[stage_idx]["status"] = "complete"
 
 
 
1469
  NovelDatabase.save_stage(
1470
  self.current_session_id,
1471
  stage_idx,
@@ -1485,19 +1694,29 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1485
  # Verify content after completion
1486
  if self.current_session_id:
1487
  verification = NovelDatabase.verify_novel_content(self.current_session_id)
1488
- logger.info(f"Content verification: {verification}")
 
 
 
1489
 
1490
- if verification['total_words'] < 12000:
1491
  logger.error(f"Final novel too short! Only {verification['total_words']} words")
1492
 
1493
  # Get complete novel from DB
1494
- complete_novel = NovelDatabase.get_all_writer_content(self.current_session_id)
 
 
 
 
1495
 
1496
  # Save final novel to DB
1497
  NovelDatabase.update_final_novel(self.current_session_id, complete_novel)
1498
 
1499
  # Final yield - ํ™”๋ฉด์—๋Š” ์™„๋ฃŒ ๋ฉ”์‹œ์ง€๋งŒ ํ‘œ์‹œ
1500
- final_message = f"โœ… Novel complete! {len(complete_novel.split())} words total. Click Download to save."
 
 
 
1501
  yield final_message, stages
1502
 
1503
  except Exception as e:
@@ -1522,8 +1741,53 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1522
  stages.append(error_stage)
1523
  yield f"Error occurred: {str(e)}", stages
1524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1525
  def get_stage_prompt(self, stage_idx: int, role: str, query: str,
1526
- language: str, stages: List[Dict]) -> str:
1527
  """Get appropriate prompt for each stage"""
1528
  # Stage 0: Director Initial
1529
  if stage_idx == 0:
@@ -1543,6 +1807,16 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1543
  writer_num = int(role.replace("writer", ""))
1544
  final_plan = stages[2]["content"] # Director's final plan
1545
 
 
 
 
 
 
 
 
 
 
 
1546
  # Initial draft or revision?
1547
  if "์ดˆ์•ˆ" in stages[stage_idx]["name"] or "Draft" in stages[stage_idx]["name"]:
1548
  # Get accumulated content from DB
@@ -1581,27 +1855,32 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1581
  return ""
1582
 
1583
  # Gradio Interface Functions
1584
- def process_query(query: str, language: str, session_id: str = None) -> Generator[Tuple[str, str, str, str], None, None]:
1585
- """Process query and yield updates with recovery status"""
1586
  if not query.strip() and not session_id:
1587
  if language == "Korean":
1588
- yield "", "", "โŒ ์†Œ์„ค ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", ""
1589
  else:
1590
- yield "", "", "โŒ Please enter a novel theme.", ""
1591
  return
1592
 
1593
  system = NovelWritingSystem()
1594
 
1595
  try:
1596
- # Recovery status message
1597
- recovery_status = ""
1598
  if session_id:
1599
  if language == "Korean":
1600
- recovery_status = "โ™ป๏ธ ์ด์ „ ์„ธ์…˜์„ ๋ณต๊ตฌํ•˜์—ฌ ๊ณ„์† ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค..."
 
 
 
 
 
1601
  else:
1602
- recovery_status = "โ™ป๏ธ Recovering previous session and continuing..."
1603
 
1604
- for final_novel, stages in system.process_novel_stream(query, language, session_id):
1605
  # Format stages for display
1606
  stages_display = format_stages_display(stages, language)
1607
 
@@ -1610,22 +1889,23 @@ def process_query(query: str, language: str, session_id: str = None) -> Generato
1610
  total = len(stages)
1611
  progress_percent = (completed / total * 100) if total > 0 else 0
1612
 
1613
- if "โœ… Novel complete!" in str(final_novel):
1614
- status = "โœ… Complete! Ready to download."
1615
  else:
1616
  if language == "Korean":
1617
- status = f"๐Ÿ”„ ์ง„ํ–‰์ค‘... ({completed}/{total} - {progress_percent:.1f}%)"
1618
  else:
1619
- status = f"๐Ÿ”„ Processing... ({completed}/{total} - {progress_percent:.1f}%)"
1620
 
1621
- yield stages_display, final_novel, status, recovery_status
 
1622
 
1623
  except Exception as e:
1624
  logger.error(f"Error in process_query: {str(e)}", exc_info=True)
1625
  if language == "Korean":
1626
- yield "", "", f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", ""
1627
  else:
1628
- yield "", "", f"โŒ Error occurred: {str(e)}", ""
1629
 
1630
  def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
1631
  """Format stages into simple display with writer save status"""
@@ -1666,17 +1946,17 @@ def get_active_sessions(language: str) -> List[Tuple[str, str]]:
1666
  logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
1667
  return []
1668
 
1669
- def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str], None, None]:
1670
  """Resume an existing session"""
1671
  if not session_id:
1672
  if language == "Korean":
1673
- yield "", "", "โŒ ์„ธ์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”."
1674
  else:
1675
- yield "", "", "โŒ Please select a session."
1676
  return
1677
 
1678
  # Process with existing session ID
1679
- yield from process_query("", language, session_id)
1680
 
1681
  def auto_recover_session(language: str) -> Tuple[str, str]:
1682
  """Auto recover the latest active session"""
@@ -1700,14 +1980,39 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1700
  logger.error("No session_id provided for download")
1701
  return None
1702
 
 
 
1703
  # DB์—์„œ ์„ธ์…˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
1704
  session = NovelDatabase.get_session(session_id)
1705
  if not session:
1706
  logger.error(f"Session not found: {session_id}")
1707
  return None
1708
 
 
 
 
 
1709
  # DB์—์„œ ๋ชจ๋“  ์Šคํ…Œ์ด์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
1710
  stages = NovelDatabase.get_stages(session_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1711
 
1712
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1713
 
@@ -1717,7 +2022,7 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1717
 
1718
  # ์ œ๋ชฉ ํŽ˜์ด์ง€
1719
  title_para = doc.add_paragraph()
1720
- title_run = title_para.add_run('AI Collaborative Novel')
1721
  title_run.font.size = Pt(24)
1722
  title_run.font.bold = True
1723
  title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
@@ -1745,59 +2050,147 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1745
 
1746
  # ๊ฐ ์ž‘๊ฐ€์˜ ์ˆ˜์ •๋ณธ๋งŒ ์ˆ˜์ง‘
1747
  writer_contents = []
1748
- for stage in stages:
1749
- if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
1750
- writer_count += 1
1751
- content = stage['content'] or ""
1752
- # ํŽ˜์ด์ง€ ๋งˆํฌ ์ œ๊ฑฐ
1753
- content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', content)
1754
- content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', content)
1755
- content = content.strip()
1756
 
1757
- if content:
1758
- word_count = stage['word_count'] or len(content.split())
1759
- total_words += word_count
1760
- writer_contents.append({
1761
- 'writer_num': writer_count,
1762
- 'content': content,
1763
- 'word_count': word_count
1764
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1765
 
1766
  # ํ†ต๊ณ„ ํŽ˜์ด์ง€
1767
  doc.add_heading('Novel Statistics', 1)
1768
- doc.add_paragraph(f'Total Writers: {writer_count}')
 
1769
  doc.add_paragraph(f'Total Words: {total_words:,}')
1770
  doc.add_paragraph(f'Estimated Pages: ~{total_words/500:.0f}')
1771
- doc.add_paragraph(f'Language: {session["language"]}')
1772
  doc.add_page_break()
1773
 
1774
  # ๋ชฉ์ฐจ
1775
- doc.add_heading('Table of Contents', 1)
1776
- for i in range(1, writer_count + 1):
1777
- doc.add_paragraph(f'Chapter {i}: Pages {(i-1)*3+1}-{i*3}')
1778
- doc.add_page_break()
1779
-
1780
- # ๊ฐ ์ž‘๊ฐ€์˜ ๋‚ด์šฉ ์ถ”๊ฐ€
1781
- for writer_data in writer_contents:
1782
- writer_num = writer_data['writer_num']
1783
- content = writer_data['content']
1784
- word_count = writer_data['word_count']
1785
-
1786
- # ์ฑ•ํ„ฐ ํ—ค๋”
1787
- doc.add_heading(f'Chapter {writer_num}', 1)
1788
- doc.add_paragraph(f'Pages {(writer_num-1)*3+1}-{writer_num*3}')
1789
- doc.add_paragraph(f'Word Count: {word_count:,}')
1790
- doc.add_paragraph()
1791
-
1792
- # ์ž‘๊ฐ€ ๋‚ด์šฉ ์ถ”๊ฐ€
1793
- paragraphs = content.split('\n\n')
1794
- for para_text in paragraphs:
1795
- if para_text.strip():
1796
- para = doc.add_paragraph(para_text.strip())
1797
- para.style.font.size = Pt(11)
1798
-
1799
- if writer_num < writer_count: # ๋งˆ์ง€๋ง‰ ์ž‘๊ฐ€ ํ›„์—๋Š” ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ์—†์Œ
1800
- doc.add_page_break()
 
 
 
 
 
 
 
 
1801
 
1802
  # ํŽ˜์ด์ง€ ์„ค์ •
1803
  for section in doc.sections:
@@ -1811,54 +2204,117 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1811
  # Save
1812
  temp_dir = tempfile.gettempdir()
1813
  safe_filename = re.sub(r'[^\w\s-]', '', session['user_query'][:30]).strip()
1814
- filename = f"Novel_Complete_{safe_filename}_{timestamp}.docx"
 
1815
  filepath = os.path.join(temp_dir, filename)
1816
  doc.save(filepath)
1817
 
1818
- logger.info(f"DOCX saved successfully: {filepath} ({total_words} words)")
1819
  return filepath
1820
  else:
1821
- # TXT format
1822
  temp_dir = tempfile.gettempdir()
1823
  safe_filename = re.sub(r'[^\w\s-]', '', session['user_query'][:30]).strip()
1824
- filename = f"Novel_Complete_{safe_filename}_{timestamp}.txt"
 
1825
  filepath = os.path.join(temp_dir, filename)
1826
 
1827
  with open(filepath, 'w', encoding='utf-8') as f:
1828
  f.write("="*60 + "\n")
1829
- f.write("AI COLLABORATIVE NOVEL - COMPLETE VERSION\n")
1830
  f.write("="*60 + "\n")
1831
  f.write(f"Theme: {session['user_query']}\n")
1832
- f.write(f"Language: {session['language']}\n")
1833
  f.write(f"Created: {datetime.now()}\n")
 
1834
  f.write("="*60 + "\n\n")
1835
 
1836
  total_words = 0
1837
  writer_count = 0
1838
 
1839
- # ๊ฐ ์ž‘๊ฐ€์˜ ์ˆ˜์ •๋ณธ๋งŒ ์ถœ๋ ฅ
1840
- for stage in stages:
1841
- if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
1842
- writer_count += 1
1843
- content = stage['content'] or ""
1844
- # ํŽ˜์ด์ง€ ๋งˆํฌ ์ œ๊ฑฐ
1845
- content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', content)
1846
- content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', content)
1847
- content = content.strip()
1848
 
1849
- if content:
1850
- word_count = stage['word_count'] or len(content.split())
1851
- total_words += word_count
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1852
 
1853
- f.write(f"\n{'='*40}\n")
1854
- f.write(f"CHAPTER {writer_count} (Pages {(writer_count-1)*3+1}-{writer_count*3})\n")
1855
- f.write(f"Word Count: {word_count:,}\n")
1856
- f.write(f"{'='*40}\n\n")
1857
- f.write(content)
1858
- f.write("\n\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1859
 
1860
  f.write(f"\n{'='*60}\n")
1861
- f.write(f"TOTAL: {writer_count} writers, {total_words:,} words\n")
1862
  f.write(f"{'='*60}\n")
1863
 
1864
  logger.info(f"TXT saved successfully: {filepath} ({total_words} words)")
@@ -1950,7 +2406,8 @@ def create_interface():
1950
  <br><br>
1951
  <span class="search-indicator">๐Ÿ” Web search enabled</span> |
1952
  <span class="auto-save-indicator">๐Ÿ’พ Auto-save to database</span> |
1953
- <span style="color: #FF9800;">โ™ป๏ธ Resume anytime</span>
 
1954
  </p>
1955
  </div>
1956
  """)
@@ -1973,6 +2430,13 @@ def create_interface():
1973
  label="Language / ์–ธ์–ด"
1974
  )
1975
 
 
 
 
 
 
 
 
1976
  # Web search status indicator
1977
  web_search_status = gr.Markdown(
1978
  value=f"๐Ÿ” **Web Search:** {'Enabled' if WebSearchIntegration().enabled else 'Disabled (Set BRAVE_SEARCH_API_KEY)'}"
@@ -2063,8 +2527,8 @@ def create_interface():
2063
  # Connect event handlers
2064
  submit_btn.click(
2065
  fn=process_query,
2066
- inputs=[query_input, language_select, current_session_id],
2067
- outputs=[stages_display, novel_output, status_text]
2068
  )
2069
 
2070
  # Update novel text state and session ID when novel output changes
@@ -2080,8 +2544,8 @@ def create_interface():
2080
  outputs=[current_session_id]
2081
  ).then(
2082
  fn=resume_session,
2083
- inputs=[current_session_id, language_select],
2084
- outputs=[stages_display, novel_output, status_text]
2085
  )
2086
 
2087
  auto_recover_btn.click(
@@ -2090,8 +2554,8 @@ def create_interface():
2090
  outputs=[current_session_id]
2091
  ).then(
2092
  fn=resume_session,
2093
- inputs=[current_session_id, language_select],
2094
- outputs=[stages_display, novel_output, status_text]
2095
  )
2096
 
2097
  refresh_btn.click(
@@ -2100,18 +2564,24 @@ def create_interface():
2100
  )
2101
 
2102
  clear_btn.click(
2103
- fn=lambda: ("", "", "๐Ÿ”„ Ready", "", None),
2104
- outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id]
2105
  )
2106
 
2107
  def handle_download(format_type, language, session_id, novel_text):
 
 
 
2108
  if not session_id:
 
2109
  return gr.update(visible=False)
2110
 
2111
  file_path = download_novel(novel_text, format_type, language, session_id)
2112
  if file_path:
 
2113
  return gr.update(value=file_path, visible=True)
2114
  else:
 
2115
  return gr.update(visible=False)
2116
 
2117
  download_btn.click(
 
309
  """Save stage content with word count"""
310
  word_count = len(content.split()) if content else 0
311
 
312
+ logger.info(f"Saving stage: session={session_id}, stage_num={stage_number}, role={role}, stage_name={stage_name}, words={word_count}")
313
+
314
  with NovelDatabase.get_db() as conn:
315
  cursor = conn.cursor()
316
 
 
333
  ''', (stage_number, stage_number, session_id))
334
 
335
  conn.commit()
336
+ logger.info(f"Stage saved successfully")
337
 
338
  @staticmethod
339
  def get_session(session_id: str) -> Optional[Dict]:
 
369
  return cursor.fetchall()
370
 
371
  @staticmethod
372
+ def get_all_writer_content(session_id: str, test_mode: bool = False) -> str:
373
+ """๋ชจ๋“  ์ž‘๊ฐ€์˜ ์ˆ˜์ •๋ณธ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™€์„œ ํ•ฉ์น˜๊ธฐ"""
374
  with NovelDatabase.get_db() as conn:
375
  cursor = conn.cursor()
376
 
 
378
  writer_count = 0
379
  total_word_count = 0
380
 
381
+ if test_mode:
382
+ # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ: writer1 revision + writer10 content
383
+ # Writer 1 ์ˆ˜์ •๋ณธ - ์–ธ์–ด ๋ฌด๊ด€
384
  cursor.execute('''
385
  SELECT content, stage_name, word_count FROM stages
386
+ WHERE session_id = ? AND role = 'writer1'
387
+ AND (stage_name LIKE '%Revision%' OR stage_name LIKE '%์ˆ˜์ •๋ณธ%')
388
+ ''', (session_id,))
389
+
390
 
391
  row = cursor.fetchone()
392
  if row and row['content']:
 
393
  clean_content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', row['content'])
394
  clean_content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', clean_content)
395
  clean_content = clean_content.strip()
 
399
  word_count = row['word_count'] or len(clean_content.split())
400
  total_word_count += word_count
401
  all_content.append(clean_content)
402
+ logger.info(f"Test mode - Writer 1: {word_count} words")
403
+
404
+ # Writer 10 content (๋‚˜๋จธ์ง€ ์ฑ•ํ„ฐ๋“ค)
405
+ cursor.execute('''
406
+ SELECT content, stage_name, word_count FROM stages
407
+ WHERE session_id = ? AND role = 'writer10'
408
+ ''', (session_id,))
409
+
410
+ row = cursor.fetchone()
411
+ if row and row['content']:
412
+ # Writer 10์€ ์ด๋ฏธ ์—ฌ๋Ÿฌ ์ฑ•ํ„ฐ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ์ถ”๊ฐ€
413
+ clean_content = row['content'].strip()
414
+ if clean_content:
415
+ word_count = row['word_count'] or len(clean_content.split())
416
+ total_word_count += word_count
417
+ all_content.append(clean_content)
418
+ writer_count = 10 # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ์—์„œ๋Š” ์ด 10๊ฐœ ์ฑ•ํ„ฐ
419
+ logger.info(f"Test mode - Writer 10 (Chapters 2-10): {word_count} words")
420
+ else:
421
+ # ์ผ๋ฐ˜ ๋ชจ๋“œ: ๋ชจ๋“  ์ž‘๊ฐ€์˜ ์ˆ˜์ •๋ณธ
422
+ writer_stages_to_check = WRITER_REVISION_STAGES
423
+
424
+ for stage_num in writer_stages_to_check:
425
+ cursor.execute('''
426
+ SELECT content, stage_name, word_count FROM stages
427
+ WHERE session_id = ? AND stage_number = ?
428
+ ''', (session_id, stage_num))
429
+
430
+ row = cursor.fetchone()
431
+ if row and row['content']:
432
+ # ํŽ˜์ด์ง€ ๋งˆํฌ ์™„์ „ ์ œ๊ฑฐ
433
+ clean_content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', row['content'])
434
+ clean_content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', clean_content)
435
+ clean_content = clean_content.strip()
436
+
437
+ if clean_content:
438
+ writer_count += 1
439
+ word_count = row['word_count'] or len(clean_content.split())
440
+ total_word_count += word_count
441
+ all_content.append(clean_content)
442
+ logger.info(f"Writer {writer_count} (stage {stage_num}): {word_count} words")
443
 
444
  full_content = '\n\n'.join(all_content)
 
445
 
446
+ if test_mode:
447
+ logger.info(f"Test mode - Total: {writer_count} chapters, {total_word_count} words")
448
+ if total_word_count < 2800: # ์ตœ์†Œ ์˜ˆ์ƒ์น˜
449
+ logger.warning(f"Test mode content short! Only {total_word_count} words")
450
+ else:
451
+ logger.info(f"Total: {writer_count} writers, {total_word_count} words")
452
+ if total_word_count < 12000:
453
+ logger.warning(f"Content too short! Only {total_word_count} words instead of ~14,500")
454
 
455
  return full_content
456
 
 
467
  conn.commit()
468
  logger.info(f"Updated final novel for session {session_id}, length: {len(final_novel)}")
469
 
470
+ @staticmethod
471
+ def verify_novel_content(session_id: str) -> Dict[str, Any]:
472
+ """์„ธ์…˜์˜ ์ „์ฒด ์†Œ์„ค ๋‚ด์šฉ ๊ฒ€์ฆ"""
473
+ with NovelDatabase.get_db() as conn:
474
+ cursor = conn.cursor()
475
+
476
+ # ๋ชจ๋“  ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ ํ™•์ธ
477
+ cursor.execute(f'''
478
+ SELECT stage_number, stage_name, LENGTH(content) as content_length, word_count
479
+ FROM stages
480
+ WHERE session_id = ? AND stage_number IN ({','.join(map(str, WRITER_REVISION_STAGES))})
481
+ ORDER BY stage_number
482
+ ''', (session_id,))
483
+
484
+ results = []
485
+ total_length = 0
486
+ total_words = 0
487
+
488
+ for row in cursor.fetchall():
489
+ results.append({
490
+ 'stage': row['stage_number'],
491
+ 'name': row['stage_name'],
492
+ 'length': row['content_length'] or 0,
493
+ 'words': row['word_count'] or 0
494
+ })
495
+ total_length += row['content_length'] or 0
496
+ total_words += row['word_count'] or 0
497
+
498
+ # ์ตœ์ข… ์†Œ์„ค ํ™•์ธ
499
+ cursor.execute('''
500
+ SELECT LENGTH(final_novel) as final_length
501
+ FROM sessions
502
+ WHERE session_id = ?
503
+ ''', (session_id,))
504
+
505
+ final_row = cursor.fetchone()
506
+ final_length = final_row['final_length'] if final_row else 0
507
+
508
+ return {
509
+ 'writer_stages': results,
510
+ 'total_writer_content': total_length,
511
+ 'total_words': total_words,
512
+ 'final_novel_length': final_length,
513
+ 'expected_words': 14500 # 10 ์ž‘๊ฐ€ * 1450 ํ‰๊ท 
514
+ }
515
+
516
  @staticmethod
517
  def get_active_sessions() -> List[Dict]:
518
  """Get all active sessions"""
 
1073
  Present the revised final version. Never use page markers.
1074
  You MUST maintain 1,400-1,500 words."""
1075
 
1076
+ def create_test_writer_remaining_prompt(self, director_plan: str, writer1_content: str, language: str) -> str:
1077
+ """Test mode - Writer 10 writes remaining novel (chapters 2-10)"""
1078
+ if language == "Korean":
1079
+ return f"""[ํ…Œ์ŠคํŠธ ๋ชจ๋“œ] ๋‹น์‹ ์€ ๋‚˜๋จธ์ง€ 9๊ฐœ ์ฑ•ํ„ฐ(Chapter 2-10)๋ฅผ ์ž‘์„ฑํ•˜๋Š” ํŠน๋ณ„ ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค.
1080
+
1081
+ ๊ฐ๋…์ž์˜ ๋งˆ์Šคํ„ฐํ”Œ๋žœ:
1082
+ {director_plan}
1083
+
1084
+ ์ž‘์„ฑ์ž 1์ด ์ด๋ฏธ ์ž‘์„ฑํ•œ Chapter 1:
1085
+ {writer1_content[-1000:] if writer1_content else '(์ฒซ ๋ฒˆ์งธ ์ฑ•ํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค)'}
1086
+
1087
+ **์ค‘์š” ์ง€์นจ:**
1088
+ 1. Chapter 2๋ถ€ํ„ฐ Chapter 10๊นŒ์ง€ 9๊ฐœ ์ฑ•ํ„ฐ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”
1089
+ 2. ๊ฐ ์ฑ•ํ„ฐ๋Š” ์•ฝ 1,400-1,500 ๋‹จ์–ด๋กœ ์ž‘์„ฑํ•˜์„ธ์š”
1090
+ 3. ์ด 12,600-13,500 ๋‹จ์–ด (9๊ฐœ ์ฑ•ํ„ฐ)
1091
+ 4. Chapter 1๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ด์–ด์ง€๋„๋ก ์ž‘์„ฑํ•˜์„ธ์š”
1092
+ 5. ๋งˆ์Šคํ„ฐํ”Œ๋žœ์˜ ๋ชจ๋“  ์š”์†Œ๋ฅผ ํฌํ•จํ•˜์—ฌ ์™„๊ฒฐ๋œ ์ด์•ผ๊ธฐ๋ฅผ ๋งŒ๋“œ์„ธ์š”
1093
+
1094
+ **ํ•„์ˆ˜ ํ˜•์‹:**
1095
+ ๋ฐ˜๋“œ์‹œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฑ•ํ„ฐ๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•˜์„ธ์š”:
1096
+
1097
+ [Chapter 2]
1098
+ (1,400-1,500 ๋‹จ์–ด์˜ ๋‚ด์šฉ)
1099
+
1100
+ [Chapter 3]
1101
+ (1,400-1,500 ๋‹จ์–ด์˜ ๋‚ด์šฉ)
1102
+
1103
+ ...์ด๋Ÿฐ ์‹์œผ๋กœ [Chapter 10]๊นŒ์ง€...
1104
+
1105
+ ๊ฐ ์ฑ•ํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ [Chapter ์ˆซ์ž] ํ˜•์‹์œผ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
1106
+ ์ฑ•ํ„ฐ ์‚ฌ์ด์—๋Š” ๋นˆ ์ค„์„ ๋„ฃ์–ด ๊ตฌ๋ถ„ํ•˜์„ธ์š”.
1107
+ Chapter 2๋ถ€ํ„ฐ 10๊นŒ์ง€ ์ž‘์„ฑํ•˜์„ธ์š”."""
1108
+ else:
1109
+ return f"""[TEST MODE] You are a special writer creating the remaining 9 chapters (Chapters 2-10).
1110
+
1111
+ Director's Masterplan:
1112
+ {director_plan}
1113
+
1114
+ Writer 1 has already written Chapter 1:
1115
+ {writer1_content[-1000:] if writer1_content else '(No first chapter available)'}
1116
+
1117
+ **CRITICAL INSTRUCTIONS:**
1118
+ 1. Write Chapters 2 through 10 (9 chapters total)
1119
+ 2. Each chapter should be ~1,400-1,500 words
1120
+ 3. Total 12,600-13,500 words (9 chapters)
1121
+ 4. Continue naturally from Chapter 1
1122
+ 5. Include all elements from the masterplan to create a complete story
1123
+
1124
+ **MANDATORY FORMAT:**
1125
+ You MUST clearly separate chapters as follows:
1126
+
1127
+ [Chapter 2]
1128
+ (1,400-1,500 words of content)
1129
+
1130
+ [Chapter 3]
1131
+ (1,400-1,500 words of content)
1132
+
1133
+ ...continue this way until [Chapter 10]...
1134
+
1135
+ Each chapter MUST start with [Chapter number] format.
1136
+ Leave blank lines between chapters for separation.
1137
+ Write Chapters 2-10 now."""
1138
+
1139
  def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
1140
  """Simulate streaming in test mode"""
1141
  words = text.split()
 
1188
  ]
1189
 
1190
  # ์ž‘์„ฑ์ž๋“ค์—๊ฒŒ๋Š” ์ ์ ˆํ•œ ํ† ํฐ ํ• ๋‹น
1191
+ if role == "writer10" and stage_info and stage_info.get('test_mode'):
1192
+ max_tokens = 30000 # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ: 9๊ฐœ ์ฑ•ํ„ฐ ์ž‘์„ฑ (์ถฉ๋ถ„ํ•œ ํ† ํฐ)
1193
+ temperature = 0.8
1194
+ top_p = 0.95
1195
+ elif role.startswith("writer"):
1196
  max_tokens = 10000 # ์ถฉ๋ถ„ํ•œ ํ† ํฐ
1197
  temperature = 0.8
1198
  top_p = 0.95
 
1324
  yield f"โŒ Error occurred: {str(e)}"
1325
 
1326
  def get_system_prompts(self, language: str) -> Dict[str, str]:
1327
+ """Get system prompts for all writers including test mode"""
1328
  if language == "Korean":
1329
  prompts = {
1330
  "director": "๋‹น์‹ ์€ 30ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์„ ๊ธฐํšํ•˜๊ณ  ๊ฐ๋…ํ•˜๋Š” ๋ฌธํ•™ ๊ฐ๋…์ž์ž…๋‹ˆ๋‹ค. ์ฒด๊ณ„์ ์ด๊ณ  ์ฐฝ์˜์ ์ธ ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.",
1331
+ "critic": "๋‹น์‹ ์€ ๋‚ ์นด๋กœ์šด ํ†ต์ฐฐ๋ ฅ์„ ๊ฐ€์ง„ ๋ฌธํ•™ ๋น„ํ‰๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ฑด์„ค์ ์ด๊ณ  ๊ตฌ์ฒด์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.",
1332
+ "writer10": "[ํ…Œ์ŠคํŠธ ๋ชจ๋“œ] ๋‹น์‹ ์€ ์ฑ•ํ„ฐ 2-10์„ ์ž‘์„ฑํ•˜๋Š” ํŠน๋ณ„ ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. 9๊ฐœ ์ฑ•ํ„ฐ๋กœ ๊ตฌ์„ฑ๋œ ๋‚˜๋จธ์ง€ ์†Œ์„ค์„ ์ž‘์„ฑํ•˜์„ธ์š”. ๊ฐ ์ฑ•ํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ 1,400-1,500๋‹จ์–ด๋กœ ์ž‘์„ฑํ•˜์„ธ์š”."
1333
  }
1334
 
1335
  # 10๋ช…์˜ ์ž‘๊ฐ€ ํ”„๋กฌํ”„ํŠธ
 
1353
  else:
1354
  prompts = {
1355
  "director": "You are a literary director planning and supervising a 30-page novella. You create systematic and creative story structures.",
1356
+ "critic": "You are a literary critic with sharp insights. You provide constructive and specific feedback.",
1357
+ "writer10": "[TEST MODE] You are a special writer creating chapters 2-10. Write the remaining novel organized into 9 chapters. Each chapter MUST be 1,400-1,500 words."
1358
  }
1359
 
1360
  # 10 writer prompts
 
1377
  return prompts
1378
 
1379
  def get_test_response(self, role: str, language: str) -> str:
1380
+ """Get test response based on role - updated for writer10 chapters 2-10"""
1381
  if language == "Korean":
1382
  return self.get_korean_test_response(role)
1383
  else:
 
1455
  writer_content += "\n\n"
1456
 
1457
  test_responses[f"writer{i}"] = writer_content
1458
+
1459
+ # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ์šฉ writer10 ์‘๋‹ต
1460
+ if role == "writer10":
1461
+ full_novel = ""
1462
+ for i in range(2, 11): # Chapter 2๋ถ€ํ„ฐ 10๊นŒ์ง€
1463
+ full_novel += f"[Chapter {i}]\n\n"
1464
+ # ๊ฐ ์ฑ•ํ„ฐ๋งˆ๋‹ค ์•ฝ 300๋‹จ์–ด์”ฉ 5๋‹จ๋ฝ
1465
+ for j in range(5):
1466
+ full_novel += sample_story + f"\n\n๊ทธ๊ฒƒ์€ ์ฑ•ํ„ฐ {i}์˜ {j+1}๋ฒˆ์งธ ๋‹จ๋ฝ์ด์—ˆ๋‹ค. "
1467
+ full_novel += "์ด์•ผ๊ธฐ๋Š” ๊ณ„์† ์ „๊ฐœ๋˜์—ˆ๊ณ , ์ธ๋ฌผ๋“ค์˜ ๊ฐ์ •์€ ์ ์  ๋” ๋ณต์žกํ•ด์กŒ๋‹ค. " * 20
1468
+ full_novel += "\n\n"
1469
+ full_novel += "\n\n" # ์ฑ•ํ„ฐ ๊ฐ„ ๊ตฌ๋ถ„
1470
+ test_responses["writer10"] = full_novel
1471
 
1472
  return test_responses.get(role, "ํ…Œ์ŠคํŠธ ์‘๋‹ต์ž…๋‹ˆ๋‹ค.")
1473
 
 
1536
  writer_content += "\n\n"
1537
 
1538
  test_responses[f"writer{i}"] = writer_content
1539
+
1540
+ # Test mode writer10 response
1541
+ if role == "writer10":
1542
+ full_novel = ""
1543
+ for i in range(2, 11): # Chapters 2-10
1544
+ full_novel += f"[Chapter {i}]\n\n"
1545
+ # About 300 words per paragraph, 5 paragraphs per chapter
1546
+ for j in range(5):
1547
+ full_novel += sample_story + f"\n\nThis was paragraph {j+1} of chapter {i}. "
1548
+ full_novel += "The story continued to unfold, and the characters' emotions grew increasingly complex. " * 20
1549
+ full_novel += "\n\n"
1550
+ full_novel += "\n\n" # Chapter separation
1551
+ test_responses["writer10"] = full_novel
1552
 
1553
  return test_responses.get(role, "Test response.")
1554
 
1555
  def process_novel_stream(self, query: str, language: str = "English",
1556
  session_id: Optional[str] = None,
1557
+ resume_from_stage: int = 0,
1558
+ test_quick_mode: bool = False) -> Generator[Tuple[str, List[Dict[str, str]]], None, None]:
1559
  """Process novel writing with streaming updates - ์ตœ์ข… Director/Critic ์ œ๊ฑฐ"""
1560
  try:
1561
  global conversation_history
 
1568
  query = session['user_query']
1569
  language = session['language']
1570
  resume_from_stage = session['current_stage'] + 1
1571
+ logger.info(f"Resuming session {session_id} from stage {resume_from_stage}")
1572
  else:
1573
  self.current_session_id = NovelDatabase.create_session(query, language)
1574
  resume_from_stage = 0
1575
+ logger.info(f"Created new session: {self.current_session_id}")
1576
 
1577
+ logger.info(f"Processing novel for session {self.current_session_id}, starting from stage {resume_from_stage}, test_mode={test_quick_mode}")
1578
 
1579
  # Initialize conversation
1580
  conversation_history = [{
 
1595
  })
1596
 
1597
  # Define all stages for 10 writers (์ตœ์ข… ํ‰๊ฐ€ ์ œ๊ฑฐ)
1598
+ if test_quick_mode:
1599
+ # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ: 1,2,3๋‹จ๊ณ„ + Writer 1 + Writer 10
1600
+ stage_definitions = [
1601
+ ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ดˆ๊ธฐ ๊ธฐํš' if language == 'Korean' else 'Director: Initial Planning'}"),
1602
+ ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ๊ธฐํš ๊ฒ€ํ† ' if language == 'Korean' else 'Critic: Plan Review'}"),
1603
+ ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ˆ˜์ •๋œ ๋งˆ์Šคํ„ฐํ”Œ๋žœ' if language == 'Korean' else 'Director: Revised Masterplan'}"),
1604
+ ("writer1", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} 1: {'์ดˆ์•ˆ' if language == 'Korean' else 'Draft'}"),
1605
+ ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ์ž‘์„ฑ์ž' if language == 'Korean' else 'Critic: Writer'} 1 {'๊ฒ€ํ† ' if language == 'Korean' else 'Review'}"),
1606
+ ("writer1", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} 1: {'์ˆ˜์ •๋ณธ' if language == 'Korean' else 'Revision'}"),
1607
+ ("writer10", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} 10 (TEST): {'๋‚˜๋จธ์ง€ ์†Œ์„ค' if language == 'Korean' else 'Remaining Novel'}"),
1608
+ ]
1609
+ else:
1610
+ stage_definitions = [
1611
+ ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ดˆ๊ธฐ ๊ธฐํš' if language == 'Korean' else 'Director: Initial Planning'}"),
1612
+ ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ๊ธฐํš ๊ฒ€ํ† ' if language == 'Korean' else 'Critic: Plan Review'}"),
1613
+ ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ˆ˜์ •๋œ ๋งˆ์Šคํ„ฐํ”Œ๋žœ' if language == 'Korean' else 'Director: Revised Masterplan'}"),
1614
+ ]
1615
+
1616
+ # Add writer stages for 10 writers
1617
+ for writer_num in range(1, 11):
1618
+ stage_definitions.extend([
1619
+ (f"writer{writer_num}", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} {writer_num}: {'์ดˆ์•ˆ' if language == 'Korean' else 'Draft'}"),
1620
+ ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ์ž‘์„ฑ์ž' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒ€ํ† ' if language == 'Korean' else 'Review'}"),
1621
+ (f"writer{writer_num}", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} {writer_num}: {'์ˆ˜์ •๋ณธ' if language == 'Korean' else 'Revision'}")
1622
+ ])
1623
 
1624
  # ์ตœ์ข… Director์™€ Critic ๋‹จ๊ณ„ ์ œ๊ฑฐ
1625
 
 
1647
  yield "", stages
1648
 
1649
  # Get appropriate prompt based on stage
1650
+ prompt = self.get_stage_prompt(stage_idx, role, query, language, stages, test_quick_mode)
1651
 
1652
  # Create stage info for web search
1653
  stage_info = {
1654
  'stage_idx': stage_idx,
1655
  'query': query,
1656
+ 'stage_name': stage_name,
1657
+ 'test_mode': test_quick_mode
1658
  }
1659
 
1660
  stage_content = ""
 
1672
 
1673
  # Mark stage complete and save to DB
1674
  stages[stage_idx]["status"] = "complete"
1675
+
1676
+ # Test mode์—์„œ๋Š” writer1๊ณผ writer2๋งŒ ์ฒ˜๋ฆฌ
1677
+ # writer10 ๊ด€๋ จ ํŠน๋ณ„ ์ฒ˜๋ฆฌ ์ œ๊ฑฐ - ์ผ๋ฐ˜ ํ”„๋กœ์„ธ์Šค์™€ ๋™์ผ
1678
  NovelDatabase.save_stage(
1679
  self.current_session_id,
1680
  stage_idx,
 
1694
  # Verify content after completion
1695
  if self.current_session_id:
1696
  verification = NovelDatabase.verify_novel_content(self.current_session_id)
1697
+ if test_quick_mode:
1698
+ logger.info(f"[TEST MODE] Content verification: {verification}")
1699
+ else:
1700
+ logger.info(f"Content verification: {verification}")
1701
 
1702
+ if verification['total_words'] < 12000 and not test_quick_mode:
1703
  logger.error(f"Final novel too short! Only {verification['total_words']} words")
1704
 
1705
  # Get complete novel from DB
1706
+ complete_novel = NovelDatabase.get_all_writer_content(self.current_session_id, test_quick_mode)
1707
+
1708
+ # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ๋ฉด ์™„๋ฃŒ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
1709
+ if test_quick_mode and self.current_session_id:
1710
+ NovelDatabase.update_final_novel(self.current_session_id, complete_novel)
1711
 
1712
  # Save final novel to DB
1713
  NovelDatabase.update_final_novel(self.current_session_id, complete_novel)
1714
 
1715
  # Final yield - ํ™”๋ฉด์—๋Š” ์™„๋ฃŒ ๋ฉ”์‹œ์ง€๋งŒ ํ‘œ์‹œ
1716
+ if test_quick_mode:
1717
+ final_message = f"โœ… [TEST MODE] Novel complete! 2 chapters, {len(complete_novel.split())} words total. Click Download to save."
1718
+ else:
1719
+ final_message = f"โœ… Novel complete! {len(complete_novel.split())} words total. Click Download to save."
1720
  yield final_message, stages
1721
 
1722
  except Exception as e:
 
1741
  stages.append(error_stage)
1742
  yield f"Error occurred: {str(e)}", stages
1743
 
1744
+ def create_test_writer_complete_prompt(self, director_plan: str, language: str = "English") -> str:
1745
+ """Test mode - Writer 10 writes complete novel"""
1746
+ if language == "Korean":
1747
+ return f"""[ํ…Œ์ŠคํŠธ ๋ชจ๋“œ] ๋‹น์‹ ์€ ์ „์ฒด 30ํŽ˜์ด์ง€ ์†Œ์„ค์„ ํ•œ ๋ฒˆ์— ์ž‘์„ฑํ•˜๋Š” ํŠน๋ณ„ ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค.
1748
+
1749
+ ๊ฐ๋…์ž์˜ ๋งˆ์Šคํ„ฐํ”Œ๋žœ:
1750
+ {director_plan}
1751
+
1752
+ **์ค‘์š” ์ง€์นจ:**
1753
+ 1. ์ „์ฒด 30ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์˜ ์™„์„ฑ๋œ ์†Œ์„ค์„ ์ž‘์„ฑํ•˜์„ธ์š”
1754
+ 2. ์ด 14,000-15,000 ๋‹จ์–ด๋กœ ์ž‘์„ฑํ•˜์„ธ์š”
1755
+ 3. 10๊ฐœ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ์ฑ•ํ„ฐ๋กœ ๊ตฌ์„ฑํ•˜์„ธ์š” (๊ฐ ์ฑ•ํ„ฐ ์•ฝ 1,400-1,500 ๋‹จ์–ด)
1756
+ 4. ์‹œ์ž‘๋ถ€ํ„ฐ ๊ฒฐ๋ง๊นŒ์ง€ ์™„์ „ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ๋งŒ๋“œ์„ธ์š”
1757
+ 5. ๋งˆ์Šคํ„ฐํ”Œ๋žœ์˜ ๋ชจ๋“  ์š”์†Œ๋ฅผ ํฌํ•จํ•˜์„ธ๏ฟฝ๏ฟฝ๏ฟฝ
1758
+
1759
+ ์ฑ•ํ„ฐ ๊ตฌ๋ถ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œ์‹œํ•˜์„ธ์š”:
1760
+ [Chapter 1]
1761
+ ๋‚ด์šฉ...
1762
+
1763
+ [Chapter 2]
1764
+ ๋‚ด์šฉ...
1765
+
1766
+ ์™„์„ฑ๋„ ๋†’์€ ์ „์ฒด ์†Œ์„ค์„ ์ž‘์„ฑํ•˜์„ธ์š”."""
1767
+ else:
1768
+ return f"""[TEST MODE] You are a special writer creating the complete 30-page novel at once.
1769
+
1770
+ Director's Masterplan:
1771
+ {director_plan}
1772
+
1773
+ **CRITICAL INSTRUCTIONS:**
1774
+ 1. Write a complete 30-page novel
1775
+ 2. Total 14,000-15,000 words
1776
+ 3. Organize into 10 natural chapters (each chapter ~1,400-1,500 words)
1777
+ 4. Create a complete story from beginning to end
1778
+ 5. Include all elements from the masterplan
1779
+
1780
+ Mark chapters as follows:
1781
+ [Chapter 1]
1782
+ content...
1783
+
1784
+ [Chapter 2]
1785
+ content...
1786
+
1787
+ Write a high-quality complete novel."""
1788
+
1789
  def get_stage_prompt(self, stage_idx: int, role: str, query: str,
1790
+ language: str, stages: List[Dict], test_mode: bool = False) -> str:
1791
  """Get appropriate prompt for each stage"""
1792
  # Stage 0: Director Initial
1793
  if stage_idx == 0:
 
1807
  writer_num = int(role.replace("writer", ""))
1808
  final_plan = stages[2]["content"] # Director's final plan
1809
 
1810
+ # Test mode special writer10
1811
+ if role == "writer10" and test_mode:
1812
+ # Get writer1's revision content
1813
+ writer1_content = ""
1814
+ for i, stage in enumerate(stages):
1815
+ if "Writer 1: Revision" in stage["name"] or "์ž‘์„ฑ์ž 1: ์ˆ˜์ •๋ณธ" in stage["name"]:
1816
+ writer1_content = stage["content"]
1817
+ break
1818
+ return self.create_test_writer_remaining_prompt(final_plan, writer1_content, language)
1819
+
1820
  # Initial draft or revision?
1821
  if "์ดˆ์•ˆ" in stages[stage_idx]["name"] or "Draft" in stages[stage_idx]["name"]:
1822
  # Get accumulated content from DB
 
1855
  return ""
1856
 
1857
  # Gradio Interface Functions
1858
+ def process_query(query: str, language: str, session_id: str = None, test_mode: bool = False) -> Generator[Tuple[str, str, str, str], None, None]:
1859
+ """Process query and yield updates with session ID"""
1860
  if not query.strip() and not session_id:
1861
  if language == "Korean":
1862
+ yield "", "", "โŒ ์†Œ์„ค ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", None
1863
  else:
1864
+ yield "", "", "โŒ Please enter a novel theme.", None
1865
  return
1866
 
1867
  system = NovelWritingSystem()
1868
 
1869
  try:
1870
+ # Status message with mode indicator
1871
+ mode_status = ""
1872
  if session_id:
1873
  if language == "Korean":
1874
+ mode_status = " [โ™ป๏ธ ๋ณต๊ตฌ ๋ชจ๋“œ]"
1875
+ else:
1876
+ mode_status = " [โ™ป๏ธ Recovery Mode]"
1877
+ elif test_mode:
1878
+ if language == "Korean":
1879
+ mode_status = " [๐Ÿงช ํ…Œ์ŠคํŠธ ๋ชจ๋“œ: 7๋‹จ๊ณ„]"
1880
  else:
1881
+ mode_status = " [๐Ÿงช Test Mode: 7 stages]"
1882
 
1883
+ for final_novel, stages in system.process_novel_stream(query, language, session_id, test_quick_mode=test_mode):
1884
  # Format stages for display
1885
  stages_display = format_stages_display(stages, language)
1886
 
 
1889
  total = len(stages)
1890
  progress_percent = (completed / total * 100) if total > 0 else 0
1891
 
1892
+ if "โœ… Novel complete!" in str(final_novel) or "โœ… [TEST MODE] Novel complete!" in str(final_novel):
1893
+ status = f"โœ… Complete! Ready to download.{mode_status}"
1894
  else:
1895
  if language == "Korean":
1896
+ status = f"๐Ÿ”„ ์ง„ํ–‰์ค‘... ({completed}/{total} - {progress_percent:.1f}%){mode_status}"
1897
  else:
1898
+ status = f"๐Ÿ”„ Processing... ({completed}/{total} - {progress_percent:.1f}%){mode_status}"
1899
 
1900
+ # Return current session ID
1901
+ yield stages_display, final_novel, status, system.current_session_id
1902
 
1903
  except Exception as e:
1904
  logger.error(f"Error in process_query: {str(e)}", exc_info=True)
1905
  if language == "Korean":
1906
+ yield "", "", f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", None
1907
  else:
1908
+ yield "", "", f"โŒ Error occurred: {str(e)}", None
1909
 
1910
  def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
1911
  """Format stages into simple display with writer save status"""
 
1946
  logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
1947
  return []
1948
 
1949
+ def resume_session(session_id: str, language: str, test_mode: bool = False) -> Generator[Tuple[str, str, str, str], None, None]:
1950
  """Resume an existing session"""
1951
  if not session_id:
1952
  if language == "Korean":
1953
+ yield "", "", "โŒ ์„ธ์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.", None
1954
  else:
1955
+ yield "", "", "โŒ Please select a session.", None
1956
  return
1957
 
1958
  # Process with existing session ID
1959
+ yield from process_query("", language, session_id, test_mode)
1960
 
1961
  def auto_recover_session(language: str) -> Tuple[str, str]:
1962
  """Auto recover the latest active session"""
 
1980
  logger.error("No session_id provided for download")
1981
  return None
1982
 
1983
+ logger.info(f"Starting download for session: {session_id}, format: {format}")
1984
+
1985
  # DB์—์„œ ์„ธ์…˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
1986
  session = NovelDatabase.get_session(session_id)
1987
  if not session:
1988
  logger.error(f"Session not found: {session_id}")
1989
  return None
1990
 
1991
+ # ์„ธ์…˜์˜ ์‹ค์ œ ์–ธ์–ด ์‚ฌ์šฉ (ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์€ language ๋Œ€์‹ )
1992
+ actual_language = session['language']
1993
+ logger.info(f"Using session language: {actual_language} (parameter was: {language})")
1994
+
1995
  # DB์—์„œ ๋ชจ๋“  ์Šคํ…Œ์ด์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
1996
  stages = NovelDatabase.get_stages(session_id)
1997
+ logger.info(f"Found {len(stages)} stages in database")
1998
+
1999
+ # ๋””๋ฒ„๊น…: ๋ชจ๋“  stage ์ •๋ณด ์ถœ๋ ฅ
2000
+ for i, stage in enumerate(stages):
2001
+ role = stage['role'] if 'role' in stage.keys() else 'None'
2002
+ stage_name = stage['stage_name'] if 'stage_name' in stage.keys() else 'None'
2003
+ content_len = len(stage['content']) if 'content' in stage.keys() and stage['content'] else 0
2004
+ status = stage['status'] if 'status' in stage.keys() else 'None'
2005
+ logger.debug(f"Stage {i}: role={role}, stage_name={stage_name}, content_length={content_len}, status={status}")
2006
+
2007
+ # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ ๊ฐ์ง€ - writer10์ด ์žˆ์œผ๋ฉด ํ…Œ์ŠคํŠธ ๋ชจ๋“œ
2008
+ is_test_mode = False
2009
+ has_writer10 = any(stage['role'] == 'writer10' for stage in stages if 'role' in stage.keys())
2010
+ has_writer1 = any(stage['role'] == 'writer1' for stage in stages if 'role' in stage.keys())
2011
+ has_writer3 = any(stage['role'] == 'writer3' for stage in stages if 'role' in stage.keys())
2012
+
2013
+ if has_writer10 and has_writer1 and not has_writer3:
2014
+ is_test_mode = True
2015
+ logger.info("Test mode detected - writer1 + writer10 mode")
2016
 
2017
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
2018
 
 
2022
 
2023
  # ์ œ๋ชฉ ํŽ˜์ด์ง€
2024
  title_para = doc.add_paragraph()
2025
+ title_run = title_para.add_run('AI Collaborative Novel' + (' - Test Mode' if is_test_mode else ''))
2026
  title_run.font.size = Pt(24)
2027
  title_run.font.bold = True
2028
  title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
 
2050
 
2051
  # ๊ฐ ์ž‘๊ฐ€์˜ ์ˆ˜์ •๋ณธ๋งŒ ์ˆ˜์ง‘
2052
  writer_contents = []
2053
+
2054
+ if is_test_mode:
2055
+ # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ: writer1 revision + writer10 ๋‚ด์šฉ ์ฒ˜๋ฆฌ
2056
+ for stage in stages:
2057
+ role = stage['role'] if 'role' in stage.keys() else None
2058
+ stage_name = stage['stage_name'] if 'stage_name' in stage.keys() else ''
2059
+ content = stage['content'] if 'content' in stage.keys() else ''
 
2060
 
2061
+ logger.info(f"[TEST MODE] Checking stage: role={role}, name={stage_name}, status={stage['status'] if 'status' in stage.keys() else 'None'}")
2062
+
2063
+ # Writer 1 ์ˆ˜์ •๋ณธ
2064
+ if role == 'writer1' and stage_name and ('Revision' in stage_name or '์ˆ˜์ •๋ณธ' in stage_name):
2065
+ content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', content)
2066
+ content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', content)
2067
+ content = content.strip()
2068
+
2069
+ if content:
2070
+ word_count = stage['word_count'] if 'word_count' in stage.keys() else len(content.split())
2071
+ total_words += word_count
2072
+ writer_contents.append({
2073
+ 'writer_num': 1,
2074
+ 'content': content,
2075
+ 'word_count': word_count
2076
+ })
2077
+ writer_count = 1
2078
+ logger.info(f"Added writer 1 (Chapter 1): {word_count} words")
2079
+
2080
+ # Writer 10 (ํ…Œ์ŠคํŠธ ๋ชจ๋“œ์—์„œ ๋‚˜๋จธ์ง€ ์ฑ•ํ„ฐ๋“ค)
2081
+ elif role == 'writer10':
2082
+ logger.info(f"Processing writer10 content: {len(content)} chars")
2083
+
2084
+ # [Chapter X] ํŒจํ„ด์œผ๋กœ ์ฑ•ํ„ฐ ๋ถ„๋ฆฌ
2085
+ chapters = re.split(r'\[Chapter\s+(\d+)\]', content)
2086
+
2087
+ # chapters๋Š” ['', '2', 'content2', '3', 'content3', ...] ํ˜•ํƒœ
2088
+ for i in range(1, len(chapters), 2):
2089
+ if i+1 < len(chapters):
2090
+ chapter_num = int(chapters[i])
2091
+ chapter_content = chapters[i+1].strip()
2092
+
2093
+ if chapter_content:
2094
+ word_count = len(chapter_content.split())
2095
+ total_words += word_count
2096
+ writer_contents.append({
2097
+ 'writer_num': chapter_num,
2098
+ 'content': chapter_content,
2099
+ 'word_count': word_count
2100
+ })
2101
+ writer_count = max(writer_count, chapter_num)
2102
+ logger.info(f"Added Chapter {chapter_num}: {word_count} words")
2103
+ else:
2104
+ # ์ผ๋ฐ˜ ๋ชจ๋“œ: ๋ชจ๋“  ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ ์ฒ˜๋ฆฌ
2105
+ logger.info("[NORMAL MODE] Processing all writer revisions...")
2106
+
2107
+ for stage in stages:
2108
+ role = stage['role'] if 'role' in stage.keys() else None
2109
+ stage_name = stage['stage_name'] if 'stage_name' in stage.keys() else ''
2110
+ content = stage['content'] if 'content' in stage.keys() else ''
2111
+ status = stage['status'] if 'status' in stage.keys() else ''
2112
+
2113
+ # ๋””๋ฒ„๊น… ์ •๋ณด
2114
+ logger.debug(f"[NORMAL MODE] Checking: role={role}, name={stage_name}, status={status}, content_len={len(content)}")
2115
+
2116
+ # ์–ธ์–ด์— ์ƒ๊ด€์—†์ด ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ ์ฐพ๊ธฐ
2117
+ is_writer = role and role.startswith('writer')
2118
+ is_revision = stage_name and ('Revision' in stage_name or '์ˆ˜์ •๋ณธ' in stage_name)
2119
+
2120
+ if is_writer and is_revision:
2121
+ # ์ž‘๊ฐ€ ๋ฒˆํ˜ธ ์ถ”์ถœ
2122
+ try:
2123
+ writer_num = int(role.replace('writer', ''))
2124
+ logger.info(f"Found writer {writer_num} revision - stage_name: {stage_name}")
2125
+ except:
2126
+ logger.warning(f"Could not extract writer number from role: {role}")
2127
+ continue
2128
+
2129
+ # ํŽ˜์ด์ง€ ๋งˆํฌ ์ œ๊ฑฐ
2130
+ content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', content)
2131
+ content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', content)
2132
+ content = content.strip()
2133
+
2134
+ if content:
2135
+ word_count = stage['word_count'] if 'word_count' in stage.keys() else len(content.split())
2136
+ total_words += word_count
2137
+ writer_contents.append({
2138
+ 'writer_num': writer_num,
2139
+ 'content': content,
2140
+ 'word_count': word_count
2141
+ })
2142
+ writer_count += 1 # ์‹ค์ œ ์ž‘๊ฐ€ ์ˆ˜ ์นด์šดํŠธ
2143
+ logger.info(f"Added writer {writer_num}: {word_count} words, content length: {len(content)}")
2144
+ else:
2145
+ logger.warning(f"Writer {writer_num} has empty content after cleaning")
2146
+
2147
+ logger.info(f"Total writers collected: {writer_count}, Total words: {total_words}")
2148
+ logger.info(f"Writer contents: {len(writer_contents)} entries")
2149
 
2150
  # ํ†ต๊ณ„ ํŽ˜์ด์ง€
2151
  doc.add_heading('Novel Statistics', 1)
2152
+ doc.add_paragraph(f'Mode: {"Test Mode (Actual chapters written)" if is_test_mode else "Full Mode (10 chapters)"}')
2153
+ doc.add_paragraph(f'Total Chapters: {writer_count}')
2154
  doc.add_paragraph(f'Total Words: {total_words:,}')
2155
  doc.add_paragraph(f'Estimated Pages: ~{total_words/500:.0f}')
2156
+ doc.add_paragraph(f'Language: {actual_language}')
2157
  doc.add_page_break()
2158
 
2159
  # ๋ชฉ์ฐจ
2160
+ if writer_contents:
2161
+ doc.add_heading('Table of Contents', 1)
2162
+ # writer_contents๋ฅผ writer_num์œผ๋กœ ์ •๋ ฌ
2163
+ sorted_contents = sorted(writer_contents, key=lambda x: x['writer_num'])
2164
+
2165
+ for item in sorted_contents:
2166
+ chapter_num = item['writer_num']
2167
+ doc.add_paragraph(f'Chapter {chapter_num}: Pages {(chapter_num-1)*3+1}-{chapter_num*3}')
2168
+ doc.add_page_break()
2169
+
2170
+ # ๊ฐ ์ž‘๊ฐ€์˜ ๋‚ด์šฉ ์ถ”๊ฐ€ (์ •๋ ฌ๋œ ์ˆœ์„œ๋Œ€๋กœ)
2171
+ for idx, writer_data in enumerate(sorted_contents):
2172
+ writer_num = writer_data['writer_num']
2173
+ content = writer_data['content']
2174
+ word_count = writer_data['word_count']
2175
+
2176
+ # ์ฑ•ํ„ฐ ํ—ค๋”
2177
+ doc.add_heading(f'Chapter {writer_num}', 1)
2178
+ doc.add_paragraph(f'Pages {(writer_num-1)*3+1}-{writer_num*3}')
2179
+ doc.add_paragraph(f'Word Count: {word_count:,}')
2180
+ doc.add_paragraph()
2181
+
2182
+ # ์ž‘๊ฐ€ ๋‚ด์šฉ ์ถ”๊ฐ€
2183
+ paragraphs = content.split('\n\n')
2184
+ for para_text in paragraphs:
2185
+ if para_text.strip():
2186
+ para = doc.add_paragraph(para_text.strip())
2187
+ para.style.font.size = Pt(11)
2188
+
2189
+ if idx < len(sorted_contents) - 1: # ๋งˆ์ง€๋ง‰ ์ฑ•ํ„ฐ ํ›„์—๋Š” ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ์—†์Œ
2190
+ doc.add_page_break()
2191
+ else:
2192
+ logger.warning("No writer contents found! Creating empty document.")
2193
+ doc.add_paragraph("No content found. Please check if the novel generation completed successfully.")
2194
 
2195
  # ํŽ˜์ด์ง€ ์„ค์ •
2196
  for section in doc.sections:
 
2204
  # Save
2205
  temp_dir = tempfile.gettempdir()
2206
  safe_filename = re.sub(r'[^\w\s-]', '', session['user_query'][:30]).strip()
2207
+ mode_suffix = "_TestMode" if is_test_mode else "_Complete"
2208
+ filename = f"Novel{mode_suffix}_{safe_filename}_{timestamp}.docx"
2209
  filepath = os.path.join(temp_dir, filename)
2210
  doc.save(filepath)
2211
 
2212
+ logger.info(f"DOCX saved successfully: {filepath} ({total_words} words, {writer_count} writers)")
2213
  return filepath
2214
  else:
2215
+ # TXT format - ๋™์ผํ•œ ์ˆ˜์ • ์ ์šฉ
2216
  temp_dir = tempfile.gettempdir()
2217
  safe_filename = re.sub(r'[^\w\s-]', '', session['user_query'][:30]).strip()
2218
+ mode_suffix = "_TestMode" if is_test_mode else "_Complete"
2219
+ filename = f"Novel{mode_suffix}_{safe_filename}_{timestamp}.txt"
2220
  filepath = os.path.join(temp_dir, filename)
2221
 
2222
  with open(filepath, 'w', encoding='utf-8') as f:
2223
  f.write("="*60 + "\n")
2224
+ f.write(f"AI COLLABORATIVE NOVEL - {'TEST MODE' if is_test_mode else 'COMPLETE VERSION'}\n")
2225
  f.write("="*60 + "\n")
2226
  f.write(f"Theme: {session['user_query']}\n")
2227
+ f.write(f"Language: {actual_language}\n")
2228
  f.write(f"Created: {datetime.now()}\n")
2229
+ f.write(f"Mode: {'Test Mode (Actual chapters)' if is_test_mode else 'Full Mode (10 chapters)'}\n")
2230
  f.write("="*60 + "\n\n")
2231
 
2232
  total_words = 0
2233
  writer_count = 0
2234
 
2235
+ if is_test_mode:
2236
+ # ํ…Œ์ŠคํŠธ ๋ชจ๋“œ: writer1 + writer10 ์ฒ˜๋ฆฌ
2237
+ for stage in stages:
2238
+ role = stage['role'] if 'role' in stage.keys() else None
2239
+ stage_name = stage['stage_name'] if 'stage_name' in stage.keys() else ''
2240
+ content = stage['content'] if 'content' in stage.keys() else ''
 
 
 
2241
 
2242
+ # Writer 1 ์ˆ˜์ •๋ณธ
2243
+ if role == 'writer1' and stage_name and ('Revision' in stage_name or '์ˆ˜์ •๋ณธ' in stage_name):
2244
+ content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', content)
2245
+ content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', content)
2246
+ content = content.strip()
2247
+
2248
+ if content:
2249
+ word_count = stage['word_count'] if 'word_count' in stage.keys() else len(content.split())
2250
+ total_words += word_count
2251
+ writer_count = 1
2252
+
2253
+ f.write(f"\n{'='*40}\n")
2254
+ f.write(f"CHAPTER 1 (Pages 1-3)\n")
2255
+ f.write(f"Word Count: {word_count:,}\n")
2256
+ f.write(f"{'='*40}\n\n")
2257
+ f.write(content)
2258
+ f.write("\n\n")
2259
+
2260
+ # Writer 10
2261
+ elif role == 'writer10':
2262
+ # [Chapter X] ํŒจํ„ด์œผ๋กœ ์ฑ•ํ„ฐ ๋ถ„๋ฆฌ
2263
+ chapters = re.split(r'\[Chapter\s+(\d+)\]', content)
2264
 
2265
+ for i in range(1, len(chapters), 2):
2266
+ if i+1 < len(chapters):
2267
+ chapter_num = int(chapters[i])
2268
+ chapter_content = chapters[i+1].strip()
2269
+
2270
+ if chapter_content:
2271
+ word_count = len(chapter_content.split())
2272
+ total_words += word_count
2273
+ writer_count = max(writer_count, chapter_num)
2274
+
2275
+ f.write(f"\n{'='*40}\n")
2276
+ f.write(f"CHAPTER {chapter_num} (Pages {(chapter_num-1)*3+1}-{chapter_num*3})\n")
2277
+ f.write(f"Word Count: {word_count:,}\n")
2278
+ f.write(f"{'='*40}\n\n")
2279
+ f.write(chapter_content)
2280
+ f.write("\n\n")
2281
+ else:
2282
+ # ์ผ๋ฐ˜ ๋ชจ๋“œ - ์ˆ˜์ •๋œ ๋กœ์ง
2283
+ for stage in stages:
2284
+ role = stage['role'] if 'role' in stage.keys() else None
2285
+ stage_name = stage['stage_name'] if 'stage_name' in stage.keys() else ''
2286
+ content = stage['content'] if 'content' in stage.keys() else ''
2287
+
2288
+ # ์–ธ์–ด์— ์ƒ๊ด€์—†์ด ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ ์ฐพ๊ธฐ
2289
+ is_writer = role and role.startswith('writer')
2290
+ is_revision = stage_name and ('Revision' in stage_name or '์ˆ˜์ •๋ณธ' in stage_name)
2291
+
2292
+ if is_writer and is_revision:
2293
+ # ์ž‘๊ฐ€ ๋ฒˆํ˜ธ ์ถ”์ถœ
2294
+ try:
2295
+ writer_num = int(role.replace('writer', ''))
2296
+ except:
2297
+ continue
2298
+
2299
+ # ํŽ˜์ด์ง€ ๋งˆํฌ ์ œ๊ฑฐ
2300
+ content = re.sub(r'\[(?:ํŽ˜์ด์ง€|Page|page)\s*\d+\]', '', content)
2301
+ content = re.sub(r'(?:ํŽ˜์ด์ง€|Page)\s*\d+:', '', content)
2302
+ content = content.strip()
2303
+
2304
+ if content:
2305
+ word_count = stage['word_count'] if 'word_count' in stage.keys() else len(content.split())
2306
+ total_words += word_count
2307
+ writer_count += 1
2308
+
2309
+ f.write(f"\n{'='*40}\n")
2310
+ f.write(f"CHAPTER {writer_num} (Pages {(writer_num-1)*3+1}-{writer_num*3})\n")
2311
+ f.write(f"Word Count: {word_count:,}\n")
2312
+ f.write(f"{'='*40}\n\n")
2313
+ f.write(content)
2314
+ f.write("\n\n")
2315
 
2316
  f.write(f"\n{'='*60}\n")
2317
+ f.write(f"TOTAL: {writer_count} chapters, {total_words:,} words\n")
2318
  f.write(f"{'='*60}\n")
2319
 
2320
  logger.info(f"TXT saved successfully: {filepath} ({total_words} words)")
 
2406
  <br><br>
2407
  <span class="search-indicator">๐Ÿ” Web search enabled</span> |
2408
  <span class="auto-save-indicator">๐Ÿ’พ Auto-save to database</span> |
2409
+ <span style="color: #FF9800;">โ™ป๏ธ Resume anytime</span> |
2410
+ <span style="color: #FFC107;">๐Ÿงช Test mode: 7 stages (Writer 1 + Writer 10)</span>
2411
  </p>
2412
  </div>
2413
  """)
 
2430
  label="Language / ์–ธ์–ด"
2431
  )
2432
 
2433
+ # Test mode checkbox
2434
+ test_mode_check = gr.Checkbox(
2435
+ label="๐Ÿงช Test Mode (Quick: Writer 1 & 10 only) / ํ…Œ์ŠคํŠธ ๋ชจ๋“œ (๋น ๋ฅธ ์ƒ์„ฑ: ์ž‘๊ฐ€ 1, 10๋งŒ)",
2436
+ value=False,
2437
+ info="Complete Writer 1 & 10 process for 10 chapters / ์ž‘๊ฐ€ 1, 10๋งŒ ์ž‘์„ฑํ•˜์—ฌ 10๊ฐœ ์ฑ•ํ„ฐ ์ƒ์„ฑ"
2438
+ )
2439
+
2440
  # Web search status indicator
2441
  web_search_status = gr.Markdown(
2442
  value=f"๐Ÿ” **Web Search:** {'Enabled' if WebSearchIntegration().enabled else 'Disabled (Set BRAVE_SEARCH_API_KEY)'}"
 
2527
  # Connect event handlers
2528
  submit_btn.click(
2529
  fn=process_query,
2530
+ inputs=[query_input, language_select, current_session_id, test_mode_check],
2531
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2532
  )
2533
 
2534
  # Update novel text state and session ID when novel output changes
 
2544
  outputs=[current_session_id]
2545
  ).then(
2546
  fn=resume_session,
2547
+ inputs=[current_session_id, language_select, test_mode_check],
2548
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2549
  )
2550
 
2551
  auto_recover_btn.click(
 
2554
  outputs=[current_session_id]
2555
  ).then(
2556
  fn=resume_session,
2557
+ inputs=[current_session_id, language_select, test_mode_check],
2558
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2559
  )
2560
 
2561
  refresh_btn.click(
 
2564
  )
2565
 
2566
  clear_btn.click(
2567
+ fn=lambda: ("", "", "๐Ÿ”„ Ready", "", None, False),
2568
+ outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id, test_mode_check]
2569
  )
2570
 
2571
  def handle_download(format_type, language, session_id, novel_text):
2572
+ logger.info(f"Download requested: format={format_type}, session_id={session_id}, language={language}")
2573
+ logger.info(f"Session ID type: {type(session_id)}, value: {repr(session_id)}")
2574
+
2575
  if not session_id:
2576
+ logger.error("No session_id available for download")
2577
  return gr.update(visible=False)
2578
 
2579
  file_path = download_novel(novel_text, format_type, language, session_id)
2580
  if file_path:
2581
+ logger.info(f"Download successful: {file_path}")
2582
  return gr.update(value=file_path, visible=True)
2583
  else:
2584
+ logger.error("Download failed - no file generated")
2585
  return gr.update(visible=False)
2586
 
2587
  download_btn.click(