From 2452af761a9bc655ebf07733719b0e4869a9a935 Mon Sep 17 00:00:00 2001 From: xueyinfei <1207092115@qq.com> Date: Tue, 7 Oct 2025 00:24:10 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A4=A7=E6=A8=A1=E5=9E=8Bbug=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/aichat/aichat.vue | 140 +++++++++--------- 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/vue-fastapi-frontend/src/views/aichat/aichat.vue b/vue-fastapi-frontend/src/views/aichat/aichat.vue index d8026ff..5f5983e 100644 --- a/vue-fastapi-frontend/src/views/aichat/aichat.vue +++ b/vue-fastapi-frontend/src/views/aichat/aichat.vue @@ -150,7 +150,7 @@ - + { + // 修复点1:将tempResult改为局部变量,避免递归状态污染 let tempResult = ''; - /** - * 处理流式数据 - * @param {done, value} obj - 包含 done 和 value 属性的对象 - */ - const write_stream = ({ done,value }) => { - try { - if (done) { - return + + const write_stream = ({ done, value }) => { + if (done) return; + const decoder = new TextDecoder('utf-8'); + let str = decoder.decode(value, { stream: true }); + console.log(str) + tempResult += str; + + // 修复点2:使用非贪婪匹配确保精确分块 + const split = tempResult.match(/data:.*?}\r\n/g); + + if (split) { + // 修复点3:按顺序处理每个匹配块,避免遗漏 + for (let i = 0; i < split.length; i++) { + const chunkStr = split[i]; + tempResult = tempResult.replace(chunkStr, '', 1); // 修复点4:单次替换避免残留 + + try { + const chunk = JSON.parse(chunkStr.replace('data:', '').trim()); + processChunk(chunk); + } catch (e) { + console.error('解析错误:', e, chunkStr); + } } - const decoder = new TextDecoder('utf-8'); - let str = decoder.decode(value, { stream: true }); - // 解释:数据流返回的 chunk 可能不完整,因此我们需要累积并拆分它们。 - tempResult += str; - console.log(tempResult) - const split = tempResult.match(/data:.*}\r\n/g); - if (split) { - str = split.join(''); - tempResult = tempResult.replace(str, ''); + + // 递归处理剩余数据 + return reader.read().then(write_stream); + } else { + // 无匹配块时继续累积数据 + return reader.read().then(write_stream); + } + }; + + const processChunk = (chunk) => { + const lastMsg = chatList.value[chatList.value.length - 1]; + + // 修复点5:统一处理所有类型的数据块 + if (chunk.docs?.length) { + lastMsg.content.push({ content: chunk.docs[0], type: "docs" }); + } else if (chunk.G6_ER?.length) { + lastMsg.content.push({ content: chunk.G6_ER, type: "G6_ER" }); + } else if (chunk.html_image?.length) { + lastMsg.content.push({ content: chunk.html_image, type: "html_image" }); + } else if (chunk.table?.length) { + lastMsg.content.push({ content: chunk.table, type: "table" }); + } else if (chunk.choices?.length) { + // 修复点6:纯文本处理增加防重复校验 + // const text = chunk.choices[0].delta.content.replace(/\n/g, "\n\n"); + // 智能换行处理 + const content = chunk.choices[0].delta.content; + const isNewParagraph = content.startsWith('\n\n'); + // 仅当需要时添加换行 + const text = isNewParagraph + ? content.replace(/\n{2,}/g, '\n\n') + : content.replace(/\n/g, ' '); + const lastContent = lastMsg.content[lastMsg.content.length - 1]; + console.log(lastContent) + if (lastContent?.type === "text") { + lastContent.content += text; } else { - return reader.read().then(write_stream); - } - // 如果 str 存在且以 "data:" 开头,则处理数据块。 - if (str && str.startsWith('data:')) { - split.forEach((chunkStr) => { - const chunk = JSON.parse(chunkStr.replace('data:', '').trim()); - if (chunk.docs && chunk.docs[0].length > 0){ - //超链接 - chatList.value[chatList.value.length - 1].content.push({"content":chunk.docs[0],"type":"docs"}) - }else if (chunk.G6_ER && chunk.G6_ER.length > 0){ - //说明是ER图 - chatList.value[chatList.value.length - 1].content.push({"content":chunk.G6_ER,"type":"G6_ER"}) - }else if (chunk.html_image && chunk.html_image.length > 0){ - //说明是html的echarts图 - chatList.value[chatList.value.length - 1].content.push({"content":chunk.html_image,"type":"html_image"}) - }else if (chunk.table && chunk.table.length > 0){ - //说明是 表格 - chatList.value[chatList.value.length - 1].content.push({"content":chunk.table,"type":"table"}) - }else { - // 纯文本 - let last_answer = chatList.value[chatList.value.length - 1].content - chunk.choices[0].delta.content = chunk.choices[0].delta.content.replace("\n","\n\n") - if (last_answer.length > 0) { - if(last_answer[last_answer.length - 1].type === 'text'){ - chatList.value[chatList.value.length - 1].content[last_answer.length - 1].content += chunk.choices[0].delta.content - }else{ - chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"}) - } - }else{ - chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"}) - } - } - nextTick(() => { - // 将滚动条滚动到最下面 - scrollDiv.value.setScrollTop(getMaxHeight()) - }) - if (chunk.isEnd || chunk.is_end){ - chatList.value[chatList.value.length - 1].isEnd = true - chatList.value[chatList.value.length - 1].time = formatDate(new Date()) - nextTick(() => { - // 将滚动条滚动到最下面 - scrollDiv.value.setScrollTop(getMaxHeight()) - }) - return Promise.resolve(); - } - }); + lastMsg.content.push({ content: text, type: "text" }); } - } catch (e) { - return Promise.reject(e); } - return reader.read().then(write_stream); + + // 修复点7:统一处理结束标志 + if (chunk.isEnd || chunk.is_end) { + lastMsg.isEnd = true; + console.log(lastMsg) + lastMsg.time = formatDate(new Date()); + } + + nextTick(() => scrollDiv.value.setScrollTop(getMaxHeight())); }; - return write_stream + + return write_stream; }; const stopChat = (index) => { From c84a6d2e896ea49080653f183cd09f9f0af85772 Mon Sep 17 00:00:00 2001 From: xueyinfei <1207092115@qq.com> Date: Tue, 7 Oct 2025 01:35:10 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=A4=A7=E6=A8=A1=E5=9E=8B=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=8B=E8=BD=BD=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vue-fastapi-frontend/src/main.js | 3 +-- .../src/views/aichat/MdRenderer.vue | 14 +++++++++++++- vue-fastapi-frontend/src/views/aichat/aichat.vue | 3 +-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/vue-fastapi-frontend/src/main.js b/vue-fastapi-frontend/src/main.js index a772047..1e38b0d 100644 --- a/vue-fastapi-frontend/src/main.js +++ b/vue-fastapi-frontend/src/main.js @@ -52,8 +52,7 @@ const app = createApp(App) // 全局方法挂载 app.config.globalProperties.useDict = useDict app.config.globalProperties.download = download -app.config.globalProperties.download = download -app.config.globalProperties.formatDateTime = formatDateTime +app.config.globalProperties.formatDateTime = formatDateTime app.config.globalProperties.resetForm = resetForm app.config.globalProperties.handleTree = handleTree app.config.globalProperties.addDateRange = addDateRange diff --git a/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue b/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue index b93546d..c747797 100644 --- a/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue +++ b/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue @@ -15,6 +15,11 @@ +
+
+ {{ doc.file_name }} +
+
@@ -29,7 +34,7 @@ import chatTable from './chatTable.vue' import htmlCharts from './htmlCharts.vue' import {Download, FullScreen} from "@element-plus/icons-vue"; import { ref, watch} from 'vue' - +const { proxy } = getCurrentInstance(); const props = defineProps({ source: Array, is_large: Boolean, @@ -41,6 +46,13 @@ function fullscreenG6(data){ emit('fullscreenG6',data) } +function downLoadFile(doc){ + let data = {file:doc.file_name,bucket: doc.bucket,sessionId: doc.session_id} + proxy.download("/default-api/aichat/file/download", { + ...data, + }, doc.file_name); +} + function downLoadTable(data){ const worksheet = XLSX.utils.json_to_sheet(data); // 创建一个新的工作簿并将工作表添加到工作簿中 diff --git a/vue-fastapi-frontend/src/views/aichat/aichat.vue b/vue-fastapi-frontend/src/views/aichat/aichat.vue index 5f5983e..976a817 100644 --- a/vue-fastapi-frontend/src/views/aichat/aichat.vue +++ b/vue-fastapi-frontend/src/views/aichat/aichat.vue @@ -180,7 +180,6 @@