Skip to content

Commit f3f5f01

Browse files
committed
feat: #20 完成单模型单会话的AI问答
1 parent 945b20c commit f3f5f01

File tree

22 files changed

+916
-5
lines changed

22 files changed

+916
-5
lines changed

index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
rel="stylesheet"
1010
href="/node_modules/@fortawesome/fontawesome-free/css/v4-shims.css"
1111
/>
12+
<link rel="stylesheet" href="https://g.alicdn.com/code/npm/@ali/chatui-sdk/6.6.2/ChatSDK.css" />
1213
<title>Amis Playground</title>
1314
<script>
1415
window.enableAMISDebug = true;
@@ -27,6 +28,11 @@
2728
</head>
2829
<body>
2930
<div id="root"></div>
31+
<div id="aiChatRoot"></div>
3032
<script type="module" src="/src/main.tsx"></script>
33+
<!--TODO: 将 react 从 chatSdk 剥离出来 使用 pure.js () -->
34+
<script src="https://g.alicdn.com/code/npm/@ali/chatui-sdk/6.6.2/ChatSDK.js"></script>
35+
<script src="https://g.alicdn.com/chatui/icons/2.0.2/index.js"></script>
36+
<script type="module" src="/src/aiChat/index.tsx"></script>
3137
</body>
3238
</html>

src/AiChat/assets/def-bot.jpeg

92 KB
Loading

src/AiChat/assets/def-user.png

