import { filterVisibleMessageMeta } from '../../utils/assistantMessageMeta.js' import { resolveExpenseTypeLabel } from './travelReimbursementReviewModel.js' import { FLOW_MISSING_SLOT_LABELS, FLOW_STEP_FALLBACKS } from './travelReimbursementConversationSessionModel.js' let messageSeed = 0 export function nowTime() { return new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }) } export function createMessage(role, text, attachments = [], extras = {}) { messageSeed += 1 const message = { id: `msg-${messageSeed}`, role, text, attachments, time: nowTime(), meta: [], citations: [], suggestedActions: [], suggestedActionsLocked: false, selectedSuggestedActionKey: '', selectedSuggestedActionLabel: '', querySelectionLocked: false, selectedQueryRecordId: '', queryPayload: null, draftPayload: null, reviewPayload: null, reviewPanelScope: '', riskFlags: [], pendingAttachmentAssociation: null, applicationPreview: null, budgetReport: null, stewardPlan: null, operationFeedback: null, ...extras } message.meta = filterVisibleMessageMeta(message.meta) return message } export function buildExpenseIntentConfirmationMessage(rawText) { const text = String(rawText || '').trim() return [ text ? `我看到了「${text}」这类业务事项描述。` : '我看到了这类业务事项描述。', '但现在还不能确定你是要发起报销,还是要处理其他事项,所以我先暂停后续识别。', '如果你是想报销,请点击下面的“我要报销”,我再继续引导你选择具体报销场景。' ].join('\n') } export function buildExpenseSceneSelectionMessage(rawText) { const text = String(rawText || '').trim() const hasBusinessTime = /业务发生时间|发生时间|20\d{2}[-年\/.]\d{1,2}/.test(text) const prefix = hasBusinessTime ? '我已看到你提供了业务发生时间和报销意图。' : '我已识别到这是报销申请。' return [ `${prefix}先选一下这笔费用属于哪一类,我再按对应流程继续。`, '差旅和业务招待通常需要先关联申请单;交通、住宿、办公用品这类一般可以直接继续填写。', '选完后我会把下一步需要准备的内容整理给你。' ].join('\n') } export function formatMessageTime(value) { if (!value) { return nowTime() } const parsed = new Date(value) if (Number.isNaN(parsed.getTime())) { return nowTime() } return parsed.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }) } export function formatSemanticEntityValue(entity) { const normalizedValue = String(entity?.normalized_value || '').trim() const rawValue = String(entity?.value || '').trim() const entityType = String(entity?.type || '').trim() if (entityType === 'amount') { const numericValue = Number(normalizedValue || rawValue) if (Number.isFinite(numericValue) && numericValue > 0) { return Number.isInteger(numericValue) ? `${numericValue}元` : `${numericValue.toFixed(2)}元` } } return rawValue || normalizedValue } export function summarizeSemanticParseDetail(semanticParse, ontologyJson = {}) { if (!semanticParse || typeof semanticParse !== 'object') { return FLOW_STEP_FALLBACKS.extraction.completedText } const entities = Array.isArray(semanticParse.entities_json) ? semanticParse.entities_json : [] const entityMap = new Map() for (const item of entities) { const entityType = String(item?.type || '').trim() if (!entityType || entityMap.has(entityType)) continue entityMap.set(entityType, item) } const extractedParts = [] const timeRange = semanticParse.time_range_json && typeof semanticParse.time_range_json === 'object' ? semanticParse.time_range_json : {} const startDate = String(timeRange.start_date || '').trim() const endDate = String(timeRange.end_date || '').trim() if (startDate) { extractedParts.push(`时间 ${startDate}${endDate && endDate !== startDate ? ` 至 ${endDate}` : ''}`) } const amountEntity = entityMap.get('amount') if (amountEntity) { const amountValue = formatSemanticEntityValue(amountEntity) if (amountValue) { extractedParts.push(`金额 ${amountValue}`) } } const expenseTypeEntity = entityMap.get('expense_type') if (expenseTypeEntity) { const expenseTypeLabel = resolveExpenseTypeLabel( String(expenseTypeEntity?.normalized_value || '').trim(), String(expenseTypeEntity?.value || '').trim() ) if (expenseTypeLabel) { extractedParts.push(`费用类型 ${expenseTypeLabel}`) } } const customerEntity = entityMap.get('customer') if (customerEntity) { const customerValue = formatSemanticEntityValue(customerEntity) if (customerValue) { extractedParts.push(`客户 ${customerValue}`) } } const missingSlots = Array.isArray(ontologyJson?.missing_slots) ? ontologyJson.missing_slots : [] const missingLabels = missingSlots .map((item) => FLOW_MISSING_SLOT_LABELS[String(item || '').trim()] || String(item || '').trim()) .filter(Boolean) if (extractedParts.length && missingLabels.length) { return `已提取${extractedParts.join('、')};待补充 ${missingLabels.join('、')}` } if (extractedParts.length) { return `已提取${extractedParts.join('、')}` } if (missingLabels.length) { return `已完成信息提取;待补充 ${missingLabels.join('、')}` } return FLOW_STEP_FALLBACKS.extraction.completedText } export function sanitizeRequest(request) { if (!request || typeof request !== 'object') return null const normalized = { claimId: String(request.claimId || request.claim_id || '').trim(), claimNo: String(request.claimNo || request.claim_no || request.documentNo || '').trim(), id: String(request.id || '').trim(), typeLabel: String(request.typeLabel || request.category || '').trim(), reason: String(request.reason || request.title || '').trim(), entity: String(request.entity || '').trim(), city: String(request.city || request.location || '').trim(), period: String(request.period || '').trim(), applyTime: String(request.applyTime || request.occurredAt || '').trim(), amount: String(request.amount || '').trim(), node: String(request.node || '').trim(), approval: String(request.approval || '').trim(), travel: String(request.travel || '').trim(), applicationEditMode: Boolean(request.applicationEditMode || request.application_edit_mode) } return Object.values(normalized).some(Boolean) ? normalized : null } export function resolveStatusLabel(status) { if (status === 'succeeded') return '已完成' if (status === 'blocked') return '已阻断' return '失败' } export function resolveStatusTone(status) { if (status === 'succeeded') return 'success' if (status === 'blocked') return 'warning' return 'note' } export function buildMessageMeta(payload, fileNames = []) { const items = [] if (payload?.trace_summary?.degraded) { items.push('已降级') } if (payload?.requires_confirmation) { items.push('待确认') } if (fileNames.length) { items.push(`附件: ${fileNames.length}`) } return filterVisibleMessageMeta(items) } export function buildStoredMessageMeta(messageJson, attachmentNames = []) { const payload = messageJson?.orchestrator_payload if (payload) { return buildMessageMeta(payload, attachmentNames) } const items = [] if (messageJson?.status) { items.push(`状态: ${messageJson.status}`) } if (attachmentNames.length) { items.push(`附件: ${attachmentNames.length}`) } return filterVisibleMessageMeta(items) } export function buildRestoredMessageId(sourceId = '') { const normalizedId = String(sourceId || '').trim() return `restored-${normalizedId || ++messageSeed}` }