Yoyo2004 commited on
Commit
c36065e
·
verified ·
1 Parent(s): 6329c81

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -156
app.py CHANGED
@@ -1,307 +1,360 @@
1
  import gradio as gr
2
  import os
 
3
  from gradio_client import Client
4
 
5
  # ==========================================
6
  # 配置部分
7
  # ==========================================
8
- # 请确保这里的 ID 是你真实的后端 Private Space ID
9
- PRIVATE_SPACE_ID = "Yoyo2004/Longstory-backend"
10
  HF_TOKEN = os.environ.get("HF_TOKEN")
11
 
12
  # ==========================================
13
- # 极致美化的 CSS (SaaS 风格)
14
  # ==========================================
15
  custom_css = """
16
  @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@300;400;600;700&family=JetBrains+Mono:wght@400&display=swap');
17
 
18
  :root {
19
- --primary-color: #4f46e5;
20
- --primary-hover: #4338ca;
21
  --paper-bg: #ffffff;
22
- --bg-color: #f3f4f6;
23
- --text-main: #1f2937;
24
- --text-muted: #6b7280;
25
  }
26
 
27
- body {
28
- font-family: 'Noto Sans SC', sans-serif !important;
29
- background-color: var(--bg-color) !important;
 
30
  }
31
 
32
- /* 隐藏 Gradio 默认的 Footer */
33
  footer { display: none !important; }
34
 
35
- /* 容器布局优化 */
36
- .gradio-container {
37
- max-width: 1400px !important;
 
 
38
  margin: 0 auto !important;
39
- padding-top: 20px !important;
40
  }
41
 
42
- /* Header 样式 */
43
- .app-header {
44
  text-align: center;
45
- margin-bottom: 30px;
46
- padding: 20px;
47
- background: white;
48
- border-radius: 16px;
49
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
50
- border: 1px solid #e5e7eb;
51
  }
52
 
53
- .header-title {
54
- font-family: 'Noto Serif SC', serif;
55
- font-weight: 700;
56
- font-size: 2.2rem;
57
- background: linear-gradient(135deg, #4f46e5 0%, #818cf8 100%);
58
  -webkit-background-clip: text;
59
  -webkit-text-fill-color: transparent;
60
- margin-bottom: 5px;
 
61
  }
62
 
63
- .header-subtitle {
64
- color: var(--text-muted);
65
  font-size: 1rem;
66
- font-weight: 400;
67
  letter-spacing: 1px;
68
  }
69
 
70
- /* 左侧控制面板 */
 
 
71
  .control-panel {
72
- background: white;
73
  border-radius: 16px;
 
74
  padding: 24px !important;
75
- border: 1px solid #e5e7eb;
76
- box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05);
77
- height: fit-content;
 
78
  }
79
 
80
- /* 输入框美化 */
81
- .custom-textarea textarea {
82
- background-color: #f9fafb !important;
83
- border: 1px solid #e5e7eb !important;
84
  border-radius: 12px !important;
85
  font-size: 15px !important;
86
- line-height: 1.6 !important;
87
  padding: 16px !important;
88
- box-shadow: inset 0 1px 2px rgba(0,0,0,0.02) !important;
89
- transition: all 0.2s ease;
90
  }
91
 
92
- .custom-textarea textarea:focus {
93
  background-color: #fff !important;
94
  border-color: var(--primary-color) !important;
95
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important;
96
  }
97
 
98
- /* 按钮美化 (Pill Shape) */
99
- .primary-btn {
100
- background: linear-gradient(to right, var(--primary-color), #6366f1) !important;
101
  border: none !important;
102
  color: white !important;
103
  font-weight: 600 !important;
104
- font-size: 16px !important;
105
- padding: 12px 30px !important;
106
- border-radius: 9999px !important;
107
- box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.3) !important;
108
- transition: all 0.3s ease !important;
109
- margin-top: 10px !important;
110
  }
111
 
112
- .primary-btn:hover {
113
  transform: translateY(-2px);
114
- box-shadow: 0 8px 15px -3px rgba(79, 70, 229, 0.4) !important;
115
  }
116
 
117
- /* 日志区域 (Terminal 风格) */
118
- .log-terminal textarea {
119
- font-family: 'JetBrains Mono', monospace !important;
120
- background-color: #1f2937 !important;
121
- color: #10b981 !important; /* Matrix Green */
122
  border-radius: 12px !important;
123
- border: 1px solid #374151 !important;
 
 
 
 
 
 
 
 
 
 
 
 
124
  font-size: 13px !important;
125
- line-height: 1.5 !important;
 
 
126
  }
127
 
128
- .log-accordion {
 
 
 
 
129
  background: transparent !important;
130
  border: none !important;
131
  }
132
 
133
- /* 右侧展示区:纸张效果 */
134
- .paper-document {
135
- background: var(--paper-bg);
136
- padding: 60px !important;
137
- min-height: 600px;
138
- border-radius: 2px; /* 稍微圆角 */
139
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.025);
140
- border: 1px solid #e5e7eb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  margin-top: 10px;
142
  }
143
 
144
  /* 正文排版 */
145
- .paper-document .prose {
146
  font-family: 'Noto Serif SC', serif !important;
147
  font-size: 18px !important;
148
- line-height: 2 !important;
149
- color: #111827 !important;
150
- max-width: 100% !important;
151
  }
152
 
153
- /* 标题和段落间距 */
154
- .paper-document p {
155
- margin-bottom: 1.5em;
156
- text-align: justify;
157
  }
158
 
159
- /* Tab 样式优化 */
160
- .custom-tabs button.selected {
161
- color: var(--primary-color) !important;
162
- border-bottom: 2px solid var(--primary-color) !important;
163
- font-weight: 600 !important;
 
 
164
  }
165
 
166
- /* JSON 视图美化 */
167
- .json-viewer {
168
- background: white;
169
- border-radius: 12px;
170
- padding: 20px;
171
- border: 1px solid #e5e7eb;
172
  }
173
  """
