Update app.py
Browse files
app.py
CHANGED
|
@@ -17,9 +17,9 @@ custom_css = """
|
|
| 17 |
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@400;700&family=JetBrains+Mono:wght@400&family=Inter:wght@400;600&display=swap');
|
| 18 |
|
| 19 |
:root {
|
| 20 |
-
--primary-color: #4f46e5;
|
| 21 |
-
--paper-bg: #fdf6e3;
|
| 22 |
-
--glass-bg: rgba(255, 255, 255, 0.95);
|
| 23 |
}
|
| 24 |
|
| 25 |
body, .gradio-container {
|
|
@@ -32,7 +32,6 @@ body, .gradio-container {
|
|
| 32 |
.header-box { text-align: center; padding: 25px; background: var(--glass-bg); border-radius: 16px; margin-bottom: 20px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); backdrop-filter: blur(10px); }
|
| 33 |
.title-text { font-size: 2.2rem; font-weight: 800; font-family: 'Noto Serif SC', serif; color: #1a202c; letter-spacing: -0.5px; }
|
| 34 |
.subtitle-text { color: #718096; letter-spacing: 1.5px; text-transform: uppercase; font-size: 0.85rem; font-weight: 600; margin-top: 5px; }
|
| 35 |
-
.title-badge { display: inline-block; background: #000; color: #fff; font-size: 0.7rem; padding: 2px 6px; border-radius: 4px; vertical-align: super; margin-left: 5px; }
|
| 36 |
|
| 37 |
/* --- 左侧控制面板 --- */
|
| 38 |
.control-panel {
|
|
@@ -43,7 +42,7 @@ body, .gradio-container {
|
|
| 43 |
box-shadow: 0 8px 32px rgba(0,0,0,0.05);
|
| 44 |
}
|
| 45 |
|
| 46 |
-
/*
|
| 47 |
.input-label { font-weight: 700; color: #4a5568; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
|
| 48 |
.input-box textarea { background: #f8fafc !important; border: 2px solid #e2e8f0 !important; border-radius: 12px !important; padding: 12px !important; }
|
| 49 |
.input-box textarea:focus { border-color: var(--primary-color) !important; background: #fff !important; }
|
|
@@ -124,17 +123,13 @@ body, .gradio-container {
|
|
| 124 |
|
| 125 |
.page-footer { height: 50px; display: flex; align-items: center; justify-content: center; font-size: 0.8rem; color: #999; border-top: 1px dashed rgba(0,0,0,0.05); z-index: 2; }
|
| 126 |
|
| 127 |
-
/*
|
| 128 |
-
Navigation Buttons (Refined)
|
| 129 |
-
========================================= */
|
| 130 |
-
|
| 131 |
-
/* 1. 侧边箭头 (极简风格) */
|
| 132 |
.arrow-btn {
|
| 133 |
background: transparent !important;
|
| 134 |
border: none !important;
|
| 135 |
box-shadow: none !important;
|
| 136 |
-
color: #cbd5e0 !important;
|
| 137 |
-
font-size: 3rem !important;
|
| 138 |
font-weight: 200 !important;
|
| 139 |
padding: 0 !important;
|
| 140 |
height: 100% !important;
|
|
@@ -144,7 +139,7 @@ body, .gradio-container {
|
|
| 144 |
justify-content: center !important;
|
| 145 |
transition: all 0.2s ease !important;
|
| 146 |
}
|
| 147 |
-
/* Hover
|
| 148 |
.arrow-btn:hover {
|
| 149 |
color: var(--primary-color) !important;
|
| 150 |
transform: scale(1.1);
|
|
@@ -188,12 +183,11 @@ def paginate_story(story_data, chars_per_page=600):
|
|
| 188 |
return [], []
|
| 189 |
|
| 190 |
flat_pages = []
|
| 191 |
-
chapter_start_indices = [] # 记录
|
| 192 |
|
| 193 |
current_global_page_index = 0
|
| 194 |
|
| 195 |
for chapter in story_data:
|
| 196 |
-
# 记录当前章是从第几页开始的
|
| 197 |
chapter_start_indices.append(current_global_page_index)
|
| 198 |
|
| 199 |
title = chapter.get("title", "")
|
|
@@ -301,14 +295,13 @@ def render_book_page(flat_pages, page_index):
|
|
| 301 |
# ==========================================
|
| 302 |
def bridge_to_backend(premise):
|
| 303 |
if not premise.strip():
|
| 304 |
-
# yield error
|
| 305 |
yield "⚠️ 请输入故事梗概...", None, None, None, [], [], render_book_page([], 0)
|
| 306 |
return
|
| 307 |
|
| 308 |
log_buffer = "🚀 初始化前端连接...\n"
|
| 309 |
initial_html = render_book_page([], 0)
|
| 310 |
|
| 311 |
-
#
|
| 312 |
yield log_buffer, None, None, None, [], [], initial_html
|
| 313 |
|
| 314 |
try:
|
|
@@ -342,15 +335,14 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"),
|
|
| 342 |
# --- 状态管理 ---
|
| 343 |
story_pages_state = gr.State([]) # 所有的页面 List[Dict]
|
| 344 |
chapter_indices_state = gr.State([]) # 每一章起始页的 index List[int]
|
| 345 |
-
current_page_state = gr.State(0) #
|
| 346 |
|
| 347 |
# --- 顶部标题 ---
|
| 348 |
with gr.Row(elem_classes=["header-box"]):
|
| 349 |
gr.HTML("""
|
| 350 |
<div style="display:flex; flex-direction:column; align-items:center;">
|
| 351 |
<div class="title-wrapper">
|
| 352 |
-
<span class="title-text">LongStory
|
| 353 |
-
<span class="title-badge">PRO</span>
|
| 354 |
</div>
|
| 355 |
<div class="subtitle-text">Deep Persona-Driven Recursive Novel Generation</div>
|
| 356 |
</div>
|
|
@@ -369,7 +361,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"),
|
|
| 369 |
""")
|
| 370 |
premise_input = gr.Textbox(label="Premise", show_label=False, lines=5, elem_classes=["input-box"], placeholder="输入故事创意...")
|
| 371 |
|
| 372 |
-
#
|
| 373 |
gr.HTML('<div class="examples-container"><div class="examples-label">⚡ 快速开始 (Quick Inspirations)</div></div>')
|
| 374 |
|
| 375 |
example_prompts = [
|
|
@@ -395,19 +387,15 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"),
|
|
| 395 |
with gr.Column(scale=8):
|
| 396 |
with gr.Tabs(elem_classes=["tabs-container"]):
|
| 397 |
|
| 398 |
-
# Tab 1:
|
| 399 |
with gr.TabItem("📖 正文阅读", id="tab-story"):
|
| 400 |
-
|
| 401 |
-
# 1. 阅读区:[左箭] [书] [右箭]
|
| 402 |
-
# equal_height=True 保证高度对其
|
| 403 |
-
# gap=0 去除列间隙,让箭头紧贴
|
| 404 |
with gr.Row(elem_classes=["book-reader-row"], equal_height=True):
|
| 405 |
|
| 406 |
-
#
|
| 407 |
with gr.Column(scale=1, min_width=40, elem_classes=["arrow-col"]):
|
| 408 |
btn_prev_page = gr.Button("‹", elem_classes=["arrow-btn"])
|
| 409 |
|
| 410 |
-
#
|
| 411 |
with gr.Column(scale=15):
|
| 412 |
story_display = gr.HTML(label="Book View", value=render_book_page([], 0))
|
| 413 |
|
|
@@ -415,17 +403,24 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"),
|
|
| 415 |
with gr.Column(scale=1, min_width=40, elem_classes=["arrow-col"]):
|
| 416 |
btn_next_page = gr.Button("›", elem_classes=["arrow-btn"])
|
| 417 |
|
| 418 |
-
# 2.
|
| 419 |
with gr.Row(elem_classes=["chapter-nav-row"]):
|
| 420 |
btn_prev_chap = gr.Button("⏮️ 上一章", elem_classes=["chapter-nav-btn"])
|
| 421 |
# 占位符,把按钮挤到两边或中间
|
| 422 |
# gr.Spacer()
|
| 423 |
btn_next_chap = gr.Button("⏭️ 下一章", elem_classes=["chapter-nav-btn"])
|
| 424 |
|
| 425 |
-
# Tab 2
|
| 426 |
-
with gr.TabItem("🗺️
|
| 427 |
-
|
| 428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
|
| 430 |
# ==========================================
|
| 431 |
# 5. 事件交互
|
|
|
|
| 17 |
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@400;700&family=JetBrains+Mono:wght@400&family=Inter:wght@400;600&display=swap');
|
| 18 |
|
| 19 |
:root {
|
| 20 |
+
--primary-color: #4f46e5; /* indigo 靛蓝色 */
|
| 21 |
+
--paper-bg: #fdf6e3; /* 米黄色 */
|
| 22 |
+
--glass-bg: rgba(255, 255, 255, 0.95); /* 半透明白 */
|
| 23 |
}
|
| 24 |
|
| 25 |
body, .gradio-container {
|
|
|
|
| 32 |
.header-box { text-align: center; padding: 25px; background: var(--glass-bg); border-radius: 16px; margin-bottom: 20px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); backdrop-filter: blur(10px); }
|
| 33 |
.title-text { font-size: 2.2rem; font-weight: 800; font-family: 'Noto Serif SC', serif; color: #1a202c; letter-spacing: -0.5px; }
|
| 34 |
.subtitle-text { color: #718096; letter-spacing: 1.5px; text-transform: uppercase; font-size: 0.85rem; font-weight: 600; margin-top: 5px; }
|
|
|
|
| 35 |
|
| 36 |
/* --- 左侧控制面板 --- */
|
| 37 |
.control-panel {
|
|
|
|
| 42 |
box-shadow: 0 8px 32px rgba(0,0,0,0.05);
|
| 43 |
}
|
| 44 |
|
| 45 |
+
/* 输入框与按钮 */
|
| 46 |
.input-label { font-weight: 700; color: #4a5568; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
|
| 47 |
.input-box textarea { background: #f8fafc !important; border: 2px solid #e2e8f0 !important; border-radius: 12px !important; padding: 12px !important; }
|
| 48 |
.input-box textarea:focus { border-color: var(--primary-color) !important; background: #fff !important; }
|
|
|
|
| 123 |
|
| 124 |
.page-footer { height: 50px; display: flex; align-items: center; justify-content: center; font-size: 0.8rem; color: #999; border-top: 1px dashed rgba(0,0,0,0.05); z-index: 2; }
|
| 125 |
|
| 126 |
+
/* 1. 侧边箭头*/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
.arrow-btn {
|
| 128 |
background: transparent !important;
|
| 129 |
border: none !important;
|
| 130 |
box-shadow: none !important;
|
| 131 |
+
color: #cbd5e0 !important;
|
| 132 |
+
font-size: 3rem !important;
|
| 133 |
font-weight: 200 !important;
|
| 134 |
padding: 0 !important;
|
| 135 |
height: 100% !important;
|
|
|
|
| 139 |
justify-content: center !important;
|
| 140 |
transition: all 0.2s ease !important;
|
| 141 |
}
|
| 142 |
+
/* Hover */
|
| 143 |
.arrow-btn:hover {
|
| 144 |
color: var(--primary-color) !important;
|
| 145 |
transform: scale(1.1);
|
|
|
|
| 183 |
return [], []
|
| 184 |
|
| 185 |
flat_pages = []
|
| 186 |
+
chapter_start_indices = [] # 记录 Chapter i 的起始页 index
|
| 187 |
|
| 188 |
current_global_page_index = 0
|
| 189 |
|
| 190 |
for chapter in story_data:
|
|
|
|
| 191 |
chapter_start_indices.append(current_global_page_index)
|
| 192 |
|
| 193 |
title = chapter.get("title", "")
|
|
|
|
| 295 |
# ==========================================
|
| 296 |
def bridge_to_backend(premise):
|
| 297 |
if not premise.strip():
|
|
|
|
| 298 |
yield "⚠️ 请输入故事梗概...", None, None, None, [], [], render_book_page([], 0)
|
| 299 |
return
|
| 300 |
|
| 301 |
log_buffer = "🚀 初始化前端连接...\n"
|
| 302 |
initial_html = render_book_page([], 0)
|
| 303 |
|
| 304 |
+
# 初始状态 [log, outline, plan, personas, pages, chap_indices, html]
|
| 305 |
yield log_buffer, None, None, None, [], [], initial_html
|
| 306 |
|
| 307 |
try:
|
|
|
|
| 335 |
# --- 状态管理 ---
|
| 336 |
story_pages_state = gr.State([]) # 所有的页面 List[Dict]
|
| 337 |
chapter_indices_state = gr.State([]) # 每一章起始页的 index List[int]
|
| 338 |
+
current_page_state = gr.State(0) # 当前页面
|
| 339 |
|
| 340 |
# --- 顶部标题 ---
|
| 341 |
with gr.Row(elem_classes=["header-box"]):
|
| 342 |
gr.HTML("""
|
| 343 |
<div style="display:flex; flex-direction:column; align-items:center;">
|
| 344 |
<div class="title-wrapper">
|
| 345 |
+
<span class="title-text">LongStory Agent</span>
|
|
|
|
| 346 |
</div>
|
| 347 |
<div class="subtitle-text">Deep Persona-Driven Recursive Novel Generation</div>
|
| 348 |
</div>
|
|
|
|
| 361 |
""")
|
| 362 |
premise_input = gr.Textbox(label="Premise", show_label=False, lines=5, elem_classes=["input-box"], placeholder="输入故事创意...")
|
| 363 |
|
| 364 |
+
# Examples
|
| 365 |
gr.HTML('<div class="examples-container"><div class="examples-label">⚡ 快速开始 (Quick Inspirations)</div></div>')
|
| 366 |
|
| 367 |
example_prompts = [
|
|
|
|
| 387 |
with gr.Column(scale=8):
|
| 388 |
with gr.Tabs(elem_classes=["tabs-container"]):
|
| 389 |
|
| 390 |
+
# Tab 1: 正文
|
| 391 |
with gr.TabItem("📖 正文阅读", id="tab-story"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
with gr.Row(elem_classes=["book-reader-row"], equal_height=True):
|
| 393 |
|
| 394 |
+
# 左侧箭头
|
| 395 |
with gr.Column(scale=1, min_width=40, elem_classes=["arrow-col"]):
|
| 396 |
btn_prev_page = gr.Button("‹", elem_classes=["arrow-btn"])
|
| 397 |
|
| 398 |
+
# 核心内容
|
| 399 |
with gr.Column(scale=15):
|
| 400 |
story_display = gr.HTML(label="Book View", value=render_book_page([], 0))
|
| 401 |
|
|
|
|
| 403 |
with gr.Column(scale=1, min_width=40, elem_classes=["arrow-col"]):
|
| 404 |
btn_next_page = gr.Button("›", elem_classes=["arrow-btn"])
|
| 405 |
|
| 406 |
+
# 2. 章节导航
|
| 407 |
with gr.Row(elem_classes=["chapter-nav-row"]):
|
| 408 |
btn_prev_chap = gr.Button("⏮️ 上一章", elem_classes=["chapter-nav-btn"])
|
| 409 |
# 占位符,把按钮挤到两边或中间
|
| 410 |
# gr.Spacer()
|
| 411 |
btn_next_chap = gr.Button("⏭️ 下一章", elem_classes=["chapter-nav-btn"])
|
| 412 |
|
| 413 |
+
# Tab 2: 大纲
|
| 414 |
+
with gr.TabItem("🗺️ 故事大纲", id="tab-outline"):
|
| 415 |
+
outline_output = gr.JSON(label="Structure Data")
|
| 416 |
+
|
| 417 |
+
# Tab 3: 规划
|
| 418 |
+
with gr.TabItem("📅 剧情规划", id="tab-planning"):
|
| 419 |
+
plan_output = gr.JSON(label="Event Planning")
|
| 420 |
+
|
| 421 |
+
# Tab 4: 人设
|
| 422 |
+
with gr.TabItem("👥 人物档案", id="tab-persona"):
|
| 423 |
+
persona_output = gr.HTML(label="Character Cards")
|
| 424 |
|
| 425 |
# ==========================================
|
| 426 |
# 5. 事件交互
|