|
|
@ -150,7 +150,7 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- 文件导入对话框 --> |
|
|
|
<el-dialog :z-index="9999" title="文件导入" v-model="upload.open" width="400px" append-to-body> |
|
|
|
<el-dialog :z-index="99999" title="文件导入" v-model="upload.open" width="400px" append-to-body> |
|
|
|
<el-upload v-if="upload.open" |
|
|
|
ref="uploadRef" |
|
|
|
:headers="upload.headers" |
|
|
@ -485,79 +485,83 @@ watch( |
|
|
|
} |
|
|
|
) |
|
|
|
const getWrite = (reader) => { |
|
|
|
// 修复点1:将tempResult改为局部变量,避免递归状态污染 |
|
|
|
let tempResult = ''; |
|
|
|
/** |
|
|
|
* 处理流式数据 |
|
|
|
* @param {done, value} obj - 包含 done 和 value 属性的对象 |
|
|
|
*/ |
|
|
|
|
|
|
|
const write_stream = ({ done, value }) => { |
|
|
|
try { |
|
|
|
if (done) { |
|
|
|
return |
|
|
|
} |
|
|
|
if (done) return; |
|
|
|
const decoder = new TextDecoder('utf-8'); |
|
|
|
let str = decoder.decode(value, { stream: true }); |
|
|
|
// 解释:数据流返回的 chunk 可能不完整,因此我们需要累积并拆分它们。 |
|
|
|
console.log(str) |
|
|
|
tempResult += str; |
|
|
|
console.log(tempResult) |
|
|
|
const split = tempResult.match(/data:.*}\r\n/g); |
|
|
|
|
|
|
|
// 修复点2:使用非贪婪匹配确保精确分块 |
|
|
|
const split = tempResult.match(/data:.*?}\r\n/g); |
|
|
|
|
|
|
|
if (split) { |
|
|
|
str = split.join(''); |
|
|
|
tempResult = tempResult.replace(str, ''); |
|
|
|
} else { |
|
|
|
return reader.read().then(write_stream); |
|
|
|
} |
|
|
|
// 如果 str 存在且以 "data:" 开头,则处理数据块。 |
|
|
|
if (str && str.startsWith('data:')) { |
|
|
|
split.forEach((chunkStr) => { |
|
|
|
// 修复点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()); |
|
|
|
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 |
|
|
|
processChunk(chunk); |
|
|
|
} catch (e) { |
|
|
|
console.error('解析错误:', e, chunkStr); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 递归处理剩余数据 |
|
|
|
return reader.read().then(write_stream); |
|
|
|
} else { |
|
|
|
chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"}) |
|
|
|
// 无匹配块时继续累积数据 |
|
|
|
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 { |
|
|
|
chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"}) |
|
|
|
lastMsg.content.push({ content: text, type: "text" }); |
|
|
|
} |
|
|
|
} |
|
|
|
nextTick(() => { |
|
|
|
// 将滚动条滚动到最下面 |
|
|
|
scrollDiv.value.setScrollTop(getMaxHeight()) |
|
|
|
}) |
|
|
|
|
|
|
|
// 修复点7:统一处理结束标志 |
|
|
|
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.isEnd = true; |
|
|
|
console.log(lastMsg) |
|
|
|
lastMsg.time = formatDate(new Date()); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
return Promise.reject(e); |
|
|
|
} |
|
|
|
return reader.read().then(write_stream); |
|
|
|
|
|
|
|
nextTick(() => scrollDiv.value.setScrollTop(getMaxHeight())); |
|
|
|
}; |
|
|
|
return write_stream |
|
|
|
|
|
|
|
return write_stream; |
|
|
|
}; |
|
|
|
|
|
|
|
const stopChat = (index) => { |
|
|
|