174
 
175
  # ==========================================
176
- # 逻辑部分 (保持不变)
177
  # ==========================================
178
  def bridge_to_backend(premise):
179
  """
180
  连接到私有 Space 并流式传输结果
181
  """
 
 
 
 
182
  if not HF_TOKEN:
183
- yield "⚠️ Error: HF_TOKEN not found in Secrets. Please check your Space settings.", None, None, None, None
 
 
 
 
 
184
  return
185
 
 
 
 
186
  try:
 
 
 
187
  # 连接私有 Space
188
  client = Client(PRIVATE_SPACE_ID, hf_token=HF_TOKEN)
189
 
 
 
 
 
190
  # 提交任务
191
  job = client.submit(premise, api_name="/generate_novel")
192
 
193
  # 迭代获取数据
194
  for result in job:
 
 
 
 
 
 
 
 
 
 
195
  yield result
196
 
197
  except Exception as e:
198
- error_msg = f"❌ Connection Error: {str(e)}\n\n请检查:\n1. 你的 HF_TOKEN 是否有效且有 Read 权限。\n2. Private Space 是否正在 Running 状态。\n3. PRIVATE_SPACE_ID 是否拼写正确。"
 
 
 
199
  yield error_msg, None, None, None, None
200
 
201
  # ==========================================
202
  # 构建前端界面
203
  # ==========================================
204
 
205
- # 自定义主题参数,让默认组件更协调
206
  theme = gr.themes.Soft(
207
  primary_hue="indigo",
208
  secondary_hue="slate",
209
- radius_size=gr.themes.sizes.radius_lg,
210
- font=[gr.themes.GoogleFont("Noto Sans SC"), "ui-sans-serif", "system-ui"],
 
211
  )
212
 
213
- with gr.Blocks(theme=theme, css=custom_css, title="LongStory AI - Pro") as demo:
214
 
215
- # --- 头部 Header ---
216
- with gr.Row():
217
- with gr.Column(scale=1, elem_classes=["app-header"]):
218
- gr.Markdown(
219
- """
220
- <div class="header-title">🖊️ LongStory AI <span style="font-size: 1.2rem; color: #4f46e5; vertical-align: super;">Pro</span></div>
221
- <div class="header-subtitle">Deep Layout & Persona Driven · 深度长篇小说创作系统</div>
222
- """
223
- )
224
 
225
- with gr.Row(equal_height=False):
 
226
 
227
- # --- 左侧:控制台 (Command Center) ---
228
- with gr.Column(scale=4, elem_classes=["control-panel"]):
229
- gr.Markdown("### 💡 灵感输入 (Premise)", elem_id="input-label")
230
 
