import { fetchExpenseClaims } from '../../services/reimbursements.js' import { applyAiExpenseAnswer, buildAiExpenseStepPrompt, buildAiExpenseSummary, createAiExpenseDraft, isAiExpenseDraftComplete } from '../../utils/aiExpenseDraftModel.js' import { buildExpenseSceneSelectionMessage, SESSION_TYPE_EXPENSE } from '../../views/scripts/travelReimbursementConversationModel.js' import { buildExpenseSceneSelectionActions } from '../../utils/expenseAssistantActions.js' import { buildRequiredApplicationActions, buildRequiredApplicationMissingText, buildRequiredApplicationSelectionText, filterRequiredApplicationCandidates } from '../../views/scripts/travelReimbursementApplicationLinkModel.js' export { SESSION_TYPE_EXPENSE } export function useWorkbenchAiExpenseFlow({ activateInlineConversation, aiExpenseDraft, assistantDraft, clearAiModeFiles, closeWorkbenchDatePicker, conversationMessages, conversationStarted, createInlineMessage, currentUser, persistCurrentConversation, pushInlineUserMessage, removeWorkbenchDateTag, resolveLatestInlineUserPrompt, scrollInlineConversationToBottom, startAiApplicationPreview }) { function pushInlineExpenseSceneSelectionPrompt(originalMessage, selectedLabel = '') { const sourceText = String(originalMessage || '我要报销').trim() if (!conversationStarted.value) { activateInlineConversation({ title: String(selectedLabel || sourceText || '报销').trim().slice(0, 18) || '报销' }) } assistantDraft.value = '' removeWorkbenchDateTag() closeWorkbenchDatePicker() conversationMessages.value.push(createInlineMessage('user', String(selectedLabel || sourceText).trim())) conversationMessages.value.push(createInlineMessage('assistant', buildExpenseSceneSelectionMessage(sourceText), { suggestedActions: buildExpenseSceneSelectionActions(sourceText) })) persistCurrentConversation() scrollInlineConversationToBottom() } function startAiApplicationPreviewFromAction(payload = {}, fallbackLabel = '') { const expenseType = String(payload.expense_type || '').trim() const expenseTypeLabel = String(payload.expense_type_label || fallbackLabel || '').trim() return startAiApplicationPreview( expenseType, expenseTypeLabel, payload.carry_text || resolveLatestInlineUserPrompt() ) } function startAiExpenseDraft(expenseType, expenseTypeLabel, requiresApplicationBeforeReimbursement) { if (!conversationStarted.value) { activateInlineConversation({ title: String(expenseTypeLabel || '报销').trim().slice(0, 18) || '报销' }) } assistantDraft.value = '' removeWorkbenchDateTag() closeWorkbenchDatePicker() clearAiModeFiles() pushInlineUserMessage(`选择${expenseTypeLabel || expenseType || '报销'}`) if (requiresApplicationBeforeReimbursement) { void resolveAiExpenseApplicationLink(expenseType, expenseTypeLabel) return } const draft = createAiExpenseDraft(expenseType, expenseTypeLabel) aiExpenseDraft.value = draft conversationMessages.value.push(createInlineMessage('assistant', buildAiExpenseStepPrompt(draft))) persistCurrentConversation() scrollInlineConversationToBottom() } function advanceAiExpenseDraft(answer, files = []) { const fileNames = Array.from(files || []) pushInlineUserMessage(answer || (fileNames.length ? `上传 ${fileNames.length} 份附件` : '')) assistantDraft.value = '' clearAiModeFiles() const next = applyAiExpenseAnswer(aiExpenseDraft.value, answer, fileNames) aiExpenseDraft.value = next if (isAiExpenseDraftComplete(next)) { conversationMessages.value.push(createInlineMessage('assistant', `${buildAiExpenseSummary(next)}\n\n如果哪一项需要修改,直接告诉我;确认无误后我再帮你生成报销草稿。`)) aiExpenseDraft.value = null } else { conversationMessages.value.push(createInlineMessage('assistant', buildAiExpenseStepPrompt(next))) } persistCurrentConversation() scrollInlineConversationToBottom() } async function resolveAiExpenseApplicationLink(expenseType, expenseTypeLabel) { let claims = null try { claims = await fetchExpenseClaims() } catch { aiExpenseDraft.value = null conversationMessages.value.push(createInlineMessage('assistant', '查询可关联申请单时出现异常,请稍后再试,我先暂停这次报销流程。')) persistCurrentConversation() scrollInlineConversationToBottom() return } const candidates = filterRequiredApplicationCandidates(claims, expenseType, currentUser.value || {}) aiExpenseDraft.value = createAiExpenseDraft(expenseType, expenseTypeLabel) if (!candidates.length) { conversationMessages.value.push(createInlineMessage('assistant', buildRequiredApplicationMissingText(expenseType), { suggestedActions: [{ label: '确认发起出差申请', description: '生成完整申请表,并预填已识别的时间、地点和事由', icon: 'mdi mdi-file-plus-outline', action_type: 'ai_application_start_inline', payload: { expense_type: expenseType, expense_type_label: expenseTypeLabel } }] })) persistCurrentConversation() scrollInlineConversationToBottom() return } conversationMessages.value.push(createInlineMessage('assistant', buildRequiredApplicationSelectionText(expenseType, candidates), { suggestedActions: buildRequiredApplicationActions(candidates, 'select_required_application') })) persistCurrentConversation() scrollInlineConversationToBottom() } function linkAiExpenseApplication(application = {}) { const draft = aiExpenseDraft.value if (!draft) { return } const claimNo = String(application.application_claim_no || '').trim() pushInlineUserMessage(`关联申请单 ${claimNo}`.trim()) const linked = { ...draft, applicationClaim: application, values: { ...draft.values, reason: String(application.application_reason || '').trim(), location: String(application.application_location || '').trim(), time_range: String(application.application_business_time || '').trim(), amount: String(application.application_amount_label || application.application_amount || '').trim() }, stepKey: 'attachments' } aiExpenseDraft.value = linked conversationMessages.value.push(createInlineMessage('assistant', [ `已关联申请单${claimNo ? ` ${claimNo}` : ''},事由、时间、地点、金额我先用申请单的内容预填了。`, '', '再确认一下票据:可以现在上传,或回复“稍后上传”。' ].join('\n'))) persistCurrentConversation() scrollInlineConversationToBottom() } return { advanceAiExpenseDraft, linkAiExpenseApplication, pushInlineExpenseSceneSelectionPrompt, startAiApplicationPreviewFromAction, startAiExpenseDraft } }