sunzhongkai588 commited on
Commit
e12c950
·
1 Parent(s): 8c56dcc
Files changed (1) hide show
  1. app.py +164 -50
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import base64
 
2
  import mimetypes
3
  import os
4
  from pathlib import Path
@@ -11,6 +12,7 @@ DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", "ERNIE-4.5-VL-28B-A3B-Thinking")
11
  BASE_URL = os.getenv("BASE_URL","")
12
  api_key = os.getenv("ERNIE_API_KEY","")
13
 
 
14
  CUSTOM_CSS = """
15
  body {
16
  background: radial-gradient(circle at top, #fdfbff 0%, #e7ecf7 45%, #dfe6f5 100%);
@@ -29,6 +31,18 @@ body {
29
  margin-bottom: 0;
30
  font-weight: 500;
31
  }
 
 
 
 
 
 
 
 
 
 
 
 
32
  #examples-panel {
33
  margin-top: 20px;
34
  padding: 18px 22px;
@@ -76,12 +90,48 @@ body {
76
  border: 1px solid rgba(15, 23, 42, 0.1);
77
  box-shadow: 0 25px 60px rgba(15, 23, 42, 0.12);
78
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  @media (prefers-color-scheme: dark) {
81
  body {
82
  background: radial-gradient(circle at top, #1f264b 0%, #0f172a 45%, #040713 100%);
83
  color: #ecf2ff;
84
  }
 
 
 
85
  #examples-panel {
86
  border: 1px solid rgba(255, 255, 255, 0.05);
87
  background: rgba(8, 13, 30, 0.85);
@@ -100,6 +150,25 @@ body {
100
  border: 1px solid rgba(99, 102, 241, 0.25);
101
  box-shadow: 0 25px 70px rgba(2, 6, 23, 0.7);
102
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
104
  """
105
 
@@ -130,6 +199,27 @@ def _text_content(text: str) -> Dict[str, Any]:
130
  def _message(role: str, content: Any) -> Dict[str, Any]:
131
  return {"role": role, "content": content}
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  def _build_user_message(message: Dict[str, Any]) -> Dict[str, Any]:
134
  files = message.get("files") or []
135
  text = (message.get("text") or "").strip()
@@ -176,38 +266,58 @@ def stream_response(message: Dict[str, Any], history: List[Dict[str, Any]], mode
176
  messages=messages,
177
  stream=True
178
  )
179
- thinking = "" # 收集推理内容
180
- answer = "" # 收集回答内容
181
- thinking_complete = False # 推理是否完成的标记
182
 
183
- # 第一阶段:只输出Thinking
184
- for chunk in stream:
185
- delta = chunk.choices[0].delta
186
- # 处理推理内容
187
- if hasattr(delta, "reasoning_content") and delta.reasoning_content:
188
- thinking += delta.reasoning_content
189
- # 直接yield Thinking(不带Answer)
190
- yield f"Thinking:\n{thinking}\n\n"
191
- # 检测到回答内容,标记推理结束
192
- if hasattr(delta, "content") and delta.content:
193
- answer += delta.content
194
- thinking_complete = True
195
- break
196
-
197
- # 若推理未完成但流已结束,强制标记完成
198
- if not thinking_complete:
199
- thinking_complete = True
200
-
201
- # 第二阶段:先输出完整Thinking,再输出Answer(流式)
202
  for chunk in stream:
203
  delta = chunk.choices[0].delta
204
- if hasattr(delta, "content") and delta.content:
205
- answer += delta.content
206
- # 直接yield 完整Thinking + 当前Answer
207
- yield f"Thinking:\n{thinking}\n\nAnswer:\n{answer}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  except Exception as e:
209
  yield f"Failed to get response: {e}"
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
  def build_demo() -> gr.Blocks:
213
  theme = gr.themes.Soft(primary_hue="violet", secondary_hue="cyan", neutral_hue="slate")
@@ -224,7 +334,23 @@ def build_demo() -> gr.Blocks:
224
  """,
225
  elem_id="hero-text",
226
  )
 
 
 
 
 
 
 
 
 
 
227
 
 
 
 
 
 
 
228
  chatbot = gr.Chatbot(
229
  type="messages",
230
  allow_tags=["think"],
@@ -233,12 +359,6 @@ def build_demo() -> gr.Blocks:
233
  bubble_full_width=False,
234
  show_copy_button=True,
235
  )
236
- textbox = gr.MultimodalTextbox(
237
- show_label=False,
238
- placeholder="Enter text, or upload one or more images...",
239
- file_types=["image","video"],
240
- file_count="multiple"
241
- )
242
 
243
  examples = [
244
  {
@@ -246,28 +366,22 @@ def build_demo() -> gr.Blocks:
246
  "files": ["examples/case1.png"]
247
  },
248
  {
249
- "text": "图中实际上有几个真人",
250
  "files": ["examples/case2.png"]
251
  },
252
  ]
253
 
254
- with gr.Row(elem_id="examples-panel"):
255
- with gr.Column(scale=1):
256
- gr.Markdown(
257
- """
258
- <h4>Quick Examples</h4>
259
- <p>选择一个示例,即可快速体验 ERNIE 的视觉 + 推理能力。</p>
260
- """,
261
- elem_id="examples-copy",
262
- )
263
- with gr.Column(scale=2):
264
- gr.Examples(
265
- examples=examples,
266
- inputs=textbox,
267
- label=None,
268
- examples_per_page=4,
269
- elem_id="examples-grid",
270
- )
271
 
272
  with gr.Column(elem_id="chat-wrapper"):
273
  chat_interface = gr.ChatInterface(
 
1
  import base64
2
+ import html
3
  import mimetypes
4
  import os
5
  from pathlib import Path
 
12
  BASE_URL = os.getenv("BASE_URL","")
13
  api_key = os.getenv("ERNIE_API_KEY","")
14
 
15
+
16
  CUSTOM_CSS = """
