This commit is contained in:
Your Name
2026-06-03 14:42:48 +08:00
parent ec4e0b9f29
commit 37df208706
6 changed files with 917 additions and 15 deletions
@@ -0,0 +1,203 @@
import { ref, computed } from 'vue'
import { requestAiStream, parsePartialBloodCarePlainText } from '../utils/aiStreamRequest.js'
const emptyBloodCarePlan = () => ({
summary: '',
blood_advice: '',
tcm_plan: '',
watch_points: [],
next_steps: '',
control_level: '',
control_label: '',
disclaimer: '',
analysis: [],
source: '',
date: ''
})
/** AI 血糖照护建议(累计 7 天有记录后) */
export function useBloodCareAi(proxy, { diagnosisId, chartUseMockData, showUserToast, formatUserMessage }) {
const bloodCarePlan = ref(emptyBloodCarePlan())
const bloodCareLoading = ref(false)
const bloodCareStreaming = ref(false)
const bloodCareStreamField = ref('')
let careStreamBuffer = ''
/** 用户点击后才展开照护建议卡片 */
const bloodCarePanelOpen = ref(false)
const bloodCareEligible = computed(() => {
return !!diagnosisId.value && !chartUseMockData.value
})
const hasBloodCareContent = computed(() => {
const p = bloodCarePlan.value
return !!(p.summary || p.blood_advice || p.tcm_plan)
})
const bloodCareAnalysis = computed(() => {
const list = bloodCarePlan.value?.analysis
return Array.isArray(list) ? list : []
})
const bloodCareControlLabel = computed(() => {
const label = String(bloodCarePlan.value?.control_label || '').trim()
return label
})
const bloodCareDisclaimer = computed(() => {
return String(bloodCarePlan.value?.disclaimer || '').trim()
})
function detectCareStreamField(text) {
const tail = String(text || '').match(/(?:^|\n)(总评|血糖|中医|留意|复诊)[:]\s*([^\n]*)$/)
if (!tail) return 'summary'
const map = { '总评': 'summary', '血糖': 'blood_advice', '中医': 'tcm_plan', '留意': 'watch_points', '复诊': 'next_steps' }
return map[tail[1]] || 'summary'
}
function mergePartialCareFromStream() {
const partial = parsePartialBloodCarePlainText(careStreamBuffer)
const next = { ...bloodCarePlan.value }
for (const key of ['summary', 'blood_advice', 'tcm_plan', 'next_steps']) {
if (partial[key] != null) next[key] = partial[key]
}
if (Array.isArray(partial.watch_points)) next.watch_points = partial.watch_points
bloodCarePlan.value = next
bloodCareStreamField.value = detectCareStreamField(careStreamBuffer)
}
function applyBloodCarePayload(payload) {
if (!payload) return
const prevAnalysis = bloodCarePlan.value.analysis
bloodCarePlan.value = {
...emptyBloodCarePlan(),
...payload,
watch_points: Array.isArray(payload.watch_points) ? payload.watch_points : [],
analysis: Array.isArray(payload.analysis) && payload.analysis.length
? payload.analysis
: (Array.isArray(prevAnalysis) ? prevAnalysis : [])
}
}
function onBloodCareStreamEvent(event, payload) {
if (event === 'analysis') {
if (Array.isArray(payload?.analysis)) {
bloodCarePlan.value = { ...bloodCarePlan.value, analysis: payload.analysis }
}
} else if (event === 'delta') {
bloodCareStreaming.value = true
if (payload.buffer != null) {
careStreamBuffer = payload.buffer
} else {
careStreamBuffer += payload.text || ''
}
mergePartialCareFromStream()
} else if (event === 'done') {
applyBloodCarePayload(payload)
careStreamBuffer = ''
bloodCareStreamField.value = ''
} else if (event === 'error') {
showUserToast(formatUserMessage(payload?.message, '照护建议加载失败'))
}
}
async function fetchBloodCareAiFallback(refresh) {
const res = await proxy.apiUrl({
url: '/api/tcm/dailyBloodCareAiRecommend',
method: 'GET',
timeout: 35000,
data: {
diagnosis_id: diagnosisId.value,
refresh: refresh ? 1 : 0,
...(refresh ? { _t: Date.now() } : {})
}
}, false)
if (res?.code === 1 && res.data) {
applyBloodCarePayload(res.data)
} else if (res?.msg) {
showUserToast(res.msg)
}
}
function closeBloodCarePanel() {
bloodCarePanelOpen.value = false
}
/** 趋势图 AI 按钮:未展开则打开并生成;已展开则收起 */
async function toggleBloodCareFromChart() {
if (!bloodCareEligible.value || bloodCareLoading.value) return
if (bloodCarePanelOpen.value) {
closeBloodCarePanel()
return
}
bloodCarePanelOpen.value = true
if (!hasBloodCareContent.value) {
await fetchBloodCareAi(false)
}
}
async function fetchBloodCareAi(refresh = false) {
if (!bloodCareEligible.value || bloodCareLoading.value) return
bloodCareLoading.value = true
bloodCareStreaming.value = false
careStreamBuffer = ''
bloodCareStreamField.value = ''
if (refresh) {
applyBloodCarePayload(emptyBloodCarePlan())
}
try {
await requestAiStream({
baseUrl: proxy.$url,
url: '/api/tcm/dailyBloodCareAiRecommendStream',
method: 'GET',
data: {
diagnosis_id: diagnosisId.value,
refresh: refresh ? 1 : 0,
...(refresh ? { _t: Date.now() } : {})
},
onEvent: onBloodCareStreamEvent,
fallback: () => fetchBloodCareAiFallback(refresh)
})
} catch (e) {
try {
await fetchBloodCareAiFallback(refresh)
} catch (e2) {
showUserToast('照护建议加载失败')
}
} finally {
bloodCareLoading.value = false
bloodCareStreaming.value = false
bloodCareStreamField.value = ''
}
}
function refreshBloodCareAi() {
if (bloodCareLoading.value) return
fetchBloodCareAi(true)
}
function resetBloodCarePlan() {
applyBloodCarePayload(emptyBloodCarePlan())
careStreamBuffer = ''
bloodCareStreamField.value = ''
bloodCarePanelOpen.value = false
}
return {
bloodCarePlan,
bloodCareLoading,
bloodCareStreaming,
bloodCareStreamField,
bloodCareEligible,
bloodCarePanelOpen,
hasBloodCareContent,
bloodCareAnalysis,
bloodCareControlLabel,
bloodCareDisclaimer,
fetchBloodCareAi,
toggleBloodCareFromChart,
closeBloodCarePanel,
refreshBloodCareAi,
resetBloodCarePlan
}
}