From b883f2215e5b017e4405b3a4fc95d5ac35426e2c Mon Sep 17 00:00:00 2001 From: "yuanjs@qutke.com" Date: Mon, 12 May 2025 17:19:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springaigu/config/XiaozhiAgentConfig.java | 6 +- ui/package.json | 1 + ui/src/components/ChatWindow.vue | 153 +++++++++++++----- 3 files changed, 113 insertions(+), 47 deletions(-) diff --git a/src/main/java/cn/ai/springaigu/config/XiaozhiAgentConfig.java b/src/main/java/cn/ai/springaigu/config/XiaozhiAgentConfig.java index 139e954..0d4b700 100644 --- a/src/main/java/cn/ai/springaigu/config/XiaozhiAgentConfig.java +++ b/src/main/java/cn/ai/springaigu/config/XiaozhiAgentConfig.java @@ -38,9 +38,9 @@ public class XiaozhiAgentConfig { ContentRetriever contentRetrieverXiaozhi() { //使用FileSystemDocumentLoader读取指定目录下的知识库文档 //并使用默认的文档解析器对文档进行解析 - Document document1 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/医院信息.md"); - Document document2 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/科室信息.md"); - Document document3 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/神经内科.md"); + Document document1 = FileSystemDocumentLoader.loadDocument("D:/学习/sping-ai/knowledge/医院信息.md"); + Document document2 = FileSystemDocumentLoader.loadDocument("D:/学习/sping-ai/knowledge/科室信息.md"); + Document document3 = FileSystemDocumentLoader.loadDocument("D:/学习/sping-ai/knowledge/神经内科.md"); List documents = Arrays.asList(document1, document2, document3); //使用内存向量存储 生产不建议使用 InMemoryEmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>(); diff --git a/ui/package.json b/ui/package.json index 49ec475..b5caf54 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,6 +13,7 @@ "@element-plus/icons-vue": "^2.3.1", "axios": "^1.7.7", "element-plus": "^2.8.4", + "marked": "^15.0.11", "uuid": "^10.0.0", "vue": "^3.5.13" }, diff --git a/ui/src/components/ChatWindow.vue b/ui/src/components/ChatWindow.vue index a8a0d37..8bb3d37 100644 --- a/ui/src/components/ChatWindow.vue +++ b/ui/src/components/ChatWindow.vue @@ -14,15 +14,15 @@
- + @@ -44,12 +44,12 @@
发送发送
@@ -61,6 +61,7 @@ import { onMounted, ref, watch } from 'vue' import axios from 'axios' import { v4 as uuidv4 } from 'uuid' +import { marked } from 'marked' const messaggListRef = ref() const isSending = ref(false) @@ -100,12 +101,11 @@ const sendRequest = (message) => { isTyping: false, isThinking: false, } - //第一条默认发送的用户消息”你好“不放入会话列表 + //第一条默认发送的用户消息"你好"不放入会话列表 if(messages.value.length > 0){ messages.value.push(userMsg) } - // 添加机器人加载消息 const botMsg = { isUser: false, @@ -118,31 +118,30 @@ const sendRequest = (message) => { scrollToBottom() axios - .post( - '/api/xiaozhi/chat', - { memoryId: uuid.value, message }, - { - responseType: 'stream', // 必须为合法值 "text" - onDownloadProgress: (e) => { - const fullText = e.event.target.responseText // 累积的完整文本 - let newText = fullText.substring(lastMsg.content.length) - lastMsg.content += newText //增量更新 - console.log(lastMsg) - scrollToBottom() // 实时滚动 - }, - } - ) - .then(() => { - // 流结束后隐藏加载动画 - messages.value.at(-1).isTyping = false - isSending.value = false - }) - .catch((error) => { - console.error('流式错误:', error) - messages.value.at(-1).content = '请求失败,请重试' - messages.value.at(-1).isTyping = false - isSending.value = false - }) + .post( + '/api/xiaozhi/chat', + { memoryId: uuid.value, message }, + { + responseType: 'stream', // 必须为合法值 "text" + onDownloadProgress: (e) => { + const fullText = e.event.target.responseText // 累积的完整文本 + let newText = fullText.substring(lastMsg.content.length) + lastMsg.content += newText //增量更新 + scrollToBottom() // 实时滚动 + }, + } + ) + .then(() => { + // 流结束后隐藏加载动画 + messages.value.at(-1).isTyping = false + isSending.value = false + }) + .catch((error) => { + console.error('流式错误:', error) + messages.value.at(-1).content = '请求失败,请重试' + messages.value.at(-1).isTyping = false + isSending.value = false + }) } // 初始化 UUID @@ -166,12 +165,12 @@ const uuidToNumber = (uuid) => { // 转换特殊字符 const convertStreamOutput = (output) => { - return output - .replace(/\n/g, '
') - .replace(/\t/g, '    ') - .replace(/&/g, '&') // 新增转义,避免 HTML 注入 - .replace(//g, '>') + // 使用 marked 解析 Markdown + return marked(output, { + breaks: true, // 支持换行 + gfm: true, // 支持 GitHub 风格的 Markdown + sanitize: true // 防止 XSS 攻击 + }) } const newChat = () => { @@ -378,4 +377,70 @@ const newChat = () => { margin-top: 20px; } } + +/* Markdown 样式 */ +:deep(p) { + margin: 0.5em 0; +} + +:deep(pre) { + background-color: #f6f8fa; + border-radius: 6px; + padding: 16px; + overflow: auto; + margin: 0.5em 0; +} + +:deep(code) { + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; + background-color: rgba(175, 184, 193, 0.2); + padding: 0.2em 0.4em; + border-radius: 6px; + font-size: 85%; +} + +:deep(ul), :deep(ol) { + padding-left: 2em; + margin: 0.5em 0; +} + +:deep(li) { + margin: 0.25em 0; +} + +:deep(blockquote) { + margin: 0.5em 0; + padding: 0 1em; + color: #57606a; + border-left: 0.25em solid #d0d7de; +} + +:deep(table) { + border-collapse: collapse; + width: 100%; + margin: 0.5em 0; +} + +:deep(th), :deep(td) { + border: 1px solid #d0d7de; + padding: 6px 13px; +} + +:deep(th) { + background-color: #f6f8fa; +} + +:deep(img) { + max-width: 100%; + height: auto; +} + +:deep(a) { + color: #0969da; + text-decoration: none; +} + +:deep(a:hover) { + text-decoration: underline; +}