Yoyo2004 commited on
Commit
9cbc975
·
verified ·
1 Parent(s): ab21d03

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -119
app.py CHANGED
@@ -11,84 +11,153 @@ HF_TOKEN = os.environ.get("HF_TOKEN")
11
  # 1. CSS 样式设计 (电子书风格)
12
  # ==========================================
13
  custom_css = """
14
- @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&display=swap');
15
 
16
  :root {
17
  --primary-color: #4f46e5;
 
18
  --paper-bg: #fdf6e3;
19
  --text-ink: #2c3e50;
 
 
20
  }
21
 
22
  body, .gradio-container {
23
  font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif !important;
24
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important;
25
  background-attachment: fixed !important;
26
  }
27
 
28
- /* 通用布局 */
29
- .header-box { text-align: center; padding: 20px; background: rgba(255,255,255,0.9); border-radius: 16px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
30
- .title-text { font-size: 2rem; font-weight: 800; font-family: 'Noto Serif SC', serif; color: #1f2937; }
31
- .subtitle-text { color: #6b7280; letter-spacing: 1px; text-transform: uppercase; font-size: 0.9rem; }
 
32
 
33
- /* 控制面板 */
34
- .control-panel { background: rgba(255,255,255,0.9); padding: 20px !important; border-radius: 16px; }
35
- .generate-btn { background: linear-gradient(90deg, #4f46e5, #7c3aed) !important; color: white !important; font-weight: 600; margin-top: 15px; }
36
- .terminal-log textarea { font-family: 'JetBrains Mono', monospace !important; background: #1e1e1e !important; color: #a9b7c6 !important; border-radius: 8px; font-size: 12px; }
 
 
 
 
37
 
38
- /* --- 📖 电子书阅读器核心样式 --- */
39
- .book-container {
40
- background-color: var(--paper-bg); /* 羊皮纸底色 */
41
- padding: 60px 80px;
42
- min-height: 700px;
43
- border-radius: 8px 16px 16px 8px;
44
- box-shadow:
45
- inset 20px 0 50px rgba(0,0,0,0.05), /* 书脊阴影 */
46
- 10px 10px 30px rgba(0,0,0,0.1); /* 外部投影 */
47
- font-family: 'Noto Serif SC', serif; /* 衬线字体,更有文学感 */
48
- color: var(--text-ink);
49
- position: relative;
50
- margin-top: 10px;
51
- line-height: 2.0; /* 宽松行高 */
52
  }
53
 
54
- /* 书页左侧装饰线 */
55
- .book-container::before {
56
- content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 6px;
57
- background: linear-gradient(to right, rgba(0,0,0,0.1), transparent);
 
 
 
 
 
 
 
 
 
 
58
  }
 
 
 
 
 
 
59
 
60
- /* 章节标题设计 */
61
- .chapter-header {
62
- text-align: center; margin-bottom: 40px; padding-bottom: 20px;
63
- border-bottom: 2px solid rgba(139, 69, 19, 0.2);
 
 
 
 
 
 
 
64
  }
65
- .chapter-subtitle { font-size: 0.9em; color: #8b4513; opacity: 0.6; font-style: italic; margin-bottom: 5px; }
66
- .chapter-title {
67
- font-size: 2.4em;
68
- font-weight: 700;
69
- color: #8b4513; /* 深棕色标题 */
70
- text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
71
  }
72
 
73
- /* 正文段落 */
74
- .chapter-content { font-size: 1.2em; text-align: justify; }
75
- .chapter-content p {
76
- margin-bottom: 1.5em;
77
- text-indent: 2em; /* 首行缩进 */
 
 
 
 
 
 
 
 
 
 
78
  }
 
 
 
 
 
 
79
 
80
- /* 页脚 */
81
- .page-footer { text-align: center; margin-top: 60px; font-size: 0.9em; color: #a0aec0; font-family: sans-serif; }
 
 
 
 
 
 
 
 
 
82
 
83
- /* 翻页按钮容器 */
84
- .nav-row { margin-top: 20px; display: flex; justify-content: center; gap: 20px; }
85
- .nav-btn { width: 120px !important; border-radius: 30px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  /* 移动端适配 */
88
  @media (max-width: 768px) {
89
- .book-container { padding: 30px 20px; }
90
- .chapter-title { font-size: 1.8em; }
91
- .chapter-content { font-size: 1.1em; }
92
  }
93
  """
94
 
