Spaces:
Running
Running
Update app.py
Browse files
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"
|
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κ°μ
|
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
|
1060 |
4. Create a complete story from beginning to end
|
1061 |
5. Include all elements from the masterplan
|
1062 |
|
1063 |
-
|
|
|
|
|
1064 |
[Chapter 1]
|
1065 |
-
content
|
1066 |
|
1067 |
[Chapter 2]
|
1068 |
-
content
|
1069 |
|
1070 |
-
|
|
|
|
|
|
|
|
|
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"
|
1397 |
-
|
1398 |
-
|
1399 |
-
|
|
|
|
|
|
|
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"
|
1475 |
-
|
1476 |
-
|
1477 |
-
|
|
|
|
|
|
|
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'\[
|
1603 |
-
chapters_with_headers = re.split(f'({chapter_pattern})', stage_content)
|
1604 |
|
1605 |
-
# μ±ν°
|
1606 |
-
|
1607 |
-
|
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 |
-
|
1614 |
-
|
1615 |
-
|
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 |
-
|
1622 |
-
|
1623 |
-
|
1624 |
-
|
1625 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
|
|
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']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2012 |
-
|
2013 |
-
|
2014 |
-
|
2015 |
-
|
2016 |
-
|
2017 |
-
|
2018 |
-
|
2019 |
-
|
2020 |
-
|
2021 |
-
|
2022 |
-
|
2023 |
-
|
2024 |
-
|
2025 |
-
|
2026 |
-
|
2027 |
-
|
2028 |
-
|
2029 |
-
|
2030 |
-
|
2031 |
-
|
2032 |
-
|
2033 |
-
|
2034 |
-
|
2035 |
-
|
2036 |
-
|
|
|
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(
|