Yoyo2004 commited on
Commit
43e19b8
·
verified ·
1 Parent(s): ec3e8e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +234 -96
app.py CHANGED
@@ -1,166 +1,304 @@
1
- import os
2
  import gradio as gr
 
3
  from gradio_client import Client
4
 
 
 
 
 
5
  PRIVATE_SPACE_ID = "Yoyo2004/Longstory-backend"
6
-
7
  HF_TOKEN = os.environ.get("HF_TOKEN")
8
 
 
 
 
9
  custom_css = """
10
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;700&family=Noto+Serif+SC:wght@400;700&display=swap');
11
 
12
- body { font-family: 'Noto Sans SC', sans-serif !important; }
 
 
 
 
 
 
 
13
 
14
- .container { max-width: 1200px; margin: 0 auto; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- /* 标题样式 */
17
  .header-title {
18
  font-family: 'Noto Serif SC', serif;
19
  font-weight: 700;
20
- font-size: 2.5rem;
21
- color: #2d3436;
22
- margin-bottom: 0.5rem;
 
 
23
  }
 
24
  .header-subtitle {
25
- color: #636e72;
26
- font-size: 1.1rem;
27
- margin-bottom: 2rem;
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
 
30
  /* 输入框美化 */
31
- textarea {
32
- border: 2px solid #dfe6e9 !important;
 
33
  border-radius: 12px !important;
34
- transition: all 0.3s ease;
35
- font-size: 16px !important;
 
 
 
36
  }
37
- textarea:focus {
38
- border-color: #74b9ff !important;
39
- box-shadow: 0 0 10px rgba(116, 185, 255, 0.2) !important;
 
 
40
  }
41
 
42
- /* 按钮美化 */
43
- #generate-btn {
44
- background: linear-gradient(135deg, #6c5ce7, #a29bfe) !important;
45
- border: none;
46
- color: white;
47
- font-weight: bold;
48
- font-size: 18px;
49
- padding: 12px 24px;
50
- border-radius: 30px;
51
- box-shadow: 0 4px 15px rgba(108, 92, 231, 0.4);
52
- transition: transform 0.2s;
 
53
  }
54
- #generate-btn:hover {
 
55
  transform: translateY(-2px);
56
- box-shadow: 0 6px 20px rgba(108, 92, 231, 0.6);
57
  }
58
 
59
- /* 输出区域卡片化 */
60
- .output-card {
61
- background: white;
62
- border: 1px solid #f1f2f6;
63
- border-radius: 16px;
64
- padding: 20px;
65
- box-shadow: 0 4px 20px rgba(0,0,0,0.03);
 
 
66
  }
67
 
68
- /* 小说正文阅读模式 */
69
- .story-content {
70
- font-family: 'Noto Serif SC', serif;
71
- line-height: 1.8;
72
- font-size: 18px;
73
- color: #2d3436;
74
- background-color: #fafafa;
75
- padding: 40px;
76
- border-radius: 8px;
77
- border-left: 5px solid #6c5ce7;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
  """
80
 
81
  # ==========================================
82
- # 客户端连接逻辑
83
  # ==========================================
84
  def bridge_to_backend(premise):
85
  """
86
  连接到私有 Space 并流式传输结果
87
  """
88
  if not HF_TOKEN:
89
- yield "⚠️ Error: HF_TOKEN not found in Secrets.", None, None, None, None
90
  return
91
 
92
  try:
93
  # 连接私有 Space
94
  client = Client(PRIVATE_SPACE_ID, hf_token=HF_TOKEN)
95
 
96
- # 提交任务 (使用 generate_novel 函数)
97
- # 注意:这里的 fn_index 或 api_name 取决于后端 Gradio 的自动命名,
98
- # 通常如果后端只有一个函数,可以直接用 submit
99
  job = client.submit(premise, api_name="/generate_novel")
100
 
101
- # 迭代获取生成器产生的数据
102
  for result in job:
103
- # result 是一个元组,对应后端的 outputs
104
- # (log, outline, plan, persona, story)
105
  yield result
106
 
107
  except Exception as e:
108
- yield f"❌ Connection Error: {str(e)}", None, None, None, None
109
-
110
 
111
  # ==========================================
112
  # 构建前端界面
113
  # ==========================================
114
- with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="LongStory AI") as demo:
115
-
116
- with gr.Row(elem_classes=["container"]):
117
- with gr.Column(scale=1):
118
- gr.Markdown("# 🖊️ LongStory AI", elem_classes=["header-title"])
119
- gr.Markdown("智能长篇小说辅助创作系统 · Deep Layout & Persona Driven", elem_classes=["header-subtitle"])
 
 
 
 
120
 