17
  body {
18
  background: radial-gradient(circle at top, #fdfbff 0%, #e7ecf7 45%, #dfe6f5 100%);
 
31
  margin-bottom: 0;
32
  font-weight: 500;
33
  }
34
+ #model-link {
35
+ margin-top: 6px;
36
+ font-size: 0.95rem;
37
+ }
38
+ #model-link a {
39
+ color: #4c1d95;
40
+ text-decoration: none;
41
+ font-weight: 500;
42
+ }
43
+ #model-link a:hover {
44
+ text-decoration: underline;
45
+ }
46
  #examples-panel {
47
  margin-top: 20px;
48
  padding: 18px 22px;
 
90
  border: 1px solid rgba(15, 23, 42, 0.1);
91
  box-shadow: 0 25px 60px rgba(15, 23, 42, 0.12);
92
  }
93
+ .ernie-section {
94
+ border-radius: 18px;
95
+ margin-bottom: 14px;
96
+ padding: 16px 18px;
97
+ border: 1px solid rgba(15, 23, 42, 0.1);
98
+ background: rgba(255, 255, 255, 0.95);
99
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
100
+ }
101
+ .ernie-section-header {
102
+ font-size: 0.85rem;
103
+ text-transform: uppercase;
104
+ letter-spacing: 0.08em;
105
+ font-weight: 600;
106
+ color: rgba(15, 23, 42, 0.65);
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 6px;
110
+ }
111
+ .ernie-section-body {
112
+ margin-top: 10px;
113
+ font-size: 1rem;
114
+ color: rgba(15, 23, 42, 0.92);
115
+ white-space: pre-wrap;
116
+ line-height: 1.55;
117
+ }
118
+ .ernie-thinking {
119
+ border-color: rgba(79, 70, 229, 0.35);
120
+ background: rgba(129, 140, 248, 0.08);
121
+ }
122
+ .ernie-answer {
123
+ border-color: rgba(16, 185, 129, 0.35);
124
+ background: rgba(110, 231, 183, 0.08);
125
+ }
126
 
127
  @media (prefers-color-scheme: dark) {
128
  body {
129
  background: radial-gradient(circle at top, #1f264b 0%, #0f172a 45%, #040713 100%);
130
  color: #ecf2ff;
131
  }
132
+ #model-link a {
133
+ color: #a5b4fc;
134
+ }
135
  #examples-panel {
136
  border: 1px solid rgba(255, 255, 255, 0.05);
137
  background: rgba(8, 13, 30, 0.85);
 
150
  border: 1px solid rgba(99, 102, 241, 0.25);
151
  box-shadow: 0 25px 70px rgba(2, 6, 23, 0.7);
152
  }
153
+ .ernie-section {
154
+ border: 1px solid rgba(255, 255, 255, 0.08);
155
+ background: rgba(15, 23, 42, 0.85);
156
+ box-shadow: 0 10px 30px rgba(2, 6, 23, 0.55);
157
+ }
158
+ .ernie-section-header {
159
+ color: rgba(236, 242, 255, 0.75);
160
+ }
161
+ .ernie-section-body {
162
+ color: rgba(248, 250, 255, 0.95);
163
+ }
164
+ .ernie-answer {
165
+ border-color: rgba(45, 212, 191, 0.45);
166
+ background: rgba(8, 47, 56, 0.65);
167
+ }
168
+ .ernie-thinking {
169
+ border-color: rgba(165, 180, 252, 0.4);
170
+ background: rgba(30, 27, 75, 0.65);
171
+ }
172
  }
