Skip to content

Commit 369e50a

Browse files
authored
docs: add copilot demo for chatbot (#92)
1 parent dc9230f commit 369e50a

6 files changed

Lines changed: 392 additions & 34 deletions

File tree

.changeset/pink-carrots-hammer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'modelscope_studio': patch
3+
---
4+
5+
docs: add copilot demo for chatbot

backend/modelscope_studio/components/antd/card/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(
5959
*,
6060
actions: str | None = None,
6161
active_tab_key: str | None = None,
62+
variant: Literal['outlined', 'borderless'] | None = None,
6263
bordered: bool | None = None,
6364
cover: str | None = None,
6465
default_active_tab_key: str | None = None,
@@ -87,6 +88,7 @@ def __init__(
8788
Parameters:
8889
actions: The action list, shows at the bottom of the Card.
8990
active_tab_key: Current TabPane's key.
91+
variant: Card variant.
9092
bordered: Toggles rendering of the border around the card.
9193
cover: Card cover.
9294
default_active_tab_key: Initial active TabPane's key, if activeTabKey is not set.
@@ -111,6 +113,7 @@ def __init__(
111113
self.actions = actions
112114
self.active_tab_key = active_tab_key
113115
self.bordered = bordered
116+
self.variant = variant
114117
self.cover = cover
115118
self.default_active_tab_key = default_active_tab_key
116119
self.extra = extra

docs/layout_templates/chatbot/README-zh_CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@
1515
<demo name="basic" position="bottom" collapsible="true"></demo>
1616

1717
<demo name="fine_grained_control" title="细粒度控制" position="bottom" collapsible="true"></demo>
18+
19+
<demo name="copilot" title="助手式" position="bottom" collapsible="true"></demo>

docs/layout_templates/chatbot/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ This template provides the following features:
1515
<demo name="basic" position="bottom" collapsible="true"></demo>
1616

1717
<demo name="fine_grained_control" title="Fine-grained Control" position="bottom" collapsible="true"></demo>
18+
19+
<demo name="copilot" title="Copilot" position="bottom" collapsible="true"></demo>
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
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_ACCESS_TOKEN"), # ModelScope Token
20+
)
21+
22+
model = "Qwen/Qwen2.5-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 format_history(history):
54+
messages = [{"role": "system", "content": "You are a helpful assistant."}]
55+
for item in history:
56+
if item["role"] == "user":
57+
messages.append({
58+
"role":
59+
"user",
60+
"content": [{
61+
"type": "text",
62+
"text": item["content"]
63+
}]
64+
})
65+
elif item["role"] == "assistant":
66+
# ignore thought message
67+
messages.append({"role": "assistant", "content": item["content"]})
68+
return messages
69+
70+
71+
def submit(input_value, chatbot_value):
72+
if input_value is not None:
73+
chatbot_value.append({
74+
"role": "user",
75+
"content": input_value["text"],
76+
})
77+
history_messages = format_history(chatbot_value)
78+
chatbot_value.append({
79+
"role": "assistant",
80+
"content": "",
81+
"loading": True,
82+
"status": "pending"
83+
})
84+
yield {
85+
input: gr.update(value=None, loading=True),
86+
clear_btn: gr.update(disabled=True),
87+
chatbot: gr.update(value=chatbot_value)
88+
}
89+
90+
try:
91+
response = client.chat.completions.create(model=model,
92+
messages=history_messages,
93+
stream=True)
94+
start_time = time.time()
95+
96+
for chunk in response:
97+
chatbot_value[-1]["content"] += chunk.choices[0].delta.content
98+
chatbot_value[-1]["loading"] = False
99+
100+
yield {chatbot: gr.update(value=chatbot_value)}
101+
102+
chatbot_value[-1]["footer"] = "{:.2f}".format(time.time() -
103+
start_time) + 's'
104+
chatbot_value[-1]["status"] = "done"
105+
yield {
106+
clear_btn: gr.update(disabled=False),
107+
input: gr.update(loading=False),
108+
chatbot: gr.update(value=chatbot_value),
109+
}
110+
except Exception as e:
111+
chatbot_value[-1]["loading"] = False
112+
chatbot_value[-1]["status"] = "done"
113+
chatbot_value[-1]["content"] = "Failed to respond, please try again."
114+
yield {
115+
clear_btn: gr.update(disabled=False),
116+
input: gr.update(loading=False),
117+
chatbot: gr.update(value=chatbot_value),
118+
}
119+
raise e
120+
121+
122+
def close_copilot():
123+
return gr.update(md=24), gr.update(md=0), gr.update(elem_style=dict(
124+
display=""))
125+
126+
127+
def open_copilot():
128+
return gr.update(md=16), gr.update(md=8), gr.update(elem_style=dict(
129+
display="none"))
130+
131+
132+
def resize_window(e: gr.EventData):
133+
screen_width = e._data["screen"]["width"]
134+
is_mobile = screen_width < 768
135+
return gr.update(visible=False if is_mobile else True), gr.update(
136+
visible=False if is_mobile else True)
137+
138+
139+
css = """
140+
.copilot-container {
141+
height: calc(100vh - 32px - 21px - 16px);
142+
}
143+
144+
.copilot-container .copilot {
145+
height: 100%;
146+
padding: 10px;
147+
background: var(--background-fill-secondary);
148+
border-top: 1px solid var(--border-color-primary);
149+
border-right: 1px solid var(--border-color-primary);
150+
border-bottom: 1px solid var(--border-color-primary);
151+
}
152+
153+
154+
.copilot-container .content-body {
155+
height: 100%;
156+
overflow: auto;
157+
}
158+
159+
@media (max-width: 768px) {
160+
.copilot-container {
161+
height: auto;
162+
}
163+
.copilot-container .copilot {
164+
height: 600px;
165+
border-left: 1px solid var(--border-color-primary);
166+
}
167+
.copilot-container .content-body {
168+
height: auto;
169+
max-height: 400px;
170+
}
171+
}
172+
"""
173+
174+
with gr.Blocks(css=css) as demo, ms.Application() as app, antdx.XProvider():
175+
with antd.Row(elem_classes="copilot-container", wrap=True):
176+
# Content column
177+
with antd.Col(md=16, xs=24,
178+
elem_style=dict(height="100%")) as content_col:
179+
with ms.AutoLoading(elem_style=dict(height="100%")):
180+
with antd.Card(elem_style=dict(height="100%",
181+
borderRadius=0,
182+
display="flex",
183+
flexDirection="column"),
184+
class_names=dict(body="content-body")):
185+
# Title
186+
with ms.Slot("title"):
187+
with antd.Typography.Title(level=1,
188+
elem_style=dict(
189+
margin=0,
190+
textAlign="center",
191+
fontSize=30)):
192+
ms.Text("🤖 Copilot Template")
193+
194+
# Copilot button
195+
with ms.Slot("extra"):
196+
copilot_open_btn = antd.Button(
197+
"✨ AI Copilot",
198+
shape="round",
199+
variant='filled',
200+
color="primary",
201+
elem_style=dict(display="none"))
202+
203+
# Content
204+
ms.Markdown("""
205+
<img src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*48RLR41kwHIAAAAAAAAAAAAADgCCAQ/fmt.webp" style="width: 100%;" />
206+
<h4>What is the RICH design paradigm?</h4>
207+
<div>
208+
RICH is an AI interface design paradigm we propose, similar to how the WIMP paradigm
209+
relates to graphical user interfaces.
210+
</div>
211+
<br />
212+
<div>
213+
The ACM SIGCHI 2005 (the premier conference on human-computer interaction) defined
214+
that the core issues of human-computer interaction can be divided into three levels:
215+
</div>
216+
<ul>
217+
<li>
218+
Interface Paradigm Layer: Defines the design elements of human-computer
219+
interaction interfaces, guiding designers to focus on core issues.
220+
</li>
221+
<li>
222+
User model layer: Build an interface experience evaluation model to measure the
223+
quality of the interface experience.
224+
</li>
225+
<li>
226+
Software framework layer: The underlying support algorithms and data structures
227+
for human-computer interfaces, which are the contents hidden behind the front-end
228+
interface.
229+
</li>
230+
</ul>
231+
<div>
232+
The interface paradigm is the aspect that designers need to focus on and define the
233+
most when a new human-computer interaction technology is born. The interface
234+
paradigm defines the design elements that designers should pay attention to, and
235+
based on this, it is possible to determine what constitutes good design and how to
236+
achieve it.
237+
</div>
238+
""")
239+
240+
# Copilot column
241+
with antd.Col(md=8, xs=24,
242+
elem_style=dict(height="100%")) as copilot_col:
243+
with ms.AutoLoading(elem_style=dict(height="100%")):
244+
with antd.Flex(
245+
vertical=True,
246+
gap="small",
247+
elem_classes="copilot",
248+
):
249+
with antd.Flex(justify="space-between", align="center"):
250+
antd.Typography.Title("✨ AI Copilot",
251+
level=4,
252+
elem_style=dict(margin=0))
253+
with antd.Flex(align="center", gap="small"):
254+
with antd.Button(
255+
variant="text",
256+
color="default") as copilot_close_btn:
257+
with ms.Slot("icon"):
258+
antd.Icon("CloseOutlined")
259+
antd.Divider(size="small")
260+
chatbot = pro.Chatbot(
261+
# for flex=1 to fill the remaining space
262+
height=0,
263+
elem_style=dict(flex=1),
264+
welcome_config=ChatbotWelcomeConfig(
265+
variant="filled",
266+
title="👋🏻 Hello, I'm AI Copilot",
267+
description="Enter a prompt to get started",
268+
prompts=ChatbotPromptsConfig(
269+
title="I can help: ",
270+
vertical=True,
271+
items=[{
272+
"description":
273+
"Help me with a plan to start a business"
274+
}, {
275+
"description":
276+
"Help me with a plan to achieve my goals"
277+
}, {
278+
"description":
279+
"Help me with a plan for a successful interview"
280+
}])),
281+
user_config=dict(
282+
avatar=
283+
"https://api.dicebear.com/7.x/miniavs/svg?seed=3"),
284+
bot_config=ChatbotBotConfig(
285+
header="Copilot",
286+
actions=["copy", "retry"],
287+
avatar=
288+
"https://api.dicebear.com/7.x/miniavs/svg?seed=2"),
289+
)
290+
291+
with pro.MultimodalInput(
292+
upload_config=MultimodalInputUploadConfig(
293+
allow_upload=False)) as input:
294+
with ms.Slot("prefix"):
295+
with antd.Button(value=None,
296+
color="default",
297+
variant="text") as clear_btn:
298+
with ms.Slot("icon"):
299+
antd.Icon("ClearOutlined")
300+
clear_btn.click(fn=clear, outputs=[chatbot])
301+
submit_event = input.submit(fn=submit,
302+
inputs=[input, chatbot],
303+
outputs=[input, chatbot, clear_btn])
304+
input.cancel(fn=cancel,
305+
inputs=[chatbot],
306+
outputs=[chatbot, input, clear_btn],
307+
cancels=[submit_event],
308+
queue=False)
309+
chatbot.retry(fn=retry,
310+
inputs=[chatbot],
311+
outputs=[input, chatbot, clear_btn])
312+
chatbot.welcome_prompt_select(fn=prompt_select,
313+
inputs=[input],
314+
outputs=[input])
315+
copilot_open_btn.click(
316+
fn=open_copilot,
317+
outputs=[content_col, copilot_col, copilot_open_btn])
318+
copilot_close_btn.click(
319+
fn=close_copilot,
320+
outputs=[content_col, copilot_col, copilot_open_btn])
321+
gr.on([app.mount, app.resize],
322+
fn=resize_window,
323+
outputs=[copilot_open_btn, copilot_close_btn])
324+
if __name__ == "__main__":
325+
demo.queue().launch()

0 commit comments

Comments
 (0)