refactor: enforce 800 line source limits
This commit is contained in:
186
web/src/composables/workbenchAiMode/useWorkbenchAiExpenseFlow.js
Normal file
186
web/src/composables/workbenchAiMode/useWorkbenchAiExpenseFlow.js
Normal file
@@ -0,0 +1,186 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user