121
- with gr.Row(elem_classes=["container"]):
122
- # 左侧控制栏
123
- with gr.Column(scale=1, min_width=300):
124
- with gr.Group():
125
- premise_input = gr.Textbox(
126
- label="💡 小说核心梗概 (Premise)",
127
- placeholder="例如:构造一个长篇青春校园小说, 事件覆盖从刚刚入学报道到高考之前...",
128
- lines=6
129
- )
130
- submit_btn = gr.Button("✨ 开始创作 (Generate)", elem_id="generate-btn")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- # 日志区
133
- with gr.Accordion("构建日志 (System Logs)", open=True):
134
- log_output = gr.Textbox(label="实时进度", lines=10, interactive=False, show_label=False)
135
-
136
- # 右侧展示栏
137
- with gr.Column(scale=2, min_width=500):
138
- with gr.Tabs():
139
- # Tab 1: 最终正文
140
- with gr.TabItem("📖 正文草稿 (Draft)"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  story_output = gr.Markdown(
142
- value="等待生成...",
143
- elem_classes=["story-content"]
144
  )
145
 
146
- # Tab 2: 大纲
147
- with gr.TabItem("🗺️ 故事大纲 (Outline)"):
148
- outline_output = gr.JSON(label="事件流 (Event Flow)")
 
 
 
149
 
150
- # Tab 3: 细纲
151
- with gr.TabItem("📅 详细规划 (Planning)"):
152
- plan_output = gr.JSON(label="子事件 (Sub-events)")
 
 
 
153
 
154
- # Tab 4: 人物
155
- with gr.TabItem("👥 人物设定 (Personas)"):
156
- persona_output = gr.JSON(label="人物弧光 (Character Arc)")
 
 
 
157
 
158
- # 事件绑定
159
  submit_btn.click(
160
  bridge_to_backend,
161
  inputs=[premise_input],
162
  outputs=[log_output, outline_output, plan_output, persona_output, story_output]
163
  )
164
 
 
165
  if __name__ == "__main__":
166
  demo.queue().launch()
 
 
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
+ gr.Markdown("### 📟 系统终端 (System Logs)", style="margin-top: 20px; color: #6b7280;")
245
+
246
+ # 使用 Accordion 包裹日志,默认展开但看起来像终端
247
+ log_output = gr.Textbox(
248
+ label="",
249
+ placeholder="等待任务指令...",
250
+ lines=12,
251
+ interactive=False,
252
+ elem_classes=["log-terminal"],
253
+ show_label=False
254
+ )
255
+
256
+ # --- 右侧:创作台 (Writing Desk) ---
257
+ with gr.Column(scale=8):
258
+ with gr.Tabs(elem_classes=["custom-tabs"]):
259
+
260
+ # Tab 1: 沉浸式阅读正文
261
+ with gr.TabItem("📖 正文草稿 (Draft)", id="tab-story"):
262
+ gr.Markdown(
263
+ """
264
+ <div style="text-align: center; color: #9ca3af; padding-top: 20px; margin-bottom: -20px;">
265
+ *生成的内容将显示在下方“纸张”中*
266
+ </div>
267
+ """
268
+ )
269
  story_output = gr.Markdown(
270
+ value="&nbsp;", # 占位符,防止空塌陷
271
+ elem_classes=["paper-document"]
272
  )
273
 
274
+ # Tab 2: 结构化大纲
275
+ with gr.TabItem("🗺️ 故事大纲 (Outline)", id="tab-outline"):
276
+ outline_output = gr.JSON(
277
+ label="事件流结构",
278
+ elem_classes=["json-viewer"]
279
+ )
280
 
281
+ # Tab 3: 详细规划
282
+ with gr.TabItem("📅 详细规划 (Planning)", id="tab-planning"):
283
+ plan_output = gr.JSON(
284
+ label="子事件拆解",
285
+ elem_classes=["json-viewer"]
286
+ )
287
 
288
+ # Tab 4: 人物档案
289
+ with gr.TabItem("👥 人物设定 (Personas)", id="tab-persona"):
290
+ persona_output = gr.JSON(
291
+ label="角色动态轨迹",
292
+ elem_classes=["json-viewer"]
293
+ )
294
 
295
+ # --- 事件绑定 ---
296
  submit_btn.click(
297
  bridge_to_backend,
298
  inputs=[premise_input],
299
  outputs=[log_output, outline_output, plan_output, persona_output, story_output]
300
  )
301
 
302
+ # 启动应用
303
  if __name__ == "__main__":
304
  demo.queue().launch()