@@ -196,105 +265,124 @@ def bridge_to_backend(premise):
196
  # ==========================================
197
  # 4. 前端 UI 布局 (Blocks)
198
  # ==========================================
199
- with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="LongStory Agent") as demo:
 
 
200
 
201
- # --- 状态管理 (State) ---
202
- # story_state: 存储完整的小说章节列表 (List[Dict])
203
- # page_state: 存储当前阅读的页码 (int)
204
  story_state = gr.State([])
205
  page_state = gr.State(0)
206
 
207
  # --- 顶部标题 ---
208
  with gr.Row(elem_classes=["header-box"]):
209
  gr.HTML("""
210
- <div class="title-wrapper">
211
- <div class="title-text">LongStory AI</div>
212
- <div class="title-badge">PRO</div>
 
 
 
213
  </div>
214
- <div class="subtitle-text">Deep Persona-Driven Recursive Novel Generation System</div>
215
  """)
216
 
217
  with gr.Row(elem_classes=["main-container"]):
218
 
219
- # --- 左侧:控制面板 ---
220
- with gr.Column(scale=4, elem_classes=["control-panel-col"]):
221
  with gr.Column(elem_classes=["control-panel"]):
222
- gr.Markdown("### 💡 故事梗概 (Story Premise)", elem_id="input_title")
 
 
 
 
 
 
 
223
 
224
  premise_input = gr.Textbox(
225
- label="输入你的创意...", show_label=False,
226
- placeholder="例如:赛博朋克背景下,一个因旧型号义肢而被歧视的侦探...",
227
- lines=6, elem_classes=["input-box"]
 
 
228
  )
229
 
230
- with gr.Group(elem_classes=["examples-table"]):
231
- gr.Examples(
232
- examples=[
233
- ["高中时互相看不顺眼的死对头,一个是高冷学霸,一个是调皮体育生。十年后的同学聚会上..."],
234
- ["天生'废灵根'的宗门弃徒,在被逐出师门当晚,意外捡到一个黑色小鼎..."]
235
- ],
236
- inputs=premise_input,
237
- label="⚡ 快速灵感"
238
- )
 
 
 
 
 
 
 
239
 
 
240
  submit_btn = gr.Button(
241
- "🚀 启动生成 (GENERATE)",
242
  elem_classes=["generate-btn"]
243
  )
244
 
245
- # 终端日志样式
246
- gr.HTML("<div style='margin-top:20px;color:#888;font-size:12px;'>TERMINAL OUTPUT</div>")
 
 
 
 
 
 
 
 
247
  log_output = gr.Textbox(
248
- label="System Log", lines=12, interactive=False,
249
- elem_classes=["terminal-log"], show_label=False, value="> System Ready."
 
 
 
 
250
  )
 
251
 
252
- # --- 右侧:内容展示区 ---
253
- with gr.Column(scale=8):
254
  with gr.Tabs(elem_classes=["tabs-container"]):
255
 
256
- # Tab 1: 电子书阅读器 (重点优化)
257
- with gr.TabItem("📖 正文 (Reader)", id="tab-story"):
258
- # 用于显示渲染后的 HTML
259
  story_display = gr.HTML(label="Book View")
260
-
261
- # 翻页按钮
262
  with gr.Row(elem_classes=["nav-row"]):
263
- prev_btn = gr.Button("← 上一章", elem_classes=["nav-btn"])
264
- next_btn = gr.Button("下一章 →", elem_classes=["nav-btn"])
265
 
266
  # Tab 2: 大纲
267
- with gr.TabItem("🗺️ 大纲 (Outline)", id="tab-outline"):
268
- outline_output = gr.JSON(label=None, elem_classes=["json-panel"])
269
 
270
  # Tab 3: 规划
271
- with gr.TabItem("📅 规划 (Plan)", id="tab-planning"):
272
- plan_output = gr.JSON(label=None, elem_classes=["json-panel"])
273
 
274
- # Tab 4: 人设 (HTML 卡片)
275
- with gr.TabItem("👥 档案 (Personas)", id="tab-persona"):
276
- persona_output = gr.HTML(label=None, elem_classes=["html-panel"])
277
 
278
  # ==========================================
279
- # 5. 事件交互逻辑
280
  # ==========================================
