Spaces:
Running
Running
feat: pro components
Browse files- README-zh_CN.md +1 -1
- README.md +1 -1
- app.py +26 -3
- components/pro/chatbot/README-zh_CN.md +342 -0
- components/pro/chatbot/README.md +341 -0
- components/pro/chatbot/app.py +6 -0
- components/pro/chatbot/demos/basic.py +73 -0
- components/pro/chatbot/demos/chatbot_config.py +122 -0
- components/pro/chatbot/demos/message_config.py +64 -0
- components/pro/chatbot/demos/multimodal.py +223 -0
- components/pro/chatbot/demos/thinking.py +217 -0
- components/pro/chatbot/resources/scenery.jpeg +0 -0
- components/pro/multimodal_input/README-zh_CN.md +100 -0
- components/pro/multimodal_input/README.md +100 -0
- components/pro/multimodal_input/app.py +6 -0
- components/pro/multimodal_input/demos/basic.py +16 -0
- components/pro/multimodal_input/demos/extra_button.py +77 -0
- components/pro/multimodal_input/demos/upload_config.py +33 -0
- components/pro/multimodal_input/demos/with_chatbot.py +56 -0
- layout_templates/chatbot/README-zh_CN.md +4 -2
- layout_templates/chatbot/README.md +4 -2
- layout_templates/chatbot/demos/basic.py +693 -0
- layout_templates/chatbot/demos/{app.py → fine_grained_control.py} +14 -23
- requirements.txt +1 -1
- src/pyproject.toml +4 -2
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(
|
|
|
|
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="
|
|
|
|
|
|
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="
|
|
|
|
|
|
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 |
-
"
|
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 |
-
"
|
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]["
|
159 |
-
history[-1]["
|
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:
|
207 |
-
|
208 |
-
|
|
|
|
|
|
|
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 |
-
|
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
|
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
|
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]
|