Coloring commited on
Commit
2e77168
·
1 Parent(s): 33cf5a0

feat: pro components

Browse files
README-zh_CN.md CHANGED
@@ -25,7 +25,7 @@
25
 
26
  ## 依赖
27
 
28
- - Gradio >= 4.0
29
 
30
  ## 安装
31
 
 
25
 
26
  ## 依赖
27
 
28
+ - Gradio >= 4.43.0
29
 
30
  ## 安装
31
 
README.md CHANGED
@@ -42,7 +42,7 @@ However, when your application needs Gradio to handle more built-in data on the
42
 
43
  ## Dependencies
44
 
45
- - Gradio >= 4.0
46
 
47
  ## Installation
48
 
 
42
 
43
  ## Dependencies
44
 
45
+ - Gradio >= 4.43.0
46
 
47
  ## Installation
48
 
app.py CHANGED
@@ -15,7 +15,7 @@ def get_text(text: str, cn_text: str):
15
  return text
16
 
17
 
18
- def get_docs(type: Literal["antd", "antdx", "base"]):
19
  import importlib.util
20
 
21
  components = []
@@ -67,6 +67,7 @@ index_docs = {"overview": Docs(__file__), **layout_templates}
67
  base_docs = get_docs("base")
68
  antd_docs = get_docs("antd")
69
  antdx_docs = get_docs("antdx")
 
70
 
71
  default_active_tab = "index"
