Skip to content

Commit e037c33

Browse files
authored
Merge pull request #66 from jianyi-gronk/main
前端优化
2 parents 450fb39 + 0c9dce0 commit e037c33

File tree

2 files changed

+216
-108
lines changed

2 files changed

+216
-108
lines changed

spring-ai-alibaba-integration-example/frontend/src/App.tsx

Lines changed: 130 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
useXAgent,
1010
useXChat
1111
} from "@ant-design/x";
12-
import { createStyles } from "antd-style";
1312
import React, { useEffect } from "react";
1413
import {
1514
CloudUploadOutlined,
@@ -25,9 +24,23 @@ import {
2524
EditOutlined,
2625
ShareAltOutlined
2726
} from "@ant-design/icons";
28-
import { Flex, App, Badge, Button, type GetProp, Space, theme } from "antd";
27+
import {
28+
Flex,
29+
App,
30+
Badge,
31+
Button,
32+
Space,
33+
Typography,
34+
Tag,
35+
type GetProp,
36+
Tooltip,
37+
Select
38+
} from "antd";
2939
import ReactMarkdown from "react-markdown";
30-
import { getChat } from "./request";
40+
import { getChat, getModels } from "./request";
41+
import { useStyle } from "./style";
42+
43+
const DEFAULT_MODEL = "qwen-plus";
3144

3245
const decoder = new TextDecoder("utf-8");
3346

@@ -38,9 +51,7 @@ const renderTitle = (icon: React.ReactElement, title: string) => (
3851
</Space>
3952
);
4053

41-
// 用于临时保存会话记录
42-
const messagesMap = {} as Record<string, Array<any>>;
43-
54+
// 新会话默认展示
4455
const placeholderPromptsItems: GetProp<typeof Prompts, "items"> = [
4556
{
4657
key: "1",
@@ -88,92 +99,24 @@ const placeholderPromptsItems: GetProp<typeof Prompts, "items"> = [
8899
}
89100
];
90101

102+
// 默认会话
91103
const defaultKey = Date.now().toString();
92-
93104
const defaultConversationsItems = [
94105
{
95106
key: defaultKey,
96-
label: "What is Spring Ai Alibaba?"
107+
label: (
108+
<span>
109+
Conversation 1
110+
<Tag style={{ marginLeft: 8 }} color="green">
111+
{DEFAULT_MODEL}
112+
</Tag>
113+
</span>
114+
)
97115
}
98116
];
99117

100-
const useStyle = createStyles(({ token, css }) => {
101-
return {
102-
layout: css`
103-
width: 100%;
104-
min-width: 1000px;
105-
height: 722px;
106-
border-radius: ${token.borderRadius}px;
107-
display: flex;
108-
background: ${token.colorBgContainer};
109-
font-family: AlibabaPuHuiTi, ${token.fontFamily}, sans-serif;
110-
111-
.ant-prompts {
112-
color: ${token.colorText};
113-
}
114-
`,
115-
menu: css`
116-
background: ${token.colorBgLayout}80;
117-
width: 280px;
118-
height: 100%;
119-
display: flex;
120-
flex-direction: column;
121-
`,
122-
conversations: css`
123-
padding: 0 12px;
124-
flex: 1;
125-
overflow-y: auto;
126-
`,
127-
chat: css`
128-
height: 100%;
129-
width: 100%;
130-
max-width: 700px;
131-
margin: 0 auto;
132-
box-sizing: border-box;
133-
display: flex;
134-
flex-direction: column;
135-
padding: ${token.paddingLG}px;
136-
gap: 16px;
137-
`,
138-
messages: css`
139-
flex: 1;
140-
`,
141-
placeholder: css`
142-
padding-top: 32px;
143-
`,
144-
sender: css`
145-
box-shadow: ${token.boxShadow};
146-
`,
147-
logo: css`
148-
display: flex;
149-
height: 72px;
150-
align-items: center;
151-
justify-content: start;
152-
padding: 0 24px;
153-
box-sizing: border-box;
154-
155-
img {
156-
width: 24px;
157-
height: 24px;
158-
display: inline-block;
159-
}
160-
161-
span {
162-
display: inline-block;
163-
margin: 0 8px;
164-
font-weight: bold;
165-
color: ${token.colorText};
166-
font-size: 16px;
167-
}
168-
`,
169-
addBtn: css`
170-
background: #1677ff0f;
171-
border: 1px solid #1677ff34;
172-
width: calc(100% - 24px);
173-
margin: 0 12px 24px 12px;
174-
`
175-
};
176-
});
118+
// 用于临时保存会话记录
119+
const messagesMap = {} as Record<string, { model: string; messages: any[] }>;
177120

178121
const senderPromptsItems: GetProp<typeof Prompts, "items"> = [
179122
{
@@ -188,6 +131,7 @@ const senderPromptsItems: GetProp<typeof Prompts, "items"> = [
188131
}
189132
];
190133

134+
// 会话中角色列表
191135
const roles: GetProp<typeof Bubble.List, "roles"> = {
192136
ai: {
193137
placement: "start",
@@ -196,7 +140,25 @@ const roles: GetProp<typeof Bubble.List, "roles"> = {
196140
content: {
197141
borderRadius: 16
198142
}
199-
}
143+
},
144+
messageRender: (content) => (
145+
<Typography>
146+
<ReactMarkdown>{content}</ReactMarkdown>
147+
</Typography>
148+
)
149+
},
150+
aiHistory: {
151+
placement: "start",
152+
styles: {
153+
content: {
154+
borderRadius: 16
155+
}
156+
},
157+
messageRender: (content) => (
158+
<Typography>
159+
<ReactMarkdown>{content}</ReactMarkdown>
160+
</Typography>
161+
)
200162
},
201163
local: {
202164
placement: "end",
@@ -237,13 +199,17 @@ const Independent: React.FC = () => {
237199
>([]);
238200

239201
const { message } = App.useApp();
240-
const [recording, setRecording] = React.useState(false);
241-
const { token } = theme.useToken();
202+
203+
// 当前会话的模型
204+
const [model, setModel] = React.useState(DEFAULT_MODEL);
205+
// 将要新增会话的模型
206+
const [nextModel, setNextModel] = React.useState(DEFAULT_MODEL);
242207

243208
// ==================== Runtime ====================
244209
const [agent] = useXAgent({
245-
request: async ({ message }, { onSuccess }) => {
210+
request: async ({ message }, { onSuccess, onUpdate }) => {
246211
let buffer = "";
212+
onUpdate(JSON.stringify({ role: "ai", value: "" }));
247213

248214
const res = await getChat(
249215
JSON.parse(message || "{}")?.value || "",
@@ -257,13 +223,15 @@ const Independent: React.FC = () => {
257223
res.forEach((item) => {
258224
if (item?.message === "success") {
259225
buffer = buffer + item?.data;
226+
onUpdate(JSON.stringify({ role: "ai", value: buffer }));
260227
}
261228
});
262229
}
263230
},
264231
{
265232
image: attachedFiles?.[0]?.originFileObj,
266-
chatId: activeKey
233+
chatId: activeKey,
234+
model
267235
}
268236
);
269237

@@ -274,11 +242,29 @@ const Independent: React.FC = () => {
274242
value =
275243
"Request failed." + (res?.statusText ? " " + res?.statusText : "");
276244
}
245+
277246
onSuccess(JSON.stringify({ role: "ai", value }));
278247
},
279248
customParams: [attachedFiles]
280249
});
281250

251+
// 获取模型列表
252+
const [modelItems, setModelItems] = React.useState([]);
253+
useEffect(() => {
254+
getModels().then((res) => {
255+
setModelItems(
256+
res.map(({ model, desc }) => ({
257+
value: model,
258+
label: (
259+
<Tooltip title={desc} placement="right">
260+
{model}
261+
</Tooltip>
262+
)
263+
}))
264+
);
265+
});
266+
}, []);
267+
282268
const [items, setItems] = React.useState<
283269
GetProp<typeof Bubble.List, "items">
284270
>([]);
@@ -322,30 +308,61 @@ const Independent: React.FC = () => {
322308
onRequest(info.data.description as string);
323309
};
324310

311+
// 将模型返回的消息的 role 转换成历史记录,避免切换会话触发渲染动效
312+
const getMessageHistory = () => {
313+
return messages.map((item) => {
314+
const value = JSON.parse(item.message);
315+
if (value.role === "ai") {
316+
value.role = "aiHistory";
317+
item.message = JSON.stringify(value);
318+
return item;
319+
} else {
320+
return item;
321+
}
322+
});
323+
};
324+
325+
// 新增会话
325326
const onAddConversation = async () => {
326327
const newKey = Date.now().toString();
327328
setConversationsItems([
328329
...conversationsItems,
329330
{
330331
key: newKey,
331-
label: `New Conversation ${conversationsItems.length}`
332+
label: (
333+
<span>
334+
{`Conversation ${conversationsItems.length + 1}`}
335+
<Tag style={{ marginLeft: 8 }} color="green">
336+
{nextModel}
337+
</Tag>
338+
</span>
339+
)
332340
}
333341
]);
334-
messagesMap[activeKey] = messages;
342+
messagesMap[activeKey] = {
343+
model,
344+
messages: getMessageHistory()
345+
};
335346
setHeaderOpen(false);
336347
setAttachedFiles([]);
337-
setMessages([]);
338348
setActiveKey(newKey);
349+
setMessages([]);
350+
setModel(nextModel);
339351
};
340352

353+
// 切换会话
341354
const onConversationClick: GetProp<typeof Conversations, "onActiveChange"> = (
342355
key
343356
) => {
344-
messagesMap[activeKey] = messages;
357+
messagesMap[activeKey] = {
358+
model,
359+
messages: getMessageHistory()
360+
};
345361
setHeaderOpen(false);
346362
setAttachedFiles([]);
347-
setMessages(messagesMap[key] || []);
348363
setActiveKey(key);
364+
setMessages(messagesMap[key].messages || []);
365+
setModel(messagesMap[key].model || DEFAULT_MODEL);
349366
};
350367

351368
const handleFileChange: GetProp<typeof Attachments, "onChange"> = (info) => {
@@ -402,6 +419,7 @@ const Independent: React.FC = () => {
402419
</Space>
403420
);
404421

422+
// messages 转 items
405423
useEffect(() => {
406424
setItems(
407425
messages.map(({ id, message, status }) => {
@@ -410,8 +428,8 @@ const Independent: React.FC = () => {
410428
const value = item?.value;
411429
return {
412430
key: id,
413-
loading: status === "loading",
414-
role: "file",
431+
role: item?.role,
432+
loading: !value,
415433
content: [
416434
{
417435
uid: value?.uid,
@@ -421,11 +439,12 @@ const Independent: React.FC = () => {
421439
]
422440
};
423441
} else {
442+
const value = item?.value;
424443
return {
425444
key: id,
426-
loading: status === "loading",
427-
role: status === "local" ? "local" : "ai",
428-
content: <ReactMarkdown>{item.value}</ReactMarkdown>
445+
role: item?.role,
446+
loading: !value,
447+
content: value
429448
};
430449
}
431450
})
@@ -488,6 +507,16 @@ const Independent: React.FC = () => {
488507
<div className={styles.menu}>
489508
{/* 🌟 Logo */}
490509
{logoNode}
510+
{/* 🌟 模型选择 */}
511+
<div className={styles.chooseModel}>
512+
选择模型类型
513+
<Select
514+
onChange={setNextModel}
515+
options={modelItems}
516+
style={{ width: 120 }}
517+
value={nextModel}
518+
/>
519+
</div>
491520
{/* 🌟 添加会话 */}
492521
<Button
493522
onClick={onAddConversation}
@@ -524,14 +553,7 @@ const Independent: React.FC = () => {
524553
value={content}
525554
header={senderHeader}
526555
onSubmit={onSubmit}
527-
allowSpeech={{
528-
// When setting `recording`, the built-in speech recognition feature will be disabled
529-
recording,
530-
onRecordingChange: (nextRecording) => {
531-
message.info(`Mock Customize Recording: ${nextRecording}`);
532-
setRecording(nextRecording);
533-
}
534-
}}
556+
allowSpeech
535557
onChange={setContent}
536558
prefix={attachmentsNode}
537559
loading={agent.isRequesting()}

0 commit comments

Comments
 (0)