281
-
282
- # A. 点击生成按钮
283
  submit_btn.click(
284
  fn=bridge_to_backend,
285
  inputs=[premise_input],
286
- outputs=[
287
- log_output, # 1. 日志
288
- outline_output, # 2. 大纲
289
- plan_output, # 3. 规划
290
- persona_output, # 4. 人设(HTML)
291
- story_state, # 5. 故事数据(Hidden State)
292
- story_display # 6. 故事显示(HTML)
293
- ],
294
  concurrency_limit=1
295
  )
296
 
297
- # B. 翻页逻辑函数 (纯前端交互,不需要请求后端)
298
  def go_prev(story_data, current_page):
299
  new_page = max(0, current_page - 1)
300
  return new_page, render_book_page(story_data, new_page)
@@ -304,18 +392,8 @@ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="LongStory Agent")
304
  new_page = min(len(story_data) - 1, current_page + 1)
305
  return new_page, render_book_page(story_data, new_page)
306
 
307
- # C. 绑定翻页按钮
308
- prev_btn.click(
309
- fn=go_prev,
310
- inputs=[story_state, page_state],
311
- outputs=[page_state, story_display]
312
- )
313
-
314
- next_btn.click(
315
- fn=go_next,
316
- inputs=[story_state, page_state],
317
- outputs=[page_state, story_display]
318
- )
319
 
320
  if __name__ == "__main__":
321
  demo.queue().launch()
 
11
  # 1. CSS 样式设计 (电子书风格)
12
  # ==========================================
13
  custom_css = """
14
+ @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');
15
 
16
  :root {
17
  --primary-color: #4f46e5;
18
+ --primary-hover: #4338ca;
19
  --paper-bg: #fdf6e3;
20
  --text-ink: #2c3e50;
21
+ --glass-bg: rgba(255, 255, 255, 0.95);
22
+ --border-radius: 12px;
23
  }
24
 
25
  body, .gradio-container {
26
  font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif !important;
27
+ background: linear-gradient(135deg, #eef2f3 0%, #8e9eab 100%) !important;
28
  background-attachment: fixed !important;
29
  }
30
 
31
+ /* --- 通用容器 --- */
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 {
39
+ background: var(--glass-bg);
40
+ padding: 24px !important;
41
+ border-radius: 16px;
42
+ border: 1px solid rgba(255,255,255,0.6);
43
+ box-shadow: 0 8px 32px rgba(0,0,0,0.05);
44
+ }
45
 
46
+ /* 1. 输入框美化 */
47
+ .input-label { font-weight: 700; color: #4a5568; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
48
+ .input-box textarea {
49
+ background: #f8fafc !important;
50
+ border: 2px solid #e2e8f0 !important;
51
+ border-radius: var(--border-radius) !important;
52
+ transition: all 0.3s ease;
53
+ padding: 12px !important;
54
+ font-size: 14px !important;
55
+ }
56
+ .input-box textarea:focus {
57
+ border-color: var(--primary-color) !important;
58
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important;
59
+ background: #fff !important;
60
  }
61
 
62
+ /* 2. Examples (灵感卡片) 深度定制 */
63
+ .examples-container { margin-top: 15px; }
64
+ .examples-label { font-size: 0.85rem; color: #a0aec0; font-weight: 600; margin-bottom: 10px; text-transform: uppercase; }
65
+
66
+ /* 隐藏原有表格边框,改为卡片式 */
67
+ .examples-table table { border-collapse: separate !important; border-spacing: 0 8px !important; }
68
+ .examples-table thead { display: none; } /* 隐藏表头 */
69
+ .examples-table tbody tr {
70
+ background: #fff;
71
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
72
+ border-radius: 8px;
73
+ cursor: pointer;
74
+ transition: transform 0.2s, box-shadow 0.2s;
75
+ border: 1px solid #edf2f7;
76
  }
77
+ .examples-table tbody tr:hover {
78
+ transform: translateY(-2px);
79
+ box-shadow: 0 5px 12px rgba(0,0,0,0.1);
80
+ border-color: var(--primary-color);
81
+ }
82
+ .examples-table td { padding: 12px 15px !important; font-size: 13px !important; color: #4a5568 !important; line-height: 1.5 !important; border: none !important; }
83
 
84
+ /* 3. 按钮设计 */
85
+ .generate-btn {
86
+ background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%) !important;
87
+ color: white !important;
88
+ font-weight: 600;
89
+ border-radius: 12px !important;
90
+ padding: 12px !important;
91
+ font-size: 1.1rem !important;
92
+ letter-spacing: 0.5px;
93
+ box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4);
94
+ transition: all 0.3s ease;
95
  }
96
+ .generate-btn:hover {
97
+ transform: translateY(-2px);
98
+ box-shadow: 0 6px 20px rgba(79, 70, 229, 0.6);
99
+ filter: brightness(110%);
 
 
100
  }
101
 
102
+ /* 4. 终端 (Terminal) 窗口风格 */
103
+ .terminal-wrapper {
104
+ margin-top: 25px;
105
+ background: #1e1e1e;
106
+ border-radius: 10px;
107
+ overflow: hidden;
108
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
109
+ }
110
+ .terminal-header {
111
+ background: #2d2d2d;
112
+ padding: 8px 12px;
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 6px;
116
+ border-bottom: 1px solid #333;
117
  }
118
+ /* 红黄绿 按钮模拟 */
119
+ .dot { width: 10px; height: 10px; border-radius: 50%; }
120
+ .dot-red { background: #ff5f56; }
121
+ .dot-yellow { background: #ffbd2e; }
122
+ .dot-green { background: #27c93f; }
123
+ .terminal-title { color: #888; font-size: 10px; margin-left: auto; font-family: sans-serif; }
124
 
125
+ .terminal-log textarea {
126
+ font-family: 'JetBrains Mono', monospace !important;
127
+ background: #1e1e1e !important;
128
+ color: #4ade80 !important; /* 骇客绿 */
129
+ border: none !important;
130
+ border-radius: 0 0 10px 10px !important;
131
+ font-size: 11px !important;
132
+ line-height: 1.4 !important;
133
+ padding: 10px !important;
134
+ box-shadow: none !important;
135
+ }
136
 
137
+ /* --- 右侧电子书 (保持原样微调) --- */
138
+ .book-container {
139
+ background-color: var(--paper-bg);
140
+ padding: 60px 80px;
141
+ min-height: 750px;
142
+ border-radius: 8px 16px 16px 8px;
143
+ box-shadow: inset 20px 0 50px rgba(0,0,0,0.05), 10px 10px 30px rgba(0,0,0,0.1);
144
+ font-family: 'Noto Serif SC', serif;
145
+ color: var(--text-ink);
146
+ position: relative;
147
+ line-height: 2.0;
148
+ }
149
+ .book-container::before {
150
+ content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 6px;
151
+ background: linear-gradient(to right, rgba(0,0,0,0.1), transparent);
152
+ }
153
+ .chapter-title { font-size: 2.4em; font-weight: 700; color: #8b4513; margin-bottom: 10px; }
154
+ .nav-btn { background: white !important; border: 1px solid #e2e8f0 !important; color: #4a5568 !important; }
155
+ .nav-btn:hover { background: #f7fafc !important; border-color: #cbd5e0 !important; }
156
 
157
  /* 移动端适配 */
158
  @media (max-width: 768px) {
159
+ .book-container { padding: 30px 20px; min-height: 500px; }
160
+ .control-panel { padding: 15px !important; }
 
161
  }
162
  """