72
  index_menu_items = [{
@@ -136,6 +137,20 @@ base_menu_items = [{
136
  }]
137
  }]
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  antd_menu_items = [{
140
  "label": get_text("Overview", "概览"),
141
  "key": "overview"
@@ -494,6 +509,12 @@ tabs = [
494
  "default_active_key": "application",
495
  "menus": base_menu_items
496
  },
 
 
 
 
 
 
497
  {
498
  "label": get_text("Antd Components", "Antd 组件"),
499
  "key": "antd",
@@ -521,7 +542,8 @@ site = Site(
521
  "index": index_docs,
522
  "antd": antd_docs,
523
  "antdx": antdx_docs,
524
- "base": base_docs
 
525
  },
526
  default_active_tab=default_active_tab,
527
  logo=logo)
@@ -529,4 +551,5 @@ site = Site(
529
  demo = site.render()
530
 
531
  if __name__ == "__main__":
532
- demo.queue().launch(ssr_mode=False)
 
 
15
  return text
16
 
17
 
18
+ def get_docs(type: Literal["antd", "antdx", "pro", "base"]):
19
  import importlib.util
20
 
21
  components = []
 
67
  base_docs = get_docs("base")
68
  antd_docs = get_docs("antd")
69
  antdx_docs = get_docs("antdx")
70
+ pro_docs = get_docs("pro")
71
 
72
  default_active_tab = "index"
73
  index_menu_items = [{
 
137
  }]
138
  }]
139
 
140
+ pro_menu_items = [{
141
+ "label":
142
+ get_text("Chat", "对话"),
143
+ "type":
144
+ "group",
145
+ "children": [{
146
+ "label": get_text("Chatbot", "Chatbot 聊天机器人"),
147
+ "key": "chatbot"
148
+ }, {
149
+ "label": get_text("MultimodalInput", "MultimodalInput 多模态输入框"),
150
+ "key": "multimodal_input"
151
+ }]
152
+ }]
153
+
154
  antd_menu_items = [{
155
  "label": get_text("Overview", "概览"),
156
  "key": "overview"
 
509
  "default_active_key": "application",
510
  "menus": base_menu_items
511
  },
512
+ {
513
+ "label": get_text("Pro Components", "高级组件"),
514
+ "key": "pro",
515
+ "default_active_key": "chatbot",
516
+ "menus": pro_menu_items
517
+ },
518
  {
519
  "label": get_text("Antd Components", "Antd 组件"),
520
  "key": "antd",
 
542
  "index": index_docs,
543
  "antd": antd_docs,
544
  "antdx": antdx_docs,
545
+ "base": base_docs,
546
+ "pro": pro_docs
547
  },
548
  default_active_tab=default_active_tab,
549
  logo=logo)
 
551
  demo = site.render()
552
 
553
  if __name__ == "__main__":
554
+ demo.queue(default_concurrency_limit=100,
555
+ max_size=100).launch(ssr_mode=False, max_threads=100)
components/pro/chatbot/README-zh_CN.md ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chatbot
2
+
3
+ 基于 [Ant Design X](https://x.ant.design) 封装的对话机器人组件。
4
+
5
+ ## 示例
6
+
7
+ ### 基础使用
8
+
9
+ 支持传入:
10
+
11
+ - `user`,`assistant`两种 role。
12
+ - `text`,`file`,`tool`,`suggestion`四种消息类型。
13
+
14
+ <demo name="basic" position="bottom" collapsible="true"></demo>
15
+
16
+ ### Chatbot 配置
17
+
18
+ 支持传入配置项:
19
+
20
+ - `markdown_config`:Chatbot 中 Markdown 文本的渲染配置。
21
+ - `welcome_config`:当 Chatbot 中内容为空时显示的欢迎界面配置。
22
+ - `bot_config`:Chatbot 中 bot 信息的展示配置。
23
+ - `user_config`:Chatbot 中 user 信息的展示配置。
24
+
25
+ <demo name="chatbot_config" position="bottom" collapsible="true"></demo>
26
+
27
+ ### Message 配置
28
+
29
+ `message`对象包含了`bot_config`和`user_config`的所有配置,这意味着用户可以自定义每条消息的显示方式。
30
+
31
+ <demo name="message_config" position="bottom" collapsible="true"></demo>
32
+
33
+ ### 多模态示例
34
+
35
+ <demo name="multimodal" position="bottom" collapsible="true"></demo>
36
+
37
+ ### Thinking 示例
38
+
39
+ <demo name="thinking" position="bottom" collapsible="true"></demo>
40
+
41
+ ### API
42
+
43
+ #### 属性
44
+
45
+ | 属性 | 类型 | 默认值 | 描述 |
46
+ | --------------- | ----------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
47
+ | value | `list[dict \| ChatbotDataMessage] \| ChatbotDataMessages \| None` | None | 聊天机器人中显示的默认消息列表,每条消息的格式为`ChatbotDataMessage`。 |
48
+ | welcome_config | `ChatbotWelcomeConfig \| dict \| None` | None | 欢迎界面配置。若`value`为空则会显示欢迎界面。 |
49
+ | markdown_config | `ChatbotMarkdownConfig \| dict \| None` | None | 所有消息的 Markdown 配置。 |
50
+ | user_config | `ChatbotUserConfig \| dict \| None` | None | 用户配置,当 message 的 role 为 'user'时生效。 |
51
+ | bot_config | `ChatbotBotConfig \| dict \| None` | None | 机器人配置,当 message 的 role 为 'assistant'时生效。 |
52
+ | auto_scroll | `bool` | True | 如果为 `True`,则当值改变时将自动滚动到文本框底部,除非用户向上滚动。如果为 `False`,则当值改变时不会滚动到文本框底部。 |
53
+ | height | `int \| float \| str` | 400 | 组件的高度,如果值为数字,则以像素为单位指定,如果传递的是字符串,则以 CSS 单位指定。如果 messages 超出了组件高度,则组件将可滚动。 |
54
+ | max_height | `int \| float \| str \| None` | None | 组件的最大高度,如果传递的是数字,则以像素为单位指定,如果传递的是字符串,则以 CSS 单位指定。 |
55
+ | min_height | `int \| float \| str \| None` | None | 组件的最小高度,如果传递的是数字,则以像素为单位指定,如果传递的是字符串,则以 CSS 单位指定。 |
56
+
57
+ #### 事件
58
+
59
+ | 事件 | 描述 |
60
+ | -------------------------------------------- | ------------------------------------ |
61
+ | `pro.Chatbot.change(fn, ···)` | 当 Chatbot 的值发生变化时触发。 |
62
+ | `pro.Chatbot.copy(fn, ···)` | 当点击复制按钮时触发。 |
63
+ | `pro.Chatbot.edit(fn, ···)` | 当用户编辑消息时触发。 |
64
+ | `pro.Chatbot.delete(fn, ···)` | 当用户删除消息时触发。 |
65
+ | `pro.Chatbot.like(fn, ···)` | 当点击喜欢/不喜欢按钮时触发。 |
66
+ | `pro.Chatbot.retry(fn, ···)` | 当点击重试按钮时触发。 |
67
+ | `pro.Chatbot.suggestion_select(fn, ···)` | 当选择 suggestion 消息的选项时触发。 |
68
+ | `pro.Chatbot.welcome_prompt_select(fn, ···)` | 当选择欢迎提示词的选项时触发。 |
69
+
70
+ ### 插槽
71
+
72
+ ```python
73
+ SLOTS=["roles"]
74
+ ```
75
+
76
+ 另外,如果已有的 role 样式不符合预期,也可以和`Ant Design X`的`Bubble`组件一样使用`ms.Slot("roles")`进行自定义。
77
+
78
+ ### 类型
79
+
80
+ ```python
81
+ # Ant Design X prompt props: https://x.ant.design/components/prompts#promptprops
82
+ class ChatbotPromptConfig(GradioModel):
83
+ disabled: Optional[bool] = None
84
+ description: Optional[str] = None
85
+ label: Optional[str] = None
86
+ icon: Optional[str] = None
87
+ key: Optional[str] = None
88
+ children: Optional[List[Union[
89
+ dict,
90
+ ]]] = None
91
+
92
+
93
+ # Ant Design X prompts props: https://x.ant.design/components/prompts
94
+ class ChatbotPromptsConfig(GradioModel):
95
+ title: Optional[str] = None
96
+ vertical: Optional[bool] = False
97
+ wrap: Optional[bool] = False
98
+ styles: Optional[dict] = None
99
+ class_names: Optional[dict] = None
100
+ elem_style: Optional[dict] = None
101
+ elem_classes: Optional[Union[str, List[str]]] = None
102
+ items: Optional[List[Union[ChatbotPromptConfig, dict]]] = None
103
+
104
+
105
+ # Ant Design X welcome props: https://x.ant.design/components/welcome
106
+ class ChatbotWelcomeConfig(GradioModel):
107
+ variant: Optional[Literal['borderless', 'filled']] = 'borderless'
108
+ icon: Optional[Union[str, Path]] = None
109
+ title: Optional[str] = None
110
+ description: Optional[str] = None
111
+ extra: Optional[str] = None
112
+ elem_style: Optional[dict] = None
113
+ elem_classes: Optional[Union[str, List[str]]] = None
114
+ styles: Optional[dict] = None
115
+ class_names: Optional[dict] = None
116
+ prompts: Optional[Union[ChatbotPromptsConfig, dict]] = None
117
+
118
+
119
+ class ChatbotMarkdownConfig(GradioModel):
120
+ render_markdown: Optional[bool] = True
121
+ latex_delimiters: Optional[list[dict[str, str | bool]]] = field(
122
+ default_factory=lambda: [
123
+ {
124
+ "left": "$$",
125
+ "right": "$$",
126
+ "display": True
127
+ },
128
+ {
129
+ "left": "\\(",
130
+ "right": "\\)",
131
+ "display": False
132
+ },
133
+ {
134
+ "left": "\\begin{equation}",
135
+ "right": "\\end{equation}",
136
+ "display": True
137
+ },
138
+ {
139
+ "left": "\\begin{align}",
140
+ "right": "\\end{align}",
141
+ "display": True
142
+ },
143
+ {
144
+ "left": "\\begin{alignat}",
145
+ "right": "\\end{alignat}",
146
+ "display": True
147
+ },
148
+ {
149
+ "left": "\\begin{gather}",
150
+ "right": "\\end{gather}",
151
+ "display": True
152
+ },
153
+ {
154
+ "left": "\\begin{CD}",
155
+ "right": "\\end{CD}",
156
+ "display": True
157
+ },
158
+ {
159
+ "left": "\\[",
160
+ "right": "\\]",
161
+ "display": True
162
+ },
163
+ ])
164
+ sanitize_html: Optional[bool] = True
165
+ line_breaks: Optional[bool] = True
166
+ rtl: Optional[bool] = False
167
+ allow_tags: Optional[List[str]] = None
168
+
169
+
170
+ class ChatbotActionConfig(GradioModel):
171
+ action: Literal['copy', 'like', 'dislike', 'retry', 'edit', 'delete']
172
+ disabled: Optional[bool] = None
173
+ # Ant Design tooltip: https://ant.design/components/tooltip
174
+ tooltip: Optional[Union[str, dict]] = None
175
+ # Ant Design popconfirm props: https://ant.design/components/popconfirm
176
+ popconfirm: Optional[Union[str, dict]] = None
177
+
178
+
179
+ class ChatbotUserConfig(GradioModel):
180
+ # Action buttons for user message
181
+ actions: Optional[List[Union[Literal[
182
+ 'copy',
183
+ 'edit',
184
+ 'delete',
185
+ ], ChatbotActionConfig, dict]]] = field(default_factory=lambda: [
186
+ "copy"
187
+ # 'edit',
188
+ # ChatbotActionConfig(
189
+ # action='delete',
190
+ # popconfirm=dict(title="Delete the message",
191
+ # description="Are you sure to delete this message?",
192
+ # okButtonProps=dict(danger=True)))
193
+ ])
194
+ disabled_actions: Optional[List[Union[Literal[
195
+ 'copy',
196
+ 'edit',
197
+ 'delete',
198
+ ]]]] = None
199
+ header: Optional[str] = None
200
+ footer: Optional[str] = None
201
+ # Ant Design avatar props: https://ant.design/components/avatar
202
+ avatar: Optional[Union[str, Path, dict, None]] = None
203
+ variant: Optional[Literal['filled', 'borderless', 'outlined',
204
+ 'shadow']] = None
205
+ shape: Optional[Literal['round', 'corner']] = None
206
+ placement: Optional[Literal['start', 'end']] = 'end'
207
+ loading: Optional[bool] = None
208
+ typing: Optional[Union[bool, dict]] = None
209
+ elem_style: Optional[dict] = None
210
+ elem_classes: Optional[Union[str, List[str]]] = None
211
+ styles: Optional[dict] = None
212
+ class_names: Optional[dict] = None
213
+
214
+
215
+ class ChatbotBotConfig(ChatbotUserConfig):
216
+ # Action buttons for bot message
217
+ actions: Optional[List[Union[Literal[
218
+ 'copy',
219
+ 'like',
220
+ 'dislike',
221
+ 'retry',
222
+ 'edit',
223
+ 'delete',
224
+ ], ChatbotActionConfig, dict]]] = field(default_factory=lambda: [
225
+ 'copy',
226
+ # 'like', 'dislike', 'retry', 'edit',
227
+ # ChatbotActionConfig(
228
+ # action='delete',
229
+ # popconfirm=dict(title="Delete the message",
230
+ # description="Are you sure to delete this message?",
231
+ # okButtonProps=dict(danger=True)))
232
+ ])
233
+ disabled_actions: Optional[List[Union[Literal[
234
+ 'copy',
235
+ 'like',
236
+ 'dislike',
237
+ 'retry',
238
+ 'edit',
239
+ 'delete',
240
+ ]]]] = None
241
+ placement: Optional[Literal['start', 'end']] = 'start'
242
+
243
+
244
+ class ChatbotDataTextContentOptions(ChatbotMarkdownConfig):
245
+ pass
246
+
247
+
248
+ class ChatbotDataToolContentOptions(ChatbotDataTextContentOptions):
249
+ """
250
+ title: tool message title.
251
+ status: tool message status, if status is 'done', the message will be collapsed.
252
+ """
253
+ title: Optional[str] = None
254
+ status: Optional[Literal['pending', 'done']] = None
255
+
256
+
257
+ # Ant Design flex props: https://ant.design/components/flex
258
+ class ChatbotDataFileContentOptions(GradioModel):
259
+ vertical: Union[bool] = False
260
+ wrap: Optional[Union[Literal['nowrap', 'wrap', 'wrap-reverse'],
261
+ bool]] = True
262
+ justify: Optional[Literal['normal', 'start', 'end', 'flex-start',
263
+ 'flex-end', 'center', 'left', 'right',
264
+ 'space-between', 'space-around', 'space-evenly',
265
+ 'stretch', 'safe', 'unsafe']] = "normal"
266
+ align: Optional[Literal['normal', 'start', 'end', 'flex-start', 'flex-end',
267
+ 'center', 'self-start', 'self-end', 'baseline',
268
+ 'unsafe', 'stretch']] = "normal"
269
+ flex: Optional[str] = "normal"
270
+ gap: Optional[Union[Literal["small", "middle", "large"], str, int,
271
+ float]] = "small"
272
+
273
+
274
+ # Ant Design X prompts props: https://x.ant.design/components/prompts
275
+ class ChatbotDataSuggestionContentOptions(ChatbotPromptsConfig):
276
+ pass
277
+
278
+
279
+ # Ant Design X prompt props: https://x.ant.design/components/prompts#promptprops
280
+ class ChatbotDataSuggestionContentItem(ChatbotPromptConfig):
281
+ pass
282
+
283
+
284
+ class ChatbotDataMeta(GradioModel):
285
+ feedback: Optional[Literal['like', 'dislike', None]] = None
286
+
287
+
288
+ class ChatbotDataMessageContent(GradioModel):
289
+ """
290
+ type: Content type, support 'text', 'tool', 'file', 'suggestion'.
291
+
292
+ content: Content value.
293
+
294
+ options: Content options, each content type has different options.
295
+
296
+ copyable: Whether the content can be copied via the 'copy' button.
297
+
298
+ editable: Whether the content can be edited via the 'edit' button. Only available for content type 'text' and 'tool'.
299
+ """
300
+ type: Optional[Literal['text', 'tool', 'file', 'suggestion']] = 'text'
301
+ copyable: Optional[bool] = True
302
+ editable: Optional[bool] = True
303
+ content: Optional[Union[str, List[Union[FileData,
304
+ ChatbotDataSuggestionContentItem,
305
+ dict, str]]]] = None
306
+ options: Optional[Union[dict, ChatbotDataTextContentOptions,
307
+ ChatbotDataFileContentOptions,
308
+ ChatbotDataToolContentOptions,
309
+ ChatbotDataSuggestionContentOptions]] = None
310
+
311
+
312
+ class ChatbotDataMessage(ChatbotBotConfig):
313
+ role: Union[Literal['user', 'assistant', 'system'], str] = None
314
+ key: Optional[Union[str, int, float]] = None
315
+ # If status is 'pending', the message will not render the footer area (including 'actions' and 'footer').
316
+ status: Optional[Literal['pending', 'done']] = None
317
+ content: Optional[Union[str, ChatbotDataMessageContent, dict,
318
+ List[ChatbotDataMessageContent],
319
+ List[dict]]] = None
320
+ placement: Optional[Literal['start', 'end']] = None
321
+ actions: Optional[List[Union[Literal[
322
+ 'copy',
323
+ 'like',
324
+ 'dislike',
325
+ 'retry',
326
+ 'edit',
327
+ 'delete',
328
+ ], ChatbotActionConfig, dict]]] = None
329
+ disabled_actions: Optional[List[Union[Literal[
330
+ 'copy',
331
+ 'like',
332
+ 'dislike',
333
+ 'retry',
334
+ 'edit',
335
+ 'delete',
336
+ ]]]] = None
337
+ meta: Optional[Union[ChatbotDataMeta, dict]] = None
338
+
339
+
340
+ class ChatbotDataMessages(GradioRootModel):
341
+ root: List[ChatbotDataMessage]
342
+ ```
components/pro/chatbot/README.md ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chatbot
2
+
3
+ A chatbot component based on [Ant Design X](https://x.ant.design).
4
+
5
+ ## Examples
6
+
7
+ ### Basic
8
+
9
+ Supports:
10
+
11
+ - Two roles: user and assistant.
12
+ - Four message types: text, file, tool, and suggestion.
13
+
14
+ <demo name="basic" position="bottom" collapsible="true"></demo>
15
+
16
+ ### Chatbot Config
17
+
18
+ Supports configuration options:
19
+
20
+ - `markdown_config`: Rendering configuration for Markdown text in the Chatbot.
21
+ - `welcome_config`: Welcome interface configuration displayed when the Chatbot is empty.
22
+ - `bot_config`: Display configuration for bot messages.
23
+ - `user_config`: Display configuration for user messages.
24
+ <demo name="chatbot_config" position="bottom" collapsible="true"></demo>
25
+
26
+ ### Message Config
27
+
28
+ The `message` object includes all configurations from `bot_config` and `user_config`, allowing users to customize the display of individual messages.
29
+
30
+ <demo name="message_config" position="bottom" collapsible="true"></demo>
31
+
32
+ ### Multimodal
33
+
34
+ <demo name="multimodal" position="bottom" collapsible="true"></demo>
35
+
36
+ ### Thinking
37
+
38
+ <demo name="thinking" position="bottom" collapsible="true"></demo>
39
+
40
+ ### API
41
+
42
+ ### Props
43
+
44
+ | Attribute | Type | Default Value | Description |
45
+ | --------------- | ----------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | value | `list[dict \| ChatbotDataMessage] \| ChatbotDataMessages \| None` | None | Default list of messages to show in chatbot, where each message is of the format `ChatbotDataMessage`. |
47
+ | welcome_config | `ChatbotWelcomeConfig \| dict \| None` | None | Configuration of the welcome interface. If the `value` is empty, the welcome interface will be displayed. |
48
+ | markdown_config | `ChatbotMarkdownConfig \| dict \| None` | None | Markdown configuration for all messages. |
49
+ | user_config | `ChatbotUserConfig \| dict \| None` | None | User configuration, will be applied when the message role is 'user'. |
50
+ | bot_config | `ChatbotBotConfig \| dict \| None` | None | Bot configuration, will be applied when the message role is 'assistant'. |
51
+ | auto_scroll | `bool` | True | If True, will automatically scroll to the bottom of the textbox when the value changes, unless the user scrolls up. If False, will not scroll to the bottom of the textbox when the value changes. |
52
+ | height | `int \| float \| str` | 400 | The height of the component, specified in pixels if a number is passed, or in CSS units if a string is passed. If messages exceed the height, the component will scroll. |
53
+ | max_height | `int \| float \| str \| None` | None | The maximum height of the component, specified in pixels if a number is passed, or in CSS units if a string is passed.  |
54
+ | min_height | `int \| float \| str \| None` | None | The minimum height of the component, specified in pixels if a number is passed, or in CSS units if a string is passed. |
55
+
56
+ #### Events
57
+
58
+ | Event | Description |
59
+ | -------------------------------------------- | ------------------------------------------------------- |
60
+ | `pro.Chatbot.change(fn, ···)` | Triggered when the Chatbot value changed. |
61
+ | `pro.Chatbot.copy(fn, ···)` | Triggered when the copy button is clicked. |
62
+ | `pro.Chatbot.edit(fn, ···)` | Triggered when user edit a message. |
63
+ | `pro.Chatbot.delete(fn, ···)` | Triggered when user delete a message. |
64
+ | `pro.Chatbot.like(fn, ···)` | Triggered when the like/dislike button is clicked |
65
+ | `pro.Chatbot.retry(fn, ···)` | Triggered when the retry button is clicked |
66
+ | `pro.Chatbot.suggestion_select(fn, ···)` | Triggered when the suggestion message item is selected. |
67
+ | `pro.Chatbot.welcome_prompt_select(fn, ···)` | Triggered when the welcome prompt item is selected. |
68
+
69
+ ### Slots
70
+
71
+ ```python
72
+ SLOTS=["roles"]
73
+ ```
74
+
75
+ Additionally, if the role style does not meet your expectations, you can also use `ms.Slot("roles")` to customize it, just like the `Bubble` component of `Ant ​​Design X`.
76
+
77
+ ### Types
78
+
79
+ ```python
80
+ # Ant Design X prompt props: https://x.ant.design/components/prompts#promptprops
81
+ class ChatbotPromptConfig(GradioModel):
82
+ disabled: Optional[bool] = None
83
+ description: Optional[str] = None
84
+ label: Optional[str] = None
85
+ icon: Optional[str] = None
86
+ key: Optional[str] = None
87
+ children: Optional[List[Union[
88
+ dict,
89
+ ]]] = None
90
+
91
+
92
+ # Ant Design X prompts props: https://x.ant.design/components/prompts
93
+ class ChatbotPromptsConfig(GradioModel):
94
+ title: Optional[str] = None
95
+ vertical: Optional[bool] = False
96
+ wrap: Optional[bool] = False
97
+ styles: Optional[dict] = None
98
+ class_names: Optional[dict] = None
99
+ elem_style: Optional[dict] = None
100
+ elem_classes: Optional[Union[str, List[str]]] = None
101
+ items: Optional[List[Union[ChatbotPromptConfig, dict]]] = None
102
+
103
+
104
+ # Ant Design X welcome props: https://x.ant.design/components/welcome
105
+ class ChatbotWelcomeConfig(GradioModel):
106
+ variant: Optional[Literal['borderless', 'filled']] = 'borderless'
107
+ icon: Optional[Union[str, Path]] = None
108
+ title: Optional[str] = None
109
+ description: Optional[str] = None
110
+ extra: Optional[str] = None
111
+ elem_style: Optional[dict] = None
112
+ elem_classes: Optional[Union[str, List[str]]] = None
113
+ styles: Optional[dict] = None
114
+ class_names: Optional[dict] = None
115
+ prompts: Optional[Union[ChatbotPromptsConfig, dict]] = None
116
+
117
+
118
+ class ChatbotMarkdownConfig(GradioModel):
119
+ render_markdown: Optional[bool] = True
120
+ latex_delimiters: Optional[list[dict[str, str | bool]]] = field(
121
+ default_factory=lambda: [
122
+ {
123
+ "left": "$$",
124
+ "right": "$$",
125
+ "display": True
126
+ },
127
+ {
128
+ "left": "\\(",
129
+ "right": "\\)",
130
+ "display": False
131
+ },
132
+ {
133
+ "left": "\\begin{equation}",
134
+ "right": "\\end{equation}",
135
+ "display": True
136
+ },
137
+ {
138
+ "left": "\\begin{align}",
139
+ "right": "\\end{align}",
140
+ "display": True
141
+ },
142
+ {
143
+ "left": "\\begin{alignat}",
144
+ "right": "\\end{alignat}",
145
+ "display": True
146
+ },
147
+ {
148
+ "left": "\\begin{gather}",
149
+ "right": "\\end{gather}",
150
+ "display": True
151
+ },
152
+ {
153
+ "left": "\\begin{CD}",
154
+ "right": "\\end{CD}",
155
+ "display": True
156
+ },
157
+ {
158
+ "left": "\\[",
159
+ "right": "\\]",
160
+ "display": True
161
+ },
162
+ ])
163
+ sanitize_html: Optional[bool] = True
164
+ line_breaks: Optional[bool] = True
165
+ rtl: Optional[bool] = False
166
+ allow_tags: Optional[List[str]] = None
167
+
168
+
169
+ class ChatbotActionConfig(GradioModel):
170
+ action: Literal['copy', 'like', 'dislike', 'retry', 'edit', 'delete']
171
+ disabled: Optional[bool] = None
172
+ # Ant Design tooltip: https://ant.design/components/tooltip
173
+ tooltip: Optional[Union[str, dict]] = None
174
+ # Ant Design popconfirm props: https://ant.design/components/popconfirm
175
+ popconfirm: Optional[Union[str, dict]] = None
176
+
177
+
178
+ class ChatbotUserConfig(GradioModel):
179
+ # Action buttons for user message
180
+ actions: Optional[List[Union[Literal[
181
+ 'copy',
182
+ 'edit',
183
+ 'delete',
184
+ ], ChatbotActionConfig, dict]]] = field(default_factory=lambda: [
185
+ "copy"
186
+ # 'edit',
187
+ # ChatbotActionConfig(
188
+ # action='delete',
189
+ # popconfirm=dict(title="Delete the message",
190
+ # description="Are you sure to delete this message?",
191
+ # okButtonProps=dict(danger=True)))
192
+ ])
193
+ disabled_actions: Optional[List[Union[Literal[
194
+ 'copy',
195
+ 'edit',
196
+ 'delete',
197
+ ]]]] = None
198
+ header: Optional[str] = None
199
+ footer: Optional[str] = None
200
+ # Ant Design avatar props: https://ant.design/components/avatar
201
+ avatar: Optional[Union[str, Path, dict, None]] = None
202
+ variant: Optional[Literal['filled', 'borderless', 'outlined',
203
+ 'shadow']] = None
204
+ shape: Optional[Literal['round', 'corner']] = None
205
+ placement: Optional[Literal['start', 'end']] = 'end'
206
+ loading: Optional[bool] = None
207
+ typing: Optional[Union[bool, dict]] = None
208
+ elem_style: Optional[dict] = None
209
+ elem_classes: Optional[Union[str, List[str]]] = None
210
+ styles: Optional[dict] = None
211
+ class_names: Optional[dict] = None
212
+
213
+
214
+ class ChatbotBotConfig(ChatbotUserConfig):
215
+ # Action buttons for bot message
216
+ actions: Optional[List[Union[Literal[
217
+ 'copy',
218
+ 'like',
219
+ 'dislike',
220
+ 'retry',
221
+ 'edit',
222
+ 'delete',
223
+ ], ChatbotActionConfig, dict]]] = field(default_factory=lambda: [
224
+ 'copy',
225
+ # 'like', 'dislike', 'retry', 'edit',
226
+ # ChatbotActionConfig(
227
+ # action='delete',
228
+ # popconfirm=dict(title="Delete the message",
229
+ # description="Are you sure to delete this message?",
230
+ # okButtonProps=dict(danger=True)))
231
+ ])
232
+ disabled_actions: Optional[List[Union[Literal[
233
+ 'copy',
234
+ 'like',
235
+ 'dislike',
236
+ 'retry',
237
+ 'edit',
238
+ 'delete',
239
+ ]]]] = None
240
+ placement: Optional[Literal['start', 'end']] = 'start'
241
+
242
+
243
+ class ChatbotDataTextContentOptions(ChatbotMarkdownConfig):
244
+ pass
245
+
246
+
247
+ class ChatbotDataToolContentOptions(ChatbotDataTextContentOptions):
248
+ """
249
+ title: tool message title.
250
+ status: tool message status, if status is 'done', the message will be collapsed.
251
+ """
252
+ title: Optional[str] = None
253
+ status: Optional[Literal['pending', 'done']] = None
254
+
255
+
256
+ # Ant Design flex props: https://ant.design/components/flex
257
+ class ChatbotDataFileContentOptions(GradioModel):
258
+ vertical: Union[bool] = False
259
+ wrap: Optional[Union[Literal['nowrap', 'wrap', 'wrap-reverse'],
260
+ bool]] = True
261
+ justify: Optional[Literal['normal', 'start', 'end', 'flex-start',
262
+ 'flex-end', 'center', 'left', 'right',
263
+ 'space-between', 'space-around', 'space-evenly',
264
+ 'stretch', 'safe', 'unsafe']] = "normal"
265
+ align: Optional[Literal['normal', 'start', 'end', 'flex-start', 'flex-end',
266
+ 'center', 'self-start', 'self-end', 'baseline',
267
+ 'unsafe', 'stretch']] = "normal"
268
+ flex: Optional[str] = "normal"
269
+ gap: Optional[Union[Literal["small", "middle", "large"], str, int,
270
+ float]] = "small"
271
+
272
+
273
+ # Ant Design X prompts props: https://x.ant.design/components/prompts
274
+ class ChatbotDataSuggestionContentOptions(ChatbotPromptsConfig):
275
+ pass
276
+
277
+
278
+ # Ant Design X prompt props: https://x.ant.design/components/prompts#promptprops
279
+ class ChatbotDataSuggestionContentItem(ChatbotPromptConfig):
280
+ pass
281
+
282
+
283
+ class ChatbotDataMeta(GradioModel):
284
+ feedback: Optional[Literal['like', 'dislike', None]] = None
285
+
286
+
287
+ class ChatbotDataMessageContent(GradioModel):
288
+ """
289
+ type: Content type, support 'text', 'tool', 'file', 'suggestion'.
290
+
291
+ content: Content value.
292
+
293
+ options: Content options, each content type has different options.
294
+
295
+ copyable: Whether the content can be copied via the 'copy' button.
296
+
297
+ editable: Whether the content can be edited via the 'edit' button. Only available for content type 'text' and 'tool'.
298
+ """
299
+ type: Optional[Literal['text', 'tool', 'file', 'suggestion']] = 'text'
300
+ copyable: Optional[bool] = True
301
+ editable: Optional[bool] = True
302
+ content: Optional[Union[str, List[Union[FileData,
303
+ ChatbotDataSuggestionContentItem,
304
+ dict, str]]]] = None
305
+ options: Optional[Union[dict, ChatbotDataTextContentOptions,
306
+ ChatbotDataFileContentOptions,
307
+ ChatbotDataToolContentOptions,
308
+ ChatbotDataSuggestionContentOptions]] = None
309
+
310
+
311
+ class ChatbotDataMessage(ChatbotBotConfig):
312
+ role: Union[Literal['user', 'assistant', 'system'], str] = None
313
+ key: Optional[Union[str, int, float]] = None
314
+ # If status is 'pending', the message will not render the footer area (including 'actions' and 'footer').
315
+ status: Optional[Literal['pending', 'done']] = None
316
+ content: Optional[Union[str, ChatbotDataMessageContent, dict,
317
+ List[ChatbotDataMessageContent],
318
+ List[dict]]] = None
319
+ placement: Optional[Literal['start', 'end']] = None
320
+ actions: Optional[List[Union[Literal[
321
+ 'copy',
322
+ 'like',
323
+ 'dislike',
324
+ 'retry',
325
+ 'edit',
326
+ 'delete',
327
+ ], ChatbotActionConfig, dict]]] = None
328
+ disabled_actions: Optional[List[Union[Literal[
329
+ 'copy',
330
+ 'like',
331
+ 'dislike',
332
+ 'retry',
333
+ 'edit',
334
+ 'delete',
335
+ ]]]] = None
336
+ meta: Optional[Union[ChatbotDataMeta, dict]] = None
337
+
338
+
339
+ class ChatbotDataMessages(GradioRootModel):
340
+ root: List[ChatbotDataMessage]
341
+ ```
components/pro/chatbot/app.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from helper.Docs import Docs
2
+
3
+ docs = Docs(__file__)
4
+
5
+ if __name__ == "__main__":
6
+ docs.render().queue().launch()
components/pro/chatbot/demos/basic.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import gradio as gr
4
+ import modelscope_studio.components.antdx as antdx
5
+ import modelscope_studio.components.base as ms
6
+ import modelscope_studio.components.pro as pro
7
+ from modelscope_studio.components.pro.chatbot import (
8
+ ChatbotDataMessage, ChatbotDataMessageContent,
9
+ ChatbotDataSuggestionContentItem, ChatbotDataSuggestionContentOptions)
10
+
11
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
12
+ pro.Chatbot(
13
+ value=[
14
+ ChatbotDataMessage(role="user", content="Hello"),
15
+ {
16
+ "role": "assistant",
17
+ "content": "World"
18
+ },
19
+ ChatbotDataMessage(
20
+ role="user",
21
+ # other content type
22
+ content=ChatbotDataMessageContent(
23
+ type="file",
24
+ content=[
25
+ "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
26
+ os.path.join(os.path.dirname(__file__),
27
+ "../resources/scenery.jpeg")
28
+ ])),
29
+ ChatbotDataMessage(role="assistant",
30
+ content=ChatbotDataMessageContent(
31
+ type="tool",
32
+ content="Tool content",
33
+ options={"title": "Tool"})),
34
+ ChatbotDataMessage(role="assistant",
35
+ content=ChatbotDataMessageContent(
36
+ type="suggestion",
37
+ content=[
38
+ ChatbotDataSuggestionContentItem(
39
+ description="Option 1"),
40
+ ChatbotDataSuggestionContentItem(
41
+ description="Option 2"),
42
+ ChatbotDataSuggestionContentItem(
43
+ description="Option 3")
44
+ ],
45
+ options=ChatbotDataSuggestionContentOptions(
46
+ title="Suggestion"))),
47
+ ChatbotDataMessage(
48
+ role="assistant",
49
+ # multiple content type
50
+ content=[
51
+ ChatbotDataMessageContent(type="tool",
52
+ content="Thought content",
53
+ options={"title": "Thinking"}),
54
+ ChatbotDataMessageContent(type="text",
55
+ content="Hello World"),
56
+ ChatbotDataMessageContent(
57
+ type="suggestion",
58
+ content=[
59
+ ChatbotDataSuggestionContentItem(
60
+ description="Option 1"),
61
+ ChatbotDataSuggestionContentItem(
62
+ description="Option 2"),
63
+ ChatbotDataSuggestionContentItem(
64
+ description="Option 3")
65
+ ],
66
+ options=ChatbotDataSuggestionContentOptions(
67
+ title="Suggestion"))
68
+ ]),
69
+ ],
70
+ height=600)
71
+
72
+ if __name__ == "__main__":
73
+ demo.queue().launch()
components/pro/chatbot/demos/chatbot_config.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+
3
+ import gradio as gr
4
+ import modelscope_studio.components.antdx as antdx
5
+ import modelscope_studio.components.base as ms
6
+ import modelscope_studio.components.pro as pro
7
+ from modelscope_studio.components.pro.chatbot import (
8
+ ChatbotActionConfig, ChatbotBotConfig, ChatbotMarkdownConfig,
9
+ ChatbotPromptsConfig, ChatbotUserConfig, ChatbotWelcomeConfig)
10
+
11
+
12
+ def welcome_prompt_select(chatbot_value, e: gr.EventData):
13
+ chatbot_value.append({
14
+ "role":
15
+ "user",
16
+ "content":
17
+ e._data["payload"][0]["value"]["description"]
18
+ })
19
+ chatbot_value.append({
20
+ "role": "assistant",
21
+ "loading": True,
22
+ "status": "pending",
23
+ })
24
+ yield gr.update(value=chatbot_value)
25
+
26
+ time.sleep(1)
27
+ chatbot_value[-1]["loading"] = False
28
+ chatbot_value[-1]["status"] = 'done'
29
+ chatbot_value[-1]["content"] = """
30
+ <think>Thought Content</think>
31
+
32
+ Content
33
+ """
34
+ yield gr.update(value=chatbot_value)
35
+
36
+
37
+ def retry(chatbot_value, e: gr.EventData):
38
+ index = e._data["payload"][0]["index"]
39
+ chatbot_value[index]["content"] = "Regenerated Content"
40
+ return gr.update(value=chatbot_value)
41
+
42
+
43
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
44
+ chatbot = pro.Chatbot(
45
+ markdown_config=ChatbotMarkdownConfig(allow_tags=["think"]),
46
+ welcome_config=ChatbotWelcomeConfig(
47
+ variant="borderless",
48
+ icon=
49
+ "https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp",
50
+ title="Hello I'm Chatbot",
51
+ description="Let's start a conversation.",
52
+ prompts=ChatbotPromptsConfig(
53
+ title="How can I help you today?",
54
+ styles={
55
+ "list": {
56
+ "width": '100%',
57
+ },
58
+ "item": {
59
+ "flex": 1,
60
+ },
61
+ },
62
+ items=[{
63
+ "label":
64
+ "🖋 Make a plan",
65
+ "children": [{
66
+ "description":
67
+ "Help me with a plan to start a business"
68
+ }, {
69
+ "description":
70
+ "Help me with a plan to achieve my goals"
71
+ }, {
72
+ "description":
73
+ "Help me with a plan for a successful interview"
74
+ }]
75
+ }, {
76
+ "label":
77
+ "📅 Help me write",
78
+ "children": [{
79
+ "description":
80
+ "Help me write a story with a twist ending"
81
+ }, {
82
+ "description":
83
+ "Help me write a blog post on mental health"
84
+ }, {
85
+ "description":
86
+ "Help me write a letter to my future self"
87
+ }]
88
+ }])),
89
+ user_config=ChatbotUserConfig(
90
+ actions=[
91
+ "copy", "edit",
92
+ ChatbotActionConfig(
93
+ action="delete",
94
+ popconfirm=dict(
95
+ title="Delete the message",
96
+ description="Are you sure to delete this message?",
97
+ okButtonProps=dict(danger=True)))
98
+ ],
99
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=3"),
100
+ bot_config=ChatbotBotConfig(
101
+ # custom actions
102
+ actions=[
103
+ "copy", "like", "dislike", "retry", "edit",
104
+ ChatbotActionConfig(
105
+ action="delete",
106
+ popconfirm=dict(
107
+ title="Delete the message",
108
+ description="Are you sure to delete this message?",
109
+ okButtonProps=dict(danger=True)))
110
+ ],
111
+ avatar=
112
+ "https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
113
+ ),
114
+ height=600)
115
+
116
+ chatbot.welcome_prompt_select(fn=welcome_prompt_select,
117
+ inputs=[chatbot],
118
+ outputs=[chatbot])
119
+ chatbot.retry(fn=retry, inputs=[chatbot], outputs=[chatbot])
120
+
121
+ if __name__ == "__main__":
122
+ demo.queue().launch()
components/pro/chatbot/demos/message_config.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import modelscope_studio.components.antdx as antdx
3
+ import modelscope_studio.components.base as ms
4
+ import modelscope_studio.components.pro as pro
5
+ from modelscope_studio.components.pro.chatbot import (ChatbotActionConfig,
6
+ ChatbotBotConfig,
7
+ ChatbotDataMessage,
8
+ ChatbotUserConfig)
9
+
10
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
11
+ pro.Chatbot(
12
+ value=[
13
+ ChatbotDataMessage(role="user", content="Hello"),
14
+ ChatbotDataMessage(role="assistant", content="World"),
15
+ ChatbotDataMessage(role="assistant",
16
+ content="Liked message",
17
+ meta=dict(feedback="like")),
18
+ ChatbotDataMessage(role="assistant",
19
+ content="Message only has copy button",
20
+ actions=["copy"]),
21
+ ChatbotDataMessage(
22
+ role="assistant",
23
+ content="Pending message will not show action buttons",
24
+ status="pending"),
25
+ ChatbotDataMessage(
26
+ role="assistant",
27
+ content="Bot 1",
28
+ header="bot1",
29
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=1"),
30
+ ChatbotDataMessage(
31
+ role="assistant",
32
+ content="Bot 2",
33
+ header="bot2",
34
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=2"),
35
+ ],
36
+ user_config=ChatbotUserConfig(
37
+ actions=[
38
+ "copy", "edit",
39
+ ChatbotActionConfig(
40
+ action="delete",
41
+ popconfirm=dict(
42
+ title="Delete the message",
43
+ description="Are you sure to delete this message?",
44
+ okButtonProps=dict(danger=True)))
45
+ ],
46
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=3"),
47
+ bot_config=ChatbotBotConfig(
48
+ # custom actions
49
+ actions=[
50
+ "copy", "like", "dislike", "edit",
51
+ ChatbotActionConfig(
52
+ action="delete",
53
+ popconfirm=dict(
54
+ title="Delete the message",
55
+ description="Are you sure to delete this message?",
56
+ okButtonProps=dict(danger=True)))
57
+ ],
58
+ avatar=
59
+ "https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
60
+ ),
61
+ height=600)
62
+
63
+ if __name__ == "__main__":
64
+ demo.queue().launch()
components/pro/chatbot/demos/multimodal.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+ import time
4
+
5
+ import gradio as gr
6
+ import modelscope_studio.components.antd as antd
7
+ import modelscope_studio.components.antdx as antdx
8
+ import modelscope_studio.components.base as ms
9
+ import modelscope_studio.components.pro as pro
10
+ from modelscope_studio.components.pro.chatbot import (ChatbotBotConfig,
11
+ ChatbotPromptsConfig,
12
+ ChatbotWelcomeConfig)
13
+ from modelscope_studio.components.pro.multimodal_input import \
14
+ MultimodalInputUploadConfig
15
+ from openai import OpenAI
16
+
17
+ client = OpenAI(
18
+ base_url='https://api-inference.modelscope.cn/v1/',
19
+ api_key=os.getenv("MODELSCOPE_API_KEY"), # ModelScope Token
20
+ )
21
+
22
+ model = "Qwen/Qwen2.5-VL-72B-Instruct"
23
+
24
+
25
+ def prompt_select(input_value, e: gr.EventData):
26
+ input_value["text"] = e._data["payload"][0]["value"]["description"]
27
+ return gr.update(value=input_value)
28
+
29
+
30
+ def clear():
31
+ return gr.update(value=None)
32
+
33
+
34
+ def cancel(chatbot_value):
35
+ chatbot_value[-1]["loading"] = False
36
+ chatbot_value[-1]["status"] = "done"
37
+ chatbot_value[-1]["footer"] = "Chat completion paused"
38
+
39
+ return gr.update(value=chatbot_value), gr.update(loading=False), gr.update(
40
+ disabled=False)
41
+
42
+
43
+ def retry(chatbot_value, e: gr.EventData):
44
+ index = e._data["payload"][0]["index"]
45
+ chatbot_value = chatbot_value[:index]
46
+
47
+ yield gr.update(loading=True), gr.update(value=chatbot_value), gr.update(
48
+ disabled=True)
49
+ for chunk in submit(None, chatbot_value):
50
+ yield chunk
51
+
52
+
53
+ def image_to_base64(image_path):
54
+ with open(image_path, "rb") as image_file:
55
+ encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
56
+ return f"data:image/jpeg;base64,{encoded_string}"
57
+
58
+
59
+ def format_history(history):
60
+ messages = [{"role": "system", "content": "You are a helpful assistant."}]
61
+ for item in history:
62
+ if item["role"] == "user":
63
+ messages.append({
64
+ "role":
65
+ "user",
66
+ "content": [{
67
+ "type": "image_url",
68
+ "image_url": image_to_base64(file)
69
+ } for file in item["content"][0]["content"]
70
+ if os.path.exists(file)] +
71
+ [{
72
+ "type": "text",
73
+ "text": item["content"][1]["content"]
74
+ }]
75
+ })
76
+ elif item["role"] == "assistant":
77
+ # ignore thought message
78
+ messages.append({"role": "assistant", "content": item["content"]})
79
+ return messages
80
+
81
+
82
+ def submit(input_value, chatbot_value):
83
+ if input_value is not None:
84
+ chatbot_value.append({
85
+ "role":
86
+ "user",
87
+ "content": [{
88
+ "type": "file",
89
+ "content": [f for f in input_value["files"]]
90
+ }, {
91
+ "type": "text",
92
+ "content": input_value["text"]
93
+ }],
94
+ })
95
+ history_messages = format_history(chatbot_value)
96
+ chatbot_value.append({
97
+ "role": "assistant",
98
+ "content": "",
99
+ "loading": True,
100
+ "status": "pending"
101
+ })
102
+ yield {
103
+ input: gr.update(value=None, loading=True),
104
+ clear_btn: gr.update(disabled=True),
105
+ chatbot: gr.update(value=chatbot_value)
106
+ }
107
+
108
+ try:
109
+ response = client.chat.completions.create(model=model,
110
+ messages=history_messages,
111
+ stream=True)
112
+ start_time = time.time()
113
+
114
+ for chunk in response:
115
+ chatbot_value[-1]["content"] += chunk.choices[0].delta.content
116
+ chatbot_value[-1]["loading"] = False
117
+
118
+ yield {chatbot: gr.update(value=chatbot_value)}
119
+
120
+ chatbot_value[-1]["footer"] = "{:.2f}".format(time.time() -
121
+ start_time) + 's'
122
+ chatbot_value[-1]["status"] = "done"
123
+ yield {
124
+ clear_btn: gr.update(disabled=False),
125
+ input: gr.update(loading=False),
126
+ chatbot: gr.update(value=chatbot_value),
127
+ }
128
+ except Exception as e:
129
+ chatbot_value[-1]["loading"] = False
130
+ chatbot_value[-1]["status"] = "done"
131
+ chatbot_value[-1]["content"] = "Failed to respond, please try again."
132
+ yield {
133
+ clear_btn: gr.update(disabled=False),
134
+ input: gr.update(loading=False),
135
+ chatbot: gr.update(value=chatbot_value),
136
+ }
137
+ raise e
138
+
139
+
140
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider(), ms.AutoLoading(
141
+ ):
142
+ with antd.Flex(vertical=True, gap="middle", elem_style=dict(height=800)):
143
+ chatbot = pro.Chatbot(
144
+ # for flex=1 to fill the remaining space
145
+ height=0,
146
+ elem_style=dict(flex=1),
147
+ welcome_config=ChatbotWelcomeConfig(
148
+ variant="borderless",
149
+ icon=
150
+ "https://assets.alicdn.com/g/qwenweb/qwen-webui-fe/0.0.44/static/favicon.png",
151
+ title=f"Hello, I'm {model}",
152
+ description="You can upload images and text to get started.",
153
+ prompts=ChatbotPromptsConfig(
154
+ title="How can I help you today?",
155
+ styles={
156
+ "list": {
157
+ "width": '100%',
158
+ },
159
+ "item": {
160
+ "flex": 1,
161
+ },
162
+ },
163
+ items=[{
164
+ "label":
165
+ "🖋 Make a plan",
166
+ "children": [{
167
+ "description":
168
+ "Help me with a plan to start a business"
169
+ }, {
170
+ "description":
171
+ "Help me with a plan to achieve my goals"
172
+ }, {
173
+ "description":
174
+ "Help me with a plan for a successful interview"
175
+ }]
176
+ }, {
177
+ "label":
178
+ "📅 Help me write",
179
+ "children": [{
180
+ "description":
181
+ "Help me write a story with a twist ending"
182
+ }, {
183
+ "description":
184
+ "Help me write a blog post on mental health"
185
+ }, {
186
+ "description":
187
+ "Help me write a letter to my future self"
188
+ }]
189
+ }])),
190
+ user_config=dict(
191
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=3"),
192
+ bot_config=ChatbotBotConfig(
193
+ header=model,
194
+ actions=["copy", "retry"],
195
+ avatar=
196
+ "https://assets.alicdn.com/g/qwenweb/qwen-webui-fe/0.0.44/static/favicon.png"
197
+ ),
198
+ )
199
+
200
+ with pro.MultimodalInput(upload_config=MultimodalInputUploadConfig(
201
+ max_count=4, multiple=True, accept="image/*")) as input:
202
+ with ms.Slot("prefix"):
203
+ with antd.Button(value=None, color="default",
204
+ variant="text") as clear_btn:
205
+ with ms.Slot("icon"):
206
+ antd.Icon("ClearOutlined")
207
+ clear_btn.click(fn=clear, outputs=[chatbot])
208
+ submit_event = input.submit(fn=submit,
209
+ inputs=[input, chatbot],
210
+ outputs=[input, chatbot, clear_btn])
211
+ input.cancel(fn=cancel,
212
+ inputs=[chatbot],
213
+ outputs=[chatbot, input, clear_btn],
214
+ cancels=[submit_event],
215
+ queue=False)
216
+ chatbot.retry(fn=retry,
217
+ inputs=[chatbot],
218
+ outputs=[input, chatbot, clear_btn])
219
+ chatbot.welcome_prompt_select(fn=prompt_select,
220
+ inputs=[input],
221
+ outputs=[input])
222
+ if __name__ == "__main__":
223
+ demo.queue().launch()
components/pro/chatbot/demos/thinking.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
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
+ import modelscope_studio.components.pro as pro
9
+ from modelscope_studio.components.pro.chatbot import (ChatbotBotConfig,
10
+ ChatbotPromptsConfig,
11
+ ChatbotUserConfig,
12
+ ChatbotWelcomeConfig)
13
+ from openai import OpenAI
14
+
15
+ client = OpenAI(
16
+ base_url='https://api-inference.modelscope.cn/v1/',
17
+ api_key=os.getenv("MODELSCOPE_API_KEY"), # ModelScope Token
18
+ )
19
+
20
+ model = "Qwen/QwQ-32B"
21
+
22
+
23
+ def prompt_select(e: gr.EventData):
24
+ return gr.update(value=e._data["payload"][0]["value"]["description"])
25
+
26
+
27
+ def clear():
28
+ return gr.update(value=None)
29
+
30
+
31
+ def retry(chatbot_value, e: gr.EventData):
32
+ index = e._data["payload"][0]["index"]
33
+ chatbot_value = chatbot_value[:index]
34
+
35
+ yield gr.update(loading=True), gr.update(value=chatbot_value), gr.update(
36
+ disabled=True)
37
+ for chunk in submit(None, chatbot_value):
38
+ yield chunk
39
+
40
+
41
+ def cancel(chatbot_value):
42
+ chatbot_value[-1]["loading"] = False
43
+ chatbot_value[-1]["status"] = "done"
44
+ chatbot_value[-1]["footer"] = "Chat completion paused"
45
+ return gr.update(value=chatbot_value), gr.update(loading=False), gr.update(
46
+ disabled=False)
47
+
48
+
49
+ def format_history(history):
50
+ messages = [{"role": "system", "content": "You are a helpful assistant."}]
51
+ for item in history:
52
+ if item["role"] == "user":
53
+ messages.append({"role": "user", "content": item["content"]})
54
+ elif item["role"] == "assistant":
55
+ # ignore thought message
56
+ messages.append({
57
+ "role": "assistant",
58
+ "content": item["content"][-1]["content"]
59
+ })
60
+ return messages
61
+
62
+
63
+ def submit(sender_value, chatbot_value):
64
+ if sender_value is not None:
65
+ chatbot_value.append({
66
+ "role": "user",
67
+ "content": sender_value,
68
+ })
69
+ history_messages = format_history(chatbot_value)
70
+ chatbot_value.append({
71
+ "role": "assistant",
72
+ "content": [],
73
+ "loading": True,
74
+ "status": "pending"
75
+ })
76
+ yield {
77
+ sender: gr.update(value=None, loading=True),
78
+ clear_btn: gr.update(disabled=True),
79
+ chatbot: gr.update(value=chatbot_value)
80
+ }
81
+
82
+ try:
83
+ response = client.chat.completions.create(model=model,
84
+ messages=history_messages,
85
+ stream=True)
86
+ thought_done = False
87
+ start_time = time.time()
88
+ message_content = chatbot_value[-1]["content"]
89
+ # thought content
90
+ message_content.append({
91
+ "copyable": False,
92
+ "editable": False,
93
+ "type": "tool",
94
+ "content": "",
95
+ "options": {
96
+ "title": "Thinking..."
97
+ }
98
+ })
99
+ # content
100
+ message_content.append({
101
+ "type": "text",
102
+ "content": "",
103
+ })
104
+ for chunk in response:
105
+ reasoning_content = chunk.choices[0].delta.reasoning_content
106
+ content = chunk.choices[0].delta.content
107
+ chatbot_value[-1]["loading"] = False
108
+ message_content[-2]["content"] += reasoning_content or ""
109
+ message_content[-1]["content"] += content or ""
110
+
111
+ if content and not thought_done:
112
+ thought_done = True
113
+ thought_cost_time = "{:.2f}".format(time.time() - start_time)
114
+ message_content[-2]["options"][
115
+ "title"] = f"End of Thought ({thought_cost_time}s)"
116
+ message_content[-2]["options"]["status"] = "done"
117
+
118
+ yield {chatbot: gr.update(value=chatbot_value)}
119
+
120
+ chatbot_value[-1]["footer"] = "{:.2f}".format(time.time() -
121
+ start_time) + 's'
122
+ chatbot_value[-1]["status"] = "done"
123
+ yield {
124
+ clear_btn: gr.update(disabled=False),
125
+ sender: gr.update(loading=False),
126
+ chatbot: gr.update(value=chatbot_value),
127
+ }
128
+ except Exception as e:
129
+ chatbot_value[-1]["loading"] = False
130
+ chatbot_value[-1]["status"] = "done"
131
+ chatbot_value[-1]["content"] = "Failed to respond, please try again."
132
+ yield {
133
+ clear_btn: gr.update(disabled=False),
134
+ sender: gr.update(loading=False),
135
+ chatbot: gr.update(value=chatbot_value),
136
+ }
137
+ raise e
138
+
139
+
140
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
141
+ with antd.Flex(vertical=True, gap="middle"):
142
+ chatbot = pro.Chatbot(
143
+ height=600,
144
+ welcome_config=ChatbotWelcomeConfig(
145
+ variant="borderless",
146
+ icon=
147
+ "https://assets.alicdn.com/g/qwenweb/qwen-webui-fe/0.0.44/static/favicon.png",
148
+ title=f"Hello, I'm {model}",
149
+ description="You can input text to get started.",
150
+ prompts=ChatbotPromptsConfig(
151
+ title="How can I help you today?",
152
+ styles={
153
+ "list": {
154
+ "width": '100%',
155
+ },
156
+ "item": {
157
+ "flex": 1,
158
+ },
159
+ },
160
+ items=[{
161
+ "label":
162
+ "🖋 Make a plan",
163
+ "children": [{
164
+ "description":
165
+ "Help me with a plan to start a business"
166
+ }, {
167
+ "description":
168
+ "Help me with a plan to achieve my goals"
169
+ }, {
170
+ "description":
171
+ "Help me with a plan for a successful interview"
172
+ }]
173
+ }, {
174
+ "label":
175
+ "📅 Help me write",
176
+ "children": [{
177
+ "description":
178
+ "Help me write a story with a twist ending"
179
+ }, {
180
+ "description":
181
+ "Help me write a blog post on mental health"
182
+ }, {
183
+ "description":
184
+ "Help me write a letter to my future self"
185
+ }]
186
+ }])),
187
+ user_config=ChatbotUserConfig(
188
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=3"),
189
+ bot_config=ChatbotBotConfig(
190
+ header=model,
191
+ avatar=
192
+ "https://assets.alicdn.com/g/qwenweb/qwen-webui-fe/0.0.44/static/favicon.png",
193
+ actions=["copy", "retry"]),
194
+ )
195
+
196
+ with antdx.Sender() as sender:
197
+ with ms.Slot("prefix"):
198
+ with antd.Button(value=None, color="default",
199
+ variant="text") as clear_btn:
200
+ with ms.Slot("icon"):
201
+ antd.Icon("ClearOutlined")
202
+ clear_btn.click(fn=clear, outputs=[chatbot])
203
+ submit_event = sender.submit(fn=submit,
204
+ inputs=[sender, chatbot],
205
+ outputs=[sender, chatbot, clear_btn])
206
+ sender.cancel(fn=cancel,
207
+ inputs=[chatbot],
208
+ outputs=[chatbot, sender, clear_btn],
209
+ cancels=[submit_event],
210
+ queue=False)
211
+ chatbot.retry(fn=retry,
212
+ inputs=[chatbot],
213
+ outputs=[sender, chatbot, clear_btn])
214
+ chatbot.welcome_prompt_select(fn=prompt_select, outputs=[sender])
215
+
216
+ if __name__ == "__main__":
217
+ demo.queue().launch()
components/pro/chatbot/resources/scenery.jpeg ADDED
components/pro/multimodal_input/README-zh_CN.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MutilmodalInput 
2
+
3
+ 基于 [Ant Design X](https://x.ant.design) 封装的多模态输入框组件,支持文件上传、录音等功能。
4
+
5
+ ## 示例
6
+
7
+ ### 基本使用
8
+
9
+ <demo name="basic"></demo>
10
+
11
+ ### 与 Chatbot 配合使用
12
+
13
+ <demo name="with_chatbot"></demo>
14
+
15
+ ### 配置额外按钮
16
+
17
+ <demo name="extra_button"></demo>
18
+
19
+ ### 上传配置
20
+
21
+ <demo name="upload_config"></demo>
22
+
23
+ ## API 
24
+
25
+ ### 属性
26
+
27
+ | 属性 | 类型 | 默认值 | 描述 |
28
+ | ------------- | --------------------------------------------- | ------- | ------------------------------------------------------ |
29
+ | value | `dict \| MultimodalInputValue \| None` | None | 显示的默认值,格式为`{ "text":"", "files":[] }`。 |
30
+ | loading | `bool \| None` | None | 输入框是否处处于加载状态,此时可以触发 `cancel` 事件。 |
31
+ | read_only | `bool \| None` | None | 输入框是否为只读状态。 |
32
+ | submit_type | `Literal['enter', 'shiftEnter'] \| None` | 'enter' | 输入框触发`submit`事件的方式。 |
33
+ | placeholder | `str \| None` | None | 输入框的提示信息。 |
34
+ | upload_config | `MultimodalInputUploadConfig \| dict \| None` | None | 文件上传配置。 |
35
+
36
+ ### 插槽
37
+
38
+ ```python
39
+ SLOTS=["prefix"]
40
+ ```
41
+
42
+ ### 类型
43
+
44
+ ```python
45
+ class MultimodalInputUploadConfig(GradioModel):
46
+ """
47
+ fullscreen_drop: Whether to allow fullscreen drop files to the attachments.
48
+
49
+ allow_paste_file: Whether to allow paste file to the attachments.
50
+
51
+ allow_speech: Whether to allow speech input.
52
+
53
+ show_count: Whether to show the count of files when the attachments panel is close.
54
+
55
+ upload_button_tooltip: Tooltip of the upload button.
56
+
57
+ accept: File types that can be accepted. See [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept).
58
+
59
+ max_count: Limit the number of uploaded files. Will replace current one when maxCount is 1.
60
+
61
+ directory: Support upload whole directory.
62
+
63
+ disabled: Disable upload files.
64
+
65
+ multiple: Whether to support selected multiple files. IE10+ supported. You can select multiple files with CTRL holding down while multiple is set to be True.
66
+
67
+ overflow: Behavior when the file list overflows.
68
+ title: Title of the attachments panel.
69
+
70
+ placeholder: Placeholder information when there is no file.
71
+ """
72
+ fullscreen_drop: Optional[bool] = False
73
+ allow_paste_file: Optional[bool] = True
74
+ allow_speech: Optional[bool] = False
75
+ show_count: Optional[bool] = True
76
+ upload_button_tooltip: Optional[str] = None
77
+ accept: Optional[str] = None
78
+ max_count: Optional[int] = None
79
+ directory: Optional[bool] = False
80
+ multiple: Optional[bool] = False
81
+ disabled: Optional[bool] = False
82
+ overflow: Literal['wrap', 'scrollX', 'scrollY'] | None = None
83
+ title: Optional[str] = "Attachments"
84
+ placeholder: Optional[dict] = field(
85
+ default_factory=lambda: {
86
+ "inline": {
87
+ "title": "Upload files",
88
+ "description": "Click or drag files to this area to upload"
89
+ },
90
+ "drop": {
91
+ "title": "Drop files here",
92
+ }
93
+ })
94
+
95
+
96
+ class MultimodalInputValue(GradioModel):
97
+ files: Optional[ListFiles] = None
98
+ text: Optional[str] = None
99
+
100
+ ```
components/pro/multimodal_input/README.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MultimodalInput
2
+
3
+ A multimodal input component based on [Ant Design X](https://x.ant.design), supporting file upload, voice recording, and more.
4
+
5
+ ## Examples
6
+
7
+ ### Basic
8
+
9
+ <demo name="basic"></demo>
10
+
11
+ ### Integration with Chatbot
12
+
13
+ <demo name="with_chatbot"></demo>
14
+
15
+ ### Configuring Extra Buttons
16
+
17
+ <demo name="extra_button"></demo>
18
+
19
+ ### Upload Configuration
20
+
21
+ <demo name="upload_config"></demo>
22
+
23
+ ## API
24
+
25
+ ### Props
26
+
27
+ | Attribute | Type | Default Value | Description |
28
+ | ------------- | --------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------- |
29
+ | value | `dict \| MultimodalInputValue \| None` | None | Default value to display, formatted as `{ "text":"", "files":[] }`. |
30
+ | loading | `bool \| None` | None | Whether the input is in a loading state, in which case the `cancel` event can be triggered. |
31
+ | read_only | `bool \| None` | None | Whether the input is read-only. |
32
+ | submit_type | `Literal['enter', 'shiftEnter'] \| None` | 'enter' | How the input box triggers the `submit` event. |
33
+ | placeholder | `str \| None` | None | Input placeholder text. |
34
+ | upload_config | `MultimodalInputUploadConfig \| dict \| None` | None | File upload configuration. |
35
+
36
+ ### Slots
37
+
38
+ ```python
39
+ SLOTS = ["prefix"]
40
+ ```
41
+
42
+ ### Types
43
+
44
+ ```python
45
+ class MultimodalInputUploadConfig(GradioModel):
46
+ """
47
+ fullscreen_drop: Whether to allow fullscreen drop files to the attachments.
48
+
49
+ allow_paste_file: Whether to allow paste file to the attachments.
50
+
51
+ allow_speech: Whether to allow speech input.
52
+
53
+ show_count: Whether to show the count of files when the attachments panel is close.
54
+
55
+ upload_button_tooltip: Tooltip of the upload button.
56
+
57
+ accept: File types that can be accepted. See [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept).
58
+
59
+ max_count: Limit the number of uploaded files. Will replace current one when maxCount is 1.
60
+
61
+ directory: Support upload whole directory.
62
+
63
+ disabled: Disable upload files.
64
+
65
+ multiple: Whether to support selected multiple files. IE10+ supported. You can select multiple files with CTRL holding down while multiple is set to be True.
66
+
67
+ overflow: Behavior when the file list overflows.
68
+ title: Title of the attachments panel.
69
+
70
+ placeholder: Placeholder information when there is no file.
71
+ """
72
+ fullscreen_drop: Optional[bool] = False
73
+ allow_paste_file: Optional[bool] = True
74
+ allow_speech: Optional[bool] = False
75
+ show_count: Optional[bool] = True
76
+ upload_button_tooltip: Optional[str] = None
77
+ accept: Optional[str] = None
78
+ max_count: Optional[int] = None
79
+ directory: Optional[bool] = False
80
+ multiple: Optional[bool] = False
81
+ disabled: Optional[bool] = False
82
+ overflow: Literal['wrap', 'scrollX', 'scrollY'] | None = None
83
+ title: Optional[str] = "Attachments"
84
+ placeholder: Optional[dict] = field(
85
+ default_factory=lambda: {
86
+ "inline": {
87
+ "title": "Upload files",
88
+ "description": "Click or drag files to this area to upload"
89
+ },
90
+ "drop": {
91
+ "title": "Drop files here",
92
+ }
93
+ })
94
+
95
+
96
+ class MultimodalInputValue(GradioModel):
97
+ files: Optional[ListFiles] = None
98
+ text: Optional[str] = None
99
+
100
+ ```
components/pro/multimodal_input/app.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from helper.Docs import Docs
2
+
3
+ docs = Docs(__file__)
4
+
5
+ if __name__ == "__main__":
6
+ docs.render().queue().launch()
components/pro/multimodal_input/demos/basic.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import modelscope_studio.components.antdx as antdx
3
+ import modelscope_studio.components.base as ms
4
+ import modelscope_studio.components.pro as pro
5
+
6
+
7
+ def submit(input_value):
8
+ print(input_value)
9
+
10
+
11
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
12
+ input = pro.MultimodalInput()
13
+ input.submit(fn=submit, inputs=[input])
14
+
15
+ if __name__ == "__main__":
16
+ demo.queue().launch()
components/pro/multimodal_input/demos/extra_button.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+
3
+ import gradio as gr
4
+ import modelscope_studio.components.antd as antd
5
+ import modelscope_studio.components.antdx as antdx
6
+ import modelscope_studio.components.base as ms
7
+ import modelscope_studio.components.pro as pro
8
+
9
+
10
+ def submit(input_value, chatbot_value):
11
+ chatbot_value.append({
12
+ "role":
13
+ "user",
14
+ "content": [{
15
+ "type": "text",
16
+ "content": input_value["text"]
17
+ }, {
18
+ "type": "file",
19
+ "content": [file for file in input_value["files"]]
20
+ }]
21
+ })
22
+ chatbot_value.append({
23
+ "role": "assistant",
24
+ "loading": True,
25
+ "status": "pending"
26
+ })
27
+ yield gr.update(value=None, loading=True), gr.update(value=chatbot_value)
28
+ time.sleep(2)
29
+ chatbot_value[-1]["loading"] = False
30
+ chatbot_value[-1]["content"] = "Hello"
31
+ chatbot_value[-1]["status"] = "done"
32
+ yield gr.update(loading=False), gr.update(value=chatbot_value)
33
+
34
+
35
+ def cancel(chatbot_value):
36
+ chatbot_value[-1]["loading"] = False
37
+ chatbot_value[-1]["footer"] = "canceled"
38
+ chatbot_value[-1]["status"] = "done"
39
+ return gr.update(loading=False), gr.update(value=chatbot_value)
40
+
41
+
42
+ def clear():
43
+ return gr.update(value=None)
44
+
45
+
46
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
47
+ with antd.Flex(elem_style=dict(height=300), vertical=True):
48
+ chatbot = pro.Chatbot(height=0,
49
+ elem_style=dict(flex=1),
50
+ value=[{
51
+ "role": "user",
52
+ "content": "Hello"
53
+ }, {
54
+ "role": "assistant",
55
+ "content": "Hello"
56
+ }])
57
+ with pro.MultimodalInput(upload_config=dict(
58
+ upload_button_tooltip="Attachments")) as input:
59
+ with ms.Slot("prefix"):
60
+ with antd.Tooltip("Clear History"):
61
+ with antd.Button(value=None,
62
+ variant="text",
63
+ color="default") as clear_btn:
64
+ with ms.Slot("icon"):
65
+ antd.Icon("ClearOutlined")
66
+ submit_event = input.submit(fn=submit,
67
+ inputs=[input, chatbot],
68
+ outputs=[input, chatbot])
69
+ input.cancel(fn=cancel,
70
+ cancels=[submit_event],
71
+ inputs=[chatbot],
72
+ outputs=[input, chatbot],
73
+ queue=False)
74
+ clear_btn.click(fn=clear, outputs=[chatbot])
75
+
76
+ if __name__ == "__main__":
77
+ demo.queue().launch()
components/pro/multimodal_input/demos/upload_config.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import modelscope_studio.components.pro as pro
6
+ from modelscope_studio.components.pro.multimodal_input import \
7
+ MultimodalInputUploadConfig
8
+
9
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
10
+ with antd.Flex(vertical=True, gap="small"):
11
+ antd.Divider("Style")
12
+ pro.MultimodalInput(upload_config=MultimodalInputUploadConfig(
13
+ upload_button_tooltip="Upload",
14
+ title="Upload Attachments",
15
+ placeholder={
16
+ "inline": {
17
+ "title": "Upload files",
18
+ "description": "Click or drag files to this area to upload"
19
+ },
20
+ "drop": {
21
+ "title": "Drop files here",
22
+ }
23
+ }))
24
+ antd.Divider("Upload Limits")
25
+ pro.MultimodalInput(upload_config=MultimodalInputUploadConfig(
26
+ accept="image/*", fullscreen_drop=True, multiple=True,
27
+ max_count=4))
28
+ antd.Divider("Other Sources")
29
+ pro.MultimodalInput(upload_config=MultimodalInputUploadConfig(
30
+ allow_speech=True, allow_paste_file=True))
31
+
32
+ if __name__ == "__main__":
33
+ demo.queue().launch()
components/pro/multimodal_input/demos/with_chatbot.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+
3
+ import gradio as gr
4
+ import modelscope_studio.components.antd as antd
5
+ import modelscope_studio.components.antdx as antdx
6
+ import modelscope_studio.components.base as ms
7
+ import modelscope_studio.components.pro as pro
8
+
9
+
10
+ def submit(input_value, chatbot_value):
11
+ chatbot_value.append({
12
+ "role":
13
+ "user",
14
+ "content": [{
15
+ "type": "text",
16
+ "content": input_value["text"]
17
+ }, {
18
+ "type": "file",
19
+ "content": [file for file in input_value["files"]]
20
+ }]
21
+ })
22
+ chatbot_value.append({
23
+ "role": "assistant",
24
+ "loading": True,
25
+ "status": "pending"
26
+ })
27
+ yield gr.update(value=None, loading=True), gr.update(value=chatbot_value)
28
+ time.sleep(2)
29
+ chatbot_value[-1]["loading"] = False
30
+ chatbot_value[-1]["content"] = "Hello"
31
+ chatbot_value[-1]["status"] = "done"
32
+ yield gr.update(loading=False), gr.update(value=chatbot_value)
33
+
34
+
35
+ def cancel(chatbot_value):
36
+ chatbot_value[-1]["loading"] = False
37
+ chatbot_value[-1]["footer"] = "canceled"
38
+ chatbot_value[-1]["status"] = "done"
39
+ return gr.update(loading=False), gr.update(value=chatbot_value)
40
+
41
+
42
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
43
+ with antd.Flex(elem_style=dict(height=600), vertical=True):
44
+ chatbot = pro.Chatbot(height=0, elem_style=dict(flex=1))
45
+ input = pro.MultimodalInput()
46
+ submit_event = input.submit(fn=submit,
47
+ inputs=[input, chatbot],
48
+ outputs=[input, chatbot])
49
+ input.cancel(fn=cancel,
50
+ cancels=[submit_event],
51
+ inputs=[chatbot],
52
+ outputs=[input, chatbot],
53
+ queue=False)
54
+
55
+ if __name__ == "__main__":
56
+ demo.queue().launch()
layout_templates/chatbot/README-zh_CN.md CHANGED
@@ -5,11 +5,13 @@
5
  该模板提供以下功能:
6
 
7
  - 支持对话管理,可以同时开启多个对话。
8
- - 支持对话历史管理,可以对任意 message 进行重新生成、删除等操作,可以一键清空历史。
9
  - 支持对话中断,可以中断当前的对话消息,并给出提示。
10
  - 支持通过输入`/`为用户展示输入建议。
11
  - 支持附件上传,可以手动控制最大上传个数以及上传格式。
12
 
13
  ## 示例
14
 
15
- <demo name="app" position="bottom" collapsible="true"></demo>
 
 
 
5
  该模板提供以下功能:
6
 
7
  - 支持对话管理,可以同时开启多个对话。
8
+ - 支持对话历史管理,可以对任意 message 进行编辑、重新生成、删除等操作,可以一键清空历史。
9
  - 支持对话中断,可以中断当前的对话消息,并给出提示。
10
  - 支持通过输入`/`为用户展示输入建议。
11
  - 支持附件上传,可以手动控制最大上传个数以及上传格式。
12
 
13
  ## 示例
14
 
15
+ <demo name="basic" position="bottom" collapsible="true"></demo>
16
+
17
+ <demo name="fine_grained_control" title="细粒度控制" position="bottom" collapsible="true"></demo>
layout_templates/chatbot/README.md CHANGED
@@ -5,11 +5,13 @@ Application templates for building chatbot interfaces.
5
  This template provides the following features:
6
 
7
  - Supports conversation management, allowing multiple conversations to be opened simultaneously.
8
- - Enables conversation history management, including operations like regenerating or deleting any message, and clearing all history with one click.
9
  - Allows interrupting ongoing conversations and displaying notifications.
10
  - Displays input suggestions when users type `/`.
11
  - Supports attachment uploads with manual configuration of maximum upload count and allowed formats.
12
 
13
  ## Examples
14
 
15
- <demo name="app" position="bottom" collapsible="true"></demo>
 
 
 
5
  This template provides the following features:
6
 
7
  - Supports conversation management, allowing multiple conversations to be opened simultaneously.
8
+ - Enables conversation history management, including operations like editing, regenerating or deleting any message, and clearing all history with one click.
9
  - Allows interrupting ongoing conversations and displaying notifications.
10
  - Displays input suggestions when users type `/`.
11
  - Supports attachment uploads with manual configuration of maximum upload count and allowed formats.
12
 
13
  ## Examples
14
 
15
+ <demo name="basic" position="bottom" collapsible="true"></demo>
16
+
17
+ <demo name="fine_grained_control" title="Fine-grained Control" position="bottom" collapsible="true"></demo>
layout_templates/chatbot/demos/basic.py ADDED
@@ -0,0 +1,693 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+ import uuid
4
+
5
+ import gradio as gr
6
+ import modelscope_studio.components.antd as antd
7
+ import modelscope_studio.components.antdx as antdx
8
+ import modelscope_studio.components.base as ms
9
+ import modelscope_studio.components.pro as pro
10
+ from modelscope_studio.components.pro.chatbot import (ChatbotActionConfig,
11
+ ChatbotBotConfig,
12
+ ChatbotUserConfig,
13
+ ChatbotWelcomeConfig)
14
+ from modelscope_studio.components.pro.multimodal_input import \
15
+ MultimodalInputUploadConfig
16
+ from openai import OpenAI
17
+
18
+ # =========== Configuration
19
+
20
+ # API KEY
21
+ MODELSCOPE_ACCESS_TOKEN = os.getenv('MODELSCOPE_ACCESS_TOKEN')
22
+
23
+ client = OpenAI(
24
+ base_url='https://api-inference.modelscope.cn/v1/',
25
+ api_key=MODELSCOPE_ACCESS_TOKEN,
26
+ )
27
+
28
+ model = "Qwen/Qwen2.5-VL-72B-Instruct"
29
+
30
+ save_history = False
31
+
32
+ # =========== Configuration
33
+
34
+ DEFAULT_PROMPTS = [{
35
+ "label":
36
+ "📅 Make a plan",
37
+ "children": [{
38
+ "description": "Help me with a plan to start a business",
39
+ }, {
40
+ "description": "Help me with a plan to achieve my goals",
41
+ }, {
42
+ "description":
43
+ "Help me with a plan for a successful interview",
44
+ }]
45
+ }, {
46
+ "label":
47
+ "🖋 Help me write",
48
+ "children": [{
49
+ "description": "SHelp me write a story with a twist ending",
50
+ }, {
51
+ "description": "Help me write a blog post on mental health",
52
+ }, {
53
+ "description": "Help me write a letter to my future self",
54
+ }]
55
+ }]
56
+
57
+ DEFAULT_SUGGESTIONS = [{
58
+ "label":
59
+ 'Make a plan',
60
+ "value":
61
+ "Make a plan",
62
+ "children": [{
63
+ "label": "Start a business",
64
+ "value": "Help me with a plan to start a business"
65
+ }, {
66
+ "label": "Achieve my goals",
67
+ "value": "Help me with a plan to achieve my goals"
68
+ }, {
69
+ "label": "Successful interview",
70
+ "value": "Help me with a plan for a successful interview"
71
+ }]
72
+ }, {
73
+ "label":
74
+ 'Help me write',
75
+ "value":
76
+ "Help me write",
77
+ "children": [{
78
+ "label": "Story with a twist ending",
79
+ "value": "Help me write a story with a twist ending"
80
+ }, {
81
+ "label": "Blog post on mental health",
82
+ "value": "Help me write a blog post on mental health"
83
+ }, {
84
+ "label": "Letter to my future self",
85
+ "value": "Help me write a letter to my future self"
86
+ }]
87
+ }]
88
+
89
+ DEFAULT_LOCALE = 'en_US'
90
+
91
+ DEFAULT_THEME = {
92
+ "token": {
93
+ "colorPrimary": "#6A57FF",
94
+ }
95
+ }
96
+
97
+
98
+ def user_config(disabled_actions=None):
99
+ return ChatbotUserConfig(actions=[
100
+ "copy", "edit",
101
+ ChatbotActionConfig(
102
+ action="delete",
103
+ popconfirm=dict(title="Delete the message",
104
+ description="Are you sure to delete this message?",
105
+ okButtonProps=dict(danger=True)))
106
+ ],
107
+ disabled_actions=disabled_actions)
108
+
109
+
110
+ def bot_config(disabled_actions=None):
111
+ return ChatbotBotConfig(
112
+ actions=[
113
+ "copy", "like", "dislike", "edit",
114
+ ChatbotActionConfig(
115
+ action="retry",
116
+ popconfirm=dict(
117
+ title="Regenerate the message",
118
+ description=
119
+ "Regenerate the message will also delete all subsequent messages.",
120
+ okButtonProps=dict(danger=True))),
121
+ ChatbotActionConfig(action="delete",
122
+ popconfirm=dict(
123
+ title="Delete the message",
124
+ description=
125
+ "Are you sure to delete this message?",
126
+ okButtonProps=dict(danger=True)))
127
+ ],
128
+ avatar=
129
+ "https://assets.alicdn.com/g/qwenweb/qwen-webui-fe/0.0.44/static/favicon.png",
130
+ disabled_actions=disabled_actions)
131
+
132
+
133
+ class Gradio_Events:
134
+
135
+ @staticmethod
136
+ def submit(state_value):
137
+ # Define your code here
138
+ # The best way is to use the image url.
139
+ def image_to_base64(image_path):
140
+ with open(image_path, "rb") as image_file:
141
+ encoded_string = base64.b64encode(
142
+ image_file.read()).decode('utf-8')
143
+ return f"data:image/jpeg;base64,{encoded_string}"
144
+
145
+ def format_history(history):
146
+ messages = [{
147
+ "role": "system",
148
+ "content": "You are a helpful and harmless assistant.",
149
+ }]
150
+ for item in history:
151
+ if item["role"] == "user":
152
+ messages.append({
153
+ "role":
154
+ "user",
155
+ "content": [{
156
+ "type": "image_url",
157
+ "image_url": image_to_base64(file)
158
+ } for file in item["content"][0]["content"]
159
+ if os.path.exists(file)] +
160
+ [{
161
+ "type": "text",
162
+ "text": item["content"][1]["content"]
163
+ }]
164
+ })
165
+ elif item["role"] == "assistant":
166
+ messages.append({
167
+ "role": "assistant",
168
+ "content": item["content"]
169
+ })
170
+ return messages
171
+
172
+ history = state_value["conversations_history"][
173
+ state_value["conversation_id"]]
174
+ history_messages = format_history(history)
175
+
176
+ history.append({
177
+ "role": "assistant",
178
+ "content": "",
179
+ "loading": True,
180
+ "status": "pending"
181
+ })
182
+
183
+ yield {
184
+ chatbot: gr.update(value=history),
185
+ state: gr.update(value=state_value),
186
+ }
187
+ try:
188
+ response = client.chat.completions.create(
189
+ model=model, # ModelScope Model-Id
190
+ messages=history_messages,
191
+ stream=True)
192
+ for chunk in response:
193
+ history[-1]["content"] += chunk.choices[0].delta.content
194
+ history[-1]["loading"] = False
195
+ yield {
196
+ chatbot: gr.update(value=history),
197
+ state: gr.update(value=state_value)
198
+ }
199
+ history[-1]["status"] = "done"
200
+ yield {
201
+ chatbot: gr.update(value=history),
202
+ state: gr.update(value=state_value),
203
+ }
204
+ except Exception as e:
205
+ history[-1]["loading"] = False
206
+ history[-1]["status"] = "done"
207
+ history[-1]["content"] = "Failed to respond, please try again."
208
+ yield {
209
+ chatbot: gr.update(value=history),
210
+ state: gr.update(value=state_value)
211
+ }
212
+ raise e
213
+
214
+ @staticmethod
215
+ def add_user_message(input_value, state_value):
216
+ if not state_value["conversation_id"]:
217
+ random_id = str(uuid.uuid4())
218
+ history = []
219
+ state_value["conversation_id"] = random_id
220
+ state_value["conversations_history"][random_id] = history
221
+ state_value["conversations"].append({
222
+ "label": input_value["text"],
223
+ "key": random_id
224
+ })
225
+
226
+ history = state_value["conversations_history"][
227
+ state_value["conversation_id"]]
228
+ history.append({
229
+ "role":
230
+ "user",
231
+ "content": [{
232
+ "type": "file",
233
+ "content": [f for f in input_value["files"]]
234
+ }, {
235
+ "type": "text",
236
+ "content": input_value["text"]
237
+ }]
238
+ })
239
+ return gr.update(value=state_value)
240
+
241
+ @staticmethod
242
+ def preprocess_submit(clear_input=True):
243
+
244
+ def preprocess_submit_handler(state_value):
245
+ history = state_value["conversations_history"][
246
+ state_value["conversation_id"]]
247
+ return {
248
+ **({
249
+ input:
250
+ gr.update(value=None, loading=True) if clear_input else gr.update(loading=True),
251
+ } if clear_input else {}),
252
+ conversations:
253
+ gr.update(active_key=state_value["conversation_id"],
254
+ items=list(
255
+ map(
256
+ lambda item: {
257
+ **item,
258
+ "disabled":
259
+ True if item["key"] != state_value[
260
+ "conversation_id"] else False,
261
+ }, state_value["conversations"]))),
262
+ add_conversation_btn:
263
+ gr.update(disabled=True),
264
+ clear_btn:
265
+ gr.update(disabled=True),
266
+ conversation_delete_menu_item:
267
+ gr.update(disabled=True),
268
+ chatbot:
269
+ gr.update(value=history,
270
+ bot_config=bot_config(
271
+ disabled_actions=['edit', 'retry', 'delete']),
272
+ user_config=user_config(
273
+ disabled_actions=['edit', 'delete'])),
274
+ state:
275
+ gr.update(value=state_value),
276
+ }
277
+
278
+ return preprocess_submit_handler
279
+
280
+ @staticmethod
281
+ def postprocess_submit(state_value):
282
+ history = state_value["conversations_history"][
283
+ state_value["conversation_id"]]
284
+ return {
285
+ input:
286
+ gr.update(loading=False),
287
+ conversation_delete_menu_item:
288
+ gr.update(disabled=False),
289
+ clear_btn:
290
+ gr.update(disabled=False),
291
+ conversations:
292
+ gr.update(items=state_value["conversations"]),
293
+ add_conversation_btn:
294
+ gr.update(disabled=False),
295
+ chatbot:
296
+ gr.update(value=history,
297
+ bot_config=bot_config(),
298
+ user_config=user_config()),
299
+ state:
300
+ gr.update(value=state_value),
301
+ }
302
+
303
+ @staticmethod
304
+ def cancel(state_value):
305
+ history = state_value["conversations_history"][
306
+ state_value["conversation_id"]]
307
+ history[-1]["loading"] = False
308
+ history[-1]["status"] = "done"
309
+ history[-1]["footer"] = "Chat completion paused"
310
+ return Gradio_Events.postprocess_submit(state_value)
311
+
312
+ @staticmethod
313
+ def delete_message(state_value, e: gr.EventData):
314
+ index = e._data["payload"][0]["index"]
315
+ history = state_value["conversations_history"][
316
+ state_value["conversation_id"]]
317
+ history = history[:index] + history[index + 1:]
318
+
319
+ state_value["conversations_history"][
320
+ state_value["conversation_id"]] = history
321
+
322
+ return gr.update(value=state_value)
323
+
324
+ @staticmethod
325
+ def edit_message(state_value, chatbot_value, e: gr.EventData):
326
+ index = e._data["payload"][0]["index"]
327
+ history = state_value["conversations_history"][
328
+ state_value["conversation_id"]]
329
+ history[index]["content"] = chatbot_value[index]["content"]
330
+ return gr.update(value=state_value)
331
+
332
+ @staticmethod
333
+ def regenerate_message(state_value, e: gr.EventData):
334
+ index = e._data["payload"][0]["index"]
335
+ history = state_value["conversations_history"][
336
+ state_value["conversation_id"]]
337
+ history = history[:index]
338
+ state_value["conversations_history"][
339
+ state_value["conversation_id"]] = history
340
+ # custom code
341
+ return gr.update(value=history), gr.update(value=state_value)
342
+
343
+ @staticmethod
344
+ def select_suggestion(input_value, e: gr.EventData):
345
+ input_value["text"] = input_value["text"][:-1] + e._data["payload"][0]
346
+ return gr.update(value=input_value)
347
+
348
+ @staticmethod
349
+ def apply_prompt(input_value, e: gr.EventData):
350
+ input_value["text"] = e._data["payload"][0]["value"]["description"]
351
+ return gr.update(value=input_value)
352
+
353
+ @staticmethod
354
+ def new_chat(state_value):
355
+ if not state_value["conversation_id"]:
356
+ return gr.skip()
357
+ state_value["conversation_id"] = ""
358
+ return gr.update(active_key=state_value["conversation_id"]), gr.update(
359
+ value=None), gr.update(value=state_value)
360
+
361
+ @staticmethod
362
+ def select_conversation(state_value, e: gr.EventData):
363
+ active_key = e._data["payload"][0]
364
+ if state_value["conversation_id"] == active_key or (
365
+ active_key not in state_value["conversations_history"]):
366
+ return gr.skip()
367
+ state_value["conversation_id"] = active_key
368
+ return gr.update(active_key=active_key), gr.update(
369
+ value=state_value["conversations_history"][active_key]), gr.update(
370
+ value=state_value)
371
+
372
+ @staticmethod
373
+ def click_conversation_menu(state_value, e: gr.EventData):
374
+ conversation_id = e._data["payload"][0]["key"]
375
+ operation = e._data["payload"][1]["key"]
376
+ if operation == "delete":
377
+ del state_value["conversations_history"][conversation_id]
378
+
379
+ state_value["conversations"] = [
380
+ item for item in state_value["conversations"]
381
+ if item["key"] != conversation_id
382
+ ]
383
+
384
+ if state_value["conversation_id"] == conversation_id:
385
+ state_value["conversation_id"] = ""
386
+ return gr.update(
387
+ items=state_value["conversations"],
388
+ active_key=state_value["conversation_id"]), gr.update(
389
+ value=None), gr.update(value=state_value)
390
+ else:
391
+ return gr.update(
392
+ items=state_value["conversations"]), gr.skip(), gr.update(
393
+ value=state_value)
394
+ return gr.skip()
395
+
396
+ @staticmethod
397
+ def clear_conversation_history(state_value):
398
+ if not state_value["conversation_id"]:
399
+ return gr.skip()
400
+ state_value["conversations_history"][
401
+ state_value["conversation_id"]] = []
402
+ return gr.update(value=None), gr.update(value=state_value)
403
+
404
+ @staticmethod
405
+ def update_browser_state(state_value):
406
+
407
+ return gr.update(value=dict(
408
+ conversations=state_value["conversations"],
409
+ conversations_history=state_value["conversations_history"]))
410
+
411
+ @staticmethod
412
+ def apply_browser_state(browser_state_value, state_value):
413
+ state_value["conversations"] = browser_state_value["conversations"]
414
+ state_value["conversations_history"] = browser_state_value[
415
+ "conversations_history"]
416
+ return gr.update(
417
+ items=browser_state_value["conversations"]), gr.update(
418
+ value=state_value)
419
+
420
+
421
+ css = """
422
+ #chatbot {
423
+ height: calc(100vh - 32px - 21px - 16px);
424
+ }
425
+
426
+ #chatbot .chatbot-conversations {
427
+ height: 100%;
428
+ background-color: var(--ms-gr-ant-color-bg-layout);
429
+ }
430
+
431
+ #chatbot .chatbot-conversations .chatbot-conversations-list {
432
+ padding-left: 0;
433
+ padding-right: 0;
434
+ }
435
+
436
+ #chatbot .chatbot-chat {
437
+ padding: 32px;
438
+ height: 100%;
439
+ }
440
+
441
+ @media (max-width: 768px) {
442
+ #chatbot .chatbot-chat {
443
+ padding: 0;
444
+ }
445
+ }
446
+
447
+ #chatbot .chatbot-chat .chatbot-chat-messages {
448
+ flex: 1;
449
+ }
450
+ """
451
+
452
+
453
+ def logo():
454
+ with antd.Typography.Title(level=1,
455
+ elem_style=dict(fontSize=24,
456
+ padding=8,
457
+ margin=0)):
458
+ with antd.Flex(align="center", gap="small", justify="center"):
459
+ antd.Image(
460
+ "https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*eco6RrQhxbMAAAAAAAAAAAAADgCCAQ/original",
461
+ preview=False,
462
+ alt="logo",
463
+ width=24,
464
+ height=24)
465
+ ms.Span("Chatbot")
466
+
467
+
468
+ with gr.Blocks(css=css, fill_width=True) as demo:
469
+ state = gr.State({
470
+ "conversations_history": {},
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
+ 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("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", danger=True
505
+ ) as conversation_delete_menu_item:
506
+ with ms.Slot("icon"):
507
+ antd.Icon("DeleteOutlined")
508
+ # Right Column
509
+ with antd.Col(flex=1, elem_style=dict(height="100%")):
510
+ with antd.Flex(vertical=True, elem_classes="chatbot-chat"):
511
+ # Chatbot
512
+ chatbot = pro.Chatbot(
513
+ elem_classes="chatbot-chat-messages",
514
+ welcome_config=ChatbotWelcomeConfig(
515
+ variant="borderless",
516
+ icon=
517
+ "https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp",
518
+ title=f"Hello, I'm {model}",
519
+ description=
520
+ "You can upload images and text to get started.",
521
+ prompts=dict(
522
+ title="How can I help you today?",
523
+ styles={
524
+ "list": {
525
+ "width": '100%',
526
+ },
527
+ "item": {
528
+ "flex": 1,
529
+ },
530
+ },
531
+ items=[{
532
+ "label":
533
+ "🖋 Make a plan",
534
+ "children": [{
535
+ "description":
536
+ "Help me with a plan to start a business"
537
+ }, {
538
+ "description":
539
+ "Help me with a plan to achieve my goals"
540
+ }, {
541
+ "description":
542
+ "Help me with a plan for a successful interview"
543
+ }]
544
+ }, {
545
+ "label":
546
+ "📅 Help me write",
547
+ "children": [{
548
+ "description":
549
+ "Help me write a story with a twist ending"
550
+ }, {
551
+ "description":
552
+ "Help me write a blog post on mental health"
553
+ }, {
554
+ "description":
555
+ "Help me write a letter to my future self"
556
+ }]
557
+ }]),
558
+ ),
559
+ user_config=user_config(),
560
+ bot_config=bot_config())
561
+ # Input
562
+ with antdx.Suggestion(
563
+ items=DEFAULT_SUGGESTIONS,
564
+ # onKeyDown Handler in Javascript
565
+ should_trigger="""(e, { onTrigger, onKeyDown }) => {
566
+ switch(e.key) {
567
+ case '/':
568
+ onTrigger()
569
+ break
570
+ case 'ArrowRight':
571
+ case 'ArrowLeft':
572
+ case 'ArrowUp':
573
+ case 'ArrowDown':
574
+ break;
575
+ default:
576
+ onTrigger(false)
577
+ }
578
+ onKeyDown(e)
579
+ }""") as suggestion:
580
+ with ms.Slot("children"):
581
+ with pro.MultimodalInput(
582
+ placeholder="Enter / to get suggestions",
583
+ upload_config=MultimodalInputUploadConfig(
584
+ upload_button_tooltip=
585
+ "Upload Attachments",
586
+ max_count=6,
587
+ accept="image/*",
588
+ multiple=True)) as input:
589
+ with ms.Slot("prefix"):
590
+ # Clear Button
591
+ with antd.Tooltip(
592
+ title="Clear Conversation History"
593
+ ):
594
+ with antd.Button(
595
+ value=None,
596
+ type="text") as clear_btn:
597
+ with ms.Slot("icon"):
598
+ antd.Icon("ClearOutlined")
599
+
600
+ # Events Handler
601
+ if save_history:
602
+ browser_state = gr.BrowserState(
603
+ {
604
+ "conversations_history": {},
605
+ "conversations": [],
606
+ },
607
+ storage_key="ms_chatbot_storage")
608
+ state.change(fn=Gradio_Events.update_browser_state,
609
+ inputs=[state],
610
+ outputs=[browser_state])
611
+
612
+ demo.load(fn=Gradio_Events.apply_browser_state,
613
+ inputs=[browser_state, state],
614
+ outputs=[conversations, state])
615
+
616
+ add_conversation_btn.click(fn=Gradio_Events.new_chat,
617
+ inputs=[state],
618
+ outputs=[conversations, chatbot, state])
619
+ conversations.active_change(fn=Gradio_Events.select_conversation,
620
+ inputs=[state],
621
+ outputs=[conversations, chatbot, state])
622
+ conversations.menu_click(fn=Gradio_Events.click_conversation_menu,
623
+ inputs=[state],
624
+ outputs=[conversations, chatbot, state])
625
+ chatbot.welcome_prompt_select(fn=Gradio_Events.apply_prompt,
626
+ inputs=[input],
627
+ outputs=[input])
628
+
629
+ clear_btn.click(fn=Gradio_Events.clear_conversation_history,
630
+ inputs=[state],
631
+ outputs=[chatbot, state])
632
+
633
+ suggestion.select(fn=Gradio_Events.select_suggestion,
634
+ inputs=[input],
635
+ outputs=[input])
636
+ chatbot.delete(fn=Gradio_Events.delete_message,
637
+ inputs=[state],
638
+ outputs=[state])
639
+ chatbot.edit(fn=Gradio_Events.edit_message,
640
+ inputs=[state, chatbot],
641
+ outputs=[state])
642
+
643
+ regenerating_event = chatbot.retry(
644
+ fn=Gradio_Events.regenerate_message,
645
+ inputs=[state],
646
+ outputs=[chatbot, state
647
+ ]).then(fn=Gradio_Events.preprocess_submit(clear_input=False),
648
+ inputs=[state],
649
+ outputs=[
650
+ input, clear_btn, conversation_delete_menu_item,
651
+ add_conversation_btn, conversations, chatbot,
652
+ state
653
+ ]).then(fn=Gradio_Events.submit,
654
+ inputs=[state],
655
+ outputs=[chatbot, state])
656
+
657
+ submit_event = input.submit(
658
+ fn=Gradio_Events.add_user_message,
659
+ inputs=[input, state],
660
+ outputs=[state
661
+ ]).then(fn=Gradio_Events.preprocess_submit(clear_input=True),
662
+ inputs=[state],
663
+ outputs=[
664
+ input, clear_btn, conversation_delete_menu_item,
665
+ add_conversation_btn, conversations, chatbot,
666
+ state
667
+ ]).then(fn=Gradio_Events.submit,
668
+ inputs=[state],
669
+ outputs=[chatbot, state])
670
+ regenerating_event.then(fn=Gradio_Events.postprocess_submit,
671
+ inputs=[state],
672
+ outputs=[
673
+ input, conversation_delete_menu_item,
674
+ clear_btn, conversations, add_conversation_btn,
675
+ chatbot, state
676
+ ])
677
+ submit_event.then(fn=Gradio_Events.postprocess_submit,
678
+ inputs=[state],
679
+ outputs=[
680
+ input, conversation_delete_menu_item, clear_btn,
681
+ conversations, add_conversation_btn, chatbot, state
682
+ ])
683
+ input.cancel(fn=Gradio_Events.cancel,
684
+ inputs=[state],
685
+ outputs=[
686
+ input, conversation_delete_menu_item, clear_btn,
687
+ conversations, add_conversation_btn, chatbot, state
688
+ ],
689
+ cancels=[submit_event, regenerating_event],
690
+ queue=False)
691
+
692
+ if __name__ == "__main__":
693
+ demo.queue().launch(ssr_mode=False)
layout_templates/chatbot/demos/{app.py → fine_grained_control.py} RENAMED
@@ -26,7 +26,7 @@ save_history = False
26
 
27
  DEFAULT_PROMPTS = [{
28
  "category":
29
- "🖋 Make a plan",
30
  "prompts": [
31
  "Help me with a plan to start a business",
32
  "Help me with a plan to achieve my goals",
@@ -34,7 +34,7 @@ DEFAULT_PROMPTS = [{
34
  ]
35
  }, {
36
  "category":
37
- "📅 Help me write",
38
  "prompts": [
39
  "Help me write a story with a twist ending",
40
  "Help me write a blog post on mental health",
@@ -155,8 +155,8 @@ class Gradio_Events:
155
  }
156
  except Exception as e:
157
  history[-1]["loading"] = False
158
- history[-1]["content"] = ""
159
- history[-1]["role"] = "assistant-error"
160
  yield {
161
  chatbot: gr.update(items=history),
162
  state: gr.update(value=state_value)
@@ -203,9 +203,12 @@ class Gradio_Events:
203
  conversation["meta"]["disabled"] = True
204
  return {
205
  **({
206
- sender: gr.update(value=None, loading=True),
207
- attachments: gr.update(value=[]),
208
- attachments_badge: gr.update(dot=False),
 
 
 
209
  } if clear_input else {}),
210
  conversations:
211
  gr.update(active_key=state_value["conversation_id"],
@@ -824,20 +827,7 @@ with gr.Blocks(css=css, fill_width=True) as demo:
824
  as_item="delete_btn"):
825
  with ms.Slot("icon"):
826
  antd.Icon("DeleteOutlined")
827
- # Error Chatbot Role
828
- with antdx.Bubble.List.Role(
829
- role="assistant-error",
830
- placement="start",
831
- styles=dict(content=dict(
832
- maxWidth="100%", overflow='auto'))):
833
- with ms.Slot("avatar"):
834
- with antd.Avatar():
835
- with ms.Slot("icon"):
836
- antd.Icon("RobotOutlined")
837
- with ms.Slot("messageRender"):
838
- antd.Typography.Text(
839
- "Failed to respond, please try again.",
840
- type="danger")
841
  # Sender
842
  with antdx.Suggestion(
843
  items=DEFAULT_SUGGESTIONS,
@@ -1033,13 +1023,14 @@ with gr.Blocks(css=css, fill_width=True) as demo:
1033
  sender, conversation_delete_menu_item, clear_btn,
1034
  conversations, add_conversation_btn, chatbot, state
1035
  ])
1036
- sender.cancel(fn=None, cancels=[submit_event, regenerating_event])
1037
  sender.cancel(fn=Gradio_Events.cancel,
1038
  inputs=[state],
1039
  outputs=[
1040
  sender, conversation_delete_menu_item, clear_btn,
1041
  conversations, add_conversation_btn, chatbot, state
1042
- ])
 
 
1043
 
1044
  if __name__ == "__main__":
1045
  demo.queue().launch(ssr_mode=False)
 
26
 
27
  DEFAULT_PROMPTS = [{
28
  "category":
29
+ "📅 Make a plan",
30
  "prompts": [
31
  "Help me with a plan to start a business",
32
  "Help me with a plan to achieve my goals",
 
34
  ]
35
  }, {
36
  "category":
37
+ "🖋 Help me write",
38
  "prompts": [
39
  "Help me write a story with a twist ending",
40
  "Help me write a blog post on mental health",
 
155
  }
156
  except Exception as e:
157
  history[-1]["loading"] = False
158
+ history[-1]["meta"]["end"] = True
159
+ history[-1]["content"] = "Failed to respond, please try again."
160
  yield {
161
  chatbot: gr.update(items=history),
162
  state: gr.update(value=state_value)
 
203
  conversation["meta"]["disabled"] = True
204
  return {
205
  **({
206
+ sender:
207
+ gr.update(value=None, loading=True) if clear_input else gr.update(loading=True),
208
+ attachments:
209
+ gr.update(value=[]),
210
+ attachments_badge:
211
+ gr.update(dot=False),
212
  } if clear_input else {}),
213
  conversations:
214
  gr.update(active_key=state_value["conversation_id"],
 
827
  as_item="delete_btn"):
828
  with ms.Slot("icon"):
829
  antd.Icon("DeleteOutlined")
830
+
 
 
 
 
 
 
 
 
 
 
 
 
 
831
  # Sender
832
  with antdx.Suggestion(
833
  items=DEFAULT_SUGGESTIONS,
 
1023
  sender, conversation_delete_menu_item, clear_btn,
1024
  conversations, add_conversation_btn, chatbot, state
1025
  ])
 
1026
  sender.cancel(fn=Gradio_Events.cancel,
1027
  inputs=[state],
1028
  outputs=[
1029
  sender, conversation_delete_menu_item, clear_btn,
1030
  conversations, add_conversation_btn, chatbot, state
1031
+ ],
1032
+ cancels=[submit_event, regenerating_event],
1033
+ queue=False)
1034
 
1035
  if __name__ == "__main__":
1036
  demo.queue().launch(ssr_mode=False)
requirements.txt CHANGED
@@ -1,2 +1,2 @@
1
- modelscope_studio==1.1.7
2
  openai
 
1
+ modelscope_studio==1.2.1
2
  openai
src/pyproject.toml CHANGED
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
 
9
  [project]
10
  name = "modelscope_studio"
11
- version = "1.1.7"
12
  description = "A third-party component library based on Gradio."
13
  readme = "README.md"
14
  license = "Apache-2.0"
@@ -23,7 +23,7 @@ keywords = [
23
  'gradio-antdx',
24
  ]
25
  # Add dependencies here
26
- dependencies = ["gradio>=4.0,<6.0"]
27
  classifiers = [
28
  'Development Status :: 3 - Alpha',
29
  'License :: OSI Approved :: Apache Software License',
@@ -224,6 +224,8 @@ artifacts = [
224
  "/backend/modelscope_studio/components/antdx/suggestion/templates",
225
  "/backend/modelscope_studio/components/antdx/suggestion/item/templates",
226
  "/backend/modelscope_studio/components/antdx/x_provider/templates",
 
 
227
  ]
228
 
229
  [tool.yapfignore]
 
8
 
9
  [project]
10
  name = "modelscope_studio"
11
+ version = "1.2.1"
12
  description = "A third-party component library based on Gradio."
13
  readme = "README.md"
14
  license = "Apache-2.0"
 
23
  'gradio-antdx',
24
  ]
25
  # Add dependencies here
26
+ dependencies = ["gradio>=4.43.0,<6.0"]
27
  classifiers = [
28
  'Development Status :: 3 - Alpha',
29
  'License :: OSI Approved :: Apache Software License',
 
224
  "/backend/modelscope_studio/components/antdx/suggestion/templates",
225
  "/backend/modelscope_studio/components/antdx/suggestion/item/templates",
226
  "/backend/modelscope_studio/components/antdx/x_provider/templates",
227
+ "/backend/modelscope_studio/components/pro/chatbot/templates",
228
+ "/backend/modelscope_studio/components/pro/multimodal_input/templates",
229
  ]
230
 
231
  [tool.yapfignore]