cherrytest commited on
Commit
00b9f0d
·
1 Parent(s): 9ce49de
app.py ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ import time
3
+ import json
4
+ import gradio as gr
5
+ import modelscope_studio.components.antd as antd
6
+ import modelscope_studio.components.antdx as antdx
7
+ import modelscope_studio.components.base as ms
8
+ import modelscope_studio.components.pro as pro
9
+ from openai import OpenAI
10
+ from config import DEFAULT_LOCALE, DEFAULT_SETTINGS, DEFAULT_THEME, DEFAULT_SUGGESTIONS, save_history, get_text, user_config, bot_config, welcome_config, api_key, MODEL_OPTIONS_MAP
11
+ from ui_components.logo import Logo
12
+ from ui_components.settings_header import SettingsHeader
13
+ from ui_components.thinking_button import ThinkingButton
14
+
15
+ client = OpenAI(api_key=api_key,
16
+ base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
17
+
18
+
19
+ def format_history(history, sys_prompt):
20
+ messages = [{
21
+ "role": "system",
22
+ "content": sys_prompt,
23
+ }]
24
+ for item in history:
25
+ if item["role"] == "user":
26
+ messages.append({"role": "user", "content": item["content"]})
27
+ elif item["role"] == "assistant":
28
+ contents = [{
29
+ "type": "text",
30
+ "text": content["content"]
31
+ } for content in item["content"] if content["type"] == "text"]
32
+ messages.append({
33
+ "role":
34
+ "assistant",
35
+ "content":
36
+ contents[0]["text"] if len(contents) > 0 else ""
37
+ })
38
+ return messages
39
+
40
+
41
+ class Gradio_Events:
42
+
43
+ @staticmethod
44
+ def submit(state_value):
45
+
46
+ history = state_value["conversation_contexts"][
47
+ state_value["conversation_id"]]["history"]
48
+ settings = state_value["conversation_contexts"][
49
+ state_value["conversation_id"]]["settings"]
50
+ enable_thinking = state_value["conversation_contexts"][
51
+ state_value["conversation_id"]]["enable_thinking"]
52
+ model = settings.get("model")
53
+ messages = format_history(history,
54
+ sys_prompt=settings.get("sys_prompt", ""))
55
+
56
+ history.append({
57
+ "role":
58
+ "assistant",
59
+ "content": [],
60
+ "key":
61
+ str(uuid.uuid4()),
62
+ "header":
63
+ MODEL_OPTIONS_MAP.get(model, {}).get("label", None),
64
+ "loading":
65
+ True,
66
+ "status":
67
+ "pending"
68
+ })
69
+
70
+ yield {
71
+ chatbot: gr.update(value=history),
72
+ state: gr.update(value=state_value),
73
+ }
74
+ try:
75
+ response = client.chat.completions.create(
76
+ model=model,
77
+ messages=messages,
78
+ stream=True,
79
+ extra_body={
80
+ "enable_thinking":
81
+ enable_thinking,
82
+ **({
83
+ "thinking_budget":
84
+ settings.get("thinking_budget", 1) * 1024
85
+ } if enable_thinking else {})
86
+ })
87
+ start_time = time.time()
88
+ reasoning_content = ""
89
+ answer_content = ""
90
+ is_thinking = False
91
+ is_answering = False
92
+ contents = [None, None]
93
+ for chunk in response:
94
+ if not chunk.choices:
95
+ pass
96
+ else:
97
+ delta = chunk.choices[0].delta
98
+ if hasattr(
99
+ delta,
100
+ 'reasoning_content') and delta.reasoning_content:
101
+ if not is_thinking:
102
+ contents[0] = {
103
+ "type": "tool",
104
+ "content": "",
105
+ "options": {
106
+ "title": get_text("Thinking...", "思考中..."),
107
+ "status": "pending"
108
+ },
109
+ "copyable": False,
110
+ "editable": False
111
+ }
112
+ is_thinking = True
113
+ reasoning_content += delta.reasoning_content
114
+ else:
115
+ if hasattr(delta, 'content') and delta.content != None:
116
+ if not is_answering:
117
+ thought_cost_time = "{:.2f}".format(
118
+ time.time() - start_time)
119
+ if contents[0]:
120
+ contents[0]["options"]["title"] = get_text(
121
+ f"End of Thought ({thought_cost_time}s)",
122
+ f"已深度思考 (用时{thought_cost_time}s)")
123
+ contents[0]["options"]["status"] = "done"
124
+ contents[1] = {
125
+ "type": "text",
126
+ "content": "",
127
+ }
128
+
129
+ is_answering = True
130
+ answer_content += delta.content
131
+
132
+ if contents[0]:
133
+ contents[0]["content"] = reasoning_content
134
+ if contents[1]:
135
+ contents[1]["content"] = answer_content
136
+ history[-1]["content"] = [
137
+ content for content in contents if content
138
+ ]
139
+ history[-1]["loading"] = False
140
+ yield {
141
+ chatbot: gr.update(value=history),
142
+ state: gr.update(value=state_value)
143
+ }
144
+ history[-1]["status"] = "done"
145
+ cost_time = "{:.2f}".format(time.time() - start_time)
146
+ history[-1]["footer"] = get_text(f"{cost_time}s",
147
+ f"用时{cost_time}s")
148
+ yield {
149
+ chatbot: gr.update(value=history),
150
+ state: gr.update(value=state_value),
151
+ }
152
+ except Exception as e:
153
+ history[-1]["loading"] = False
154
+ history[-1]["status"] = "done"
155
+ history[-1]["content"] += [{
156
+ "type":
157
+ "text",
158
+ "content":
159
+ f'<span style="color: var(--color-red-500)">{str(e)}</span>'
160
+ }]
161
+ yield {
162
+ chatbot: gr.update(value=history),
163
+ state: gr.update(value=state_value)
164
+ }
165
+ raise e
166
+
167
+ @staticmethod
168
+ def add_message(input_value, settings_form_value, thinking_btn_state_value,
169
+ state_value):
170
+ if not state_value["conversation_id"]:
171
+ random_id = str(uuid.uuid4())
172
+ history = []
173
+ state_value["conversation_id"] = random_id
174
+ state_value["conversation_contexts"][
175
+ state_value["conversation_id"]] = {
176
+ "history": history
177
+ }
178
+ state_value["conversations"].append({
179
+ "label": input_value,
180
+ "key": random_id
181
+ })
182
+
183
+ history = state_value["conversation_contexts"][
184
+ state_value["conversation_id"]]["history"]
185
+
186
+ state_value["conversation_contexts"][
187
+ state_value["conversation_id"]] = {
188
+ "history": history,
189
+ "settings": settings_form_value,
190
+ "enable_thinking": thinking_btn_state_value["enable_thinking"]
191
+ }
192
+ history.append({
193
+ "role": "user",
194
+ "content": input_value,
195
+ "key": str(uuid.uuid4())
196
+ })
197
+ yield Gradio_Events.preprocess_submit(clear_input=True)(state_value)
198
+
199
+ try:
200
+ for chunk in Gradio_Events.submit(state_value):
201
+ yield chunk
202
+ except Exception as e:
203
+ raise e
204
+ finally:
205
+ yield Gradio_Events.postprocess_submit(state_value)
206
+
207
+ @staticmethod
208
+ def preprocess_submit(clear_input=True):
209
+
210
+ def preprocess_submit_handler(state_value):
211
+ history = state_value["conversation_contexts"][
212
+ state_value["conversation_id"]]["history"]
213
+ return {
214
+ **({
215
+ input:
216
+ gr.update(value=None, loading=True) if clear_input else gr.update(loading=True),
217
+ } if clear_input else {}),
218
+ conversations:
219
+ gr.update(active_key=state_value["conversation_id"],
220
+ items=list(
221
+ map(
222
+ lambda item: {
223
+ **item,
224
+ "disabled":
225
+ True if item["key"] != state_value[
226
+ "conversation_id"] else False,
227
+ }, state_value["conversations"]))),
228
+ add_conversation_btn:
229
+ gr.update(disabled=True),
230
+ clear_btn:
231
+ gr.update(disabled=True),
232
+ conversation_delete_menu_item:
233
+ gr.update(disabled=True),
234
+ chatbot:
235
+ gr.update(value=history,
236
+ bot_config=bot_config(
237
+ disabled_actions=['edit', 'retry', 'delete']),
238
+ user_config=user_config(
239
+ disabled_actions=['edit', 'delete'])),
240
+ state:
241
+ gr.update(value=state_value),
242
+ }
243
+
244
+ return preprocess_submit_handler
245
+
246
+ @staticmethod
247
+ def postprocess_submit(state_value):
248
+ history = state_value["conversation_contexts"][
249
+ state_value["conversation_id"]]["history"]
250
+ return {
251
+ input:
252
+ gr.update(loading=False),
253
+ conversation_delete_menu_item:
254
+ gr.update(disabled=False),
255
+ clear_btn:
256
+ gr.update(disabled=False),
257
+ conversations:
258
+ gr.update(items=state_value["conversations"]),
259
+ add_conversation_btn:
260
+ gr.update(disabled=False),
261
+ chatbot:
262
+ gr.update(value=history,
263
+ bot_config=bot_config(),
264
+ user_config=user_config()),
265
+ state:
266
+ gr.update(value=state_value),
267
+ }
268
+
269
+ @staticmethod
270
+ def cancel(state_value):
271
+ history = state_value["conversation_contexts"][
272
+ state_value["conversation_id"]]["history"]
273
+ history[-1]["loading"] = False
274
+ history[-1]["status"] = "done"
275
+ history[-1]["footer"] = get_text("Chat completion paused", "对话已暂停")
276
+ return Gradio_Events.postprocess_submit(state_value)
277
+
278
+ @staticmethod
279
+ def delete_message(state_value, e: gr.EventData):
280
+ index = e._data["payload"][0]["index"]
281
+ history = state_value["conversation_contexts"][
282
+ state_value["conversation_id"]]["history"]
283
+ history = history[:index] + history[index + 1:]
284
+
285
+ state_value["conversation_contexts"][
286
+ state_value["conversation_id"]]["history"] = history
287
+
288
+ return gr.update(value=state_value)
289
+
290
+ @staticmethod
291
+ def edit_message(state_value, chatbot_value, e: gr.EventData):
292
+ index = e._data["payload"][0]["index"]
293
+ history = state_value["conversation_contexts"][
294
+ state_value["conversation_id"]]["history"]
295
+ history[index]["content"] = chatbot_value[index]["content"]
296
+ return gr.update(value=state_value)
297
+
298
+ @staticmethod
299
+ def regenerate_message(settings_form_value, thinking_btn_state_value,
300
+ state_value, e: gr.EventData):
301
+ index = e._data["payload"][0]["index"]
302
+ history = state_value["conversation_contexts"][
303
+ state_value["conversation_id"]]["history"]
304
+ history = history[:index]
305
+
306
+ state_value["conversation_contexts"][
307
+ state_value["conversation_id"]] = {
308
+ "history": history,
309
+ "settings": settings_form_value,
310
+ "enable_thinking": thinking_btn_state_value["enable_thinking"]
311
+ }
312
+
313
+ yield Gradio_Events.preprocess_submit()(state_value)
314
+ try:
315
+ for chunk in Gradio_Events.submit(state_value):
316
+ yield chunk
317
+ except Exception as e:
318
+ raise e
319
+ finally:
320
+ yield Gradio_Events.postprocess_submit(state_value)
321
+
322
+ @staticmethod
323
+ def select_suggestion(input_value, e: gr.EventData):
324
+ input_value = input_value[:-1] + e._data["payload"][0]
325
+ return gr.update(value=input_value)
326
+
327
+ @staticmethod
328
+ def apply_prompt(e: gr.EventData):
329
+ return gr.update(value=e._data["payload"][0]["value"]["description"])
330
+
331
+ @staticmethod
332
+ def new_chat(state_value):
333
+ if not state_value["conversation_id"]:
334
+ return gr.skip()
335
+ state_value["conversation_id"] = ""
336
+ return gr.update(active_key=state_value["conversation_id"]), gr.update(
337
+ value=None), gr.update(value=DEFAULT_SETTINGS), gr.update(
338
+ value=state_value)
339
+
340
+ @staticmethod
341
+ def select_conversation(state_value, e: gr.EventData):
342
+ active_key = e._data["payload"][0]
343
+ if state_value["conversation_id"] == active_key or (
344
+ active_key not in state_value["conversation_contexts"]):
345
+ return gr.skip()
346
+ state_value["conversation_id"] = active_key
347
+ return gr.update(active_key=active_key), gr.update(
348
+ value=state_value["conversation_contexts"][active_key]["history"]
349
+ ), gr.update(value=state_value["conversation_contexts"][active_key]
350
+ ["settings"]), gr.update(value=state_value)
351
+
352
+ @staticmethod
353
+ def click_conversation_menu(state_value, e: gr.EventData):
354
+ conversation_id = e._data["payload"][0]["key"]
355
+ operation = e._data["payload"][1]["key"]
356
+ if operation == "delete":
357
+ del state_value["conversation_contexts"][conversation_id]
358
+
359
+ state_value["conversations"] = [
360
+ item for item in state_value["conversations"]
361
+ if item["key"] != conversation_id
362
+ ]
363
+
364
+ if state_value["conversation_id"] == conversation_id:
365
+ state_value["conversation_id"] = ""
366
+ return gr.update(
367
+ items=state_value["conversations"],
368
+ active_key=state_value["conversation_id"]), gr.update(
369
+ value=None), gr.update(value=state_value)
370
+ else:
371
+ return gr.update(
372
+ items=state_value["conversations"]), gr.skip(), gr.update(
373
+ value=state_value)
374
+ return gr.skip()
375
+
376
+ @staticmethod
377
+ def toggle_settings_header(settings_header_state_value):
378
+ settings_header_state_value[
379
+ "open"] = not settings_header_state_value["open"]
380
+ return gr.update(value=settings_header_state_value)
381
+
382
+ @staticmethod
383
+ def apply_thinking_btn_state(thinking_btn_state_value, state_value):
384
+ if not state_value["conversation_id"]:
385
+ return gr.skip()
386
+ state_value["conversation_contexts"][state_value["conversation_id"]][
387
+ "enable_thinking"] = thinking_btn_state_value["enable_thinking"]
388
+ return gr.update(value=state_value)
389
+
390
+ @staticmethod
391
+ def clear_conversation_history(state_value):
392
+ if not state_value["conversation_id"]:
393
+ return gr.skip()
394
+ state_value["conversation_contexts"][
395
+ state_value["conversation_id"]]["history"] = []
396
+ return gr.update(value=None), gr.update(value=state_value)
397
+
398
+ @staticmethod
399
+ def update_browser_state(state_value):
400
+
401
+ return gr.update(value=dict(
402
+ conversations=state_value["conversations"],
403
+ conversation_contexts=state_value["conversation_contexts"]))
404
+
405
+ @staticmethod
406
+ def apply_browser_state(browser_state_value, state_value):
407
+ state_value["conversations"] = browser_state_value["conversations"]
408
+ state_value["conversation_contexts"] = browser_state_value[
409
+ "conversation_contexts"]
410
+ return gr.update(
411
+ items=browser_state_value["conversations"]), gr.update(
412
+ value=state_value)
413
+
414
+
415
+ css = """
416
+ .gradio-container {
417
+ padding: 0 !important;
418
+ }
419
+
420
+ .gradio-container > main.fillable {
421
+ padding: 0 !important;
422
+ }
423
+
424
+ #chatbot {
425
+ height: calc(100vh - 21px - 16px);
426
+ max-height: 1500px;
427
+ }
428
+
429
+ #chatbot .chatbot-conversations {
430
+ height: 100vh;
431
+ background-color: var(--ms-gr-ant-color-bg-layout);
432
+ padding-left: 4px;
433
+ padding-right: 4px;
434
+ }
435
+
436
+
437
+ #chatbot .chatbot-conversations .chatbot-conversations-list {
438
+ padding-left: 0;
439
+ padding-right: 0;
440
+ }
441
+
442
+ #chatbot .chatbot-chat {
443
+ padding: 32px;
444
+ padding-bottom: 0;
445
+ height: 100%;
446
+ }
447
+
448
+ @media (max-width: 768px) {
449
+ #chatbot .chatbot-chat {
450
+ padding: 0;
451
+ }
452
+ }
453
+
454
+ #chatbot .chatbot-chat .chatbot-chat-messages {
455
+ flex: 1;
456
+ }
457
+
458
+
459
+ #chatbot .setting-form-thinking-budget .ms-gr-ant-form-item-control-input-content {
460
+ display: flex;
461
+ flex-wrap: wrap;
462
+ }
463
+ """
464
+
465
+ model_options_map_json = json.dumps(MODEL_OPTIONS_MAP)
466
+ js = "function init() { window.MODEL_OPTIONS_MAP=" + model_options_map_json + "}"
467
+
468
+ with gr.Blocks(css=css, js=js, fill_width=True) as demo:
469
+ state = gr.State({
470
+ "conversation_contexts": {},
471
+ "conversations": [],
472
+ "conversation_id": "",
473
+ })
474
+
475
+ with ms.Application(), antdx.XProvider(
476
+ theme=DEFAULT_THEME, locale=DEFAULT_LOCALE), ms.AutoLoading():
477
+ with antd.Row(gutter=[20, 20], wrap=False, elem_id="chatbot"):
478
+ # Left Column
479
+ with antd.Col(md=dict(flex="0 0 260px", span=24, order=0),
480
+ span=0,
481
+ order=1):
482
+ with ms.Div(elem_classes="chatbot-conversations"):
483
+ with antd.Flex(vertical=True,
484
+ gap="small",
485
+ elem_style=dict(height="100%")):
486
+ # Logo
487
+ Logo()
488
+
489
+ # New Conversation Button
490
+ with antd.Button(value=None,
491
+ color="primary",
492
+ variant="filled",
493
+ block=True) as add_conversation_btn:
494
+ ms.Text(get_text("New Conversation", "新建对话"))
495
+ with ms.Slot("icon"):
496
+ antd.Icon("PlusOutlined")
497
+
498
+ # Conversations List
499
+ with antdx.Conversations(
500
+ elem_classes="chatbot-conversations-list",
501
+ ) as conversations:
502
+ with ms.Slot('menu.items'):
503
+ with antd.Menu.Item(
504
+ label="Delete", key="delete",
505
+ danger=True
506
+ ) as conversation_delete_menu_item:
507
+ with ms.Slot("icon"):
508
+ antd.Icon("DeleteOutlined")
509
+ # Right Column
510
+ with antd.Col(flex=1, elem_style=dict(height="100%")):
511
+ with antd.Flex(vertical=True,
512
+ gap="small",
513
+ elem_classes="chatbot-chat"):
514
+ # Chatbot
515
+ chatbot = pro.Chatbot(elem_classes="chatbot-chat-messages",
516
+ height=0,
517
+ welcome_config=welcome_config(),
518
+ user_config=user_config(),
519
+ bot_config=bot_config())
520
+
521
+ # Input
522
+ with antdx.Suggestion(
523
+ items=DEFAULT_SUGGESTIONS,
524
+ # onKeyDown Handler in Javascript
525
+ should_trigger="""(e, { onTrigger, onKeyDown }) => {
526
+ switch(e.key) {
527
+ case '/':
528
+ onTrigger()
529
+ break
530
+ case 'ArrowRight':
531
+ case 'ArrowLeft':
532
+ case 'ArrowUp':
533
+ case 'ArrowDown':
534
+ break;
535
+ default:
536
+ onTrigger(false)
537
+ }
538
+ onKeyDown(e)
539
+ }""") as suggestion:
540
+ with ms.Slot("children"):
541
+ with antdx.Sender(placeholder=get_text(
542
+ "Enter \"/\" to get suggestions",
543
+ "输入 \"/\" 获取提示"), ) as input:
544
+ with ms.Slot("header"):
545
+ settings_header_state, settings_form = SettingsHeader(
546
+ )
547
+ with ms.Slot("prefix"):
548
+ with antd.Flex(
549
+ gap=4,
550
+ wrap=True,
551
+ elem_style=dict(maxWidth='40vw')):
552
+ with antd.Button(
553
+ value=None,
554
+ type="text") as setting_btn:
555
+ with ms.Slot("icon"):
556
+ antd.Icon("SettingOutlined")
557
+ with antd.Button(
558
+ value=None,
559
+ type="text") as clear_btn:
560
+ with ms.Slot("icon"):
561
+ antd.Icon("ClearOutlined")
562
+ thinking_btn_state = ThinkingButton()
563
+
564
+ # Events Handler
565
+ # Browser State Handler
566
+ if save_history:
567
+ browser_state = gr.BrowserState(
568
+ {
569
+ "conversation_contexts": {},
570
+ "conversations": [],
571
+ },
572
+ storage_key="qwen3_chat_demo_storage")
573
+ state.change(fn=Gradio_Events.update_browser_state,
574
+ inputs=[state],
575
+ outputs=[browser_state])
576
+
577
+ demo.load(fn=Gradio_Events.apply_browser_state,
578
+ inputs=[browser_state, state],
579
+ outputs=[conversations, state])
580
+
581
+ # Conversations Handler
582
+ add_conversation_btn.click(
583
+ fn=Gradio_Events.new_chat,
584
+ inputs=[state],
585
+ outputs=[conversations, chatbot, settings_form, state])
586
+ conversations.active_change(
587
+ fn=Gradio_Events.select_conversation,
588
+ inputs=[state],
589
+ outputs=[conversations, chatbot, settings_form, state])
590
+ conversations.menu_click(fn=Gradio_Events.click_conversation_menu,
591
+ inputs=[state],
592
+ outputs=[conversations, chatbot, state])
593
+ # Chatbot Handler
594
+ chatbot.welcome_prompt_select(fn=Gradio_Events.apply_prompt,
595
+ outputs=[input])
596
+
597
+ chatbot.delete(fn=Gradio_Events.delete_message,
598
+ inputs=[state],
599
+ outputs=[state])
600
+ chatbot.edit(fn=Gradio_Events.edit_message,
601
+ inputs=[state, chatbot],
602
+ outputs=[state])
603
+
604
+ regenerating_event = chatbot.retry(
605
+ fn=Gradio_Events.regenerate_message,
606
+ inputs=[settings_form, thinking_btn_state, state],
607
+ outputs=[
608
+ input, clear_btn, conversation_delete_menu_item,
609
+ add_conversation_btn, conversations, chatbot, state
610
+ ])
611
+
612
+ # Input Handler
613
+ submit_event = input.submit(
614
+ fn=Gradio_Events.add_message,
615
+ inputs=[input, settings_form, thinking_btn_state, state],
616
+ outputs=[
617
+ input, clear_btn, conversation_delete_menu_item,
618
+ add_conversation_btn, conversations, chatbot, state
619
+ ])
620
+ input.cancel(fn=Gradio_Events.cancel,
621
+ inputs=[state],
622
+ outputs=[
623
+ input, conversation_delete_menu_item, clear_btn,
624
+ conversations, add_conversation_btn, chatbot, state
625
+ ],
626
+ cancels=[submit_event, regenerating_event],
627
+ queue=False)
628
+ # Input Actions Handler
629
+ thinking_btn_state.change(fn=Gradio_Events.apply_thinking_btn_state,
630
+ inputs=[thinking_btn_state, state],
631
+ outputs=[state])
632
+ setting_btn.click(fn=Gradio_Events.toggle_settings_header,
633
+ inputs=[settings_header_state],
634
+ outputs=[settings_header_state])
635
+ clear_btn.click(fn=Gradio_Events.clear_conversation_history,
636
+ inputs=[state],
637
+ outputs=[chatbot, state])
638
+ suggestion.select(fn=Gradio_Events.select_suggestion,
639
+ inputs=[input],
640
+ outputs=[input])
641
+
642
+ if __name__ == "__main__":
643
+ demo.queue(default_concurrency_limit=100,
644
+ max_size=100).launch(ssr_mode=False, max_threads=100)
config.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from modelscope_studio.components.pro.chatbot import ChatbotActionConfig, ChatbotBotConfig, ChatbotUserConfig, ChatbotWelcomeConfig
3
+
4
+ # Env
5
+ is_cn = os.getenv('MODELSCOPE_ENVIRONMENT') == 'studio'
6
+ api_key = os.getenv('API_KEY')
7
+
8
+
9
+ def get_text(text: str, cn_text: str):
10
+ if is_cn:
11
+ return cn_text
12
+ return text
13
+
14
+
15
+ # Save history in browser
16
+ save_history = True
17
+
18
+
19
+ # Chatbot Config
20
+ def user_config(disabled_actions=None):
21
+ return ChatbotUserConfig(
22
+ class_names=dict(content="user-message-content"),
23
+ actions=[
24
+ "copy", "edit",
25
+ ChatbotActionConfig(
26
+ action="delete",
27
+ popconfirm=dict(title=get_text("Delete the message", "删除消息"),
28
+ description=get_text(
29
+ "Are you sure to delete this message?",
30
+ "确认删除该消息?"),
31
+ okButtonProps=dict(danger=True)))
32
+ ],
33
+ disabled_actions=disabled_actions)
34
+
35
+
36
+ def bot_config(disabled_actions=None):
37
+ return ChatbotBotConfig(actions=[
38
+ "copy", "edit",
39
+ ChatbotActionConfig(
40
+ action="retry",
41
+ popconfirm=dict(
42
+ title=get_text("Regenerate the message", "重新生成消息"),
43
+ description=get_text(
44
+ "Regenerate the message will also delete all subsequent messages.",
45
+ "重新生成消息会删除所有后续消息。"),
46
+ okButtonProps=dict(danger=True))),
47
+ ChatbotActionConfig(action="delete",
48
+ popconfirm=dict(
49
+ title=get_text("Delete the message", "删除消息"),
50
+ description=get_text(
51
+ "Are you sure to delete this message?",
52
+ "确认删除该消息?"),
53
+ okButtonProps=dict(danger=True)))
54
+ ],
55
+ avatar="./assets/qwen.png",
56
+ disabled_actions=disabled_actions)
57
+
58
+
59
+ def welcome_config():
60
+ return ChatbotWelcomeConfig(
61
+ variant="borderless",
62
+ icon="./assets/qwen.png",
63
+ title=get_text("Hello, I'm Qwen3", "你好,我是 Qwen3"),
64
+ description=get_text("Select a model and enter text to get started.",
65
+ "选择模型并输入文本,开始对话吧。"),
66
+ prompts=dict(
67
+ title=get_text("How can I help you today?", "有什么我能帮助你的吗?"),
68
+ styles={
69
+ "list": {
70
+ "width": '100%',
71
+ },
72
+ "item": {
73
+ "flex": 1,
74
+ },
75
+ },
76
+ items=[{
77
+ "label":
78
+ get_text("📅 Make a plan", "📅 制定计划"),
79
+ "children": [{
80
+ "description":
81
+ get_text("Help me with a plan to start a business",
82
+ "帮助我制定一个创业计划")
83
+ }, {
84
+ "description":
85
+ get_text("Help me with a plan to achieve my goals",
86
+ "帮助我制定一个实现目标的计划")
87
+ }, {
88
+ "description":
89
+ get_text("Help me with a plan for a successful interview",
90
+ "帮助我制定一个成功的面试计划")
91
+ }]
92
+ }, {
93
+ "label":
94
+ get_text("🖋 Help me write", "🖋 帮我写"),
95
+ "children": [{
96
+ "description":
97
+ get_text("Help me write a story with a twist ending",
98
+ "帮助我写一个带有意外结局的故事")
99
+ }, {
100
+ "description":
101
+ get_text("Help me write a blog post on mental health",
102
+ "帮助我写一篇关于心理健康的博客文章")
103
+ }, {
104
+ "description":
105
+ get_text("Help me write a letter to my future self",
106
+ "帮助我写一封给未来自己的信")
107
+ }]
108
+ }]),
109
+ )
110
+
111
+
112
+ DEFAULT_SUGGESTIONS = [{
113
+ "label":
114
+ get_text('Make a plan', '制定计划'),
115
+ "value":
116
+ get_text('Make a plan', '制定计划'),
117
+ "children": [{
118
+ "label":
119
+ get_text("Start a business", "开始创业"),
120
+ "value":
121
+ get_text("Help me with a plan to start a business", "帮助我制定一个创业计划")
122
+ }, {
123
+ "label":
124
+ get_text("Achieve my goals", "实现我的目标"),
125
+ "value":
126
+ get_text("Help me with a plan to achieve my goals", "帮助我制定一个实现目标的计划")
127
+ }, {
128
+ "label":
129
+ get_text("Successful interview", "成功的面试"),
130
+ "value":
131
+ get_text("Help me with a plan for a successful interview",
132
+ "帮助我制定一个成功的面试计划")
133
+ }]
134
+ }, {
135
+ "label":
136
+ get_text('Help me write', '帮我写'),
137
+ "value":
138
+ get_text("Help me write", '帮我写'),
139
+ "children": [{
140
+ "label":
141
+ get_text("Story with a twist ending", "带有意外结局的故事"),
142
+ "value":
143
+ get_text("Help me write a story with a twist ending",
144
+ "帮助我写一个带有意外结局的故事")
145
+ }, {
146
+ "label":
147
+ get_text("Blog post on mental health", "关于心理健康的博客文章"),
148
+ "value":
149
+ get_text("Help me write a blog post on mental health",
150
+ "帮助我写一篇关于心理健康的博客文章")
151
+ }, {
152
+ "label":
153
+ get_text("Letter to my future self", "给未来自己的信"),
154
+ "value":
155
+ get_text("Help me write a letter to my future self", "帮助我写一封给未来自己的信")
156
+ }]
157
+ }]
158
+
159
+ DEFAULT_SYS_PROMPT = "You are a helpful and harmless assistant."
160
+
161
+ MIN_THINKING_BUDGET = 1
162
+
163
+ MAX_THINKING_BUDGET = 38
164
+
165
+ DEFAULT_THINKING_BUDGET = 38
166
+
167
+ DEFAULT_MODEL = "qwen3-32b"
168
+
169
+ MODEL_OPTIONS = [
170
+ {
171
+ "label": get_text("Qwen3-235B-A22B", "通义千问3-235B-A22B"),
172
+ "modelId": "Qwen/Qwen3-235B-A22B",
173
+ "value": "qwen3-235b-a22b"
174
+ },
175
+ {
176
+ "label": get_text("Qwen3-32B", "通义千问3-32B"),
177
+ "modelId": "Qwen/Qwen3-32B",
178
+ "value": "qwen3-32b"
179
+ },
180
+ {
181
+ "label": get_text("Qwen3-30B-A3B", "通义千问3-30B-A3B"),
182
+ "modelId": "Qwen/Qwen3-30B-A3B",
183
+ "value": "qwen3-30b-a3b"
184
+ },
185
+ {
186
+ "label": get_text("Qwen3-14B", "通义千问3-14B"),
187
+ "modelId": "Qwen/Qwen3-14B",
188
+ "value": "qwen3-14b"
189
+ },
190
+ {
191
+ "label": get_text("Qwen3-8B", "通义千问3-8B"),
192
+ "modelId": "Qwen/Qwen3-8B",
193
+ "value": "qwen3-8b"
194
+ },
195
+ {
196
+ "label": get_text("Qwen3-4B", "通义千问3-4B"),
197
+ "modelId": "Qwen/Qwen3-4B",
198
+ "value": "qwen3-4b"
199
+ },
200
+ {
201
+ "label": get_text("Qwen3-1.7B", "通义千问3-1.7B"),
202
+ "modelId": "Qwen/Qwen3-1.7B",
203
+ "value": "qwen3-1.7b"
204
+ },
205
+ {
206
+ "label": get_text("Qwen3-0.6B", "通义千问3-0.6B"),
207
+ "modelId": "Qwen/Qwen3-0.6B",
208
+ "value": "qwen3-0.6b"
209
+ },
210
+ ]
211
+
212
+ for model in MODEL_OPTIONS:
213
+ model[
214
+ "link"] = is_cn and f"https://modelscope.cn/models/{model['modelId']}" or f"https://huggingface.co/{model['modelId']}"
215
+
216
+ MODEL_OPTIONS_MAP = {model["value"]: model for model in MODEL_OPTIONS}
217
+
218
+ DEFAULT_LOCALE = 'zh_CN' if is_cn else 'en_US'
219
+
220
+ DEFAULT_THEME = {
221
+ "token": {
222
+ "colorPrimary": "#6A57FF",
223
+ }
224
+ }
225
+
226
+ DEFAULT_SETTINGS = {
227
+ "model": DEFAULT_MODEL,
228
+ "sys_prompt": DEFAULT_SYS_PROMPT,
229
+ "thinking_budget": DEFAULT_THINKING_BUDGET
230
+ }
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ modelscope_studio
3
+ openai
ui_components/logo.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import modelscope_studio.components.antd as antd
2
+ import modelscope_studio.components.base as ms
3
+
4
+
5
+ def Logo():
6
+ with antd.Typography.Title(level=1,
7
+ elem_style=dict(fontSize=24,
8
+ padding=8,
9
+ margin=0)):
10
+ with antd.Flex(align="center", gap="small", justify="center"):
11
+ antd.Image('./assets/qwen.png',
12
+ preview=False,
13
+ alt="logo",
14
+ width=24,
15
+ height=24)
16
+ ms.Span("Qwen3")
ui_components/settings_header.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import modelscope_studio.components.antd as antd
3
+ import modelscope_studio.components.antdx as antdx
4
+ import modelscope_studio.components.base as ms
5
+
6
+ from config import DEFAULT_SETTINGS, MODEL_OPTIONS, MAX_THINKING_BUDGET, MIN_THINKING_BUDGET, get_text
7
+
8
+
9
+ def SettingsHeader():
10
+ state = gr.State({"open": True})
11
+ with antdx.Sender.Header(title=get_text("Settings", "设置"),
12
+ open=True) as settings_header:
13
+ with antd.Form(value=DEFAULT_SETTINGS) as settings_form:
14
+ with antd.Form.Item(form_name="model",
15
+ label=get_text("Chat Model", "对话模型")):
16
+ with antd.Select(options=MODEL_OPTIONS):
17
+ with ms.Slot(
18
+ "labelRender",
19
+ params_mapping=
20
+ """(option) => ({
21
+ label: option.label,
22
+ link: { href: window.MODEL_OPTIONS_MAP[option.value].link },
23
+ })"""
24
+ ):
25
+ antd.Typography.Text(as_item="label")
26
+ antd.Typography.Link(get_text("Model Link", "模型链接"),
27
+ href_target="_blank",
28
+ as_item="link")
29
+
30
+ with antd.Form.Item(form_name="thinking_budget",
31
+ label=get_text("Thinking Budget", "思考预算"),
32
+ elem_classes="setting-form-thinking-budget"):
33
+ antd.Slider(elem_style=dict(flex=1, marginRight=14),
34
+ min=MIN_THINKING_BUDGET,
35
+ max=MAX_THINKING_BUDGET,
36
+ tooltip=dict(formatter="(v) => `${v}k`"))
37
+ antd.InputNumber(max=MAX_THINKING_BUDGET,
38
+ min=MIN_THINKING_BUDGET,
39
+ elem_style=dict(width=100),
40
+ addon_after="k")
41
+ with antd.Form.Item(form_name="sys_prompt",
42
+ label=get_text("System Prompt", "系统提示")):
43
+ antd.Input.Textarea(auto_size=dict(minRows=3, maxRows=6))
44
+
45
+ def close_header(state_value):
46
+ state_value["open"] = False
47
+ return gr.update(value=state_value)
48
+
49
+ state.change(fn=lambda state_value: gr.update(open=state_value["open"]),
50
+ inputs=[state],
51
+ outputs=[settings_header])
52
+
53
+ settings_header.open_change(fn=close_header,
54
+ inputs=[state],
55
+ outputs=[state])
56
+
57
+ return state, settings_form
ui_components/thinking_button.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import modelscope_studio.components.antd as antd
2
+ import modelscope_studio.components.base as ms
3
+ import gradio as gr
4
+ from config import get_text
5
+
6
+
7
+ def ThinkingButton():
8
+ state = gr.State({"enable_thinking": True})
9
+ with antd.Button(get_text("Thinking", "深度思考"),
10
+ shape="round",
11
+ color="primary",
12
+ variant="solid") as thinking_btn:
13
+ with ms.Slot("icon"):
14
+ antd.Icon("SunOutlined")
15
+
16
+ def toggle_thinking(state_value):
17
+ state_value["enable_thinking"] = not state_value["enable_thinking"]
18
+ return gr.update(value=state_value)
19
+
20
+ def apply_state_change(state_value):
21
+ return gr.update(
22
+ variant="solid" if state_value["enable_thinking"] else "")
23
+
24
+ state.change(fn=apply_state_change, inputs=[state], outputs=[thinking_btn])
25
+
26
+ thinking_btn.click(fn=toggle_thinking, inputs=[state], outputs=[state])
27
+
28
+ return state