import { computed, nextTick, ref, watch } from 'vue' import { useSystemState } from '../../composables/useSystemState.js' import { fetchOntologyParse } from '../../services/ontology.js' export default { name: 'ChatView', props: { documents: { type: Array, required: true }, docSearch: { type: String, default: '' }, messages: { type: Array, required: true }, uploadedFiles: { type: Array, required: true }, activeCase: { type: Object, default: null }, quickPrompts: { type: Array, required: true }, draft: { type: String, default: '' }, sending: { type: Boolean, default: false }, messageList: { type: Object, default: null } }, emits: ['send', 'upload', 'draft', 'approveCase', 'rejectCase', 'selectCase'], setup(props, { emit }) { const { currentUser } = useSystemState() const localMessageList = ref(null) const promptPage = ref(0) const semanticDraft = ref('查一下本周报销超标风险') const semanticLoading = ref(false) const semanticError = ref('') const semanticResult = ref(null) const sessions = [ { title: '北京出差,酒店超标报销怎么处理?', time: '10:32', active: true }, { title: '发票抬头不一致怎么办', time: '09:48' }, { title: '借款冲销流程', time: '昨天' }, { title: '预算占用失败处理', time: '昨天' }, { title: '招待费报销范围', time: '05-11' }, { title: '差旅住宿标准如何匹配城市级别?', time: '05-10' }, { title: '电子发票验真失败如何处理?', time: '05-09' }, { title: '跨部门项目费用怎么归集?', time: '05-08' }, { title: '会议费和招待费如何区分?', time: '05-07' }, { title: '超预算申请需要哪些审批节点?', time: '05-06' }, { title: '海外差旅汇率按哪天计算?', time: '05-05' }, { title: '员工退票手续费是否可报销?', time: '05-04' } ] const prompts = [ { icon: 'mdi mdi-bed-outline', short: '差旅标准', text: '差旅报销特殊标准是什么?' }, { icon: 'mdi mdi-receipt-text-outline', short: '发票规范', text: '发票丢失如何处理?' }, { icon: 'mdi mdi-cash-refund', short: '借款冲销', text: '借款多久内需要冲销?' }, { icon: 'mdi mdi-file-chart-outline', short: '预算冲突', text: '预算不足如何申请?' }, { icon: 'mdi mdi-shield-check-outline', short: '审批要求', text: '酒店超标后如何申请例外报销?' }, { icon: 'mdi mdi-office-building-marker', short: '住宿标准', text: '差旅住宿标准按什么规则执行?' }, { icon: 'mdi mdi-file-question-outline', short: '材料补齐', text: '报销附件缺失怎么补交?' }, { icon: 'mdi mdi-alert-circle-outline', short: '风险等级', text: '哪些情况会触发中风险?' } ] const visiblePrompts = computed(() => prompts.slice((promptPage.value % 2) * 4, (promptPage.value % 2) * 4 + 4)) const hotQuestions = [ '差旅报销标准是什么?', '酒店超标后如何申请例外报销?', '发票丢失如何处理?', '借款多久内需要冲销?', '预算超支如何申请?', '招待费报销需要哪些凭证?', '发票抬头不一致是否允许报销?', '报销附件缺失怎么补交?', '差旅住宿标准按什么规则执行?', '电子发票验真失败如何处理?' ] const similarQuestions = [ { text: '酒店超标后如何申请例外报销?', score: '96%' }, { text: '发票抬头不一致是否允许报销?', score: '92%' }, { text: '差旅住宿标准按什么规则执行?', score: '89%' }, { text: '预算不足时能否先提交报销?', score: '86%' }, { text: '电子发票验真失败是否可以先报销?', score: '84%' }, { text: '跨部门项目费用如何归集?', score: '81%' }, { text: '招待费报销需要哪些凭证?', score: '78%' }, { text: '借款冲销逾期会影响报销吗?', score: '76%' } ] const semanticExamples = [ '查一下本周报销超标风险', '客户 A 这个月还有多少应收', '帮我直接付款给供应商B' ] const semanticConfidenceLabel = computed(() => semanticResult.value ? `${Math.round((semanticResult.value.confidence || 0) * 100)}%` : '未解析' ) const semanticEntitiesText = computed(() => { const items = semanticResult.value?.entities || [] return items.length ? items.map((item) => `${item.type}:${item.normalized_value}`).join(' / ') : '未识别' }) const semanticTimeRangeText = computed(() => { const value = semanticResult.value?.time_range if (!value?.start_date || !value?.end_date) { return '未识别' } return `${value.start_date} ~ ${value.end_date}${value.granularity ? ` · ${value.granularity}` : ''}` }) const semanticMetricsText = computed(() => { const items = semanticResult.value?.metrics || [] return items.length ? items .map((item) => { const suffix = item.top_n ? ` top_${item.top_n}` : '' return `${item.name}${suffix}` }) .join(' / ') : '未识别' }) const semanticConstraintsText = computed(() => { const items = semanticResult.value?.constraints || [] return items.length ? items.map((item) => `${item.field}${item.operator}${item.value}`).join(' / ') : '未识别' }) const semanticRiskFlagsText = computed(() => { const items = semanticResult.value?.risk_flags || [] return items.length ? items.join(' / ') : '未识别' }) const semanticClarificationText = computed(() => { if (!semanticResult.value) { return '未解析' } if (!semanticResult.value.clarification_required) { return '无需澄清' } return semanticResult.value.clarification_question || '需要补充更多上下文' }) const semanticResultJson = computed(() => semanticResult.value ? JSON.stringify(semanticResult.value, null, 2) : '' ) function rotatePrompts() { promptPage.value += 1 } function applyPrompt(text) { emit('draft', text) semanticDraft.value = text } function applySemanticExample(text) { semanticDraft.value = text } function useDraftAsSemanticInput() { semanticDraft.value = props.draft || semanticDraft.value } async function parseSemanticQuery() { const query = String(semanticDraft.value || '').trim() if (!query) { semanticError.value = '请输入要解析的问题。' semanticResult.value = null return } semanticLoading.value = true semanticError.value = '' try { semanticResult.value = await fetchOntologyParse({ query, user_id: currentUser.value?.username || currentUser.value?.name || 'anonymous', context_json: { role_codes: currentUser.value?.roleCodes || [], is_admin: Boolean(currentUser.value?.isAdmin), name: currentUser.value?.name || '', role: currentUser.value?.role || '' } }) } catch (error) { semanticResult.value = null semanticError.value = error.message || '语义解析失败,请稍后重试。' } finally { semanticLoading.value = false } } watch( () => props.messages.length, () => { nextTick(() => localMessageList.value?.scrollTo({ top: localMessageList.value.scrollHeight, behavior: 'smooth' })) } ) return { emit, localMessageList, promptPage, sessions, prompts, visiblePrompts, hotQuestions, similarQuestions, rotatePrompts, applyPrompt, semanticDraft, semanticLoading, semanticError, semanticResult, semanticExamples, semanticConfidenceLabel, semanticEntitiesText, semanticTimeRangeText, semanticMetricsText, semanticConstraintsText, semanticRiskFlagsText, semanticClarificationText, semanticResultJson, applySemanticExample, useDraftAsSemanticInput, parseSemanticQuery } } }