173
  """
174
 
 
199
  def _message(role: str, content: Any) -> Dict[str, Any]:
200
  return {"role": role, "content": content}
201
 
202
+ def _format_sections(thinking: str, answer: str | None = None) -> str:
203
+ """Render Thinking/Answer blocks with HTML so the chatbot can style them."""
204
+ def _build_block(kind: str, label: str, text: str, icon: str) -> str:
205
+ text = (text or "").strip()
206
+ if not text:
207
+ return ""
208
+ escaped = html.escape(text)
209
+ return (
210
+ f'<div class="ernie-section ernie-{kind}">'
211
+ f'<div class="ernie-section-header">{icon} {label}</div>'
212
+ f'<div class="ernie-section-body">{escaped}</div>'
213
+ "</div>"
214
+ )
215
+
216
+ sections = [
217
+ _build_block("thinking", "Thinking", thinking, "🧠"),
218
+ _build_block("answer", "Answer", answer, "✨") if answer is not None else "",
219
+ ]
220
+ rendered = "".join(section for section in sections if section)
221
+ return rendered
222
+
223
  def _build_user_message(message: Dict[str, Any]) -> Dict[str, Any]:
224
  files = message.get("files") or []
225
  text = (message.get("text") or "").strip()
 
266
  messages=messages,
267
  stream=True
268
  )
269
+ thinking_parts: List[str] = []
270
+ answer_parts: List[str] = []
271
+ answer_started = False
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  for chunk in stream:
274
  delta = chunk.choices[0].delta
275
+
276
+ if getattr(delta, "reasoning_content", None):
277
+ thinking_parts.append(delta.reasoning_content)
278
+
279
+ if getattr(delta, "content", None):
280
+ answer_started = True
281
+ answer_parts.append(delta.content)
282
+
283
+ thinking_text = "".join(thinking_parts)
284
+ answer_text = "".join(answer_parts) if answer_parts else None
285
+
286
+ if answer_started:
287
+ rendered = _format_sections(thinking_text, answer_text)
288
+ else:
289
+ rendered = _format_sections(thinking_text)
290
+
291
+ if rendered:
292
+ yield rendered
293
+
294
+ if not answer_started and thinking_parts:
295
+ # 流结束但模型未返回Answer时,至少保证Thinking被展示完全
296
+ rendered = _format_sections("".join(thinking_parts))
297
+ if rendered:
298
+ yield rendered
299
  except Exception as e:
300
  yield f"Failed to get response: {e}"
301
 
302
+ def run_example(message: Dict[str, Any], history: List[Dict[str, Any]] | None = None):
303
+ """
304
+ 用于 Examples 点击时直接走大模型。
305
+ - 输入还是 ChatInterface 那种 message dict:{"text": ..., "files": [...]}
306
+ - history 是 Chatbot 当前的消息列表(type="messages")
307
+ - 输出改成 Chatbot 需要的消息列表:[{role, content}, ...]
308
+ """
309
+ history = history or []
310
+
311
+ # 直接复用你现有的流式函数,只是把它返回的 HTML 包一层 messages
312
+ for rendered in stream_response(message, history):
313
+ # 这里只简单把 user 文本展示出来;图片就当“上下文里有了”,不专门渲染
314
+ user_text = (message.get("text") or "").strip() or "[Example]"
315
+ display_history = history + [
316
+ {"role": "user", "content": user_text},
317
+ {"role": "assistant", "content": rendered},
318
+ ]
319
+ # 关键:对 Chatbot 来说,返回值要是「完整的消息列表」
320
+ yield display_history
321
 
322
  def build_demo() -> gr.Blocks:
323
  theme = gr.themes.Soft(primary_hue="violet", secondary_hue="cyan", neutral_hue="slate")
 
334
  """,
335
  elem_id="hero-text",
336
  )
337
+ gr.Markdown(
338
+ """
339
+ <p id="model-link">
340
+ Here is a link to the model on Hugging Face:
341
+ <a href="https://huggingface.co/baidu/ERNIE-4.5-VL-28B-A3B-Thinking" target="_blank" rel="noopener">
342
+ Hugging Face - ERNIE-4.5-VL-28B-A3B-Thinking
343
+ </a>
344
+ </p>
345
+ """
346
+ )
347
 
348
+ textbox = gr.MultimodalTextbox(
349
+ show_label=False,
350
+ placeholder="Enter text, or upload one or more images...",
351
+ file_types=["image","video"],
352
+ file_count="multiple"
353
+ )
354
  chatbot = gr.Chatbot(
355
  type="messages",
356
  allow_tags=["think"],
 
359
  bubble_full_width=False,
360
  show_copy_button=True,
361
  )
 
 
 
 
 
 
362
 
363
  examples = [
364
  {
 
366
  "files": ["examples/case1.png"]
367
  },
368
  {
369
+ "text": "How many real people are actually in the picture?",
370
  "files": ["examples/case2.png"]
371
  },
372
  ]
373
 
374
+ with gr.Column(elem_id="examples-panel"):
375
+ gr.Examples(
376
+ examples=examples,
377
+ inputs=textbox,
378
+ label=None,
379
+ examples_per_page=4,
380
+ elem_id="examples-grid",
381
+ fn=run_example, # 点击示例时,直接走大模型
382
+ outputs=chatbot,
383
+ run_on_click=True,
384
+ )
 
 
 
 
 
 
385
 
386
  with gr.Column(elem_id="chat-wrapper"):
387
  chat_interface = gr.ChatInterface(