openfree commited on
Commit
6305a62
Β·
verified Β·
1 Parent(s): 2636781

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -85
app.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]:
@@ -1035,18 +1037,24 @@ You MUST maintain 1,400-1,500 words."""
1035
  **μ€‘μš” μ§€μΉ¨:**
1036
  1. 전체 30νŽ˜μ΄μ§€ λΆ„λŸ‰μ˜ μ™„μ„±λœ μ†Œμ„€μ„ μž‘μ„±ν•˜μ„Έμš”
1037
  2. 총 14,000-15,000 λ‹¨μ–΄λ‘œ μž‘μ„±ν•˜μ„Έμš”
1038
- 3. 10개의 μžμ—°μŠ€λŸ¬μš΄ μ±•ν„°λ‘œ κ΅¬μ„±ν•˜μ„Έμš” (각 챕터 μ•½ 1,400-1,500 단어)
1039
  4. μ‹œμž‘λΆ€ν„° κ²°λ§κΉŒμ§€ μ™„μ „ν•œ 이야기λ₯Ό λ§Œλ“œμ„Έμš”
1040
  5. λ§ˆμŠ€ν„°ν”Œλžœμ˜ λͺ¨λ“  μš”μ†Œλ₯Ό ν¬ν•¨ν•˜μ„Έμš”
1041
 
1042
- 챕터 ꡬ뢄은 λ‹€μŒκ³Ό 같이 ν‘œμ‹œν•˜μ„Έμš”:
 
 
1043
  [Chapter 1]
1044
- λ‚΄μš©...
1045
 
1046
  [Chapter 2]
1047
- λ‚΄μš©...
1048
 
1049
- 완성도 높은 전체 μ†Œμ„€μ„ μž‘μ„±ν•˜μ„Έμš”."""
 
 
 
 
1050
  else:
1051
  return f"""[TEST MODE] You are a special writer creating the complete 30-page novel at once.
1052
 
@@ -1056,18 +1064,24 @@ Director's Masterplan:
1056
  **CRITICAL INSTRUCTIONS:**
1057
  1. Write a complete 30-page novel
1058
  2. Total 14,000-15,000 words
1059
- 3. Organize into 10 natural chapters (each chapter ~1,400-1,500 words)
1060
  4. Create a complete story from beginning to end
1061
  5. Include all elements from the masterplan
1062
 
1063
- Mark chapters as follows:
 
 
1064
  [Chapter 1]
1065
- content...
1066
 
1067
  [Chapter 2]
1068
- content...
1069
 
1070
- Write a high-quality complete novel."""
 
 
 
 
1071
 
1072
  def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
1073
  """Simulate streaming in test mode"""
