diff --git a/service-web/client/package.json b/service-web/client/package.json index ced2c40..1284afd 100644 --- a/service-web/client/package.json +++ b/service-web/client/package.json @@ -18,12 +18,14 @@ "antd": "^5.25.0", "axios": "^1.9.0", "licia": "^1.48.0", + "markdown-it": "^14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^7.5.3", "styled-components": "^6.1.18" }, "devDependencies": { + "@types/markdown-it": "^14.1.2", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react-swc": "^3.9.0", diff --git a/service-web/client/pnpm-lock.yaml b/service-web/client/pnpm-lock.yaml index 1ac0a36..90e6868 100644 --- a/service-web/client/pnpm-lock.yaml +++ b/service-web/client/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: licia: specifier: ^1.48.0 version: 1.48.0 + markdown-it: + specifier: ^14.1.0 + version: 14.1.0 react: specifier: ^18.2.0 version: 18.3.1 @@ -48,6 +51,9 @@ importers: specifier: ^6.1.18 version: 6.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@types/markdown-it': + specifier: ^14.1.2 + version: 14.1.2 '@types/react': specifier: ^18.2.0 version: 18.3.21 @@ -926,6 +932,15 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/node@14.18.63': resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} @@ -1474,6 +1489,10 @@ packages: entities@2.1.0: resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1903,6 +1922,9 @@ packages: linkify-it@3.0.3: resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + listenercount@1.0.1: resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} @@ -1996,6 +2018,10 @@ packages: resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} hasBin: true + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + match-sorter@6.3.4: resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==} @@ -2009,6 +2035,9 @@ packages: mdurl@1.0.1: resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -2292,6 +2321,10 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2992,6 +3025,9 @@ packages: uc.micro@1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + uncontrollable@7.2.1: resolution: {integrity: sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==} peerDependencies: @@ -4024,6 +4060,15 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@2.0.0': {} + '@types/node@14.18.63': {} '@types/prop-types@15.7.14': {} @@ -4771,6 +4816,8 @@ snapshots: entities@2.1.0: {} + entities@4.5.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -5285,6 +5332,10 @@ snapshots: dependencies: uc.micro: 1.0.6 + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + listenercount@1.0.1: {} locate-character@3.0.0: {} @@ -5360,6 +5411,15 @@ snapshots: mdurl: 1.0.1 uc.micro: 1.0.6 + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + match-sorter@6.3.4: dependencies: '@babel/runtime': 7.27.1 @@ -5371,6 +5431,8 @@ snapshots: mdurl@1.0.1: {} + mdurl@2.0.0: {} + media-typer@1.1.0: {} merge-descriptors@2.0.0: {} @@ -5616,6 +5678,8 @@ snapshots: proxy-from-env@1.1.0: {} + punycode.js@2.3.1: {} + punycode@2.3.1: {} pure-color@1.3.0: {} @@ -6511,6 +6575,8 @@ snapshots: uc.micro@1.0.6: {} + uc.micro@2.1.0: {} + uncontrollable@7.2.1(react@18.3.1): dependencies: '@babel/runtime': 7.27.1 diff --git a/service-web/client/src/pages/ai/Conversation.tsx b/service-web/client/src/pages/ai/Conversation.tsx index 53b0d98..f521606 100644 --- a/service-web/client/src/pages/ai/Conversation.tsx +++ b/service-web/client/src/pages/ai/Conversation.tsx @@ -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(null) + const [input, setInput] = useState('') + const [think, setThink] = useState(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 ( -
- Conversation -
+ + {messages.length > 0 + ? (, + style: { + background: 'transparent', + }, + }, + messageRender: content => { + let split = content.split('') + if (split.length > 1) { + content = `${split[0]}${md.render(split[1])}` + } + return ( + +
+ + ) + }, + }, + user: { + placement: 'end', + avatar: { + icon: , + }, + }, + }} + items={messages.map(({id, message}) => ({ + key: id, + ...message, + }))} + />) + : (
+ } + title="你好,我是基于大模型深度思考技术构建的 AI 运营助手" + description="我可以帮你查询、检索Hudi 服务的运行情况,分析、处理 Hudi 服务的运营故障,输出、解读 Hudi 系统整体运营报告" + /> +
)} +
+ { + onRequest({ + message: { + role: 'user', + content: think ? message : `/no_think ${message}`, + }, + stream: true, + }) + setInput('') + }} + onCancel={() => abortController.current?.abort()} + footer={({components}) => { + const {SendButton, LoadingButton} = components + return ( + + + 深度思考 + + + +
+ ) } diff --git a/service-web/client/src/route.tsx b/service-web/client/src/route.tsx index cc5007a..b9ad255 100644 --- a/service-web/client/src/route.tsx +++ b/service-web/client/src/route.tsx @@ -76,7 +76,7 @@ export const routes: RouteObject[] = [ children: [ { index: true, - element: , + element: , }, { path: 'inspection', @@ -169,16 +169,16 @@ export const menus = { name: 'AI', icon: , routes: [ - { - path: '/ai/inspection', - name: '智能巡检', - icon: , - }, { path: '/ai/conversation', name: '智慧问答', icon: , }, + { + path: '/ai/inspection', + name: '智能巡检', + icon: , + }, ], }, ],