163
 
 
265
  # ==========================================
266
  # 4. 前端 UI 布局 (Blocks)
267
  # ==========================================
268
+ # ... (前面的 imports rendering 函数保持不变)
269
+
270
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"), css=custom_css, title="LongStory Agent") as demo:
271
 
272
+ # --- 状态管理 ---
 
 
273
  story_state = gr.State([])
274
  page_state = gr.State(0)
275
 
276
  # --- 顶部标题 ---
277
  with gr.Row(elem_classes=["header-box"]):
278
  gr.HTML("""
279
+ <div style="display:flex; flex-direction:column; align-items:center;">
280
+ <div class="title-wrapper">
281
+ <span class="title-text">LongStory AI</span>
282
+ <span class="title-badge">PRO</span>
283
+ </div>
284
+ <div class="subtitle-text">Deep Persona-Driven Recursive Novel Generation</div>
285
  </div>
 
286
  """)
287
 
288
  with gr.Row(elem_classes=["main-container"]):
289
 
290
+ # === 左侧:控制中心 ===
291
+ with gr.Column(scale=3, elem_classes=["control-panel-col"]):
292
  with gr.Column(elem_classes=["control-panel"]):
293
+
294
+ # 1. 标题与输入区
295
+ gr.HTML("""
296
+ <div class="input-label">
297
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>
298
+ Story Premise
299
+ </div>
300
+ """)
301
 
302
  premise_input = gr.Textbox(
303
+ label="Premise",
304
+ show_label=False,
305
+ placeholder="在此输入你的故事创意,越详细越好...\n例如:赛博朋克背景下,一个因旧型号义肢而被歧视的侦探...",
306
+ lines=5,
307
+ elem_classes=["input-box"]
308
  )
