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(