14 KB
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.wrapper {
2+
display: flex;
3+
justify-content: space-between;
4+
align-items: center;
5+
height: 40px;
6+
padding: 0 8px;
7+
border-bottom: 1px solid #e3e3e3;
8+
9+
button {
10+
border: none;
11+
padding: 4px;
12+
border-radius: 4px;
13+
font-size: 14px;
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
cursor: nesw-resize;
18+
&:hover {
19+
background: #e0e0e0;
20+
}
21+
}
22+
svg {
23+
width: 18px;
24+
height: 18px;
25+
}
26+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import SizeIcon from './Size.svg?raw'
2+
3+
import styles from './index.module.less'
4+
5+
export const Header = () => {
6+
const storeRef = React.useRef<any>({
7+
resizeTrigger: null,
8+
resizeContainer: null,
9+
isResizing: false,
10+
x: 0,
11+
y: 0,
12+
width: 0,
13+
height: 0,
14+
})
15+
16+
React.useEffect(() => {
17+
const { resizeTrigger } = storeRef.current
18+
if (!resizeTrigger) {
19+
return
20+
}
21+
22+
storeRef.current.resizeContainer = document.querySelector('#aiChatRoot .ChatWrapper')
23+
24+
storeRef.current.resizeTrigger.addEventListener('mousedown', (e) => {
25+
const { resizeContainer } = storeRef.current
26+
storeRef.current = {
27+
...storeRef.current,
28+
isResizing: true,
29+
x: e.clientX,
30+
y: e.clientY,
31+
width: resizeContainer.offsetWidth,
32+
height: resizeContainer.offsetHeight,
33+
}
34+
document.addEventListener('mousemove', resize)
35+
document.addEventListener('mouseup', stopResize)
36+
})
37+
38+
const resize = (e) => {
39+
const { isResizing, resizeContainer, x, y, width, height } = storeRef.current
40+
if (isResizing) {
41+
const newWidth = width + (e.clientX - x)
42+
const newHeight = height - (e.clientY - y)
43+
resizeContainer.style.width = `${newWidth}px`
44+
resizeContainer.style.height = `${newHeight}px`
45+
}
46+
}
47+
48+
const stopResize = () => {
49+
storeRef.current.isResizing = false
50+
document.removeEventListener('mousemove', resize)
51+
document.removeEventListener('mouseup', stopResize)
52+
}
53+
54+
return () => {
55+
storeRef.current.resizeTrigger.removeEventListener('mousedown')
56+
}
57+
}, [])
58+
59+
return (
60+
<div className={styles.wrapper}>
61+
<i></i>
62+
<span>Amis Bot</span>
63+
<button
64+
title='调整大小'
65+
ref={(ref) => (storeRef.current.resizeTrigger = ref)}
66+
dangerouslySetInnerHTML={{ __html: SizeIcon }}
67+
/>
68+
</div>
69+
)
70+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { uuidv4 } from 'amis-core'
2+
import { random } from 'lodash'
3+
4+
import {
5+
appendCvsMsgItem,
6+
defaultCvs,
7+
getCvsMsgList,
8+
sendMsgSync,
9+
} from '@/localServer/aiServervice'
10+
11+
let chatBot: any = null
12+
13+
export const setChatBoot = (bot) => {
14+
chatBot = bot
15+
}
16+
17+
export const getChatBoot = () => {
18+
return chatBot
19+
}
20+
21+
const helloMsgPool = [
22+
'Hi~ 我是 Amis Bot,Amis 相关问题都可以问我哦。',
23+
'我Amis玩的很溜,不信?来试试吧~',
24+
'Amis 是不是有点难?不怕,有我在,我能帮助你~',
25+
'Amis 不止只是能通过 Json 配置哦,还有很多其他高级功能呢~',
26+
'嘿!我是 Amis 小助手,快来和我一起探索 Amis 的奇妙世界吧!',
27+
'Amis 的秘密我都知道,快来问我,别害羞哦~',
28+
'想成为 Amis 达人吗?让我来帮你实现这个小目标!',
29+
'Amis 的魔法等你来发现,我是你的魔法导师!',
30+
'Amis 让开发更简单,我让 Amis 更有趣!',
31+
'Amis 的世界无奇不有,快来和我一起探险吧!',
32+
,
33+
'Amis 的小秘密我都知道,快来问我,别客气!',
34+
'Amis 的每个角落我都熟悉,快来和我一起探索吧!',
35+
'Amis 的奥秘等你来揭开,我是你的专属向导!',
36+
'Amis 的精彩由你来发现,我是你最好的伙伴!',
37+
'你好,我是 Amis 小精灵,Amis 的世界由我为你开启!',
38+
'Amis 的每个细节我都了如指掌,快来问我吧!',
39+
'想知道 Amis 的独门秘籍吗?我来为你揭秘!',
40+
'Amis 的奇妙之旅由我为你导航,准备好了吗?',
41+
'Amis 的奥秘无穷无尽,快来和我一起探索!',
42+
'Amis 的世界充满惊喜,我是你的惊喜制造机!',
43+
'Amis 的每个功能我都能解锁,快来挑战我吧!',
44+
'Amis 的精彩由我为你呈现,快来体验吧!',
45+
'Amis 的魔法等你来施展,我是你的魔法助手!',
46+
'Amis 的每个角落都有故事,我是你的故事讲述者!',
47+
]
48+
49+
export const getDefaultMsgs = () => {
50+
const helloMsg = helloMsgPool[random(0, helloMsgPool.length - 1)]
51+
return [
52+
{
53+
code: 'text',
54+
_helloMsg: true,
55+
data: {
56+
text: helloMsg || helloMsgPool[0],
57+
},
58+
},
59+
]
60+
}
61+
62+
export const isInputDisabled = () => {
63+
return !!chatBot.bot.appOptions?.config?.inputOptions?.disabled
64+
}
65+
66+
export const getHistoryMsgs = async () => {
67+
let msgList = await getCvsMsgList(defaultCvs.id)
68+
let historyMsgs = []
69+
if (msgList.length) {
70+
msgList = msgList.map((item) => {
71+
return {
72+
...item,
73+
position: item._role === 'user' ? 'right' : 'left',
74+
// meta: {
75+
// history: true,
76+
// },
77+
}
78+
})
79+
historyMsgs = msgList.concat([{ code: 'system', data: { text: '以上是历史消息' } }])
80+
}
81+
82+
console.log('historyMsgs>', historyMsgs)
83+
return {
84+
list: historyMsgs,
85+
noMore: true,
86+
}
87+
}
88+
89+
export const sendMsg = (msg: any) => {
90+
if (msg.type !== 'text') {
91+
return
92+
}
93+
let withTyping = true
94+
chatBot.bot.appendMessage({
95+
id: 'typing',
96+
code: 'typing',
97+
})
98+
chatBot.bot.setConfig({
99+
inputOptions: {
100+
disabled: true,
101+
},
102+
})
103+
sendMsgSync({
104+
csvId: defaultCvs.id,
105+
withContext: true,
106+
messages: [
107+
{
108+
role: 'user',
109+
content: msg.data.text,
110+
},
111+
],
112+
onError: ({ msg, chunks }) => {
113+
chatBot.bot.deleteMessage('typing')
114+
chatBot.bot.appendMessage({
115+
code: 'markdown',
116+
id: uuidv4(),
117+
data: {
118+
text:
119+
'<span class="text-danger font-bold">抱歉,无法正常回复您的消息,原因:</span>' +
120+
(chunks ? '\n```json \n' + JSON.stringify(chunks, ' ', 2) + '\n```' : msg),
121+
},
122+
})
123+
},
124+
onChunk({ formattedMsg }) {
125+
if (withTyping) {
126+
withTyping = false
127+
chatBot.bot.deleteMessage('typing')
128+
}
129+
chatBot.bot.updateOrAppendMessage(formattedMsg)
130+
},
131+
onDone: ({ formattedMsg, msg: errMsg }) => {
132+
chatBot.bot.setConfig({
133+
inputOptions: {
134+
disabled: false,
135+
},
136+
})
137+
appendCvsMsgItem(
138+
defaultCvs.id,
139+
[
140+
{
141+
...msg,
142+
_role: 'user',
143+
id: uuidv4(),
144+
},
145+
!errMsg && {
146+
...formattedMsg,
147+
_role: 'assistant',
148+
},
149+
].filter(Boolean)
150+
)
151+
},
152+
})
153+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.chatContainer {
2+
&[data-hidden="1"] {
3+
//
4+
}
5+
6+
:global {
7+
&.hiddenWindow {
8+
//
9+
}
10+
.ChatWrapper {
11+
width: 40vw;
12+
height: 70vh;
13+
}
14+
.ChatWrapper-body {
15+
flex-direction: row-reverse;
16+
}
17+
.Sidebar {
18+
width: 200px;
19+
border-right: 1px solid #e3e3e3;
20+
background: #eeeeee;
21+
}
22+
.ChatFooter .Composer-input {
23+
min-height: 64px;
24+
}
25+
}
26+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { toast } from 'amis-ui'
2+
3+
import {
4+
getDefaultMsgs,
5+
getHistoryMsgs,
6+
isInputDisabled,
7+
sendMsg,
8+
setChatBoot,
9+
getChatBoot,
10+
} from './chatContrl'
11+
import { Header } from './Header'
12+
import defBot from '../../assets/def-bot.jpeg'
13+
import defUser from '../../assets/def-user.png'
14+
15+
import styles from './index.module.less'
16+
17+
import { defaultCvs, setCvsMsgList } from '@/localServer/aiServervice'
18+
19+
export { getChatBoot }
20+
21+
export const initChatBot = (opts: { root: any }) => {
22+
const { root } = opts
23+
24+
root.classList.add(styles.chatContainer)
25+
26+
const chatBot = new ChatSDK({
27+
root,
28+
config: {
29+
autoLoadMore: true,
30+
robot: {
31+
avatar: defBot,
32+
},
33+
user: {
34+
avatar: defUser,
35+
},
36+
messages: getDefaultMsgs(),
37+
quickReplies: [
38+
{
39+
name: '清空会话',
40+
icon: 'refresh',
41+
onClick: async () => {
42+
if (isInputDisabled()) {
43+
toast.warning('正在回复中,无法清空会话')
44+
return
45+
}
46+
chatBot.bot.resetMessageList(
47+
[
48+
{
49+
code: 'system',
50+
data: {
51+
text: '已清空会话',
52+
},
53+
},
54+
].concat(getDefaultMsgs())
55+
)
56+
await setCvsMsgList(defaultCvs.id, [])
57+
},
58+
},
59+
],
60+
renderNavbar() {
61+
return <Header />
62+
},
63+
// sidebar: [
64+
// {
65+
// code: 'ConversionPanel',
66+
// },
67+
// ],
68+
},
69+
handlers: {
70+
beforeSendMessage() {
71+
if (isInputDisabled()) {
72+
return false // false时消息将不放到消息列表中
73+
}
74+
},
75+
// 对 消息内容进行处理
76+
// renderMessageContent() {
77+
// //
78+
// }
79+
},
80+
requests: {
81+
history: getHistoryMsgs,
82+
send: sendMsg,
83+
},
84+
components: {
85+
// ConversionPanel: () => {
86+
// return <div>123</div>
87+
// },
88+
},
89+
})
90+
91+
setChatBoot(chatBot)
92+
window.chatBot = chatBot
93+
94+
chatBot.run()
95+
}

0 commit comments

Comments
 (0)