@@ -1393,10 +1407,13 @@ Write a high-quality complete novel."""
1393
  if role == "writer10":
1394
  full_novel = ""
1395
  for i in range(1, 11):
1396
- full_novel += f"\n\n[Chapter {i}]\n\n"
1397
- full_novel += sample_story + f"\n\n이것은 챕터 {i}의 λ‚΄μš©μž…λ‹ˆλ‹€. "
1398
- full_novel += "μ΄μ•ΌκΈ°λŠ” 계속 μ „κ°œλ˜μ—ˆκ³ , μΈλ¬Όλ“€μ˜ 감정은 점점 더 λ³΅μž‘ν•΄μ‘Œλ‹€. " * 100
1399
- full_novel += "\n\n"
 
 
 
1400
  test_responses["writer10"] = full_novel
1401
 
1402
  return test_responses.get(role, "ν…ŒμŠ€νŠΈ μ‘λ‹΅μž…λ‹ˆλ‹€.")
@@ -1471,10 +1488,13 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1471
  if role == "writer10":
1472
  full_novel = ""
1473
  for i in range(1, 11):
1474
- full_novel += f"\n\n[Chapter {i}]\n\n"
1475
- full_novel += sample_story + f"\n\nThis was chapter {i} content. "
1476
- full_novel += "The story continued to unfold, and the characters' emotions grew increasingly complex. " * 100
1477
- full_novel += "\n\n"
 
 
 
1478
  test_responses["writer10"] = full_novel
1479
 
1480
  return test_responses.get(role, "Test response.")
@@ -1495,11 +1515,13 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1495
  query = session['user_query']
1496
  language = session['language']
1497
  resume_from_stage = session['current_stage'] + 1
 
1498
  else:
1499
  self.current_session_id = NovelDatabase.create_session(query, language)
1500
  resume_from_stage = 0
 
1501
 
1502
- logger.info(f"Processing novel for session {self.current_session_id}, starting from stage {resume_from_stage}")
1503
 
1504
  # Initialize conversation
1505
  conversation_history = [{
@@ -1597,46 +1619,100 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1597
 
1598
  # ν…ŒμŠ€νŠΈ λͺ¨λ“œμ—μ„œ writer10 μ €μž₯ μ‹œ νŠΉλ³„ 처리
1599
  if test_quick_mode and role == "writer10":
 
 
1600
  # 전체 λ‚΄μš©μ„ 10개 μ±•ν„°λ‘œ λΆ„ν• ν•˜μ—¬ μ €μž₯
1601
- # [Chapter 숫자] νŒ¨ν„΄μœΌλ‘œ λΆ„ν• 
1602
- chapter_pattern = r'\[Chapter\s+\d+\]'
1603
- chapters_with_headers = re.split(f'({chapter_pattern})', stage_content)
1604
 
1605
- # 챕터 λ‚΄μš©λ§Œ μΆ”μΆœ (헀더와 λ‚΄μš©μ„ 쌍으둜)
1606
- chapters = []
1607
- for i in range(1, len(chapters_with_headers), 2):
1608
- if i+1 < len(chapters_with_headers):
1609
- chapter_content = chapters_with_headers[i+1].strip()
1610
- if chapter_content:
1611
- chapters.append(chapter_content)
1612
 
1613
- # 챕터가 10κ°œκ°€ μ•ˆ 되면 κ· λ“± λΆ„ν• 
1614
- if len(chapters) < 10:
1615
- logger.warning(f"Only {len(chapters)} chapters found, splitting evenly")
1616
- # 전체 ν…μŠ€νŠΈλ₯Ό 10개둜 κ· λ“± λΆ„ν• 
1617
- total_text = stage_content.replace('[Chapter', '\n[Chapter')
1618
- words = total_text.split()
1619
- words_per_chapter = len(words) // 10
1620
  chapters = []
1621
- for i in range(10):
1622
- start_idx = i * words_per_chapter
1623
- end_idx = (i + 1) * words_per_chapter if i < 9 else len(words)
1624
- chapter_words = words[start_idx:end_idx]
1625
- chapters.append(' '.join(chapter_words))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1626
 
1627
  # 각 챕터λ₯Ό κ°œλ³„ writer둜 μ €μž₯
 
1628
  for i, chapter_content in enumerate(chapters[:10], 1):
1629
  writer_stage_num = (i-1) * 3 + 5 # 5, 8, 11, 14, 17, 20, 23, 26, 29, 32
 
 
 
 
1630
  NovelDatabase.save_stage(
1631
  self.current_session_id,
1632
  writer_stage_num,
1633
  f"Writer {i}: Revision",
1634
  f"writer{i}",
1635
- chapter_content,
1636
  "complete"
1637
  )
1638
  word_count = len(chapter_content.split())
 
1639
  logger.info(f"βœ… Test mode: Saved chapter {i} as writer {i} ({word_count} words)")
 
 
 
 
 
 
 
 
 
 
 
 
1640
  else:
1641
  NovelDatabase.save_stage(
1642
  self.current_session_id,
@@ -1812,13 +1888,13 @@ Write a high-quality complete novel."""
1812
  return ""
1813
 
1814
  # Gradio Interface Functions
1815
- def process_query(query: str, language: str, session_id: str = None, test_mode: bool = False) -> Generator[Tuple[str, str, str], None, None]:
1816
- """Process query and yield updates"""
1817
  if not query.strip() and not session_id:
1818
  if language == "Korean":
1819
- yield "", "", "❌ μ†Œμ„€ 주제λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
1820
  else:
1821
- yield "", "", "❌ Please enter a novel theme."
1822
  return
1823
 
1824
  system = NovelWritingSystem()
