|
|
@ -129,7 +129,7 @@ |
|
|
<script> |
|
|
<script> |
|
|
|
|
|
|
|
|
import { mapState } from "vuex"; |
|
|
import { mapState } from "vuex"; |
|
|
import { getapi, postapi, putapi, deletapi } from "@/api/api"; |
|
|
|
|
|
|
|
|
import { getapi, postapi, putapi, deletapi, fetchStream } from "@/api/api"; |
|
|
import { getPagePriv, checkPagePriv, deepCopy } from "../../utlis/proFunc"; |
|
|
import { getPagePriv, checkPagePriv, deepCopy } from "../../utlis/proFunc"; |
|
|
|
|
|
|
|
|
import PatientRegisterList from "../doctorCheck/PatientRegisterList.vue"; |
|
|
import PatientRegisterList from "../doctorCheck/PatientRegisterList.vue"; |
|
|
@ -166,10 +166,8 @@ export default { |
|
|
max: true, |
|
|
max: true, |
|
|
visible: false, |
|
|
visible: false, |
|
|
diagnosis: 'AI诊断信息', |
|
|
diagnosis: 'AI诊断信息', |
|
|
rawText: "", // 原始 markdown 内容(流式累加) |
|
|
|
|
|
html: "", // 渲染后的 HTML(逐字更新) |
|
|
|
|
|
typingIndex: 0, |
|
|
|
|
|
typingTimer: null |
|
|
|
|
|
|
|
|
rawText: '', |
|
|
|
|
|
html: "" |
|
|
}, |
|
|
}, |
|
|
}; |
|
|
}; |
|
|
}, |
|
|
}, |
|
|
@ -440,61 +438,73 @@ export default { |
|
|
|
|
|
|
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// AI诊断 AI重新诊断 |
|
|
|
|
|
btnAIdiagnosis(again) { |
|
|
|
|
|
|
|
|
// AI诊断 AI重新诊断(使用 fetchStream 实现流式实时渲染) |
|
|
|
|
|
async btnAIdiagnosis(again) { |
|
|
if (!again) { |
|
|
if (!again) { |
|
|
if (this.AI.visible) { |
|
|
if (this.AI.visible) { |
|
|
this.btnAImax(false) |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
this.btnAImax(false); |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let message = '' |
|
|
|
|
|
let linkStr = ';' |
|
|
|
|
|
|
|
|
let message = ''; |
|
|
|
|
|
let linkStr = ';'; |
|
|
this.sumDoctorCheck.summaryList.forEach(e => { |
|
|
this.sumDoctorCheck.summaryList.forEach(e => { |
|
|
if (message) { |
|
|
if (message) { |
|
|
linkStr = ';' |
|
|
|
|
|
|
|
|
linkStr = ';'; |
|
|
} else { |
|
|
} else { |
|
|
linkStr = '' |
|
|
|
|
|
|
|
|
linkStr = ''; |
|
|
} |
|
|
} |
|
|
message += linkStr + e.summaryTitle + ':' |
|
|
|
|
|
|
|
|
message += linkStr + e.summaryTitle + ':'; |
|
|
e.details.forEach((e1, i) => { |
|
|
e.details.forEach((e1, i) => { |
|
|
message += (i + 1) + ')、' + e1.summaryContent |
|
|
|
|
|
|
|
|
message += (i + 1) + ')、' + e1.summaryContent; |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
message = '性别:' + this.doctorCheck.prBase.sexName + ',年龄:' + this.doctorCheck.prBase.age + '岁,检查结果:' + message |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// postapi('/api/app/AIMessage/GetAIMessageResult', { message }) |
|
|
|
|
|
// .then(res => { |
|
|
|
|
|
// if (res.code > -1) { |
|
|
|
|
|
// this.AI.visible = true |
|
|
|
|
|
// this.AI.diagnosis = res.data.result |
|
|
|
|
|
// this.btnAImax(false) |
|
|
|
|
|
// } else { |
|
|
|
|
|
// this.$message.error({ showClose: true, message: res.message }) |
|
|
|
|
|
// } |
|
|
|
|
|
// }) |
|
|
|
|
|
|
|
|
message = '性别:' + this.doctorCheck.prBase.sexName + ',年龄:' + this.doctorCheck.prBase.age + '岁,检查结果:' + message; |
|
|
|
|
|
this.AI.rawText = ''; |
|
|
|
|
|
this.AI.html = ''; |
|
|
|
|
|
|
|
|
postapi('/api/app/AiMessageWs/GetAIMessageResult', { message }) |
|
|
|
|
|
.then(res => { |
|
|
|
|
|
if (!res) return; |
|
|
|
|
|
// 移除流结束标志并去掉每行的 'data: ' 前缀 |
|
|
|
|
|
let cleaned = String(res) |
|
|
|
|
|
.replace(/data:\s*\[DONE\]/g, '') |
|
|
|
|
|
.replace(/^data:\s*/gm, '') |
|
|
|
|
|
.trim(); |
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
await fetchStream('/api/app/AiMessageWs/GetAIMessageResult', { message }, (chunk) => { |
|
|
|
|
|
// 清洗 SSE 风格前缀并移除结束标志 |
|
|
|
|
|
let cleaned = String(chunk).replace(/data:\s*\[DONE\]/g, '').replace(/^data:\s*/gm, ''); |
|
|
if (!cleaned) return; |
|
|
if (!cleaned) return; |
|
|
this.AI.typingIndex = 0; |
|
|
|
|
|
|
|
|
// 首个数据到达时显示弹窗并展开 |
|
|
|
|
|
if (!this.AI.visible) { |
|
|
this.AI.visible = true; |
|
|
this.AI.visible = true; |
|
|
this.AI.rawText = cleaned; |
|
|
|
|
|
// 确保 typingIndex 在合理范围 |
|
|
|
|
|
if (!this.AI.typingIndex || this.AI.typingIndex < 0) this.AI.typingIndex = 0; |
|
|
|
|
|
if (this.AI.typingIndex > this.AI.rawText.length) this.AI.typingIndex = 0; |
|
|
|
|
|
this.startTyping(); |
|
|
|
|
|
this.btnAImax(false); |
|
|
this.btnAImax(false); |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
this.AI.rawText += cleaned; |
|
|
|
|
|
// 实时渲染 Markdown |
|
|
|
|
|
this.AI.html = md.render(this.AI.rawText); |
|
|
|
|
|
// 滚动到底部 |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
try { |
|
|
|
|
|
const el = this.$refs.aiContent; |
|
|
|
|
|
if (el) el.scrollTop = el.scrollHeight; |
|
|
|
|
|
} catch (e) {} |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
// 回退:非流式请求(axios/postapi) |
|
|
|
|
|
try { |
|
|
|
|
|
const res = await postapi('/api/app/AiMessageWs/GetAIMessageResult', { message }); |
|
|
|
|
|
if (res) { |
|
|
|
|
|
let cleaned = String(res).replace(/data:\s*\[DONE\]/g, '').replace(/^data:\s*/gm, ''); |
|
|
|
|
|
if (cleaned) { |
|
|
|
|
|
// 回退响应到达后显示弹窗并展开 |
|
|
|
|
|
if (!this.AI.visible) { |
|
|
|
|
|
this.AI.visible = true; |
|
|
|
|
|
this.btnAImax(false); |
|
|
|
|
|
} |
|
|
|
|
|
this.AI.rawText += cleaned; |
|
|
|
|
|
this.AI.html = md.render(this.AI.rawText); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
this.$message.error({ showClose: true, message: (err && err.message) || 'AI诊断请求失败' }); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
btnAImax(max) { |
|
|
btnAImax(max) { |
|
|
@ -506,30 +516,6 @@ export default { |
|
|
this.AI.width = 24 |
|
|
this.AI.width = 24 |
|
|
this.AI.height = 24 |
|
|
this.AI.height = 24 |
|
|
} |
|
|
} |
|
|
}, |
|
|
|
|
|
startTyping() { |
|
|
|
|
|
if (this.AI.typingTimer) clearInterval(this.AI.typingTimer); |
|
|
|
|
|
|
|
|
|
|
|
this.AI.typingTimer = setInterval(() => { |
|
|
|
|
|
if (this.AI.typingIndex < this.AI.rawText.length) { |
|
|
|
|
|
const current = this.AI.rawText.slice(0, this.AI.typingIndex); |
|
|
|
|
|
this.AI.html = md.render(current); |
|
|
|
|
|
this.AI.typingIndex++; |
|
|
|
|
|
// 每次更新后滚动到容器底部,确保最新输出可见 |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
try { |
|
|
|
|
|
const el = this.$refs.aiContent; |
|
|
|
|
|
if (el) { |
|
|
|
|
|
el.scrollTop = el.scrollHeight; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// ignore |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
clearInterval(this.AI.typingTimer); |
|
|
|
|
|
} |
|
|
|
|
|
}, 30); // 每 30ms 打一个字 |
|
|
|
|
|
}, |
|
|
}, |
|
|
//审核 |
|
|
//审核 |
|
|
audit() { |
|
|
audit() { |
|
|
|