309
 
310
+ # 2. 灵感卡片 (Examples)
311
+ gr.HTML('<div class="examples-container"><div class="examples-label">⚡ Quick Inspirations</div></div>')
312
+
313
+ # 定义 Examples,注意这里只放 text,不放 label,CSS 会把它变成卡片
314
+ example_prompts = [
315
+ ["高中时互相看不顺眼的死对头,十年后在公司并购案谈判桌上重逢,一个是冷血收购方,一个是绝境求生的CEO。"],
316
+ ["天生'废灵根'的宗门弃徒,在被逐出师门当晚,意外捡到一个能听见万物心声的黑色小鼎。"],
317
+ ["一觉醒来,人类全部消失,只剩下我和我养的猫。第三天,猫开口对我说话了。"]
318
+ ]
319
+
320
+ gr.Examples(
321
+ examples=example_prompts,
322
+ inputs=premise_input,
323
+ label=None,
324
+ elem_classes=["examples-table"] # 关键:应用新的卡片 CSS
325
+ )
326
 
327
+ # 3. 启动按钮
328
  submit_btn = gr.Button(
329
+ " 开始生成 (GENERATE)",
330
  elem_classes=["generate-btn"]
331
  )
332
 
333
+ # 4. 终端日志窗口 (伪装成 IDE 终端)
334
+ gr.HTML("""
335
+ <div class="terminal-wrapper">
336
+ <div class="terminal-header">
337
+ <div class="dot dot-red"></div>
338
+ <div class="dot dot-yellow"></div>
339
+ <div class="dot dot-green"></div>
340
+ <div class="terminal-title">agent_core.exe</div>
341
+ </div>
342
+ """)
343
  log_output = gr.Textbox(
344
+ label="System Log",
345
+ lines=10,
346
+ interactive=False,
347
+ elem_classes=["terminal-log"],
348
+ show_label=False,
349
+ value="> System initialized.\n> Waiting for user input..."
350
  )
351
+ gr.HTML("</div>") # Close terminal-wrapper
352
 
353
+ # === 右侧:内容展示区 ===
354
+ with gr.Column(scale=7):
355
  with gr.Tabs(elem_classes=["tabs-container"]):
356
 
357
+ # Tab 1: 电子书
358
+ with gr.TabItem("📖 正文阅读", id="tab-story"):
 
359
  story_display = gr.HTML(label="Book View")
 
 
360
  with gr.Row(elem_classes=["nav-row"]):
361
+ prev_btn = gr.Button("← Previous Chapter", elem_classes=["nav-btn"])
362
+ next_btn = gr.Button("Next Chapter →", elem_classes=["nav-btn"])
363
 
364
  # Tab 2: 大纲
365
+ with gr.TabItem("🗺️ 故事大纲", id="tab-outline"):
366
+ outline_output = gr.JSON(label="Structure Data")
367
 
368
  # Tab 3: 规划
369
+ with gr.TabItem("📅 剧情规划", id="tab-planning"):
370
+ plan_output = gr.JSON(label="Event Planning")
371
 
372
+ # Tab 4: 人设
373
+ with gr.TabItem("👥 人物档案", id="tab-persona"):
374
+ persona_output = gr.HTML(label="Character Cards")
375
 
376
  # ==========================================
377
+ # 事件交互 (保持不变)
378
  # ==========================================
 
 
379
  submit_btn.click(
380
  fn=bridge_to_backend,
381
  inputs=[premise_input],
382
+ outputs=[log_output, outline_output, plan_output, persona_output, story_state, story_display],
 
 
 
 
 
 
 
383
  concurrency_limit=1
384
  )
385
 
 
386
  def go_prev(story_data, current_page):
387
  new_page = max(0, current_page - 1)
388
  return new_page, render_book_page(story_data, new_page)
 
392
  new_page = min(len(story_data) - 1, current_page + 1)
393
  return new_page, render_book_page(story_data, new_page)
394
 
395
+ prev_btn.click(fn=go_prev, inputs=[story_state, page_state], outputs=[page_state, story_display])
396
+ next_btn.click(fn=go_next, inputs=[story_state, page_state], outputs=[page_state, story_display])
 
 
 
 
 
 
 
 
 
 
397
 
398
  if __name__ == "__main__":
399
  demo.queue().launch()