@@ -1854,14 +1930,15 @@ def process_query(query: str, language: str, session_id: str = None, test_mode:
1854
  else:
1855
  status = f"πŸ”„ Processing... ({completed}/{total} - {progress_percent:.1f}%){mode_status}"
1856
 
1857
- yield stages_display, final_novel, status
 
1858
 
1859
  except Exception as e:
1860
  logger.error(f"Error in process_query: {str(e)}", exc_info=True)
1861
  if language == "Korean":
1862
- yield "", "", f"❌ 였λ₯˜ λ°œμƒ: {str(e)}"
1863
  else:
1864
- yield "", "", f"❌ Error occurred: {str(e)}"
1865
 
1866
  def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
1867
  """Format stages into simple display with writer save status"""
@@ -1902,13 +1979,13 @@ def get_active_sessions(language: str) -> List[Tuple[str, str]]:
1902
  logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
1903
  return []
1904
 
1905
- def resume_session(session_id: str, language: str, test_mode: bool = False) -> Generator[Tuple[str, str, str], None, None]:
1906
  """Resume an existing session"""
1907
  if not session_id:
1908
  if language == "Korean":
1909
- yield "", "", "❌ μ„Έμ…˜μ„ μ„ νƒν•΄μ£Όμ„Έμš”."
1910
  else:
1911
- yield "", "", "❌ Please select a session."
1912
  return
1913
 
1914
  # Process with existing session ID
@@ -1936,6 +2013,8 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1936
  logger.error("No session_id provided for download")
1937
  return None
1938
 
 
 
1939
  # DBμ—μ„œ μ„Έμ…˜ 정보 κ°€μ Έμ˜€κΈ°
1940
  session = NovelDatabase.get_session(session_id)
1941
  if not session:
@@ -1944,6 +2023,7 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1944
 
1945
  # DBμ—μ„œ λͺ¨λ“  μŠ€ν…Œμ΄μ§€ κ°€μ Έμ˜€κΈ°
1946
  stages = NovelDatabase.get_stages(session_id)
 
1947
 
1948
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1949
 
@@ -1981,10 +2061,31 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1981
 
1982
  # 각 μž‘κ°€μ˜ μˆ˜μ •λ³Έλ§Œ μˆ˜μ§‘
1983
  writer_contents = []
 
 
 
1984
  for stage in stages:
1985
- if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1986
  writer_count += 1
1987
  content = stage['content'] or ""
 
1988
  # νŽ˜μ΄μ§€ 마크 제거
1989
  content = re.sub(r'\[(?:νŽ˜μ΄μ§€|Page|page)\s*\d+\]', '', content)
1990
  content = re.sub(r'(?:νŽ˜μ΄μ§€|Page)\s*\d+:', '', content)
@@ -1998,9 +2099,44 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
1998
  'content': content,
1999
  'word_count': word_count
2000
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2001
 
2002
  # 톡계 νŽ˜μ΄μ§€
2003
  doc.add_heading('Novel Statistics', 1)
 
2004
  doc.add_paragraph(f'Total Writers: {writer_count}')
2005
  doc.add_paragraph(f'Total Words: {total_words:,}')
2006
  doc.add_paragraph(f'Estimated Pages: ~{total_words/500:.0f}')
@@ -2008,32 +2144,33 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
2008
  doc.add_page_break()
2009
 
2010
  # λͺ©μ°¨
2011
- doc.add_heading('Table of Contents', 1)
2012
- for i in range(1, writer_count + 1):
2013
- doc.add_paragraph(f'Chapter {i}: Pages {(i-1)*3+1}-{i*3}')
2014
- doc.add_page_break()
2015
-
2016
- # 각 μž‘κ°€μ˜ λ‚΄μš© μΆ”κ°€
2017
- for writer_data in writer_contents:
2018
- writer_num = writer_data['writer_num']
2019
- content = writer_data['content']
2020
- word_count = writer_data['word_count']
2021
-
2022
- # 챕터 헀더
2023
- doc.add_heading(f'Chapter {writer_num}', 1)
2024
- doc.add_paragraph(f'Pages {(writer_num-1)*3+1}-{writer_num*3}')
2025
- doc.add_paragraph(f'Word Count: {word_count:,}')
2026
- doc.add_paragraph()
2027
-
2028
- # μž‘κ°€ λ‚΄μš© μΆ”κ°€
2029
- paragraphs = content.split('\n\n')
2030
- for para_text in paragraphs:
2031
- if para_text.strip():
2032
- para = doc.add_paragraph(para_text.strip())
2033
- para.style.font.size = Pt(11)
2034
-
2035
- if writer_num < writer_count: # λ§ˆμ§€λ§‰ μž‘κ°€ ν›„μ—λŠ” νŽ˜μ΄μ§€ ꡬ뢄 μ—†μŒ
2036
- doc.add_page_break()
 
2037
 
2038
  # νŽ˜μ΄μ§€ μ„€μ •
2039
  for section in doc.sections:
@@ -2051,7 +2188,7 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
2051
  filepath = os.path.join(temp_dir, filename)
2052
  doc.save(filepath)
2053
 
2054
- logger.info(f"DOCX saved successfully: {filepath} ({total_words} words)")
2055
  return filepath
2056
  else:
2057
  # TXT format
@@ -2074,7 +2211,7 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
2074
 
2075
  # 각 μž‘κ°€μ˜ μˆ˜μ •λ³Έλ§Œ 좜λ ₯
2076
  for stage in stages:
2077
- if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
2078
  writer_count += 1
2079
  content = stage['content'] or ""
2080
  # νŽ˜μ΄μ§€ 마크 제거
@@ -2093,6 +2230,15 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
2093
  f.write(content)
2094
  f.write("\n\n")
2095
 
 
 
 
 
 
 
 
 
 
2096
  f.write(f"\n{'='*60}\n")
2097
  f.write(f"TOTAL: {writer_count} writers, {total_words:,} words\n")
2098
  f.write(f"{'='*60}\n")
@@ -2308,7 +2454,7 @@ def create_interface():
2308
  submit_btn.click(
2309
  fn=process_query,
2310
  inputs=[query_input, language_select, current_session_id, test_mode_check],
2311
- outputs=[stages_display, novel_output, status_text]
2312
  )
2313
 
2314
  # Update novel text state and session ID when novel output changes
@@ -2325,7 +2471,7 @@ def create_interface():
2325
  ).then(
2326
  fn=resume_session,
2327
  inputs=[current_session_id, language_select, test_mode_check],
2328
- outputs=[stages_display, novel_output, status_text]
2329
  )