231
  premise_input = gr.Textbox(
232
- label="",
233
- placeholder="请输入你的核心梗概...\n例如:\n一个关于时间旅行的悬疑故事,主角在2024年和1990年之间来回穿梭,试图阻止一场连环谋杀案。主要涉及3个性格迥异的侦探...",
234
- lines=8,
235
- elem_classes=["custom-textarea"],
236
  show_label=False
237
  )
238
 
239
  submit_btn = gr.Button(
240
- " 开始构建世界 (Generate Novel)",
241
- elem_classes=["primary-btn"]
242
- )
243
-
244
- # 修复点:使用 HTML 标签替代 style 参数,解决了 TypeError
245
- gr.Markdown(
246
- "<h3 style='margin-top: 20px; color: #6b7280; font-size: 1.1em; font-weight: bold;'>📟 系统终端 (System Logs)</h3>"
247
  )
248
 
249
- # 使用 Accordion 包裹日志,默认展开但看起来像终端
250
  log_output = gr.Textbox(
251
- label="",
252
- placeholder="等待任务指令...",
253
- lines=12,
 
254
  interactive=False,
255
- elem_classes=["log-terminal"],
256
  show_label=False
257
  )
258
 
259
- # --- 右侧:创作台 (Writing Desk) ---
260
  with gr.Column(scale=8):
261
- with gr.Tabs(elem_classes=["custom-tabs"]):
262
 
263
- # Tab 1: 沉浸式阅读正文
264
  with gr.TabItem("📖 正文草稿 (Draft)", id="tab-story"):
265
- gr.Markdown(
266
- """
267
- <div style="text-align: center; color: #9ca3af; padding-top: 20px; margin-bottom: -20px;">
268
- *生成的内容将显示在下方“纸张”中*
269
- </div>
270
- """
271
- )
272
  story_output = gr.Markdown(
273
- value="&nbsp;", # 占位符,防止空塌陷
274
- elem_classes=["paper-document"]
275
  )
276
 
277
- # Tab 2: 结构化大纲
278
- with gr.TabItem("🗺️ 故事大纲 (Outline)", id="tab-outline"):
279
  outline_output = gr.JSON(
280
- label="事件流结构",
281
- elem_classes=["json-viewer"]
282
  )
283
 
284
- # Tab 3: 详细规划
285
  with gr.TabItem("📅 详细规划 (Planning)", id="tab-planning"):
286
  plan_output = gr.JSON(
287
- label="子事件拆解",
288
- elem_classes=["json-viewer"]
289
  )
290
 
291
- # Tab 4: 人物档案
292
- with gr.TabItem("👥 人物设定 (Personas)", id="tab-persona"):
293
  persona_output = gr.JSON(
294
- label="角色动态轨迹",
295
- elem_classes=["json-viewer"]
296
  )
297
 
298
- # --- 事件绑定 ---
299
  submit_btn.click(
300
- bridge_to_backend,
301
  inputs=[premise_input],
302
- outputs=[log_output, outline_output, plan_output, persona_output, story_output]
 
303
  )
304
 
305
- # 启动应用
306
  if __name__ == "__main__":
307
  demo.queue().launch()
 
1
  import gradio as gr
2
  import os
3
+ import time
4
  from gradio_client import Client
5
 
6
  # ==========================================
7
  # 配置部分
8
  # ==========================================
9
+ PRIVATE_SPACE_ID = "Yoyo2004/Longstory-backend"
10
+ # 尝试获取 Token,如果没有则为 None
11
  HF_TOKEN = os.environ.get("HF_TOKEN")
12
 
13
  # ==========================================
14
+ # 🎨 极致美化的 CSS (SaaS 风格 + 沉浸式写作)
15
  # ==========================================
