Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -4,11 +4,15 @@ import json
|
|
4 |
import requests
|
5 |
from datetime import datetime
|
6 |
import time
|
7 |
-
from typing import List, Dict, Any, Generator, Tuple
|
8 |
import logging
|
9 |
import re
|
10 |
import tempfile
|
11 |
from pathlib import Path
|
|
|
|
|
|
|
|
|
12 |
|
13 |
# ๋ก๊น
์ค์
|
14 |
logging.basicConfig(level=logging.INFO)
|
@@ -36,6 +40,186 @@ conversation_history = []
|
|
36 |
selected_language = "English" # ๊ธฐ๋ณธ ์ธ์ด
|
37 |
novel_context = {} # ์์ค ์ปจํ
์คํธ ์ ์ฅ
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
class NovelWritingSystem:
|
40 |
def __init__(self):
|
41 |
self.token = FRIENDLI_TOKEN
|
@@ -46,6 +230,13 @@ class NovelWritingSystem:
|
|
46 |
if self.test_mode:
|
47 |
logger.warning("Running in test mode.")
|
48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
# ์์ค ์์ฑ ์งํ ์ํ
|
50 |
self.novel_state = {
|
51 |
"theme": "",
|
@@ -1258,11 +1449,25 @@ Last page content from Writer {writer_num}..."""
|
|
1258 |
|
1259 |
return test_responses.get(role, "Test response.")
|
1260 |
|
1261 |
-
def process_novel_stream(self, query: str, language: str = "English"
|
|
|
|
|
1262 |
"""Process novel writing with streaming updates"""
|
1263 |
try:
|
1264 |
global conversation_history, novel_context
|
1265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1266 |
# Initialize conversation
|
1267 |
conversation_history = [{
|
1268 |
"role": "human",
|
@@ -1270,203 +1475,116 @@ Last page content from Writer {writer_num}..."""
|
|
1270 |
"timestamp": datetime.now()
|
1271 |
}]
|
1272 |
|
|
|
1273 |
stages = []
|
1274 |
-
|
1275 |
-
|
1276 |
-
|
1277 |
-
|
1278 |
-
|
1279 |
-
|
1280 |
-
|
1281 |
-
|
1282 |
-
yield "", stages
|
1283 |
-
|
1284 |
-
director_prompt = self.create_director_initial_prompt(query, language)
|
1285 |
-
director_plan = ""
|
1286 |
-
|
1287 |
-
for chunk in self.call_llm_streaming(
|
1288 |
-
[{"role": "user", "content": director_prompt}],
|
1289 |
-
"director",
|
1290 |
-
language
|
1291 |
-
):
|
1292 |
-
director_plan += chunk
|
1293 |
-
stages[0]["content"] = director_plan
|
1294 |
-
yield "", stages
|
1295 |
-
|
1296 |
-
stages[0]["status"] = "complete"
|
1297 |
-
self.novel_state["theme"] = director_plan
|
1298 |
-
|
1299 |
-
# 2. Critic Review of Plan
|
1300 |
-
stages.append({
|
1301 |
-
"name": f"๐ {'๋นํ๊ฐ: ๊ธฐํ ๊ฒํ ' if language == 'Korean' else 'Critic: Plan Review'}",
|
1302 |
-
"status": "active",
|
1303 |
-
"content": ""
|
1304 |
-
})
|
1305 |
-
yield "", stages
|
1306 |
-
|
1307 |
-
critic_prompt = self.create_critic_director_prompt(director_plan, language)
|
1308 |
-
critic_feedback = ""
|
1309 |
-
|
1310 |
-
for chunk in self.call_llm_streaming(
|
1311 |
-
[{"role": "user", "content": critic_prompt}],
|
1312 |
-
"critic",
|
1313 |
-
language
|
1314 |
-
):
|
1315 |
-
critic_feedback += chunk
|
1316 |
-
stages[1]["content"] = critic_feedback
|
1317 |
-
yield "", stages
|
1318 |
-
|
1319 |
-
stages[1]["status"] = "complete"
|
1320 |
|
1321 |
-
|
1322 |
-
|
1323 |
-
|
1324 |
-
|
1325 |
-
|
1326 |
-
|
1327 |
-
yield "", stages
|
1328 |
|
1329 |
-
|
1330 |
-
|
|
|
|
|
|
|
|
|
1331 |
|
1332 |
-
|
1333 |
-
|
1334 |
-
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
1338 |
-
|
1339 |
-
yield "", stages
|
1340 |
|
1341 |
-
|
1342 |
-
|
|
|
|
|
1343 |
|
1344 |
-
#
|
1345 |
-
for
|
1346 |
-
|
1347 |
-
writer_stage_idx = 3 + (writer_num - 1) * 2
|
1348 |
-
stages.append({
|
1349 |
-
"name": f"โ๏ธ {'์์ฑ์' if language == 'Korean' else 'Writer'} {writer_num}: {'์ด์' if language == 'Korean' else 'Draft'}",
|
1350 |
-
"status": "active",
|
1351 |
-
"content": ""
|
1352 |
-
})
|
1353 |
-
yield "", stages
|
1354 |
|
1355 |
-
|
1356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1357 |
|
1358 |
-
for chunk in self.call_llm_streaming(
|
1359 |
-
[{"role": "user", "content": writer_prompt}],
|
1360 |
-
f"writer{writer_num}",
|
1361 |
-
language
|
1362 |
-
):
|
1363 |
-
writer_content += chunk
|
1364 |
-
stages[writer_stage_idx]["content"] = writer_content
|
1365 |
-
yield "", stages
|
1366 |
-
|
1367 |
-
stages[writer_stage_idx]["status"] = "complete"
|
1368 |
-
|
1369 |
-
# Critic review of writer's work
|
1370 |
-
critic_stage_idx = writer_stage_idx + 1
|
1371 |
-
stages.append({
|
1372 |
-
"name": f"๐ {'๋นํ๊ฐ: ์์ฑ์' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒํ ' if language == 'Korean' else 'Review'}",
|
1373 |
-
"status": "active",
|
1374 |
-
"content": ""
|
1375 |
-
})
|
1376 |
yield "", stages
|
1377 |
|
1378 |
-
|
1379 |
-
|
|
|
1380 |
|
1381 |
-
|
1382 |
-
[{"role": "user", "content": critic_writer_prompt}],
|
1383 |
-
"critic",
|
1384 |
-
language
|
1385 |
-
):
|
1386 |
-
critic_writer_feedback += chunk
|
1387 |
-
stages[critic_stage_idx]["content"] = critic_writer_feedback
|
1388 |
-
yield "", stages
|
1389 |
-
|
1390 |
-
stages[critic_stage_idx]["status"] = "complete"
|
1391 |
-
|
1392 |
-
# Writer revision
|
1393 |
-
revision_stage_idx = critic_stage_idx + 1
|
1394 |
-
stages.append({
|
1395 |
-
"name": f"โ๏ธ {'์์ฑ์' if language == 'Korean' else 'Writer'} {writer_num}: {'์์ ๋ณธ' if language == 'Korean' else 'Revision'}",
|
1396 |
-
"status": "active",
|
1397 |
-
"content": ""
|
1398 |
-
})
|
1399 |
-
yield "", stages
|
1400 |
-
|
1401 |
-
writer_revision_prompt = self.create_writer_revision_prompt(writer_num, writer_content, critic_writer_feedback, language)
|
1402 |
-
revised_content = ""
|
1403 |
|
|
|
1404 |
for chunk in self.call_llm_streaming(
|
1405 |
-
[{"role": "user", "content":
|
1406 |
-
|
1407 |
language
|
1408 |
):
|
1409 |
-
|
1410 |
-
stages[
|
1411 |
yield "", stages
|
1412 |
|
1413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1414 |
|
1415 |
-
# Add to accumulated content
|
1416 |
-
accumulated_content += f"\n\n{revised_content}"
|
1417 |
-
self.novel_state[f"writer{writer_num}_final"] = revised_content
|
1418 |
-
|
1419 |
-
# 24. Critic Final Evaluation
|
1420 |
-
final_critic_idx = len(stages)
|
1421 |
-
stages.append({
|
1422 |
-
"name": f"๐ {'๋นํ๊ฐ: ์ต์ข
ํ๊ฐ' if language == 'Korean' else 'Critic: Final Evaluation'}",
|
1423 |
-
"status": "active",
|
1424 |
-
"content": ""
|
1425 |
-
})
|
1426 |
-
yield "", stages
|
1427 |
-
|
1428 |
-
critic_final_prompt = self.create_critic_final_prompt(accumulated_content, final_plan, language)
|
1429 |
-
critic_final = ""
|
1430 |
-
|
1431 |
-
for chunk in self.call_llm_streaming(
|
1432 |
-
[{"role": "user", "content": critic_final_prompt}],
|
1433 |
-
"critic",
|
1434 |
-
language
|
1435 |
-
):
|
1436 |
-
critic_final += chunk
|
1437 |
-
stages[final_critic_idx]["content"] = critic_final
|
1438 |
yield "", stages
|
1439 |
|
1440 |
-
|
1441 |
-
|
1442 |
-
# 25. Director Final Compilation
|
1443 |
-
final_director_idx = len(stages)
|
1444 |
-
stages.append({
|
1445 |
-
"name": f"๐ฌ {'๊ฐ๋
์: ์ต์ข
์์ฑ๋ณธ' if language == 'Korean' else 'Director: Final Version'}",
|
1446 |
-
"status": "active",
|
1447 |
-
"content": ""
|
1448 |
-
})
|
1449 |
-
yield "", stages
|
1450 |
|
1451 |
-
|
1452 |
-
final_novel
|
1453 |
-
|
1454 |
-
for chunk in self.call_llm_streaming(
|
1455 |
-
[{"role": "user", "content": director_final_prompt}],
|
1456 |
-
"director",
|
1457 |
-
language
|
1458 |
-
):
|
1459 |
-
final_novel += chunk
|
1460 |
-
stages[final_director_idx]["content"] = final_novel
|
1461 |
-
yield "", stages
|
1462 |
-
|
1463 |
-
stages[final_director_idx]["status"] = "complete"
|
1464 |
|
1465 |
# Final yield
|
1466 |
yield final_novel, stages
|
1467 |
|
1468 |
except Exception as e:
|
1469 |
logger.error(f"Error in process_novel_stream: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1470 |
error_stage = {
|
1471 |
"name": "โ Error",
|
1472 |
"status": "error",
|
@@ -1474,11 +1592,80 @@ Last page content from Writer {writer_num}..."""
|
|
1474 |
}
|
1475 |
stages.append(error_stage)
|
1476 |
yield f"Error occurred: {str(e)}", stages
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1477 |
|
1478 |
# Gradio Interface Functions
|
1479 |
-
def process_query(query: str, language: str) -> Generator[Tuple[str, str, str], None, None]:
|
1480 |
"""Process query and yield updates"""
|
1481 |
-
if not query.strip():
|
1482 |
if language == "Korean":
|
1483 |
yield "", "", "โ ์์ค ์ฃผ์ ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์."
|
1484 |
else:
|
@@ -1488,9 +1675,9 @@ def process_query(query: str, language: str) -> Generator[Tuple[str, str, str],
|
|
1488 |
system = NovelWritingSystem()
|
1489 |
|
1490 |
try:
|
1491 |
-
for final_novel, stages in system.process_novel_stream(query, language):
|
1492 |
-
# Format stages for display
|
1493 |
-
stages_html = format_stages_html(stages, language)
|
1494 |
|
1495 |
status = "๐ Processing..." if not final_novel else "โ
Complete!"
|
1496 |
|
@@ -1503,16 +1690,19 @@ def process_query(query: str, language: str) -> Generator[Tuple[str, str, str],
|
|
1503 |
else:
|
1504 |
yield "", "", f"โ Error occurred: {str(e)}"
|
1505 |
|
1506 |
-
def format_stages_html(stages: List[Dict[str, str]], language: str) -> str:
|
1507 |
-
"""Format stages into HTML"""
|
1508 |
-
|
|
|
|
|
1509 |
|
1510 |
-
for stage in stages:
|
1511 |
status_icon = "โ
" if stage.get("status") == "complete" else ("โณ" if stage.get("status") == "active" else "โ")
|
1512 |
|
1513 |
# Create collapsible section for each stage
|
|
|
1514 |
html += f'''
|
1515 |
-
<details class="stage-section" {"open" if
|
1516 |
<summary class="stage-header">
|
1517 |
<span class="status-icon">{status_icon}</span>
|
1518 |
<span class="stage-name">{stage["name"]}</span>
|
@@ -1524,8 +1714,58 @@ def format_stages_html(stages: List[Dict[str, str]], language: str) -> str:
|
|
1524 |
'''
|
1525 |
|
1526 |
html += '</div>'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1527 |
return html
|
1528 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1529 |
def download_novel(novel_text: str, format: str, language: str) -> str:
|
1530 |
"""Download novel in specified format"""
|
1531 |
if not novel_text:
|
@@ -1565,7 +1805,7 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
|
|
1565 |
|
1566 |
return filepath
|
1567 |
|
1568 |
-
# Custom CSS
|
1569 |
custom_css = """
|
1570 |
.gradio-container {
|
1571 |
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
@@ -1579,6 +1819,13 @@ custom_css = """
|
|
1579 |
background-color: rgba(255, 255, 255, 0.05);
|
1580 |
border-radius: 12px;
|
1581 |
backdrop-filter: blur(10px);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1582 |
}
|
1583 |
|
1584 |
.stage-section {
|
@@ -1622,6 +1869,7 @@ custom_css = """
|
|
1622 |
color: #e0e0e0;
|
1623 |
font-family: 'Courier New', monospace;
|
1624 |
line-height: 1.6;
|
|
|
1625 |
}
|
1626 |
|
1627 |
#novel-output {
|
@@ -1657,6 +1905,27 @@ custom_css = """
|
|
1657 |
border-radius: 8px;
|
1658 |
margin-top: 20px;
|
1659 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1660 |
"""
|
1661 |
|
1662 |
# Create Gradio Interface
|
@@ -1673,10 +1942,15 @@ def create_interface():
|
|
1673 |
<p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
|
1674 |
Enter a theme or prompt, and watch as 13 AI agents collaborate to create a complete 50-page novella.
|
1675 |
The system includes 1 Director, 1 Critic, and 10 Writers working in harmony.
|
|
|
1676 |
</p>
|
1677 |
</div>
|
1678 |
""")
|
1679 |
|
|
|
|
|
|
|
|
|
1680 |
with gr.Row():
|
1681 |
with gr.Column(scale=1):
|
1682 |
with gr.Group(elem_classes=["input-section"]):
|
@@ -1702,6 +1976,24 @@ def create_interface():
|
|
1702 |
value="๐ Ready"
|
1703 |
)
|
1704 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1705 |
gr.HTML("""
|
1706 |
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.1); border-radius: 8px; color: white;">
|
1707 |
<h4>Writing Process:</h4>
|
@@ -1714,6 +2006,7 @@ def create_interface():
|
|
1714 |
<li>Writers revise based on feedback</li>
|
1715 |
<li>Final compilation by Director</li>
|
1716 |
</ol>
|
|
|
1717 |
</div>
|
1718 |
""")
|
1719 |
|
@@ -1766,9 +2059,13 @@ def create_interface():
|
|
1766 |
def update_novel_state(process, novel, status):
|
1767 |
return process, novel, status, novel
|
1768 |
|
|
|
|
|
|
|
|
|
1769 |
submit_btn.click(
|
1770 |
fn=process_query,
|
1771 |
-
inputs=[query_input, language_select],
|
1772 |
outputs=[process_display, novel_output, status_text]
|
1773 |
).then(
|
1774 |
fn=update_novel_state,
|
@@ -1776,9 +2073,30 @@ def create_interface():
|
|
1776 |
outputs=[process_display, novel_output, status_text, novel_text_state]
|
1777 |
)
|
1778 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1779 |
clear_btn.click(
|
1780 |
-
fn=lambda: ("", "", "๐ Ready", ""),
|
1781 |
-
outputs=[process_display, novel_output, status_text, novel_text_state]
|
|
|
|
|
|
|
|
|
|
|
|
|
1782 |
)
|
1783 |
|
1784 |
def handle_download(novel_text, format_type, language):
|
@@ -1796,6 +2114,12 @@ def create_interface():
|
|
1796 |
inputs=[novel_text_state, format_select, language_select],
|
1797 |
outputs=[download_file]
|
1798 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
1799 |
|
1800 |
return interface
|
1801 |
|
|
|
4 |
import requests
|
5 |
from datetime import datetime
|
6 |
import time
|
7 |
+
from typing import List, Dict, Any, Generator, Tuple, Optional
|
8 |
import logging
|
9 |
import re
|
10 |
import tempfile
|
11 |
from pathlib import Path
|
12 |
+
import sqlite3
|
13 |
+
import hashlib
|
14 |
+
import threading
|
15 |
+
from contextlib import contextmanager
|
16 |
|
17 |
# ๋ก๊น
์ค์
|
18 |
logging.basicConfig(level=logging.INFO)
|
|
|
40 |
selected_language = "English" # ๊ธฐ๋ณธ ์ธ์ด
|
41 |
novel_context = {} # ์์ค ์ปจํ
์คํธ ์ ์ฅ
|
42 |
|
43 |
+
# DB ๊ฒฝ๋ก
|
44 |
+
DB_PATH = "novel_sessions.db"
|
45 |
+
db_lock = threading.Lock()
|
46 |
+
|
47 |
+
class NovelDatabase:
|
48 |
+
"""Novel session management database"""
|
49 |
+
|
50 |
+
@staticmethod
|
51 |
+
def init_db():
|
52 |
+
"""Initialize database tables"""
|
53 |
+
with sqlite3.connect(DB_PATH) as conn:
|
54 |
+
cursor = conn.cursor()
|
55 |
+
|
56 |
+
# Sessions table
|
57 |
+
cursor.execute('''
|
58 |
+
CREATE TABLE IF NOT EXISTS sessions (
|
59 |
+
session_id TEXT PRIMARY KEY,
|
60 |
+
user_query TEXT NOT NULL,
|
61 |
+
language TEXT NOT NULL,
|
62 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
63 |
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
64 |
+
status TEXT DEFAULT 'active',
|
65 |
+
current_stage INTEGER DEFAULT 0,
|
66 |
+
final_novel TEXT
|
67 |
+
)
|
68 |
+
''')
|
69 |
+
|
70 |
+
# Stages table
|
71 |
+
cursor.execute('''
|
72 |
+
CREATE TABLE IF NOT EXISTS stages (
|
73 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
74 |
+
session_id TEXT NOT NULL,
|
75 |
+
stage_number INTEGER NOT NULL,
|
76 |
+
stage_name TEXT NOT NULL,
|
77 |
+
role TEXT NOT NULL,
|
78 |
+
content TEXT,
|
79 |
+
status TEXT DEFAULT 'pending',
|
80 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
81 |
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
82 |
+
)
|
83 |
+
''')
|
84 |
+
|
85 |
+
# Create indices
|
86 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_session_id ON stages(session_id)')
|
87 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_stage_number ON stages(stage_number)')
|
88 |
+
|
89 |
+
conn.commit()
|
90 |
+
|
91 |
+
@staticmethod
|
92 |
+
@contextmanager
|
93 |
+
def get_db():
|
94 |
+
"""Database connection context manager"""
|
95 |
+
with db_lock:
|
96 |
+
conn = sqlite3.connect(DB_PATH)
|
97 |
+
conn.row_factory = sqlite3.Row
|
98 |
+
try:
|
99 |
+
yield conn
|
100 |
+
finally:
|
101 |
+
conn.close()
|
102 |
+
|
103 |
+
@staticmethod
|
104 |
+
def create_session(user_query: str, language: str) -> str:
|
105 |
+
"""Create new session"""
|
106 |
+
session_id = hashlib.md5(f"{user_query}{datetime.now()}".encode()).hexdigest()
|
107 |
+
|
108 |
+
with NovelDatabase.get_db() as conn:
|
109 |
+
cursor = conn.cursor()
|
110 |
+
cursor.execute('''
|
111 |
+
INSERT INTO sessions (session_id, user_query, language)
|
112 |
+
VALUES (?, ?, ?)
|
113 |
+
''', (session_id, user_query, language))
|
114 |
+
conn.commit()
|
115 |
+
|
116 |
+
return session_id
|
117 |
+
|
118 |
+
@staticmethod
|
119 |
+
def save_stage(session_id: str, stage_number: int, stage_name: str,
|
120 |
+
role: str, content: str, status: str = 'complete'):
|
121 |
+
"""Save stage content"""
|
122 |
+
with NovelDatabase.get_db() as conn:
|
123 |
+
cursor = conn.cursor()
|
124 |
+
|
125 |
+
# Check if stage exists
|
126 |
+
cursor.execute('''
|
127 |
+
SELECT id FROM stages
|
128 |
+
WHERE session_id = ? AND stage_number = ?
|
129 |
+
''', (session_id, stage_number))
|
130 |
+
|
131 |
+
existing = cursor.fetchone()
|
132 |
+
|
133 |
+
if existing:
|
134 |
+
# Update existing stage
|
135 |
+
cursor.execute('''
|
136 |
+
UPDATE stages
|
137 |
+
SET content = ?, status = ?, stage_name = ?
|
138 |
+
WHERE session_id = ? AND stage_number = ?
|
139 |
+
''', (content, status, stage_name, session_id, stage_number))
|
140 |
+
else:
|
141 |
+
# Insert new stage
|
142 |
+
cursor.execute('''
|
143 |
+
INSERT INTO stages (session_id, stage_number, stage_name, role, content, status)
|
144 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
145 |
+
''', (session_id, stage_number, stage_name, role, content, status))
|
146 |
+
|
147 |
+
# Update session
|
148 |
+
cursor.execute('''
|
149 |
+
UPDATE sessions
|
150 |
+
SET updated_at = CURRENT_TIMESTAMP, current_stage = ?
|
151 |
+
WHERE session_id = ?
|
152 |
+
''', (stage_number, session_id))
|
153 |
+
|
154 |
+
conn.commit()
|
155 |
+
|
156 |
+
@staticmethod
|
157 |
+
def get_session(session_id: str) -> Optional[Dict]:
|
158 |
+
"""Get session info"""
|
159 |
+
with NovelDatabase.get_db() as conn:
|
160 |
+
cursor = conn.cursor()
|
161 |
+
cursor.execute('SELECT * FROM sessions WHERE session_id = ?', (session_id,))
|
162 |
+
return cursor.fetchone()
|
163 |
+
|
164 |
+
@staticmethod
|
165 |
+
def get_stages(session_id: str) -> List[Dict]:
|
166 |
+
"""Get all stages for a session"""
|
167 |
+
with NovelDatabase.get_db() as conn:
|
168 |
+
cursor = conn.cursor()
|
169 |
+
cursor.execute('''
|
170 |
+
SELECT * FROM stages
|
171 |
+
WHERE session_id = ?
|
172 |
+
ORDER BY stage_number
|
173 |
+
''', (session_id,))
|
174 |
+
return cursor.fetchall()
|
175 |
+
|
176 |
+
@staticmethod
|
177 |
+
def get_completed_content(session_id: str, up_to_stage: int) -> str:
|
178 |
+
"""Get all completed content up to specified stage"""
|
179 |
+
with NovelDatabase.get_db() as conn:
|
180 |
+
cursor = conn.cursor()
|
181 |
+
cursor.execute('''
|
182 |
+
SELECT content FROM stages
|
183 |
+
WHERE session_id = ?
|
184 |
+
AND stage_number < ?
|
185 |
+
AND status = 'complete'
|
186 |
+
AND role LIKE 'writer%'
|
187 |
+
ORDER BY stage_number
|
188 |
+
''', (session_id, up_to_stage))
|
189 |
+
|
190 |
+
contents = []
|
191 |
+
for row in cursor.fetchall():
|
192 |
+
if row['content']:
|
193 |
+
contents.append(row['content'])
|
194 |
+
|
195 |
+
return '\n\n'.join(contents)
|
196 |
+
|
197 |
+
@staticmethod
|
198 |
+
def update_final_novel(session_id: str, final_novel: str):
|
199 |
+
"""Update final novel content"""
|
200 |
+
with NovelDatabase.get_db() as conn:
|
201 |
+
cursor = conn.cursor()
|
202 |
+
cursor.execute('''
|
203 |
+
UPDATE sessions
|
204 |
+
SET final_novel = ?, status = 'complete', updated_at = CURRENT_TIMESTAMP
|
205 |
+
WHERE session_id = ?
|
206 |
+
''', (final_novel, session_id))
|
207 |
+
conn.commit()
|
208 |
+
|
209 |
+
@staticmethod
|
210 |
+
def get_active_sessions() -> List[Dict]:
|
211 |
+
"""Get all active sessions"""
|
212 |
+
with NovelDatabase.get_db() as conn:
|
213 |
+
cursor = conn.cursor()
|
214 |
+
cursor.execute('''
|
215 |
+
SELECT session_id, user_query, language, created_at, current_stage
|
216 |
+
FROM sessions
|
217 |
+
WHERE status = 'active'
|
218 |
+
ORDER BY updated_at DESC
|
219 |
+
LIMIT 10
|
220 |
+
''')
|
221 |
+
return cursor.fetchall()
|
222 |
+
|
223 |
class NovelWritingSystem:
|
224 |
def __init__(self):
|
225 |
self.token = FRIENDLI_TOKEN
|
|
|
230 |
if self.test_mode:
|
231 |
logger.warning("Running in test mode.")
|
232 |
|
233 |
+
# Initialize database
|
234 |
+
NovelDatabase.init_db()
|
235 |
+
|
236 |
+
# Session management
|
237 |
+
self.current_session_id = None
|
238 |
+
self.auto_scroll = False # Auto-scroll control
|
239 |
+
|
240 |
# ์์ค ์์ฑ ์งํ ์ํ
|
241 |
self.novel_state = {
|
242 |
"theme": "",
|
|
|
1449 |
|
1450 |
return test_responses.get(role, "Test response.")
|
1451 |
|
1452 |
+
def process_novel_stream(self, query: str, language: str = "English",
|
1453 |
+
session_id: Optional[str] = None,
|
1454 |
+
resume_from_stage: int = 0) -> Generator[Tuple[str, List[Dict[str, str]]], None, None]:
|
1455 |
"""Process novel writing with streaming updates"""
|
1456 |
try:
|
1457 |
global conversation_history, novel_context
|
1458 |
|
1459 |
+
# Create or resume session
|
1460 |
+
if session_id:
|
1461 |
+
self.current_session_id = session_id
|
1462 |
+
session = NovelDatabase.get_session(session_id)
|
1463 |
+
if session:
|
1464 |
+
query = session['user_query']
|
1465 |
+
language = session['language']
|
1466 |
+
resume_from_stage = session['current_stage'] + 1
|
1467 |
+
else:
|
1468 |
+
self.current_session_id = NovelDatabase.create_session(query, language)
|
1469 |
+
resume_from_stage = 0
|
1470 |
+
|
1471 |
# Initialize conversation
|
1472 |
conversation_history = [{
|
1473 |
"role": "human",
|
|
|
1475 |
"timestamp": datetime.now()
|
1476 |
}]
|
1477 |
|
1478 |
+
# Load existing stages if resuming
|
1479 |
stages = []
|
1480 |
+
if resume_from_stage > 0:
|
1481 |
+
existing_stages = NovelDatabase.get_stages(self.current_session_id)
|
1482 |
+
for stage_data in existing_stages:
|
1483 |
+
stages.append({
|
1484 |
+
"name": stage_data['stage_name'],
|
1485 |
+
"status": stage_data['status'],
|
1486 |
+
"content": stage_data['content'] or ""
|
1487 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1488 |
|
1489 |
+
accumulated_content = ""
|
1490 |
+
if resume_from_stage > 0:
|
1491 |
+
accumulated_content = NovelDatabase.get_completed_content(
|
1492 |
+
self.current_session_id,
|
1493 |
+
resume_from_stage
|
1494 |
+
)
|
|
|
1495 |
|
1496 |
+
# Define all stages
|
1497 |
+
stage_definitions = [
|
1498 |
+
("director", f"๐ฌ {'๊ฐ๋
์: ์ด๊ธฐ ๊ธฐํ' if language == 'Korean' else 'Director: Initial Planning'}"),
|
1499 |
+
("critic", f"๐ {'๋นํ๊ฐ: ๊ธฐํ ๊ฒํ ' if language == 'Korean' else 'Critic: Plan Review'}"),
|
1500 |
+
("director", f"๐ฌ {'๊ฐ๋
์: ์์ ๋ ๋ง์คํฐํ๋' if language == 'Korean' else 'Director: Revised Masterplan'}"),
|
1501 |
+
]
|
1502 |
|
1503 |
+
# Add writer stages
|
1504 |
+
for writer_num in range(1, 11):
|
1505 |
+
stage_definitions.extend([
|
1506 |
+
(f"writer{writer_num}", f"โ๏ธ {'์์ฑ์' if language == 'Korean' else 'Writer'} {writer_num}: {'์ด์' if language == 'Korean' else 'Draft'}"),
|
1507 |
+
("critic", f"๐ {'๋นํ๊ฐ: ์์ฑ์' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒํ ' if language == 'Korean' else 'Review'}"),
|
1508 |
+
(f"writer{writer_num}", f"โ๏ธ {'์์ฑ์' if language == 'Korean' else 'Writer'} {writer_num}: {'์์ ๋ณธ' if language == 'Korean' else 'Revision'}")
|
1509 |
+
])
|
|
|
1510 |
|
1511 |
+
stage_definitions.extend([
|
1512 |
+
("critic", f"๐ {'๋นํ๊ฐ: ์ต์ข
ํ๊ฐ' if language == 'Korean' else 'Critic: Final Evaluation'}"),
|
1513 |
+
("director", f"๐ฌ {'๊ฐ๋
์: ์ต์ข
์์ฑ๋ณธ' if language == 'Korean' else 'Director: Final Version'}")
|
1514 |
+
])
|
1515 |
|
1516 |
+
# Process stages starting from resume point
|
1517 |
+
for stage_idx in range(resume_from_stage, len(stage_definitions)):
|
1518 |
+
role, stage_name = stage_definitions[stage_idx]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1519 |
|
1520 |
+
# Add stage if not already present
|
1521 |
+
if stage_idx >= len(stages):
|
1522 |
+
stages.append({
|
1523 |
+
"name": stage_name,
|
1524 |
+
"status": "active",
|
1525 |
+
"content": ""
|
1526 |
+
})
|
1527 |
+
else:
|
1528 |
+
stages[stage_idx]["status"] = "active"
|
1529 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1530 |
yield "", stages
|
1531 |
|
1532 |
+
# Get appropriate prompt based on stage
|
1533 |
+
prompt = self.get_stage_prompt(stage_idx, role, query, language,
|
1534 |
+
stages, accumulated_content)
|
1535 |
|
1536 |
+
stage_content = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1537 |
|
1538 |
+
# Stream content generation
|
1539 |
for chunk in self.call_llm_streaming(
|
1540 |
+
[{"role": "user", "content": prompt}],
|
1541 |
+
role,
|
1542 |
language
|
1543 |
):
|
1544 |
+
stage_content += chunk
|
1545 |
+
stages[stage_idx]["content"] = stage_content
|
1546 |
yield "", stages
|
1547 |
|
1548 |
+
# Mark stage complete and save to DB
|
1549 |
+
stages[stage_idx]["status"] = "complete"
|
1550 |
+
NovelDatabase.save_stage(
|
1551 |
+
self.current_session_id,
|
1552 |
+
stage_idx,
|
1553 |
+
stage_name,
|
1554 |
+
role,
|
1555 |
+
stage_content,
|
1556 |
+
"complete"
|
1557 |
+
)
|
1558 |
+
|
1559 |
+
# Accumulate writer content
|
1560 |
+
if role.startswith("writer") and "์์ ๋ณธ" in stage_name or "Revision" in stage_name:
|
1561 |
+
accumulated_content += f"\n\n{stage_content}"
|
1562 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1563 |
yield "", stages
|
1564 |
|
1565 |
+
# Get final novel from last stage
|
1566 |
+
final_novel = stages[-1]["content"] if stages else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1567 |
|
1568 |
+
# Save final novel to DB
|
1569 |
+
NovelDatabase.update_final_novel(self.current_session_id, final_novel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1570 |
|
1571 |
# Final yield
|
1572 |
yield final_novel, stages
|
1573 |
|
1574 |
except Exception as e:
|
1575 |
logger.error(f"Error in process_novel_stream: {str(e)}")
|
1576 |
+
|
1577 |
+
# Save error state to DB
|
1578 |
+
if self.current_session_id:
|
1579 |
+
NovelDatabase.save_stage(
|
1580 |
+
self.current_session_id,
|
1581 |
+
stage_idx if 'stage_idx' in locals() else 0,
|
1582 |
+
"Error",
|
1583 |
+
"error",
|
1584 |
+
str(e),
|
1585 |
+
"error"
|
1586 |
+
)
|
1587 |
+
|
1588 |
error_stage = {
|
1589 |
"name": "โ Error",
|
1590 |
"status": "error",
|
|
|
1592 |
}
|
1593 |
stages.append(error_stage)
|
1594 |
yield f"Error occurred: {str(e)}", stages
|
1595 |
+
|
1596 |
+
def get_stage_prompt(self, stage_idx: int, role: str, query: str,
|
1597 |
+
language: str, stages: List[Dict],
|
1598 |
+
accumulated_content: str) -> str:
|
1599 |
+
"""Get appropriate prompt for each stage"""
|
1600 |
+
# Stage 0: Director Initial
|
1601 |
+
if stage_idx == 0:
|
1602 |
+
return self.create_director_initial_prompt(query, language)
|
1603 |
+
|
1604 |
+
# Stage 1: Critic reviews Director's plan
|
1605 |
+
elif stage_idx == 1:
|
1606 |
+
return self.create_critic_director_prompt(stages[0]["content"], language)
|
1607 |
+
|
1608 |
+
# Stage 2: Director revision
|
1609 |
+
elif stage_idx == 2:
|
1610 |
+
return self.create_director_revision_prompt(
|
1611 |
+
stages[0]["content"], stages[1]["content"], language)
|
1612 |
+
|
1613 |
+
# Writer stages
|
1614 |
+
elif role.startswith("writer"):
|
1615 |
+
writer_num = int(role.replace("writer", ""))
|
1616 |
+
final_plan = stages[2]["content"] # Director's final plan
|
1617 |
+
|
1618 |
+
# Initial draft or revision?
|
1619 |
+
if "์ด์" in stages[stage_idx]["name"] or "Draft" in stages[stage_idx]["name"]:
|
1620 |
+
return self.create_writer_prompt(writer_num, final_plan, accumulated_content, language)
|
1621 |
+
else: # Revision
|
1622 |
+
# Find the initial draft and critic feedback
|
1623 |
+
initial_draft_idx = stage_idx - 2
|
1624 |
+
critic_feedback_idx = stage_idx - 1
|
1625 |
+
return self.create_writer_revision_prompt(
|
1626 |
+
writer_num,
|
1627 |
+
stages[initial_draft_idx]["content"],
|
1628 |
+
stages[critic_feedback_idx]["content"],
|
1629 |
+
language
|
1630 |
+
)
|
1631 |
+
|
1632 |
+
# Critic stages
|
1633 |
+
elif role == "critic":
|
1634 |
+
final_plan = stages[2]["content"]
|
1635 |
+
|
1636 |
+
# Final evaluation
|
1637 |
+
if "์ต์ข
" in stages[stage_idx]["name"] or "Final" in stages[stage_idx]["name"]:
|
1638 |
+
return self.create_critic_final_prompt(accumulated_content, final_plan, language)
|
1639 |
+
|
1640 |
+
# Writer review
|
1641 |
+
else:
|
1642 |
+
# Find which writer we're reviewing
|
1643 |
+
for i in range(1, 11):
|
1644 |
+
if f"์์ฑ์ {i}" in stages[stage_idx]["name"] or f"Writer {i}" in stages[stage_idx]["name"]:
|
1645 |
+
writer_content_idx = stage_idx - 1
|
1646 |
+
return self.create_critic_writer_prompt(
|
1647 |
+
i,
|
1648 |
+
stages[writer_content_idx]["content"],
|
1649 |
+
final_plan,
|
1650 |
+
accumulated_content,
|
1651 |
+
language
|
1652 |
+
)
|
1653 |
+
|
1654 |
+
# Director final
|
1655 |
+
elif stage_idx == len(stages) - 1:
|
1656 |
+
critic_final_idx = stage_idx - 1
|
1657 |
+
return self.create_director_final_prompt(
|
1658 |
+
accumulated_content,
|
1659 |
+
stages[critic_final_idx]["content"],
|
1660 |
+
language
|
1661 |
+
)
|
1662 |
+
|
1663 |
+
return ""
|
1664 |
|
1665 |
# Gradio Interface Functions
|
1666 |
+
def process_query(query: str, language: str, session_id: str = None) -> Generator[Tuple[str, str, str], None, None]:
|
1667 |
"""Process query and yield updates"""
|
1668 |
+
if not query.strip() and not session_id:
|
1669 |
if language == "Korean":
|
1670 |
yield "", "", "โ ์์ค ์ฃผ์ ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์."
|
1671 |
else:
|
|
|
1675 |
system = NovelWritingSystem()
|
1676 |
|
1677 |
try:
|
1678 |
+
for final_novel, stages in system.process_novel_stream(query, language, session_id):
|
1679 |
+
# Format stages for display with scroll control
|
1680 |
+
stages_html = format_stages_html(stages, language, system.auto_scroll)
|
1681 |
|
1682 |
status = "๐ Processing..." if not final_novel else "โ
Complete!"
|
1683 |
|
|
|
1690 |
else:
|
1691 |
yield "", "", f"โ Error occurred: {str(e)}"
|
1692 |
|
1693 |
+
def format_stages_html(stages: List[Dict[str, str]], language: str, auto_scroll: bool = False) -> str:
|
1694 |
+
"""Format stages into HTML with scroll control"""
|
1695 |
+
scroll_behavior = "auto" if auto_scroll else "manual"
|
1696 |
+
|
1697 |
+
html = f'<div class="stages-container" data-scroll="{scroll_behavior}">'
|
1698 |
|
1699 |
+
for idx, stage in enumerate(stages):
|
1700 |
status_icon = "โ
" if stage.get("status") == "complete" else ("โณ" if stage.get("status") == "active" else "โ")
|
1701 |
|
1702 |
# Create collapsible section for each stage
|
1703 |
+
is_active = stage.get("status") == "active"
|
1704 |
html += f'''
|
1705 |
+
<details class="stage-section" {"open" if is_active else ""} id="stage-{idx}">
|
1706 |
<summary class="stage-header">
|
1707 |
<span class="status-icon">{status_icon}</span>
|
1708 |
<span class="stage-name">{stage["name"]}</span>
|
|
|
1714 |
'''
|
1715 |
|
1716 |
html += '</div>'
|
1717 |
+
|
1718 |
+
# Add JavaScript for scroll control
|
1719 |
+
if not auto_scroll:
|
1720 |
+
html += '''
|
1721 |
+
<script>
|
1722 |
+
// Prevent auto-scrolling when content updates
|
1723 |
+
const container = document.querySelector('.stages-container[data-scroll="manual"]');
|
1724 |
+
if (container) {
|
1725 |
+
let userScrolled = false;
|
1726 |
+
container.addEventListener('scroll', () => {
|
1727 |
+
userScrolled = true;
|
1728 |
+
});
|
1729 |
+
|
1730 |
+
// Override scroll behavior on content update
|
1731 |
+
const observer = new MutationObserver(() => {
|
1732 |
+
if (!userScrolled) {
|
1733 |
+
// Don't auto-scroll
|
1734 |
+
}
|
1735 |
+
});
|
1736 |
+
observer.observe(container, { childList: true, subtree: true });
|
1737 |
+
}
|
1738 |
+
</script>
|
1739 |
+
'''
|
1740 |
+
|
1741 |
return html
|
1742 |
|
1743 |
+
def get_active_sessions(language: str) -> List[Tuple[str, str, str]]:
|
1744 |
+
"""Get list of active sessions"""
|
1745 |
+
sessions = NovelDatabase.get_active_sessions()
|
1746 |
+
|
1747 |
+
choices = []
|
1748 |
+
for session in sessions:
|
1749 |
+
created = datetime.fromisoformat(session['created_at'])
|
1750 |
+
date_str = created.strftime("%Y-%m-%d %H:%M")
|
1751 |
+
query_preview = session['user_query'][:50] + "..." if len(session['user_query']) > 50 else session['user_query']
|
1752 |
+
label = f"[{date_str}] {query_preview} (Stage {session['current_stage']})"
|
1753 |
+
choices.append((label, session['session_id']))
|
1754 |
+
|
1755 |
+
return choices
|
1756 |
+
|
1757 |
+
def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str], None, None]:
|
1758 |
+
"""Resume an existing session"""
|
1759 |
+
if not session_id:
|
1760 |
+
return
|
1761 |
+
|
1762 |
+
# Process with existing session ID
|
1763 |
+
yield from process_query("", language, session_id)
|
1764 |
+
|
1765 |
+
def toggle_auto_scroll(current_state: bool) -> bool:
|
1766 |
+
"""Toggle auto-scroll state"""
|
1767 |
+
return not current_state
|
1768 |
+
|
1769 |
def download_novel(novel_text: str, format: str, language: str) -> str:
|
1770 |
"""Download novel in specified format"""
|
1771 |
if not novel_text:
|
|
|
1805 |
|
1806 |
return filepath
|
1807 |
|
1808 |
+
# Custom CSS with improved scroll handling
|
1809 |
custom_css = """
|
1810 |
.gradio-container {
|
1811 |
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
|
|
1819 |
background-color: rgba(255, 255, 255, 0.05);
|
1820 |
border-radius: 12px;
|
1821 |
backdrop-filter: blur(10px);
|
1822 |
+
scroll-behavior: smooth;
|
1823 |
+
position: relative;
|
1824 |
+
}
|
1825 |
+
|
1826 |
+
/* Disable auto-scroll when user has scrolled */
|
1827 |
+
.stages-container[data-scroll="manual"] {
|
1828 |
+
scroll-behavior: auto;
|
1829 |
}
|
1830 |
|
1831 |
.stage-section {
|
|
|
1869 |
color: #e0e0e0;
|
1870 |
font-family: 'Courier New', monospace;
|
1871 |
line-height: 1.6;
|
1872 |
+
margin: 0;
|
1873 |
}
|
1874 |
|
1875 |
#novel-output {
|
|
|
1905 |
border-radius: 8px;
|
1906 |
margin-top: 20px;
|
1907 |
}
|
1908 |
+
|
1909 |
+
.session-section {
|
1910 |
+
background-color: rgba(255, 255, 255, 0.1);
|
1911 |
+
backdrop-filter: blur(10px);
|
1912 |
+
padding: 15px;
|
1913 |
+
border-radius: 8px;
|
1914 |
+
margin-top: 20px;
|
1915 |
+
color: white;
|
1916 |
+
}
|
1917 |
+
|
1918 |
+
/* Scroll position indicator */
|
1919 |
+
.scroll-indicator {
|
1920 |
+
position: absolute;
|
1921 |
+
right: 5px;
|
1922 |
+
top: 5px;
|
1923 |
+
background: rgba(255, 255, 255, 0.2);
|
1924 |
+
padding: 5px 10px;
|
1925 |
+
border-radius: 15px;
|
1926 |
+
font-size: 0.8em;
|
1927 |
+
color: white;
|
1928 |
+
}
|
1929 |
"""
|
1930 |
|
1931 |
# Create Gradio Interface
|
|
|
1942 |
<p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
|
1943 |
Enter a theme or prompt, and watch as 13 AI agents collaborate to create a complete 50-page novella.
|
1944 |
The system includes 1 Director, 1 Critic, and 10 Writers working in harmony.
|
1945 |
+
All progress is automatically saved and can be resumed anytime.
|
1946 |
</p>
|
1947 |
</div>
|
1948 |
""")
|
1949 |
|
1950 |
+
# State management
|
1951 |
+
auto_scroll_state = gr.State(False)
|
1952 |
+
current_session_id = gr.State(None)
|
1953 |
+
|
1954 |
with gr.Row():
|
1955 |
with gr.Column(scale=1):
|
1956 |
with gr.Group(elem_classes=["input-section"]):
|
|
|
1976 |
value="๐ Ready"
|
1977 |
)
|
1978 |
|
1979 |
+
# Session management
|
1980 |
+
with gr.Group(elem_classes=["session-section"]):
|
1981 |
+
gr.Markdown("### ๐พ Resume Previous Session / ์ด์ ์ธ์
์ฌ๊ฐ")
|
1982 |
+
session_dropdown = gr.Dropdown(
|
1983 |
+
label="Select Session / ์ธ์
์ ํ",
|
1984 |
+
choices=[],
|
1985 |
+
interactive=True
|
1986 |
+
)
|
1987 |
+
with gr.Row():
|
1988 |
+
refresh_btn = gr.Button("๐ Refresh / ์๋ก๊ณ ์นจ", scale=1)
|
1989 |
+
resume_btn = gr.Button("โถ๏ธ Resume / ์ฌ๊ฐ", variant="secondary", scale=1)
|
1990 |
+
|
1991 |
+
# Scroll control
|
1992 |
+
auto_scroll_checkbox = gr.Checkbox(
|
1993 |
+
label="Auto-scroll / ์๋ ์คํฌ๋กค",
|
1994 |
+
value=False
|
1995 |
+
)
|
1996 |
+
|
1997 |
gr.HTML("""
|
1998 |
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.1); border-radius: 8px; color: white;">
|
1999 |
<h4>Writing Process:</h4>
|
|
|
2006 |
<li>Writers revise based on feedback</li>
|
2007 |
<li>Final compilation by Director</li>
|
2008 |
</ol>
|
2009 |
+
<p style="margin-top: 10px;"><strong>Note:</strong> All progress is automatically saved to database.</p>
|
2010 |
</div>
|
2011 |
""")
|
2012 |
|
|
|
2059 |
def update_novel_state(process, novel, status):
|
2060 |
return process, novel, status, novel
|
2061 |
|
2062 |
+
def refresh_sessions():
|
2063 |
+
sessions = get_active_sessions("English")
|
2064 |
+
return gr.update(choices=sessions)
|
2065 |
+
|
2066 |
submit_btn.click(
|
2067 |
fn=process_query,
|
2068 |
+
inputs=[query_input, language_select, current_session_id],
|
2069 |
outputs=[process_display, novel_output, status_text]
|
2070 |
).then(
|
2071 |
fn=update_novel_state,
|
|
|
2073 |
outputs=[process_display, novel_output, status_text, novel_text_state]
|
2074 |
)
|
2075 |
|
2076 |
+
resume_btn.click(
|
2077 |
+
fn=lambda x: x,
|
2078 |
+
inputs=[session_dropdown],
|
2079 |
+
outputs=[current_session_id]
|
2080 |
+
).then(
|
2081 |
+
fn=resume_session,
|
2082 |
+
inputs=[current_session_id, language_select],
|
2083 |
+
outputs=[process_display, novel_output, status_text]
|
2084 |
+
)
|
2085 |
+
|
2086 |
+
refresh_btn.click(
|
2087 |
+
fn=refresh_sessions,
|
2088 |
+
outputs=[session_dropdown]
|
2089 |
+
)
|
2090 |
+
|
2091 |
clear_btn.click(
|
2092 |
+
fn=lambda: ("", "", "๐ Ready", "", None),
|
2093 |
+
outputs=[process_display, novel_output, status_text, novel_text_state, current_session_id]
|
2094 |
+
)
|
2095 |
+
|
2096 |
+
auto_scroll_checkbox.change(
|
2097 |
+
fn=toggle_auto_scroll,
|
2098 |
+
inputs=[auto_scroll_state],
|
2099 |
+
outputs=[auto_scroll_state]
|
2100 |
)
|
2101 |
|
2102 |
def handle_download(novel_text, format_type, language):
|
|
|
2114 |
inputs=[novel_text_state, format_select, language_select],
|
2115 |
outputs=[download_file]
|
2116 |
)
|
2117 |
+
|
2118 |
+
# Load sessions on startup
|
2119 |
+
interface.load(
|
2120 |
+
fn=refresh_sessions,
|
2121 |
+
outputs=[session_dropdown]
|
2122 |
+
)
|
2123 |
|
2124 |
return interface
|
2125 |
|