2330
 
2331
  auto_recover_btn.click(
@@ -2335,7 +2481,7 @@ def create_interface():
2335
  ).then(
2336
  fn=resume_session,
2337
  inputs=[current_session_id, language_select, test_mode_check],
2338
- outputs=[stages_display, novel_output, status_text]
2339
  )
2340
 
2341
  refresh_btn.click(
@@ -2349,13 +2495,19 @@ def create_interface():
2349
  )
2350
 
2351
  def handle_download(format_type, language, session_id, novel_text):
 
 
 
2352
  if not session_id:
 
2353
  return gr.update(visible=False)
2354
 
2355
  file_path = download_novel(novel_text, format_type, language, session_id)
2356
  if file_path:
 
2357
  return gr.update(value=file_path, visible=True)
2358
  else:
 
2359
  return gr.update(visible=False)
2360
 
2361
  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]:
 
1037
  **μ€‘μš” μ§€μΉ¨:**
1038
  1. 전체 30νŽ˜μ΄μ§€ λΆ„λŸ‰μ˜ μ™„μ„±λœ μ†Œμ„€μ„ μž‘μ„±ν•˜μ„Έμš”
1039
  2. 총 14,000-15,000 λ‹¨μ–΄λ‘œ μž‘μ„±ν•˜μ„Έμš”
1040
+ 3. μ •ν™•νžˆ 10개의 μ±•ν„°λ‘œ κ΅¬μ„±ν•˜μ„Έμš” (각 챕터 μ•½ 1,400-1,500 단어)
1041
  4. μ‹œμž‘λΆ€ν„° κ²°λ§κΉŒμ§€ μ™„μ „ν•œ 이야기λ₯Ό λ§Œλ“œμ„Έμš”
1042
  5. λ§ˆμŠ€ν„°ν”Œλžœμ˜ λͺ¨λ“  μš”μ†Œλ₯Ό ν¬ν•¨ν•˜μ„Έμš”
1043
 
1044
+ **ν•„μˆ˜ ν˜•μ‹:**
1045
+ λ°˜λ“œμ‹œ μ•„λž˜μ™€ 같이 챕터λ₯Ό λͺ…ν™•νžˆ κ΅¬λΆ„ν•˜μ„Έμš”:
1046
+
1047
  [Chapter 1]
1048
+ (1,400-1,500 λ‹¨μ–΄μ˜ λ‚΄μš©)
1049
 
1050
  [Chapter 2]
1051
+ (1,400-1,500 λ‹¨μ–΄μ˜ λ‚΄μš©)
1052
 
1053
+ ...이런 μ‹μœΌλ‘œ [Chapter 10]κΉŒμ§€...
1054
+
1055
+ 각 μ±•ν„°λŠ” λ°˜λ“œμ‹œ [Chapter 숫자] ν˜•μ‹μœΌλ‘œ μ‹œμž‘ν•΄μ•Ό ν•©λ‹ˆλ‹€.
1056
+ 챕터 μ‚¬μ΄μ—λŠ” 빈 쀄을 λ„£μ–΄ κ΅¬λΆ„ν•˜μ„Έμš”.
1057
+ 전체 μ†Œμ„€μ„ μž‘μ„±ν•˜μ„Έμš”."""
1058
  else:
1059
  return f"""[TEST MODE] You are a special writer creating the complete 30-page novel at once.
1060
 
 
1064
  **CRITICAL INSTRUCTIONS:**
1065
  1. Write a complete 30-page novel
1066
  2. Total 14,000-15,000 words
1067
+ 3. Organize into EXACTLY 10 chapters (each chapter ~1,400-1,500 words)
1068
  4. Create a complete story from beginning to end
1069
  5. Include all elements from the masterplan
1070
 
1071
+ **MANDATORY FORMAT:**
1072
+ You MUST clearly separate chapters as follows:
1073
+
1074
  [Chapter 1]
1075
+ (1,400-1,500 words of content)
1076
 
1077
  [Chapter 2]
1078
+ (1,400-1,500 words of content)
1079
 
