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 d8026ff..976a817 100644 --- a/vue-fastapi-frontend/src/views/aichat/aichat.vue +++ b/vue-fastapi-frontend/src/views/aichat/aichat.vue @@ -150,7 +150,7 @@ - + import { ref, nextTick, computed, watch, reactive, onMounted } from 'vue' import { useRoute } from 'vue-router' -import antvg6 from './antv-g6.vue' import OperationButton from './OperationButton.vue' import MdRenderer from '@/views/aichat/MdRenderer.vue' import fullscreenG6 from '@/views/aichat/fullscreenG6.vue' @@ -485,79 +484,83 @@ watch( } ) const getWrite = (reader) => { + // 修复点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, 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) => {