204 lines
6.8 KiB
JavaScript
204 lines
6.8 KiB
JavaScript
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
|
||
}
|
||
}
|