1080
+ ...continue this way until [Chapter 10]...
1081
+
1082
+ Each chapter MUST start with [Chapter number] format.
1083
+ Leave blank lines between chapters for separation.
1084
+ Write the complete novel now."""
1085
 
1086
  def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
1087
  """Simulate streaming in test mode"""
 
1407
  if role == "writer10":
1408
  full_novel = ""
1409
  for i in range(1, 11):
1410
+ full_novel += f"[Chapter {i}]\n\n"
1411
+ # 각 μ±•ν„°λ§ˆλ‹€ μ•½ 300단어씩 5단락
1412
+ for j in range(5):
1413
+ full_novel += sample_story + f"\n\n그것은 챕터 {i}의 {j+1}번째 λ‹¨λ½μ΄μ—ˆλ‹€. "
1414
+ full_novel += "μ΄μ•ΌκΈ°λŠ” 계속 μ „κ°œλ˜μ—ˆκ³ , μΈλ¬Όλ“€μ˜ 감정은 점점 더 λ³΅μž‘ν•΄μ‘Œλ‹€. " * 20
1415
+ full_novel += "\n\n"
1416
+ full_novel += "\n\n" # 챕터 κ°„ ꡬ뢄
1417
  test_responses["writer10"] = full_novel
1418
 
1419
  return test_responses.get(role, "ν…ŒμŠ€νŠΈ μ‘λ‹΅μž…λ‹ˆλ‹€.")
 
1488
  if role == "writer10":
1489
  full_novel = ""
1490
  for i in range(1, 11):
1491
+ full_novel += f"[Chapter {i}]\n\n"
1492
+ # About 300 words per paragraph, 5 paragraphs per chapter
1493
+ for j in range(5):
1494
+ full_novel += sample_story + f"\n\nThis was paragraph {j+1} of chapter {i}. "
1495
+ full_novel += "The story continued to unfold, and the characters' emotions grew increasingly complex. " * 20
1496
+ full_novel += "\n\n"
1497
+ full_novel += "\n\n" # Chapter separation
1498
  test_responses["writer10"] = full_novel
1499
 
1500
  return test_responses.get(role, "Test response.")
 
1515
  query = session['user_query']
1516
  language = session['language']
1517
  resume_from_stage = session['current_stage'] + 1
1518
+ logger.info(f"Resuming session {session_id} from stage {resume_from_stage}")
1519
  else:
1520
  self.current_session_id = NovelDatabase.create_session(query, language)
1521
  resume_from_stage = 0
1522
+ logger.info(f"Created new session: {self.current_session_id}")
1523
 
1524
+ logger.info(f"Processing novel for session {self.current_session_id}, starting from stage {resume_from_stage}, test_mode={test_quick_mode}")
1525
 
1526
  # Initialize conversation
1527
  conversation_history = [{
 
1619
 
1620
  # ν…ŒμŠ€νŠΈ λͺ¨λ“œμ—μ„œ writer10 μ €μž₯ μ‹œ νŠΉλ³„ 처리
1621
  if test_quick_mode and role == "writer10":
1622
+ logger.info(f"Test mode: Processing complete novel ({len(stage_content)} chars)")
1623
+
1624
  # 전체 λ‚΄μš©μ„ 10개 μ±•ν„°λ‘œ λΆ„ν• ν•˜μ—¬ μ €μž₯
1625
+ # [Chapter 숫자] λ˜λŠ” [chapter 숫자] νŒ¨ν„΄μœΌλ‘œ λΆ„ν• 
1626
+ chapter_pattern = r'\[(?i)chapter\s+\d+\]'
 
1627
 
1628
+ # 챕터 λ§ˆμ»€κ°€ μžˆλŠ”μ§€ 확인
1629
+ chapter_markers = re.findall(chapter_pattern, stage_content, re.IGNORECASE)
1630
+ logger.info(f"Found {len(chapter_markers)} chapter markers")
 
 
 
 
1631
 
1632
+ if len(chapter_markers) >= 10:
1633
+ # 챕터 마컀둜 λΆ„ν• 
1634
+ parts = re.split(chapter_pattern, stage_content, flags=re.IGNORECASE)
 
 
 
 
1635
  chapters = []
1636
+
1637
+ # 첫 번째 뢀뢄은 챕터 이전 λ‚΄μš©μ΄λ―€λ‘œ μ œμ™Έ
1638
+ for i in range(1, len(parts)):
1639
+ content = parts[i].strip()
1640
+ if content:
1641
+ chapters.append(content)
1642
+ else:
1643
+ # 챕터 λ§ˆμ»€κ°€ λΆ€μ‘±ν•˜λ©΄ 전체λ₯Ό κ· λ“± λΆ„ν• 
1644
+ logger.warning(f"Not enough chapter markers, splitting content evenly")
1645
+
1646
+ # λ¨Όμ € λ‹¨λ½μœΌλ‘œ λΆ„ν•  μ‹œλ„
1647
+ paragraphs = stage_content.split('\n\n')
1648
+ paragraphs = [p.strip() for p in paragraphs if p.strip()]
1649
+
1650
+ if len(paragraphs) >= 20: # μΆ©λΆ„ν•œ 단락이 있으면
1651
+ para_per_chapter = len(paragraphs) // 10
1652
+ chapters = []
1653
+ for i in range(10):
1654
+ start_idx = i * para_per_chapter
1655
+ end_idx = (i + 1) * para_per_chapter if i < 9 else len(paragraphs)
1656
+ chapter_content = '\n\n'.join(paragraphs[start_idx:end_idx])
1657
+ chapters.append(chapter_content)
1658
+ else:
1659
+ # 단락도 λΆ€μ‘±ν•˜λ©΄ λ‹¨μ–΄λ‘œ κ· λ“± λΆ„ν• 
1660
+ words = stage_content.split()
1661
+ words_per_chapter = max(len(words) // 10, 100) # μ΅œμ†Œ 100단어
1662
+ chapters = []
1663
+ for i in range(10):
1664
+ start_idx = i * words_per_chapter
1665
+ end_idx = (i + 1) * words_per_chapter if i < 9 else len(words)
1666
+ chapter_words = words[start_idx:end_idx]
1667
+ chapters.append(' '.join(chapter_words))
1668
+
1669
+ # 10개 μ±•ν„°λ‘œ μ œν•œ
1670
+ chapters = chapters[:10]
1671
+
1672
+ # λΆ€μ‘±ν•œ μ±•ν„°λŠ” λ§ˆμ§€λ§‰ 챕터λ₯Ό λΆ„ν• ν•΄μ„œ μ±„μš°κΈ°
1673
+ while len(chapters) < 10:
1674
+ if chapters and len(chapters[-1].split()) > 200:
1675
+ # λ§ˆμ§€λ§‰ 챕터λ₯Ό 반으둜 λ‚˜λˆ„κΈ°
1676
+ last_chapter = chapters[-1]
1677
+ words = last_chapter.split()
1678
+ mid = len(words) // 2
1679
+ chapters[-1] = ' '.join(words[:mid])
1680
+ chapters.append(' '.join(words[mid:]))
1681
+ else:
1682
+ # λ„ˆλ¬΄ 짧으면 빈 챕터 μΆ”κ°€
1683
+ chapters.append(f"[Chapter {len(chapters)+1} - Content pending]")
1684
 
1685
  # 각 챕터λ₯Ό κ°œλ³„ writer둜 μ €μž₯
1686
+ total_saved_words = 0
1687
  for i, chapter_content in enumerate(chapters[:10], 1):
1688
  writer_stage_num = (i-1) * 3 + 5 # 5, 8, 11, 14, 17, 20, 23, 26, 29, 32
1689
+
1690
+ # 챕터 헀더 μΆ”κ°€
1691
+ full_chapter_content = f"[Chapter {i}]\n\n{chapter_content}"
1692
+
1693
  NovelDatabase.save_stage(
1694
  self.current_session_id,
1695
  writer_stage_num,
1696
  f"Writer {i}: Revision",
1697
  f"writer{i}",
1698
+ full_chapter_content,
1699
  "complete"
1700
  )
1701
  word_count = len(chapter_content.split())
1702
+ total_saved_words += word_count
1703
  logger.info(f"βœ… Test mode: Saved chapter {i} as writer {i} ({word_count} words)")
1704
+
1705
+ logger.info(f"βœ… Test mode: Total saved words: {total_saved_words}")
1706
+
1707
+ # 원본도 μ €μž₯ (λ°±μ—…μš©)
1708
+ NovelDatabase.save_stage(
1709
+ self.current_session_id,
1710
+ stage_idx,
1711
+ stage_name,
1712
+ role,
1713
+ stage_content,
1714
+ "complete"
1715
+ )
1716
  else:
1717
  NovelDatabase.save_stage(
1718
  self.current_session_id,
 
1888
  return ""
1889
 
1890
  # Gradio Interface Functions
1891
+ def process_query(query: str, language: str, session_id: str = None, test_mode: bool = False) -> Generator[Tuple[str, str, str, str], None, None]:
1892
+ """Process query and yield updates with session ID"""
1893
  if not query.strip() and not session_id:
1894
  if language == "Korean":
1895
+ yield "", "", "❌ μ†Œμ„€ 주제λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.", None
1896
  else:
1897
+ yield "", "", "❌ Please enter a novel theme.", None
1898
  return
1899
 
1900
  system = NovelWritingSystem()
 
1930
  else:
1931
  status = f"πŸ”„ Processing... ({completed}/{total} - {progress_percent:.1f}%){mode_status}"
1932
 
1933
+ # Return current session ID
1934
+ yield stages_display, final_novel, status, system.current_session_id
1935
 
1936
  except Exception as e:
1937
  logger.error(f"Error in process_query: {str(e)}", exc_info=True)
1938
  if language == "Korean":
1939
+ yield "", "", f"❌ 였λ₯˜ λ°œμƒ: {str(e)}", None
1940
  else:
1941
+ yield "", "", f"❌ Error occurred: {str(e)}", None
1942
 
1943
  def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
1944
  """Format stages into simple display with writer save status"""
 
1979
  logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
1980
  return []
1981
 
1982
+ def resume_session(session_id: str, language: str, test_mode: bool = False) -> Generator[Tuple[str, str, str, str], None, None]:
1983
  """Resume an existing session"""
1984
  if not session_id:
1985
  if language == "Korean":
1986
+ yield "", "", "❌ μ„Έμ…˜μ„ μ„ νƒν•΄μ£Όμ„Έμš”.", None
1987
  else:
1988
+ yield "", "", "❌ Please select a session.", None
1989
  return
1990
 
1991
  # Process with existing session ID
 
2013
  logger.error("No session_id provided for download")
2014
  return None
2015
 
2016
+ logger.info(f"Starting download for session: {session_id}, format: {format}")
2017
+
2018
  # DBμ—μ„œ μ„Έμ…˜ 정보 κ°€μ Έμ˜€κΈ°
2019
  session = NovelDatabase.get_session(session_id)
2020
  if not session:
 
2023
 
2024
  # DBμ—μ„œ λͺ¨λ“  μŠ€ν…Œμ΄μ§€ κ°€μ Έμ˜€κΈ°
2025
  stages = NovelDatabase.get_stages(session_id)
2026
+ logger.info(f"Found {len(stages)} stages in database")
2027
 
2028
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
2029
 
 
2061
 
2062
  # 각 μž‘κ°€μ˜ μˆ˜μ •λ³Έλ§Œ μˆ˜μ§‘
2063
  writer_contents = []
2064
+
2065
+ # ν…ŒμŠ€νŠΈ λͺ¨λ“œ 체크
2066
+ is_test_mode = False
2067
  for stage in stages:
2068
+ if stage['role'] == 'writer10' and stage['stage_name'] and 'TEST' in stage['stage_name']:
2069
+ is_test_mode = True
2070
+ logger.info("Test mode detected")
2071
+ break
2072
+
2073
+ # Writer stages 검색
2074
+ for stage in stages:
2075
+ logger.debug(f"Checking stage: role={stage['role']}, name={stage['stage_name']}, status={stage['status']}")
2076
+
2077
+ if stage['role'] and stage['role'].startswith('writer') and stage['stage_name'] and 'Revision' in stage['stage_name']:
2078
+ # μž‘κ°€ 번호 μΆ”μΆœ
2079
+ try:
2080
+ writer_num_from_role = int(stage['role'].replace('writer', ''))
2081
+ logger.debug(f"Found writer {writer_num_from_role} revision")
2082
+ except:
2083
+ logger.warning(f"Could not extract writer number from role: {stage['role']}")
2084
+ continue
2085
+
2086
  writer_count += 1
2087
  content = stage['content'] or ""
2088
+
2089
  # νŽ˜μ΄μ§€ 마크 제거
2090
  content = re.sub(r'\[(?:νŽ˜μ΄μ§€|Page|page)\s*\d+\]', '', content)
2091
  content = re.sub(r'(?:νŽ˜μ΄μ§€|Page)\s*\d+:', '', content)
 
2099
  'content': content,
2100
  'word_count': word_count
2101
  })
2102
+ logger.info(f"Added writer {writer_count}: {word_count} words")
2103
+
2104
+ if not writer_contents:
2105
+ logger.error("No writer content found in database!")
2106
+
2107
+ # 디버그λ₯Ό μœ„ν•΄ λͺ¨λ“  stages 좜λ ₯
2108
+ for stage in stages:
2109
+ logger.debug(f"Stage {stage['stage_number']}: role={stage['role']}, name={stage['stage_name']}")
2110
+
2111
+ # ν…ŒμŠ€νŠΈ λͺ¨λ“œμΈ 경우 writer10 λ‚΄μš© 직접 μ‚¬μš©
2112
+ if is_test_mode:
2113
+ logger.info("Attempting to use writer10 content directly")
2114
+ for stage in stages:
2115
+ if stage['role'] == 'writer10':
2116
+ content = stage['content'] or ""
2117
+ if content:
2118
+ doc.add_heading('Complete Novel (Test Mode)', 1)
2119
+ doc.add_paragraph(f'Total length: {len(content)} characters')
2120
+ doc.add_page_break()
2121
+
2122
+ paragraphs = content.split('\n\n')
2123
+ for para_text in paragraphs:
2124
+ if para_text.strip():
2125
+ para = doc.add_paragraph(para_text.strip())
2126
+ para.style.font.size = Pt(11)
2127
+
2128
+ # Save
2129
+ temp_dir = tempfile.gettempdir()
2130
+ safe_filename = re.sub(r'[^\w\s-]', '', session['user_query'][:30]).strip()
2131
+ filename = f"Novel_TestMode_{safe_filename}_{timestamp}.docx"
2132
+ filepath = os.path.join(temp_dir, filename)
2133
+ doc.save(filepath)
2134
+ logger.info(f"DOCX saved (test mode): {filepath}")
2135
+ return filepath
2136
 
2137
  # 톡계 νŽ˜μ΄μ§€
2138
  doc.add_heading('Novel Statistics', 1)
2139
+ doc.add_paragraph(f'Mode: {"Test Mode" if is_test_mode else "Normal Mode"}')
2140
  doc.add_paragraph(f'Total Writers: {writer_count}')
2141
  doc.add_paragraph(f'Total Words: {total_words:,}')
2142
  doc.add_paragraph(f'Estimated Pages: ~{total_words/500:.0f}')
 
2144
  doc.add_page_break()
2145
 
2146
  # λͺ©μ°¨
2147
+ if writer_contents:
2148
+ doc.add_heading('Table of Contents', 1)
2149
+ for i in range(1, writer_count + 1):
2150
+ doc.add_paragraph(f'Chapter {i}: Pages {(i-1)*3+1}-{i*3}')
2151
+ doc.add_page_break()
2152
+
2153
+ # 각 μž‘κ°€μ˜ λ‚΄μš© μΆ”κ°€
2154
+ for writer_data in writer_contents:
2155
+ writer_num = writer_data['writer_num']
2156
+ content = writer_data['content']
2157
+ word_count = writer_data['word_count']
2158
+
2159
+ # 챕터 헀더
2160
+ doc.add_heading(f'Chapter {writer_num}', 1)
2161
+ doc.add_paragraph(f'Pages {(writer_num-1)*3+1}-{writer_num*3}')
2162
+ doc.add_paragraph(f'Word Count: {word_count:,}')
2163
+ doc.add_paragraph()
2164
+
2165
+ # μž‘κ°€ λ‚΄μš© μΆ”κ°€
2166
+ paragraphs = content.split('\n\n')
2167
+ for para_text in paragraphs:
2168
+ if para_text.strip():
2169
+ para = doc.add_paragraph(para_text.strip())
2170
+ para.style.font.size = Pt(11)
2171
+
2172
+ if writer_num < writer_count: # λ§ˆμ§€λ§‰ μž‘κ°€ ν›„μ—λŠ” νŽ˜μ΄μ§€ ꡬ뢄 μ—†μŒ
2173
+ doc.add_page_break()
2174
 
2175
  # νŽ˜μ΄μ§€ μ„€μ •
2176
  for section in doc.sections:
 
2188
  filepath = os.path.join(temp_dir, filename)
2189
  doc.save(filepath)
2190
 
2191
+ logger.info(f"DOCX saved successfully: {filepath} ({total_words} words, {writer_count} writers)")
2192
  return filepath
2193
  else:
2194
  # TXT format
 
2211
 
2212
  # 각 μž‘κ°€μ˜ μˆ˜μ •λ³Έλ§Œ 좜λ ₯
2213
  for stage in stages:
2214
+ if stage['role'] and stage['role'].startswith('writer') and stage['stage_name'] and 'Revision' in stage['stage_name']:
2215
  writer_count += 1
2216
  content = stage['content'] or ""
2217
  # νŽ˜μ΄μ§€ 마크 제거
 
2230
  f.write(content)
2231
  f.write("\n\n")
2232
 
2233
+ if writer_count == 0:
2234
+ # ν…ŒμŠ€νŠΈ λͺ¨λ“œ λ°±μ—…
2235
+ f.write("\n[No writer content found - displaying raw stages]\n\n")
2236
+ for stage in stages:
2237
+ if stage['content']:
2238
+ f.write(f"\n--- {stage['stage_name']} ---\n")
2239
+ f.write(stage['content'])
2240
+ f.write("\n\n")
2241
+
2242
  f.write(f"\n{'='*60}\n")
2243
  f.write(f"TOTAL: {writer_count} writers, {total_words:,} words\n")
2244
  f.write(f"{'='*60}\n")
 
2454
  submit_btn.click(
2455
  fn=process_query,
2456
  inputs=[query_input, language_select, current_session_id, test_mode_check],
2457
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2458
  )
2459
 
2460
  # Update novel text state and session ID when novel output changes
 
2471
  ).then(
2472
  fn=resume_session,
2473
  inputs=[current_session_id, language_select, test_mode_check],
2474
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2475
  )
2476
 
2477
  auto_recover_btn.click(
 
2481
  ).then(
2482
  fn=resume_session,
2483
  inputs=[current_session_id, language_select, test_mode_check],
2484
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2485
  )
2486
 
2487
  refresh_btn.click(
 
2495
  )
2496
 
2497
  def handle_download(format_type, language, session_id, novel_text):
2498
+ logger.info(f"Download requested: format={format_type}, session_id={session_id}, language={language}")
2499
+ logger.info(f"Session ID type: {type(session_id)}, value: {repr(session_id)}")
2500
+
2501
  if not session_id:
2502
+ logger.error("No session_id available for download")
2503
  return gr.update(visible=False)
2504
 
2505
  file_path = download_novel(novel_text, format_type, language, session_id)
2506
  if file_path:
2507
+ logger.info(f"Download successful: {file_path}")
2508
  return gr.update(value=file_path, visible=True)
2509
  else:
2510
+ logger.error("Download failed - no file generated")
2511
  return gr.update(visible=False)
2512
 
2513
  download_btn.click(