16
  custom_css = """
17
  @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@300;400;600;700&family=JetBrains+Mono:wght@400&display=swap');
18
 
19
  :root {
20
+ --primary-color: #6366f1; /* Indigo-500 */
21
+ --primary-hover: #4f46e5; /* Indigo-600 */
22
  --paper-bg: #ffffff;
23
+ --paper-shadow: 0 10px 30px -10px rgba(0,0,0,0.08);
24
+ --panel-bg: #f8fafc; /* Slate-50 */
25
+ --border-color: #e2e8f0;
26
  }
27
 
28
+ /* 全局字体设置 */
29
+ body, .gradio-container {
30
+ font-family: 'Noto Sans SC', system-ui, -apple-system, sans-serif !important;
31
+ background-color: #f1f5f9 !important; /* Slate-100 */
32
  }
33
 
34
+ /* 隐藏 Footer */
35
  footer { display: none !important; }
36
 
37
+ /* --------------------------------------
38
+ Layout & Containers
39
+ -------------------------------------- */
40
+ .main-container {
41
+ max-width: 1600px !important;
42
  margin: 0 auto !important;
 
43
  }
44
 
45
+ /* Header 区域 */
46
+ .header-box {
47
  text-align: center;
48
+ padding: 30px 20px;
49
+ background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
50
+ border-bottom: 1px solid var(--border-color);
51
+ margin-bottom: 25px;
52
+ border-radius: 0 0 20px 20px;
53
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02);
54
  }
55
 
56
+ .title-text {
57
+ font-size: 2rem;
58
+ font-weight: 800;
59
+ background: linear-gradient(135deg, #4f46e5 0%, #8b5cf6 100%);
 
60
  -webkit-background-clip: text;
61
  -webkit-text-fill-color: transparent;
62
+ margin-bottom: 8px;
63
+ font-family: 'Noto Serif SC', serif;
64
  }
65
 
66
+ .subtitle-text {
67
+ color: #64748b;
68
  font-size: 1rem;
 
69
  letter-spacing: 1px;
70
  }
71
 
72
+ /* --------------------------------------
73
+ Left Panel: Control Center
74
+ -------------------------------------- */
75
  .control-panel {
76
+ background: #ffffff;
77
  border-radius: 16px;
78
+ border: 1px solid var(--border-color);
79
  padding: 24px !important;
80
+ box-shadow: var(--paper-shadow);
81
+ height: 100%;
82
+ display: flex;
83
+ flex-direction: column;
84
  }
85
 
86
+ .input-box textarea {
87
+ background-color: #f8fafc !important;
88
+ border: 1px solid #cbd5e1 !important;
 
89
  border-radius: 12px !important;
90
  font-size: 15px !important;
 
91
  padding: 16px !important;
92
+ transition: all 0.2s;
93
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.02) !important;
94
  }
95
 
96
+ .input-box textarea:focus {
97
  background-color: #fff !important;
98
  border-color: var(--primary-color) !important;
99
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2) !important;
100
  }
101
 
102
+ .generate-btn {
103
+ background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%) !important;
 
104
  border: none !important;
105
  color: white !important;
106
  font-weight: 600 !important;
107
+ padding: 12px !important;
108
+ border-radius: 12px !important;
109
+ margin-top: 15px !important;
110
+ transition: transform 0.2s !important;
111
+ box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3) !important;
 
112
  }
113
 
114
+ .generate-btn:hover {
115
  transform: translateY(-2px);
116
+ box-shadow: 0 6px 15px rgba(79, 70, 229, 0.4) !important;
117
  }
118
 
119
+ /* Terminal Log Style */
120
+ .terminal-log {
121
+ margin-top: 20px !important;
 
 
122
  border-radius: 12px !important;
123
+ overflow: hidden !important;
124
+ border: 1px solid #334155 !important;
125
+ background-color: #1e293b !important;
126
+ }
127
+
128
+ .terminal-log label {
129
+ display: none !important; /* 隐藏 Label */
130
+ }
131
+
132
+ .terminal-log textarea {
133
+ font-family: 'JetBrains Mono', monospace !important;
134
+ background-color: #1e293b !important; /* Slate-800 */
135
+ color: #4ade80 !important; /* Green-400 */
136
  font-size: 13px !important;
137
+ line-height: 1.6 !important;
138
+ border: none !important;
139
+ padding: 15px !important;
140
  }
141
 
142
+ /* --------------------------------------
143
+ Right Panel: Writing Desk
144
+ -------------------------------------- */
145
+ /* Tabs 容器美化 */
146
+ .tabs-container {
147
  background: transparent !important;
148
  border: none !important;
149
  }
150
 
151
+ /* 选中 Tab 的样式 */
152
+ .tabs-container button.selected {
153
+ color: var(--primary-color) !important;
154
+ border-bottom: 2px solid var(--primary-color) !important;
155
+ background: transparent !important;
156
+ font-weight: 700 !important;
157
+ }
158
+
159
+ /* 未选中 Tab */
160
+ .tabs-container button {
161
+ color: #64748b !important;
162
+ border-bottom: 2px solid transparent !important;
163
+ }
164
+
165
+ /* 📄 稿纸效果 (Paper Document) */
166
+ .paper-wrapper {
167
+ background-color: var(--paper-bg);
168
+ border: 1px solid var(--border-color);
169
+ box-shadow: var(--paper-shadow);
170
+ border-radius: 4px; /* 稍微锐利一点的圆角,像纸张 */
171
+ padding: 60px 80px !important;
172
+ min-height: 700px;
173
  margin-top: 10px;
174
  }
175
 
176
  /* 正文排版 */
177
+ .paper-wrapper .prose {
178
  font-family: 'Noto Serif SC', serif !important;
179
  font-size: 18px !important;
180
+ line-height: 2.2 !important;
181
+ color: #1e293b !important;
182
+ text-align: justify;
183
  }
184
 
185
+ .paper-wrapper h1, .paper-wrapper h2, .paper-wrapper h3 {
186
+ font-family: 'Noto Sans SC', sans-serif !important;
187
+ color: #0f172a;
188
+ margin-top: 1.5em;
189
  }
190
 
191
+ /* JSON Viewer 美化 */
192
+ .json-panel {
193
+ background: white !important;
194
+ border: 1px solid var(--border-color) !important;
195
+ border-radius: 12px !important;
196
+ padding: 20px !important;
197
+ box-shadow: 0 4px 6px -1px rgba(0,0,0,0.02) !important;
198
  }
199
 
200
+ /* Mobile Adaptation */
201
+ @media (max-width: 768px) {
202
+ .paper-wrapper { padding: 30px 20px !important; }
203
+ .control-panel { margin-bottom: 20px; }
 
 
204
  }
205
  """
