Estazz commited on
Commit
4ea35ac
·
verified ·
1 Parent(s): 52f33d3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -100
app.py CHANGED
@@ -2,12 +2,18 @@ import gradio as gr
2
  import dashscope
3
  import os
4
 
5
- # 导入自定义模块
6
  from config import API_KEY, APP_TITLE, CHATBOT_HEIGHT, MIN_WIDTH_LEFT, MIN_WIDTH_RIGHT
7
  from styles import POKER_THEME_CSS
8
  from ai_service import design_poker_game
 
 
 
 
 
 
9
 
10
- # ====== 🔧 Hotfix: 兼容 gradio_client 对 additionalProperties 为布尔的解析 ======
11
  try:
12
  import gradio_client.utils as _gc_utils
13
 
@@ -40,8 +46,9 @@ except Exception:
40
  pass
41
  # ====== 🔧 Hotfix 结束 ======
42
 
43
- # 设置 API Key
44
- dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
 
45
 
46
  # ==================== 工具函数 ====================
47
  def _messages_to_tuples(history):
@@ -52,7 +59,6 @@ def _messages_to_tuples(history):
52
  if not history:
53
  return []
54
 
55
- # 已经是列表且第一个元素为 dict,则认为是 messages 模式
56
  if isinstance(history, list) and history and isinstance(history[0], dict):
57
  pairs = []
58
  last_user = None
@@ -64,14 +70,18 @@ def _messages_to_tuples(history):
64
  elif role == "assistant":
65
  pairs.append((last_user or "", content))
66
  last_user = None
67
- else:
68
- # 其他角色(如 system)直接忽略到导出/兼容对里
69
- pass
70
  return pairs
71
 
72
- # 否则当作老的 [(user, bot)] 结构
73
  return history
74
 
 
 
 
 
 
 
 
 
75
  def clear_cache():
76
  """清空缓存"""
77
  from cache_manager import file_cache, request_cache
@@ -79,10 +89,12 @@ def clear_cache():
79
  request_cache.clear()
80
  return "✅ 缓存已清空"
81
 
 
82
  def clear_files():
83
  """清空文件上传"""
84
  return None
85
 
 
86
  def update_file_status(files):
87
  """更新文件状态显示"""
88
  if not files:
@@ -90,23 +102,21 @@ def update_file_status(files):
90
  file_list = files if isinstance(files, (list, tuple)) else [files]
91
  names = []
92
  for f in file_list:
93
- # gr.File(type="filepath") 传回的是路径字符串
94
  if isinstance(f, str):
95
  names.append(os.path.basename(f))
96
  else:
97
- # 兼容其他情况
98
  names.append(os.path.basename(getattr(f, "name", str(f))))
99
  head = "\n".join(f" • {n}" for n in names[:3])
100
  tail = "\n ..." if len(names) > 3 else ""
101
  return f"📁 文件状态:已上传 {len(names)} 个文件\n{head}{tail}"
102
 
 
103
  def export_history_to_markdown(history):
104
  """将聊天历史导出为 Markdown 文件,并返回文件路径供下载"""
105
  import time
106
  import pathlib
107
  from datetime import datetime
108
 
109
- # 统一转为 [(user, bot)] 再导出
110
  pairs = _messages_to_tuples(history)
111
 
112
  lines = ["# 对话记录", ""]
@@ -132,15 +142,6 @@ def export_history_to_markdown(history):
132
  f.write(content)
133
  return str(filename)
134
 
135
- # 为 ChatInterface 提供一个向后兼容的适配器
136
- def chat_fn_adapter(user_message, history, files, custom_prompt, prompt_mode):
137
- """
138
- ChatInterface(type='messages') 会传入 history 为 messages 列表。
139
- 这里做一次转换,调用你现有的 design_poker_game(假设其使用 [(user, bot)] 历史)。
140
- 返回值保持为纯文本(由 ChatInterface 追加到对话)。
141
- """
142
- history_tuples = _messages_to_tuples(history)
143
- return design_poker_game(user_message, history_tuples, files, custom_prompt, prompt_mode)
144
 
