littlebird13 commited on
Commit
d9d04fe
·
verified ·
1 Parent(s): 3ac7f69

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +934 -0
app.py ADDED
@@ -0,0 +1,934 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+
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
+ from openai import OpenAI
9
+
10
+ # =========== Configuration
11
+ # API KEY
12
+ client = OpenAI(
13
+ base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
14
+ api_key=os.getenv("DASHSCOPE_API_KEY"),
15
+ )
16
+
17
+ model = "qwq-32b"
18
+
19
+ save_history = True
20
+
21
+ # =========== Configuration
22
+
23
+ is_modelscope_studio = os.getenv('MODELSCOPE_ENVIRONMENT') == 'studio'
24
+
25
+
26
+ def get_text(text: str, cn_text: str):
27
+ if is_modelscope_studio:
28
+ return cn_text
29
+ return text
30
+
31
+
32
+ qwen_logo = os.path.join(os.path.dirname(__file__), "qwen.png")
33
+
34
+ DEFAULT_PROMPTS = [{
35
+ "category":
36
+ "🖋 Make a plan",
37
+ "prompts": [
38
+ "Help me with a plan to start a business",
39
+ "Help me with a plan to achieve my goals",
40
+ "Help me with a plan for a successful interview"
41
+ ]
42
+ }, {
43
+ "category":
44
+ "📅 Help me write",
45
+ "prompts": [
46
+ "Help me write a story with a twist ending",
47
+ "Help me write a blog post on mental health",
48
+ "Help me write a letter to my future self"
49
+ ]
50
+ }]
51
+
52
+ DEFAULT_SUGGESTIONS = [{
53
+ "label":
54
+ 'Make a plan',
55
+ "value":
56
+ "Make a plan",
57
+ "children": [{
58
+ "label": "Start a business",
59
+ "value": "Help me with a plan to start a business"
60
+ }, {
61
+ "label": "Achieve my goals",
62
+ "value": "Help me with a plan to achieve my goals"
63
+ }, {
64
+ "label": "Successful interview",
65
+ "value": "Help me with a plan for a successful interview"
66
+ }]
67
+ }, {
68
+ "label":
69
+ 'Help me write',
70
+ "value":
71
+ "Help me write",
72
+ "children": [{
73
+ "label": "Story with a twist ending",
74
+ "value": "Help me write a story with a twist ending"
75
+ }, {
76
+ "label": "Blog post on mental health",
77
+ "value": "Help me write a blog post on mental health"
78
+ }, {
79
+ "label": "Letter to my future self",
80
+ "value": "Help me write a letter to my future self"
81
+ }]
82
+ }]
83
+
84
+ DEFAULT_CONVERSATIONS_HISTORY = [{"role": "placeholder"}]
85
+
86
+ DEFAULT_LOCALE = 'en_US'
87
+
88
+ DEFAULT_THEME = {
89
+ "token": {
90
+ "colorPrimary": "#6A57FF",
91
+ }
92
+ }
93
+
94
+
95
+ def format_history(history):
96
+ messages = [{
97
+ "role": "system",
98
+ "content": "You are a helpful and harmless assistant.",
99
+ }]
100
+ for item in history:
101
+ if item["role"] == "user":
102
+ messages.append({"role": "user", "content": item["content"]})
103
+ elif item["role"] == "assistant":
104
+ messages.append({"role": "assistant", "content": item["content"]})
105
+ return messages
106
+
107
+
108
+ class Gradio_Events:
109
+
110
+ @staticmethod
111
+ def submit(state_value):
112
+
113
+ history = state_value["conversations_history"][
114
+ state_value["conversation_id"]]
115
+ history_messages = format_history(history)
116
+
117
+ history.append({
118
+ "role": "assistant",
119
+ "content": "",
120
+ "key": str(uuid.uuid4()),
121
+ "meta": {
122
+ "reason_content": ""
123
+ },
124
+ "loading": True,
125
+ })
126
+
127
+ yield {
128
+ chatbot: gr.update(items=history),
129
+ state: gr.update(value=state_value),
130
+ }
131
+ try:
132
+ response = client.chat.completions.create(
133
+ model=model, # ModelScope Model-Id
134
+ messages=history_messages,
135
+ stream=True)
136
+ thought_done = False
137
+ for chunk in response:
138
+ reasoning_content = chunk.choices[0].delta.reasoning_content
139
+ content = chunk.choices[0].delta.content
140
+ history[-1]["loading"] = False
141
+
142
+ if content and not thought_done:
143
+ thought_done = True
144
+ history[-1]["meta"]["reason_content"] = history[-1][
145
+ "content"]
146
+ history[-1]["content"] = ""
147
+ history[-1]["meta"]["thought_end_message"] = get_text(
148
+ "End of Thought", "已深度思考")
149
+ if not thought_done:
150
+ history[-1]["content"] += reasoning_content or ""
151
+ else:
152
+ history[-1]["content"] += content or ""
153
+
154
+ yield {
155
+ chatbot: gr.update(items=history),
156
+ state: gr.update(value=state_value)
157
+ }
158
+ history[-1]["meta"]["end"] = True
159
+ yield {
160
+ chatbot: gr.update(items=history),
161
+ state: gr.update(value=state_value),
162
+ }
163
+ except Exception as e:
164
+ history[-1]["loading"] = False
165
+ history[-1]["meta"]["end"] = True
166
+ history[-1]["meta"]["error"] = True
167
+ history[-1]["content"] = "Failed to respond, please try again."
168
+ yield {
169
+ chatbot: gr.update(items=history),
170
+ state: gr.update(value=state_value)
171
+ }
172
+ raise e
173
+
174
+ @staticmethod
175
+ def add_user_message(sender_value, state_value):
176
+ if not state_value["conversation_id"]:
177
+ random_id = str(uuid.uuid4())
178
+ history = []
179
+ state_value["conversation_id"] = random_id
180
+ state_value["conversations_history"][random_id] = history
181
+ state_value["conversations"].append({
182
+ "label": sender_value,
183
+ "key": random_id
184
+ })
185
+
186
+ history = state_value["conversations_history"][
187
+ state_value["conversation_id"]]
188
+ history.append({
189
+ "role": "user",
190
+ "meta": {},
191
+ "key": str(uuid.uuid4()),
192
+ "content": sender_value
193
+ })
194
+ return gr.update(value=state_value)
195
+
196
+ @staticmethod
197
+ def preprocess_submit(clear_input=True):
198
+
199
+ def preprocess_submit_handler(state_value):
200
+ history = state_value["conversations_history"][
201
+ state_value["conversation_id"]]
202
+ for conversation in history:
203
+ if "meta" in conversation:
204
+ conversation["meta"]["disabled"] = True
205
+ return {
206
+ **({
207
+ sender: gr.update(value=None, loading=True),
208
+ } if clear_input else {}),
209
+ conversations:
210
+ gr.update(active_key=state_value["conversation_id"],
211
+ items=list(
212
+ map(
213
+ lambda item: {
214
+ **item,
215
+ "disabled":
216
+ True if item["key"] != state_value[
217
+ "conversation_id"] else False,
218
+ }, state_value["conversations"]))),
219
+ add_conversation_btn:
220
+ gr.update(disabled=True),
221
+ clear_btn:
222
+ gr.update(disabled=True),
223
+ conversation_delete_menu_item:
224
+ gr.update(disabled=True),
225
+ chatbot:
226
+ gr.update(items=history),
227
+ state:
228
+ gr.update(value=state_value),
229
+ }
230
+
231
+ return preprocess_submit_handler
232
+
233
+ @staticmethod
234
+ def postprocess_submit(state_value):
235
+ history = state_value["conversations_history"][
236
+ state_value["conversation_id"]]
237
+ for conversation in history:
238
+ if "meta" in conversation:
239
+ conversation["meta"]["disabled"] = False
240
+ return {
241
+ sender: gr.update(loading=False),
242
+ conversation_delete_menu_item: gr.update(disabled=False),
243
+ clear_btn: gr.update(disabled=False),
244
+ conversations: gr.update(items=state_value["conversations"]),
245
+ add_conversation_btn: gr.update(disabled=False),
246
+ chatbot: gr.update(items=history),
247
+ state: gr.update(value=state_value),
248
+ }
249
+
250
+ @staticmethod
251
+ def cancel(state_value):
252
+ history = state_value["conversations_history"][
253
+ state_value["conversation_id"]]
254
+ history[-1]["loading"] = False
255
+ history[-1]["meta"]["end"] = True
256
+ history[-1]["meta"]["canceled"] = True
257
+ return Gradio_Events.postprocess_submit(state_value)
258
+
259
+ @staticmethod
260
+ def delete_message(state_value, e: gr.EventData):
261
+ conversation_key = e._data["component"]["conversationKey"]
262
+ history = state_value["conversations_history"][
263
+ state_value["conversation_id"]]
264
+ history = [item for item in history if item["key"] != conversation_key]
265
+ state_value["conversations_history"][
266
+ state_value["conversation_id"]] = history
267
+
268
+ return gr.update(items=history if len(history) >
269
+ 0 else DEFAULT_CONVERSATIONS_HISTORY), gr.update(
270
+ value=state_value)
271
+
272
+ @staticmethod
273
+ def regenerate_message(state_value, e: gr.EventData):
274
+ conversation_key = e._data["component"]["conversationKey"]
275
+ history = state_value["conversations_history"][
276
+ state_value["conversation_id"]]
277
+ index = -1
278
+ for i, conversation in enumerate(history):
279
+ if conversation["key"] == conversation_key:
280
+ index = i
281
+ break
282
+ if index == -1:
283
+ return gr.skip()
284
+ history = history[:index]
285
+ state_value["conversations_history"][
286
+ state_value["conversation_id"]] = history
287
+
288
+ return gr.update(items=history), gr.update(value=state_value)
289
+
290
+ @staticmethod
291
+ def edit_message(state_value, e: gr.EventData):
292
+ conversation_key = e._data["component"]["conversationKey"]
293
+ history = state_value["conversations_history"][
294
+ state_value["conversation_id"]]
295
+ index = -1
296
+ for i, conversation in enumerate(history):
297
+ if conversation["key"] == conversation_key:
298
+ index = i
299
+ break
300
+ if index == -1:
301
+ return gr.skip()
302
+ state_value["editing_message_index"] = index
303
+ text = ''
304
+ if isinstance(history[index]["content"], str):
305
+ text = history[index]["content"]
306
+ else:
307
+ text = history[index]["content"]["text"]
308
+ return gr.update(value=text), gr.update(value=state_value)
309
+
310
+ @staticmethod
311
+ def confirm_edit_message(edit_textarea_value, state_value):
312
+ history = state_value["conversations_history"][
313
+ state_value["conversation_id"]]
314
+ message = history[state_value["editing_message_index"]]
315
+ if isinstance(message["content"], str):
316
+ message["content"] = edit_textarea_value
317
+ else:
318
+ message["content"]["text"] = edit_textarea_value
319
+ return gr.update(items=history), gr.update(value=state_value)
320
+
321
+ @staticmethod
322
+ def select_suggestion(sender_value, e: gr.EventData):
323
+ return gr.update(value=sender_value[:-1] + e._data["payload"][0])
324
+
325
+ @staticmethod
326
+ def apply_prompt(e: gr.EventData):
327
+ return gr.update(value=e._data["payload"][0]["data"]["description"])
328
+
329
+ @staticmethod
330
+ def new_chat(state_value):
331
+ if not state_value["conversation_id"]:
332
+ return gr.skip()
333
+ state_value["conversation_id"] = ""
334
+ return gr.update(active_key=state_value["conversation_id"]), gr.update(
335
+ items=DEFAULT_CONVERSATIONS_HISTORY), gr.update(value=state_value)
336
+
337
+ @staticmethod
338
+ def select_conversation(state_value, e: gr.EventData):
339
+ active_key = e._data["payload"][0]
340
+ if state_value["conversation_id"] == active_key or (
341
+ active_key not in state_value["conversations_history"]):
342
+ return gr.skip()
343
+ state_value["conversation_id"] = active_key
344
+ return gr.update(active_key=active_key), gr.update(
345
+ items=state_value["conversations_history"][active_key]), gr.update(
346
+ value=state_value)
347
+
348
+ @staticmethod
349
+ def click_conversation_menu(state_value, e: gr.EventData):
350
+ conversation_id = e._data["payload"][0]["key"]
351
+ operation = e._data["payload"][1]["key"]
352
+ if operation == "delete":
353
+ del state_value["conversations_history"][conversation_id]
354
+
355
+ state_value["conversations"] = [
356
+ item for item in state_value["conversations"]
357
+ if item["key"] != conversation_id
358
+ ]
359
+
360
+ if state_value["conversation_id"] == conversation_id:
361
+ state_value["conversation_id"] = ""
362
+ return gr.update(
363
+ items=state_value["conversations"],
364
+ active_key=state_value["conversation_id"]), gr.update(
365
+ items=DEFAULT_CONVERSATIONS_HISTORY), gr.update(
366
+ value=state_value)
367
+ else:
368
+ return gr.update(
369
+ items=state_value["conversations"]), gr.skip(), gr.update(
370
+ value=state_value)
371
+ return gr.skip()
372
+
373
+ @staticmethod
374
+ def clear_conversation_history(state_value):
375
+ if not state_value["conversation_id"]:
376
+ return gr.skip()
377
+ state_value["conversations_history"][
378
+ state_value["conversation_id"]] = []
379
+ return gr.update(items=DEFAULT_CONVERSATIONS_HISTORY), gr.update(
380
+ value=state_value)
381
+
382
+ @staticmethod
383
+ def close_modal():
384
+ return gr.update(open=False)
385
+
386
+ @staticmethod
387
+ def open_modal():
388
+ return gr.update(open=True)
389
+
390
+ @staticmethod
391
+ def update_browser_state(state_value):
392
+
393
+ return gr.update(value=dict(
394
+ conversations=state_value["conversations"],
395
+ conversations_history=state_value["conversations_history"]))
396
+
397
+ @staticmethod
398
+ def apply_browser_state(browser_state_value, state_value):
399
+ state_value["conversations"] = browser_state_value["conversations"]
400
+ state_value["conversations_history"] = browser_state_value[
401
+ "conversations_history"]
402
+ return gr.update(
403
+ items=browser_state_value["conversations"]), gr.update(
404
+ value=state_value)
405
+
406
+
407
+ css = """
408
+ #chatbot {
409
+ height: calc(100vh - 32px - 21px - 16px);
410
+ }
411
+
412
+ #chatbot .chatbot-conversations {
413
+ height: 100%;
414
+ background-color: var(--ms-gr-ant-color-bg-layout);
415
+ }
416
+
417
+ #chatbot .chatbot-conversations .chatbot-conversations-list {
418
+ padding-left: 0;
419
+ padding-right: 0;
420
+ }
421
+
422
+ #chatbot .chatbot-chat {
423
+ padding: 32px;
424
+ height: 100%;
425
+ }
426
+
427
+ @media (max-width: 768px) {
428
+ #chatbot .chatbot-chat {
429
+ padding: 0;
430
+ }
431
+ }
432
+
433
+ #chatbot .chatbot-chat .chatbot-chat-messages {
434
+ flex: 1;
435
+ }
436
+
437
+ #chatbot .chatbot-chat .chatbot-chat-messages .chatbot-chat-message .chatbot-chat-message-footer {
438
+ visibility: hidden;
439
+ opacity: 0;
440
+ transition: opacity 0.2s;
441
+ }
442
+
443
+ #chatbot .chatbot-chat .chatbot-chat-messages .chatbot-chat-message:last-child .chatbot-chat-message-footer {
444
+ visibility: visible;
445
+ opacity: 1;
446
+ }
447
+
448
+ #chatbot .chatbot-chat .chatbot-chat-messages .chatbot-chat-message:hover .chatbot-chat-message-footer {
449
+ visibility: visible;
450
+ opacity: 1;
451
+ }
452
+ """
453
+
454
+
455
+ def logo():
456
+ with antd.Typography.Title(level=1,
457
+ elem_style=dict(fontSize=24,
458
+ padding=8,
459
+ margin=0)):
460
+ with antd.Flex(align="center", gap="small", justify="center"):
461
+ antd.Image(qwen_logo,
462
+ preview=False,
463
+ alt="logo",
464
+ width=24,
465
+ height=24)
466
+ ms.Span("QwQ-32B")
467
+
468
+
469
+ with gr.Blocks(css=css, fill_width=True) as demo:
470
+ state = gr.State({
471
+ "conversations_history": {},
472
+ "conversations": [],
473
+ "conversation_id": "",
474
+ "editing_message_index": -1,
475
+ })
476
+
477
+ with ms.Application(), antdx.XProvider(
478
+ theme=DEFAULT_THEME, locale=DEFAULT_LOCALE), ms.AutoLoading():
479
+ with antd.Row(gutter=[20, 20], wrap=False, elem_id="chatbot"):
480
+ # Left Column
481
+ with antd.Col(md=dict(flex="0 0 260px", span=24, order=0),
482
+ span=0,
483
+ order=1,
484
+ elem_classes="chatbot-conversations"):
485
+ with antd.Flex(vertical=True,
486
+ gap="small",
487
+ elem_style=dict(height="100%")):
488
+ # Logo
489
+ logo()
490
+
491
+ # New Conversation Button
492
+ with antd.Button(value=None,
493
+ color="primary",
494
+ variant="filled",
495
+ block=True) as add_conversation_btn:
496
+ ms.Text(get_text("New Conversation", "新建对话"))
497
+ with ms.Slot("icon"):
498
+ antd.Icon("PlusOutlined")
499
+
500
+ # Conversations List
501
+ with antdx.Conversations(
502
+ elem_classes="chatbot-conversations-list",
503
+ ) as conversations:
504
+ with ms.Slot('menu.items'):
505
+ with antd.Menu.Item(
506
+ label="Delete", key="delete", danger=True
507
+ ) as conversation_delete_menu_item:
508
+ with ms.Slot("icon"):
509
+ antd.Icon("DeleteOutlined")
510
+ # Right Column
511
+ with antd.Col(flex=1, elem_style=dict(height="100%")):
512
+ with antd.Flex(vertical=True,
513
+ gap="middle",
514
+ elem_classes="chatbot-chat"):
515
+ # Chatbot
516
+ with antdx.Bubble.List(
517
+ items=DEFAULT_CONVERSATIONS_HISTORY,
518
+ elem_classes="chatbot-chat-messages") as chatbot:
519
+ # Define Chatbot Roles
520
+ with ms.Slot("roles"):
521
+ # Placeholder Role
522
+ with antdx.Bubble.List.Role(
523
+ role="placeholder",
524
+ styles=dict(content=dict(width="100%")),
525
+ variant="borderless"):
526
+ with ms.Slot("messageRender"):
527
+ with antd.Space(
528
+ direction="vertical",
529
+ size=16,
530
+ elem_style=dict(width="100%")):
531
+ with antdx.Welcome(
532
+ styles=dict(icon=dict(
533
+ flexShrink=0)),
534
+ variant="borderless",
535
+ title=get_text(
536
+ "Hello, I'm QwQ-32B",
537
+ "你好,我是 QwQ-32B"),
538
+ description=get_text(
539
+ "You can type text to get started.",
540
+ "你可以输入文本开始对话。"),
541
+ ):
542
+ with ms.Slot("icon"):
543
+ antd.Image(qwen_logo,
544
+ preview=False)
545
+ with antdx.Prompts(title=get_text(
546
+ "How can I help you today?",
547
+ "有什么我能帮助你的吗?"),
548
+ styles={
549
+ "list": {
550
+ "width":
551
+ '100%',
552
+ },
553
+ "item": {
554
+ "flex": 1,
555
+ },
556
+ }) as prompts:
557
+ for item in DEFAULT_PROMPTS:
558
+ with antdx.Prompts.Item(
559
+ label=item["category"]
560
+ ):
561
+ for prompt in item[
562
+ "prompts"]:
563
+ antdx.Prompts.Item(
564
+ description=prompt,
565
+ )
566
+
567
+ # User Role
568
+ with antdx.Bubble.List.Role(
569
+ role="user",
570
+ placement="end",
571
+ elem_classes="chatbot-chat-message",
572
+ class_names=dict(
573
+ footer="chatbot-chat-message-footer"),
574
+ styles=dict(content=dict(
575
+ maxWidth="100%",
576
+ overflow='auto',
577
+ ))):
578
+ with ms.Slot(
579
+ "messageRender",
580
+ params_mapping="(content) => content"):
581
+
582
+ ms.Markdown()
583
+ with ms.Slot("footer",
584
+ params_mapping="""(bubble) => {
585
+ return {
586
+ copy_btn: {
587
+ copyable: { text: typeof bubble.content === 'string' ? bubble.content : bubble.content?.text, tooltips: false },
588
+ },
589
+ edit_btn: { conversationKey: bubble.key, disabled: bubble.meta.disabled },
590
+ delete_btn: { conversationKey: bubble.key, disabled: bubble.meta.disabled },
591
+ };
592
+ }"""):
593
+ with antd.Typography.Text(
594
+ copyable=dict(tooltips=False),
595
+ as_item="copy_btn"):
596
+ with ms.Slot("copyable.icon"):
597
+ with antd.Button(value=None,
598
+ size="small",
599
+ color="default",
600
+ variant="text"):
601
+ with ms.Slot("icon"):
602
+ antd.Icon("CopyOutlined")
603
+ with antd.Button(value=None,
604
+ size="small",
605
+ color="default",
606
+ variant="text"):
607
+ with ms.Slot("icon"):
608
+ antd.Icon("CheckOutlined")
609
+ with antd.Button(value=None,
610
+ size="small",
611
+ color="default",
612
+ variant="text",
613
+ as_item="edit_btn"
614
+ ) as user_edit_btn:
615
+ with ms.Slot("icon"):
616
+ antd.Icon("EditOutlined")
617
+ with antd.Popconfirm(
618
+ title="Delete the message",
619
+ description=
620
+ "Are you sure to delete this message?",
621
+ ok_button_props=dict(danger=True),
622
+ as_item="delete_btn"
623
+ ) as user_delete_popconfirm:
624
+ with antd.Button(value=None,
625
+ size="small",
626
+ color="default",
627
+ variant="text",
628
+ as_item="delete_btn"):
629
+ with ms.Slot("icon"):
630
+ antd.Icon("DeleteOutlined")
631
+
632
+ # Chatbot Role
633
+ with antdx.Bubble.List.Role(
634
+ role="assistant",
635
+ placement="start",
636
+ elem_classes="chatbot-chat-message",
637
+ class_names=dict(
638
+ footer="chatbot-chat-message-footer"),
639
+ styles=dict(content=dict(
640
+ maxWidth="100%", overflow='auto'))):
641
+ with ms.Slot("avatar"):
642
+ antd.Avatar(
643
+ os.path.join(os.path.dirname(__file__),
644
+ "qwen.png"))
645
+ with ms.Slot(
646
+ "messageRender",
647
+ params_mapping="""(content, bubble) => {
648
+ const reason_content = bubble?.meta?.reason_content
649
+ const has_error = bubble?.meta?.error
650
+ return {
651
+ reasoning: reason_content || content,
652
+ reasoning_container: has_error ? { style: { display: 'none' } } : undefined,
653
+ answer: {
654
+ value: reason_content || has_error ? content : undefined
655
+ },
656
+ collapse_label: bubble.meta?.thought_end_message,
657
+ collapse_progress: bubble.meta?.thought_end_message ? { style: { display: 'none' } } : undefined,
658
+ canceled: bubble.meta?.canceled ? undefined : { style: { display: 'none' } }
659
+ }
660
+ }"""):
661
+ with antd.Flex(vertical=True,
662
+ gap="middle"):
663
+ with antd.Collapse(
664
+ default_active_key=[
665
+ "reasoning"
666
+ ],
667
+ as_item="reasoning_container"):
668
+ with antd.Collapse.Item(
669
+ key="reasoning"):
670
+ with ms.Slot("label"):
671
+ with antd.Space(
672
+ size="middle"):
673
+ ms.Span(
674
+ get_text(
675
+ "Thinking...",
676
+ "思考中..."),
677
+ as_item=
678
+ "collapse_label")
679
+ antd.Progress(
680
+ percent="100",
681
+ status="active",
682
+ elem_style=dict(
683
+ display="flex",
684
+ alignItems=
685
+ "center",
686
+ ),
687
+ show_info=False,
688
+ size=[110, 5],
689
+ as_item=
690
+ "collapse_progress"
691
+ )
692
+ with antd.Alert(
693
+ type="warning"):
694
+ with ms.Slot(
695
+ "description"):
696
+ ms.Markdown(
697
+ as_item="reasoning"
698
+ )
699
+ ms.Markdown(
700
+ as_item="answer",
701
+ elem_classes="answer-content")
702
+
703
+ antd.Divider(as_item="canceled")
704
+ antd.Typography.Text(get_text(
705
+ "Chat completion paused.", "聊天已暂停。"),
706
+ as_item="canceled",
707
+ type="warning")
708
+
709
+ with ms.Slot("footer",
710
+ params_mapping="""(bubble) => {
711
+ if (bubble?.meta?.end) {
712
+ return {
713
+ copy_btn: {
714
+ copyable: { text: bubble.content, tooltips: false },
715
+ },
716
+ regenerate_btn: { conversationKey: bubble.key, disabled: bubble.meta.disabled },
717
+ delete_btn: { conversationKey: bubble.key, disabled: bubble.meta.disabled },
718
+ edit_btn: { conversationKey: bubble.key, disabled: bubble.meta.disabled },
719
+ };
720
+ }
721
+ return { actions_container: { style: { display: 'none' } } };
722
+ }"""):
723
+ with ms.Div(as_item="actions_container"):
724
+ with antd.Typography.Text(
725
+ copyable=dict(tooltips=False),
726
+ as_item="copy_btn"):
727
+ with ms.Slot("copyable.icon"):
728
+ with antd.Button(
729
+ value=None,
730
+ size="small",
731
+ color="default",
732
+ variant="text"):
733
+ with ms.Slot("icon"):
734
+ antd.Icon(
735
+ "CopyOutlined")
736
+ with antd.Button(
737
+ value=None,
738
+ size="small",
739
+ color="default",
740
+ variant="text"):
741
+ with ms.Slot("icon"):
742
+ antd.Icon(
743
+ "CheckOutlined")
744
+
745
+ with antd.Popconfirm(
746
+ title=get_text(
747
+ "Regenerate the message",
748
+ "重新生成消息"),
749
+ description=get_text(
750
+ "Regenerate the message will also delete all subsequent messages.",
751
+ "重新生成消息将会删除所有的后续消息。"),
752
+ ok_button_props=dict(
753
+ danger=True),
754
+ as_item="regenerate_btn"
755
+ ) as chatbot_regenerate_popconfirm:
756
+ with antd.Button(
757
+ value=None,
758
+ size="small",
759
+ color="default",
760
+ variant="text",
761
+ as_item="regenerate_btn",
762
+ ):
763
+ with ms.Slot("icon"):
764
+ antd.Icon("SyncOutlined")
765
+ with antd.Button(value=None,
766
+ size="small",
767
+ color="default",
768
+ variant="text",
769
+ as_item="edit_btn"
770
+ ) as chatbot_edit_btn:
771
+ with ms.Slot("icon"):
772
+ antd.Icon("EditOutlined")
773
+ with antd.Popconfirm(
774
+ title="Delete the message",
775
+ description=get_text(
776
+ "Are you sure to delete this message?",
777
+ "确定要删除这条消息吗?"),
778
+ ok_button_props=dict(
779
+ danger=True),
780
+ as_item="delete_btn"
781
+ ) as chatbot_delete_popconfirm:
782
+ with antd.Button(
783
+ value=None,
784
+ size="small",
785
+ color="default",
786
+ variant="text",
787
+ as_item="delete_btn"):
788
+ with ms.Slot("icon"):
789
+ antd.Icon("DeleteOutlined")
790
+
791
+ # Sender
792
+ with antdx.Suggestion(
793
+ items=DEFAULT_SUGGESTIONS,
794
+ # onKeyDown Handler in Javascript
795
+ should_trigger="""(e, { onTrigger, onKeyDown }) => {
796
+ switch(e.key) {
797
+ case '/':
798
+ onTrigger()
799
+ break
800
+ case 'ArrowRight':
801
+ case 'ArrowLeft':
802
+ case 'ArrowUp':
803
+ case 'ArrowDown':
804
+ break;
805
+ default:
806
+ onTrigger(false)
807
+ }
808
+ onKeyDown(e)
809
+ }""") as suggestion:
810
+ with ms.Slot("children"):
811
+ with antdx.Sender(placeholder=get_text(
812
+ "Enter / to get suggestions",
813
+ "输入 / 获取建议"), ) as sender:
814
+ with ms.Slot("prefix"):
815
+ # Clear Button
816
+ with antd.Tooltip(title=get_text(
817
+ "Clear Conversation History",
818
+ "清空对话历史"), ):
819
+ with antd.Button(
820
+ value=None,
821
+ type="text") as clear_btn:
822
+ with ms.Slot("icon"):
823
+ antd.Icon("ClearOutlined")
824
+
825
+ # Modals
826
+ with antd.Modal(title=get_text("Edit Message", "编辑消息"),
827
+ open=False,
828
+ centered=True,
829
+ width="60%") as edit_modal:
830
+ edit_textarea = antd.Input.Textarea(auto_size=dict(minRows=2,
831
+ maxRows=6),
832
+ elem_style=dict(width="100%"))
833
+ # Events Handler
834
+ if save_history:
835
+ browser_state = gr.BrowserState(
836
+ {
837
+ "conversations_history": {},
838
+ "conversations": [],
839
+ },
840
+ storage_key="qwen_qwq_chatbot_storage")
841
+ state.change(fn=Gradio_Events.update_browser_state,
842
+ inputs=[state],
843
+ outputs=[browser_state])
844
+
845
+ demo.load(fn=Gradio_Events.apply_browser_state,
846
+ inputs=[browser_state, state],
847
+ outputs=[conversations, state])
848
+
849
+ add_conversation_btn.click(fn=Gradio_Events.new_chat,
850
+ inputs=[state],
851
+ outputs=[conversations, chatbot, state])
852
+ conversations.active_change(fn=Gradio_Events.select_conversation,
853
+ inputs=[state],
854
+ outputs=[conversations, chatbot, state])
855
+ conversations.menu_click(fn=Gradio_Events.click_conversation_menu,
856
+ inputs=[state],
857
+ outputs=[conversations, chatbot, state])
858
+ prompts.item_click(fn=Gradio_Events.apply_prompt, outputs=[sender])
859
+
860
+ clear_btn.click(fn=Gradio_Events.clear_conversation_history,
861
+ inputs=[state],
862
+ outputs=[chatbot, state])
863
+
864
+ suggestion.select(fn=Gradio_Events.select_suggestion,
865
+ inputs=[sender],
866
+ outputs=[sender])
867
+
868
+ gr.on(triggers=[user_edit_btn.click, chatbot_edit_btn.click],
869
+ fn=Gradio_Events.edit_message,
870
+ inputs=[state],
871
+ outputs=[edit_textarea, state]).then(fn=Gradio_Events.open_modal,
872
+ outputs=[edit_modal])
873
+ edit_modal.ok(fn=Gradio_Events.confirm_edit_message,
874
+ inputs=[edit_textarea, state],
875
+ outputs=[chatbot, state]).then(fn=Gradio_Events.close_modal,
876
+ outputs=[edit_modal])
877
+ edit_modal.cancel(fn=Gradio_Events.close_modal, outputs=[edit_modal])
878
+ gr.on(triggers=[
879
+ chatbot_delete_popconfirm.confirm, user_delete_popconfirm.confirm
880
+ ],
881
+ fn=Gradio_Events.delete_message,
882
+ inputs=[state],
883
+ outputs=[chatbot, state])
884
+
885
+ regenerating_event = chatbot_regenerate_popconfirm.confirm(
886
+ fn=Gradio_Events.regenerate_message,
887
+ inputs=[state],
888
+ outputs=[chatbot, state
889
+ ]).then(fn=Gradio_Events.preprocess_submit(clear_input=False),
890
+ inputs=[state],
891
+ outputs=[
892
+ sender, clear_btn, conversation_delete_menu_item,
893
+ add_conversation_btn, conversations, chatbot,
894
+ state
895
+ ]).then(fn=Gradio_Events.submit,
896
+ inputs=[state],
897
+ outputs=[chatbot, state])
898
+
899
+ submit_event = sender.submit(
900
+ fn=Gradio_Events.add_user_message,
901
+ inputs=[sender, state],
902
+ outputs=[state
903
+ ]).then(fn=Gradio_Events.preprocess_submit(clear_input=True),
904
+ inputs=[state],
905
+ outputs=[
906
+ sender, clear_btn, conversation_delete_menu_item,
907
+ add_conversation_btn, conversations, chatbot,
908
+ state
909
+ ]).then(fn=Gradio_Events.submit,
910
+ inputs=[state],
911
+ outputs=[chatbot, state])
912
+ regenerating_event.then(fn=Gradio_Events.postprocess_submit,
913
+ inputs=[state],
914
+ outputs=[
915
+ sender, conversation_delete_menu_item,
916
+ clear_btn, conversations, add_conversation_btn,
917
+ chatbot, state
918
+ ])
919
+ submit_event.then(fn=Gradio_Events.postprocess_submit,
920
+ inputs=[state],
921
+ outputs=[
922
+ sender, conversation_delete_menu_item, clear_btn,
923
+ conversations, add_conversation_btn, chatbot, state
924
+ ])
925
+ sender.cancel(fn=None, cancels=[submit_event, regenerating_event])
926
+ sender.cancel(fn=Gradio_Events.cancel,
927
+ inputs=[state],
928
+ outputs=[
929
+ sender, conversation_delete_menu_item, clear_btn,
930
+ conversations, add_conversation_btn, chatbot, state
931
+ ])
932
+
933
+ if __name__ == "__main__":
934
+ demo.queue(default_concurrency_limit=20).launch(ssr_mode=False)