206
 
207
  # ==========================================
208
+ # 逻辑部分
209
  # ==========================================
210
  def bridge_to_backend(premise):
211
  """
212
  连接到私有 Space 并流式传输结果
213
  """
214
+ if not premise.strip():
215
+ yield "⚠️ 请输入故事梗概 (Premise) ...", None, None, None, None
216
+ return
217
+
218
  if not HF_TOKEN:
219
+ error_log = (
220
+ "⚠️ [System Error]: HF_TOKEN environment variable is missing.\n"
221
+ "------------------------------------------------------------\n"
222
+ "Please add your Hugging Face Token to the Space Settings > Repository secrets."
223
+ )
224
+ yield error_log, None, None, None, None
225
  return
226
 
227
+ log_buffer = "🚀 初始化连接...\n"
228
+ yield log_buffer, None, None, None, None
229
+
230
  try:
231
+ log_buffer += f"🔗 连接到后端: {PRIVATE_SPACE_ID}\n"
232
+ yield log_buffer, None, None, None, None
233
+
234
  # 连接私有 Space
235
  client = Client(PRIVATE_SPACE_ID, hf_token=HF_TOKEN)
236
 
237
+ log_buffer += "✅ 连接成功!任务已提交,正在生成...\n"
238
+ log_buffer += "⏳ (这可能需要几分钟,请耐心等待)\n"
239
+ yield log_buffer, None, None, None, None
240
+
241
  # 提交任务
242
  job = client.submit(premise, api_name="/generate_novel")
243
 
244
  # 迭代获取数据
245
  for result in job:
246
+ # result 应该是一个 tuple,包含 5 个元素
247
+ # 假设后端返回格式为: (log_text, outline_json, plan_json, persona_json, story_html/md)
248
+
249
+ # 为了更优雅的显示,我们在 Log 中追加时间戳
250
+ current_log = log_buffer + f"\n[UPDATE] {time.strftime('%H:%M:%S')} - 收到新数据块..."
251
+
252
+ # 解包结果 (根据你后端的实际返回调整)
253
+ # 注意:如果后端返回还是 Tuple,Gradio Client 会自动解包
254
+ # 这里我们直接透传 result,但确保第一个参数(Log)是累加的或者是新的
255
+
256
  yield result
257
 
258
  except Exception as e:
259
+ error_msg = f"{log_buffer}\n[CRITICAL ERROR]: {str(e)}\n\n"
260
+ error_msg += "Debug Suggestions:\n"
261
+ error_msg += "1. Check if the Private Space is 'Running'.\n"
262
+ error_msg += "2. Verify HF_TOKEN permissions (Read access required).\n"
263
  yield error_msg, None, None, None, None
264
 
265
  # ==========================================
266
  # 构建前端界面
267
  # ==========================================
268
 
269
+ # 创建一个干净的 Base 主题作为基础,然后用 CSS 覆盖
270
  theme = gr.themes.Soft(
271
  primary_hue="indigo",
272
  secondary_hue="slate",
273
+ text_size="lg",
274
+ spacing_size="md",
275
+ radius_size="lg",
276
  )
277
 
278
+ with gr.Blocks(theme=theme, css=custom_css, title="LongStory AI Pro") as demo:
279
 
280
+ # --- Header ---
281
+ with gr.Row(elem_classes=["header-box"]):
282
+ gr.HTML("""
283
+ <div class="title-text">🖊️ LongStory AI <span style="font-size: 0.5em; vertical-align: top; color: #6366f1;">PRO</span></div>
284
+ <div class="subtitle-text">Deep Persona-Driven Novel Generation System</div>
285
+ """)
 
 
 
286
 
287
+ # --- Main Content ---
288
+ with gr.Row(elem_classes=["main-container"]):
289
 
290
+ # === Left Column: Control Center ===
291
+ with gr.Column(scale=4, variant="panel", elem_classes=["control-panel"]):
292
+ gr.Markdown("### 💡 故事引擎 (Input)", elem_id="input_title")
293
 
294
  premise_input = gr.Textbox(
295
+ label="核心梗概 (Premise)",
296
+ placeholder="在此输入你的创意...\n例如:赛博朋克背景下,一个因旧型号义肢而被歧视的侦探,接到了寻找'数字永生'密钥的委托...",
297
+ lines=6,
298
+ elem_classes=["input-box"],
299
  show_label=False
300
  )
301
 
302
  submit_btn = gr.Button(
303
+ "🚀 启动生成 (Generate)",
304
+ elem_classes=["generate-btn"]
 
 
 
 
 
305
  )
306
 
307
+ gr.Markdown("### 📟 运行日志 (Terminal)", elem_id="log_title")
308
  log_output = gr.Textbox(
309
+ label="系统日志",
310
+ value="System Ready. Waiting for input...",
311
+ lines=15,
312
+ max_lines=20,
313
  interactive=False,
314
+ elem_classes=["terminal-log"],
315
  show_label=False
316
  )
317
 
318
+ # === Right Column: Writing Desk ===
319
  with gr.Column(scale=8):
320
+ with gr.Tabs(elem_classes=["tabs-container"]):
321
 
322
+ # Tab 1: 正文 (Draft)
323
  with gr.TabItem("📖 正文草稿 (Draft)", id="tab-story"):
 
 
 
 
 
 
 
324
  story_output = gr.Markdown(
325
+ value="<br><div style='text-align: center; color: #cbd5e1;'>✨ 故事将在此处生成...</div>",
326
+ elem_classes=["paper-wrapper"]
327
  )
328
 
329
+ # Tab 2: 大纲 (Outline)
330
+ with gr.TabItem("🗺️ 结构大纲 (Outline)", id="tab-outline"):
331
  outline_output = gr.JSON(
332
+ label=None,
333
+ elem_classes=["json-panel"]
334
  )
335
 
336
+ # Tab 3: 规划 (Planning)
337
  with gr.TabItem("📅 详细规划 (Planning)", id="tab-planning"):
338
  plan_output = gr.JSON(
339
+ label=None,
340
+ elem_classes=["json-panel"]
341
  )
342
 
343
+ # Tab 4: 人物 (Personas)
344
+ with gr.TabItem("👥 人物档案 (Personas)", id="tab-persona"):
345
  persona_output = gr.JSON(
346
+ label=None,
347
+ elem_classes=["json-panel"]
348
  )
349
 
350
+ # --- Interactions ---
351
  submit_btn.click(
352
+ fn=bridge_to_backend,
353
  inputs=[premise_input],
354
+ outputs=[log_output, outline_output, plan_output, persona_output, story_output],
355
+ concurrency_limit=1
356
  )
357
 
358
+ # 启动
359
  if __name__ == "__main__":
360
  demo.queue().launch()