145
  # ==================== Gradio 界面(Poker Skin) ====================
146
  with gr.Blocks(
@@ -194,36 +195,27 @@ with gr.Blocks(
194
  "- 长输出建议分步生成(先 GDL,再自然语言规则说明)。\n"
195
  "- 需要更强制约,可在 Prompt 中明确输出格式与边界。"
196
  )
197
-
198
  with gr.Group(elem_classes="side-card"):
199
  gr.Markdown("### 快捷操作")
200
  with gr.Row():
201
  clear_cache_btn = gr.Button("清空缓存", variant="secondary")
202
  clear_files_btn = gr.Button("清空文件", variant="secondary")
203
- # “清空对话”使用 ClearButton 以兼容新版
204
- # 先在右侧定义完 chatbot / user_input 后再绑定(见下文)
205
-
206
- # 缓存状态显示
207
  cache_status = gr.Markdown("💾 缓存状态:正常", elem_classes="hint")
208
- # 文件上传状态
209
  file_status = gr.Markdown("📁 文件状态:未上传", elem_classes="hint")
210
 
211
- # 右侧:聊天区
212
- # 右侧:聊天区(改为手动事件绑定,彻底规避 ChatInterface 的样式/版本差异)
213
  with gr.Column(scale=2, min_width=MIN_WIDTH_RIGHT):
214
  with gr.Group(elem_classes="table"):
215
- # 聊天消息采用新格式 messages
216
  chatbot = gr.Chatbot(
217
  height=CHATBOT_HEIGHT,
218
  type="messages",
219
  elem_classes="custom-chatbot",
220
- #elem_id="poker_chat",
221
  avatar_images=("landlord.png", "bot.png"),
222
  )
223
- # 用一个 State 保存 messages 历史,便于多处复用
224
- chat_state = gr.State([]) # list[dict]: [{"role":"user","content":...}, {"role":"assistant","content":...}, ...]
225
 
226
- # 输入框(支持 Shift+Enter 换行;回车提交我们手动绑定)
227
  user_input = gr.Textbox(
228
  placeholder="例如:设计一个适合3-5人的派对风格扑克游戏(请写清目标人群/时长/创新点)…",
229
  show_label=False,
@@ -233,48 +225,57 @@ with gr.Blocks(
233
  autofocus=True,
234
  )
235
 
236
- # 显式“发送”按钮(大按钮)
237
  with gr.Row():
238
  send_btn = gr.Button("发送", variant="primary")
239
- stop_info = gr.Markdown("", visible=False) # 预留:如需做流式/停止提示
240
-
241
- # —— 提交处理:把 messages 历史交给你的 design_poker_game,返回助手回复并更新 UI ——
242
- def _messages_to_tuples(history_msgs):
243
- """[{role,content}] -> [(user, bot)] 供现有 design_poker_game 使用"""
244
- pairs, last_user = [], None
245
- for m in history_msgs or []:
246
- r, c = m.get("role"), m.get("content", "")
247
- if r == "user":
248
- last_user = c
249
- elif r == "assistant":
250
- pairs.append((last_user or "", c))
251
- last_user = None
252
- return pairs
253
 
 
254
  def on_submit(user_text, history_msgs, files, custom_prompt, mode):
255
  user_text = (user_text or "").strip()
256
- # 空文本不提交(与 Gradio 5 的默认交互一致)
257
  if not user_text:
258
- return gr.update(), gr.update(), history_msgs
259
-
260
- # 1) 先把用户消息写入 messages(前端立即看到气泡)
261
- history_msgs = list(history_msgs or [])
262
- history_msgs.append({"role": "user", "content": user_text})
263
-
264
- # 2) 调用你的核心函数(传入 tuple 历史以兼容)
265
- tuples_hist = _messages_to_tuples(history_msgs)
266
- try:
267
- bot_reply = design_poker_game(user_text, tuples_hist, files, custom_prompt, mode)
268
- except Exception as e:
269
- bot_reply = f"(出错){type(e).__name__}: {e}"
270
-
271
- # 3) 追加助手回复
272
- history_msgs.append({"role": "assistant", "content": str(bot_reply)})
273
-
274
- # 4) 更新 Chatbot 与清空输入框,同时回写 state
275
- return history_msgs, "", history_msgs
276
-
277
- # 绑定:回车提交(Enter=提交;Shift+Enter=换行由浏览器原生处理)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  user_input.submit(
279
  fn=on_submit,
280
  inputs=[user_input, chat_state, file_uploader, custom_prompt_box, prompt_mode],
@@ -282,7 +283,7 @@ with gr.Blocks(
282
  preprocess=True,
283
  )
284
 
285
- # 绑定:点击“发送”按钮提交(与回车完全等价)
286
  send_btn.click(
287
  fn=on_submit,
288
  inputs=[user_input, chat_state, file_uploader, custom_prompt_box, prompt_mode],
@@ -290,12 +291,12 @@ with gr.Blocks(
290
  preprocess=True,
291
  )
292
 
293
- # 导出对话(直接读取 Chatbot 的 messages 值)
294
  with gr.Row():
295
  export_btn = gr.Button("导出对话(Markdown)", variant="secondary")
296
  export_file = gr.File(label="点击下载导出文件", interactive=False)
297
 
298
- # 清空对话(同时清空 Chatbot + 输入 + state)
299
  with gr.Row():
300
  clear_dialog_btn = gr.Button("清空对话", variant="secondary")
301
 
@@ -308,26 +309,12 @@ with gr.Blocks(
308
  outputs=[chatbot, user_input, chat_state],
309
  )
310
 
 
 
 
 
311
 
312
- # ==================== 事件绑定 ====================
313
- # 清空缓存按钮
314
- clear_cache_btn.click(
315
- fn=clear_cache,
316
- outputs=[cache_status]
317
- )
318
- # 清空文件按钮
319
- clear_files_btn.click(
320
- fn=clear_files,
321
- outputs=[file_uploader]
322
- )
323
- # 文件上传状态更新
324
- file_uploader.change(
325
- fn=update_file_status,
326
- inputs=[file_uploader],
327
- outputs=[file_status]
328
- )
329
-
330
- # 导出历史对话
331
  def _export_wrapper(chat_history):
332
  try:
333
  path = export_history_to_markdown(chat_history)
@@ -336,16 +323,13 @@ with gr.Blocks(
336
  import tempfile
337
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".md")
338
  with open(tmp.name, 'w', encoding='utf-8') as f:
339
- f.write(f"导出失败:{type(e).__name__}: {str(e)}\n")
340
  return tmp.name
341
 
342
- export_btn.click(
343
- fn=_export_wrapper,
344
- inputs=[chatbot],
345
- outputs=[export_file]
346
- )
347
 
348
  # ==================== 启动应用 ====================
349
  if __name__ == "__main__":
350
- # Hugging Face Spaces 上 share 参数会被忽略,但本地调试可用
351
- demo.queue().launch(share=True, show_api=False)
 
2
  import dashscope
3
  import os
4
 
5
+ # ===== 你的自定义模块 =====
6
  from config import API_KEY, APP_TITLE, CHATBOT_HEIGHT, MIN_WIDTH_LEFT, MIN_WIDTH_RIGHT
7
  from styles import POKER_THEME_CSS
8
  from ai_service import design_poker_game
9
+ # 优先尝试原生流式;没有则自动回退为伪流式
10
+ try:
11
+ from ai_service import design_poker_game_stream
12
+ except Exception:
13
+ design_poker_game_stream = None
14
+
15
 
16
+ # ====== 🔧 Hotfix: 兼容 gradio_client 对 additionalProperties(bool) 的解析 ======
17
  try:
18
  import gradio_client.utils as _gc_utils
19
 
 
46
  pass
47
  # ====== 🔧 Hotfix 结束 ======
48
 
49
+ # ====== 设置 API Key(环境变量优先,兜底 config.API_KEY)======
50
+ dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") or os.getenv("API_KEY") or API_KEY
51
+
52
 
53
  # ==================== 工具函数 ====================
54
  def _messages_to_tuples(history):
 
59
  if not history:
60
  return []
61
 
 
62
  if isinstance(history, list) and history and isinstance(history[0], dict):
63
  pairs = []
64
  last_user = None
 
70
  elif role == "assistant":
71
  pairs.append((last_user or "", content))
72
  last_user = None
 
 
 
73
  return pairs
74
 
 
75
  return history
76
 
77
+
78
+ def _chunk_fake_stream(text, step=40):
79
+ """把整段文本切成小块,伪流式输出。"""
80
+ s = str(text or "")
81
+ for i in range(0, len(s), step):
82
+ yield s[i:i + step]
83
+
84
+
85
  def clear_cache():
86
  """清空缓存"""
87
  from cache_manager import file_cache, request_cache
 
89
  request_cache.clear()
90
  return "✅ 缓存已清空"
91
 
92
+
93
  def clear_files():
94
  """清空文件上传"""
95
  return None
96
 
97
+
98
  def update_file_status(files):
99
  """更新文件状态显示"""
100
  if not files:
 
102
  file_list = files if isinstance(files, (list, tuple)) else [files]
103
  names = []
104
  for f in file_list:
 
105
  if isinstance(f, str):
106
  names.append(os.path.basename(f))
107
  else:
 
108
  names.append(os.path.basename(getattr(f, "name", str(f))))
109
  head = "\n".join(f" • {n}" for n in names[:3])
110
  tail = "\n ..." if len(names) > 3 else ""
111
  return f"📁 文件状态:已上传 {len(names)} 个文件\n{head}{tail}"
112
 
113
+
114
  def export_history_to_markdown(history):
115
  """将聊天历史导出为 Markdown 文件,并返回文件路径供下载"""
116
  import time
117
  import pathlib
118
  from datetime import datetime
119
 
 
120
  pairs = _messages_to_tuples(history)
121
 
122
  lines = ["# 对话记录", ""]
 
142
  f.write(content)
143
  return str(filename)
144
 
 
 
 
 
 
 
 
 
 
145
 
146
  # ==================== Gradio 界面(Poker Skin) ====================
147
  with gr.Blocks(
 
195
  "- 长输出建议分步生成(先 GDL,再自然语言规则说明)。\n"
196
  "- 需要更强制约,可在 Prompt 中明确输出格式与边界。"
197
  )
198
+
199
  with gr.Group(elem_classes="side-card"):
200
  gr.Markdown("### 快捷操作")
201
  with gr.Row():
202
  clear_cache_btn = gr.Button("清空缓存", variant="secondary")
203
  clear_files_btn = gr.Button("清空文件", variant="secondary")
 
 
 
 
204
  cache_status = gr.Markdown("💾 缓存状态:正常", elem_classes="hint")
 
205
  file_status = gr.Markdown("📁 文件状态:未上传", elem_classes="hint")
206
 
207
+ # 右侧:聊天区(手动事件绑定 + 流式输出)
 
208
  with gr.Column(scale=2, min_width=MIN_WIDTH_RIGHT):
209
  with gr.Group(elem_classes="table"):
 
210
  chatbot = gr.Chatbot(
211
  height=CHATBOT_HEIGHT,
212
  type="messages",
213
  elem_classes="custom-chatbot",
 
214
  avatar_images=("landlord.png", "bot.png"),
215
  )
216
+ # 用 State 保存 messages 历史
217
+ chat_state = gr.State([]) # list[dict]: [{"role":"user","content":...}, {"role":"assistant","content":...}]
218
 
 
219
  user_input = gr.Textbox(
220
  placeholder="例如:设计一个适合3-5人的派对风格扑克游戏(请写清目标人群/时长/创新点)…",
221
  show_label=False,
 
225
  autofocus=True,
226
  )
227
 
 
228
  with gr.Row():
229
  send_btn = gr.Button("发送", variant="primary")
230
+ stop_info = gr.Markdown("", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
+ # ====== 核心:流式提交回调(生成器) ======
233
  def on_submit(user_text, history_msgs, files, custom_prompt, mode):
234
  user_text = (user_text or "").strip()
 
235
  if not user_text:
236
+ # 不提交空消息:输出不变
237
+ yield gr.update(), gr.update(), history_msgs
238
+ return
239
+
240
+ # 1) 立即显示“用户消息”
241
+ history = list(history_msgs or [])
242
+ history.append({"role": "user", "content": user_text})
243
+ yield history, "", history
244
+
245
+ # 2) 追加一个“空的助手气泡”,用于逐步填充
246
+ history.append({"role": "assistant", "content": ""})
247
+ yield history, "", history
248
+
249
+ # 3) 原生流式优先,否则退化为伪流式
250
+ tuples_hist = _messages_to_tuples(history)
251
+
252
+ if design_poker_game_stream is not None:
253
+ try:
254
+ for piece in design_poker_game_stream(
255
+ user_text, tuples_hist, files, custom_prompt, mode
256
+ ):
257
+ if not piece:
258
+ continue
259
+ history[-1]["content"] += str(piece)
260
+ # 每片刷新一次 UI(需要可做节流:攒到 N 字再 yield)
261
+ yield history, "", history
262
+ except Exception as e:
263
+ history[-1]["content"] += f"\n(流式出错){type(e).__name__}: {e}"
264
+ yield history, "", history
265
+ else:
266
+ # 伪流式:先算整段,再切片逐步输出
267
+ try:
268
+ full = design_poker_game(user_text, tuples_hist, files, custom_prompt, mode)
269
+ except Exception as e:
270
+ full = f"(出错){type(e).__name__}: {e}"
271
+ for piece in _chunk_fake_stream(str(full), step=40):
272
+ history[-1]["content"] += piece
273
+ yield history, "", history
274
+
275
+ # 4) 收尾:再 yield 一次,确保最终内容同步
276
+ yield history, "", history
277
+
278
+ # 绑定:回车提交(Enter=提交;Shift+Enter=换行由浏览器处理)
279
  user_input.submit(
280
  fn=on_submit,
281
  inputs=[user_input, chat_state, file_uploader, custom_prompt_box, prompt_mode],
 
283
  preprocess=True,
284
  )
285
 
286
+ # 绑定:点击“发送”
287
  send_btn.click(
288
  fn=on_submit,
289
  inputs=[user_input, chat_state, file_uploader, custom_prompt_box, prompt_mode],
 
291
  preprocess=True,
292
  )
293
 
294
+ # 导出对话
295
  with gr.Row():
296
  export_btn = gr.Button("导出对话(Markdown)", variant="secondary")
297
  export_file = gr.File(label="点击下载导出文件", interactive=False)
298
 
299
+ # 清空对话
300
  with gr.Row():
301
  clear_dialog_btn = gr.Button("清空对话", variant="secondary")
302
 
 
309
  outputs=[chatbot, user_input, chat_state],
310
  )
311
 
312
+ # ==================== 事件绑定(左侧) ====================
313
+ clear_cache_btn.click(fn=clear_cache, outputs=[cache_status])
314
+ clear_files_btn.click(fn=clear_files, outputs=[file_uploader])
315
+ file_uploader.change(fn=update_file_status, inputs=[file_uploader], outputs=[file_status])
316
 
317
+ # 导出按钮
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  def _export_wrapper(chat_history):
319
  try:
320
  path = export_history_to_markdown(chat_history)
 
323
  import tempfile
324
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".md")
325
  with open(tmp.name, 'w', encoding='utf-8') as f:
326
+ f.write(f"导出失败:{type(e).__name__}: {e}\n")
327
  return tmp.name
328
 
329
+ export_btn.click(fn=_export_wrapper, inputs=[chatbot], outputs=[export_file])
330
+
 
 
 
331
 
332
  # ==================== 启动应用 ====================
333
  if __name__ == "__main__":
334
+ # 更顺滑的流式 & 更高并发
335
+ demo.queue(concurrency_count=4, status_update_rate=0.2).launch(share=True, show_api=False)