Spaces:
Running
Running
Update app-backup.py
Browse files- 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"
|
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 |
-
"""๋ชจ๋ ์๊ฐ์ ์์ ๋ณธ ๋ด์ฉ์ ๊ฐ์ ธ์์ ํฉ์น๊ธฐ
|
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 |
-
|
|
|
|
|
380 |
cursor.execute('''
|
381 |
SELECT content, stage_name, word_count FROM stages
|
382 |
-
WHERE session_id = ? AND
|
383 |
-
|
|
|
|
|
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"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
|
399 |
full_content = '\n\n'.join(all_content)
|
400 |
-
logger.info(f"Total: {writer_count} writers, {total_word_count} words")
|
401 |
|
402 |
-
|
403 |
-
|
404 |
-
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
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
|
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
|
|
|
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 |
-
|
1406 |
-
|
1407 |
-
|
1408 |
-
|
1409 |
-
|
1410 |
-
|
1411 |
-
|
1412 |
-
|
1413 |
-
|
1414 |
-
(
|
1415 |
-
|
1416 |
-
|
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 |
-
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
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
|
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 |
-
#
|
1597 |
-
|
1598 |
if session_id:
|
1599 |
if language == "Korean":
|
1600 |
-
|
|
|
|
|
|
|
|
|
|
|
1601 |
else:
|
1602 |
-
|
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 |
-
|
|
|
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 |
-
|
1749 |
-
|
1750 |
-
|
1751 |
-
|
1752 |
-
|
1753 |
-
|
1754 |
-
content =
|
1755 |
-
content = content.strip()
|
1756 |
|
1757 |
-
if
|
1758 |
-
|
1759 |
-
|
1760 |
-
|
1761 |
-
|
1762 |
-
|
1763 |
-
|
1764 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1765 |
|
1766 |
# ํต๊ณ ํ์ด์ง
|
1767 |
doc.add_heading('Novel Statistics', 1)
|
1768 |
-
doc.add_paragraph(f'
|
|
|
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: {
|
1772 |
doc.add_page_break()
|
1773 |
|
1774 |
# ๋ชฉ์ฐจ
|
1775 |
-
|
1776 |
-
|
1777 |
-
|
1778 |
-
|
1779 |
-
|
1780 |
-
|
1781 |
-
|
1782 |
-
|
1783 |
-
|
1784 |
-
|
1785 |
-
|
1786 |
-
|
1787 |
-
|
1788 |
-
|
1789 |
-
|
1790 |
-
|
1791 |
-
|
1792 |
-
|
1793 |
-
|
1794 |
-
|
1795 |
-
|
1796 |
-
|
1797 |
-
|
1798 |
-
|
1799 |
-
|
1800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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 |
-
|
|
|
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: {
|
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 |
-
|
1841 |
-
|
1842 |
-
|
1843 |
-
|
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 |
-
|
1850 |
-
|
1851 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1852 |
|
1853 |
-
|
1854 |
-
|
1855 |
-
|
1856 |
-
|
1857 |
-
|
1858 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1859 |
|
1860 |
f.write(f"\n{'='*60}\n")
|
1861 |
-
f.write(f"TOTAL: {writer_count}
|
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(
|