feat(web): 增加AI对话的能力

This commit is contained in:
v-zhangjc9
2025-05-13 16:03:08 +08:00
parent 819d56fbe3
commit dd2e56e27b
4 changed files with 247 additions and 9 deletions

View File

@@ -1,8 +1,178 @@
import {ClearOutlined, UserOutlined} from '@ant-design/icons'
import {Bubble, Sender, useXAgent, useXChat, Welcome} from '@ant-design/x'
import {Button, Divider, Flex, Switch, Tooltip, Typography} from 'antd'
import markdownIt from 'markdown-it'
import {useRef, useState} from 'react'
import styled from 'styled-components'
const md = markdownIt({html: true, breaks: true})
const ConversationDiv = styled.div`
height: calc(100vh - 76px);
display: flex;
flex-direction: column;
padding: 10px;
.conversation-welcome {
flex: 1;
width: 70%;
margin: 30px auto 30px;
}
.conversation-list {
flex: 1;
margin-bottom: 30px;
padding-left: 30px;
padding-right: 30px;
think {
color: gray;
display: block;
border-left: 3px solid;
padding-left: 5px;
margin-bottom: 10px;
//white-space: pre-line;
}
}
.conversation-sender {
height: 100px;
padding-left: 30px;
padding-right: 30px;
}
`
const llmConfig = {
base: 'http://132.121.206.65:10086',
model: 'Qwen3-1.7',
secret: 'Bearer *XMySqV%>hR&v>>g*NwCs3tpQ5FVMFEF2VHVTj<MYQd$&@$sY7CgqNyea4giJi4',
}
function Conversation() {
const abortController = useRef<AbortController | null>(null)
const [input, setInput] = useState<string>('')
const [think, setThink] = useState<boolean>(true)
const [agent] = useXAgent<{ role: string, content: string }>({
baseURL: `${llmConfig.base}/v1/chat/completions`,
model: llmConfig.model,
dangerouslyApiKey: llmConfig.secret,
})
const {onRequest, messages, setMessages} = useXChat({
agent,
transformMessage: ({originMessage, chunk}) => {
let text = ''
try {
if (chunk?.data && !chunk?.data.includes('DONE')) {
const message = JSON.parse(chunk?.data)
text = !message?.choices?.[0].delta?.content
? ''
: message?.choices?.[0].delta?.content
}
} catch (error) {
console.error(error)
}
return {
content: (originMessage?.content || '') + text,
role: 'assistant',
}
},
resolveAbortController: controller => {
abortController.current = controller
},
})
return (
<div className="conversation">
Conversation
</div>
<ConversationDiv>
{messages.length > 0
? (<Bubble.List
className="conversation-list"
roles={{
assistant: {
placement: 'start',
avatar: {
icon: <img src="icon.png" alt=""/>,
style: {
background: 'transparent',
},
},
messageRender: content => {
let split = content.split('</think>')
if (split.length > 1) {
content = `${split[0]}</think>${md.render(split[1])}`
}
return (
<Typography>
<div dangerouslySetInnerHTML={{__html: content}}/>
</Typography>
)
},
},
user: {
placement: 'end',
avatar: {
icon: <UserOutlined/>,
},
},
}}
items={messages.map(({id, message}) => ({
key: id,
...message,
}))}
/>)
: (<div className="conversation-welcome">
<Welcome
variant="borderless"
icon={<img src="icon.png" alt="icon"/>}
title="你好,我是基于大模型深度思考技术构建的 AI 运营助手"
description="我可以帮你查询、检索Hudi 服务的运行情况,分析、处理 Hudi 服务的运营故障,输出、解读 Hudi 系统整体运营报告"
/>
</div>)}
<div className="conversation-sender">
<Sender
loading={agent.isRequesting()}
value={input}
onChange={setInput}
onSubmit={message => {
onRequest({
message: {
role: 'user',
content: think ? message : `/no_think ${message}`,
},
stream: true,
})
setInput('')
}}
onCancel={() => abortController.current?.abort()}
footer={({components}) => {
const {SendButton, LoadingButton} = components
return (
<Flex justify="space-between" align="center">
<Flex gap="small" align="center">
<Switch size="small" value={think} onChange={setThink}/>
<Divider type="vertical"/>
<Tooltip title="清空对话">
<Button
icon={<ClearOutlined/>}
type="text"
size="small"
onClick={() => setMessages([])}
/>
</Tooltip>
</Flex>
<Flex align="center">
{agent.isRequesting() ? (
<LoadingButton type="default"/>
) : (
<SendButton type="primary" disabled={false}/>
)}
</Flex>
</Flex>
)
}}
actions={false}
/>
</div>
</ConversationDiv>
)
}

View File

@@ -76,7 +76,7 @@ export const routes: RouteObject[] = [
children: [
{
index: true,
element: <Navigate to="/ai/inspection" replace/>,
element: <Navigate to="/ai/conversation" replace/>,
},
{
path: 'inspection',
@@ -169,16 +169,16 @@ export const menus = {
name: 'AI',
icon: <OpenAIOutlined/>,
routes: [
{
path: '/ai/inspection',
name: '智能巡检',
icon: <CheckSquareOutlined/>,
},
{
path: '/ai/conversation',
name: '智慧问答',
icon: <QuestionOutlined/>,
},
{
path: '/ai/inspection',
name: '智能巡检',
icon: <CheckSquareOutlined/>,
},
],
},
],