278 lines
11 KiB
JavaScript
278 lines
11 KiB
JavaScript
|
|
import { isReimbursementCreationIntent } from './workbenchAiApplicationGateModel.js'
|
|||
|
|
import {
|
|||
|
|
buildRuleFallbackWorkbenchAiIntentPlan,
|
|||
|
|
isLowConfidenceTravelApplicationPlan,
|
|||
|
|
normalizeWorkbenchAiIntentPlan,
|
|||
|
|
resolveExecutableTravelApplicationPlan
|
|||
|
|
} from './workbenchAiIntentPlannerModel.js'
|
|||
|
|
import {
|
|||
|
|
buildInitialModelPlanningThinkingEvents,
|
|||
|
|
buildModelPlanningProgressSchedule,
|
|||
|
|
mergeWorkbenchAiThinkingEvents
|
|||
|
|
} from './workbenchAiPlanningThinkingModel.js'
|
|||
|
|
|
|||
|
|
export function useWorkbenchAiIntentExecution(options) {
|
|||
|
|
const {
|
|||
|
|
actionRouter,
|
|||
|
|
activeConversationTitle,
|
|||
|
|
activateInlineConversation,
|
|||
|
|
applicationFlow,
|
|||
|
|
assistantDraft,
|
|||
|
|
clearAiModeFiles,
|
|||
|
|
closeWorkbenchDatePicker,
|
|||
|
|
conversationId,
|
|||
|
|
conversationMessages,
|
|||
|
|
createInlineMessage,
|
|||
|
|
expenseFlow,
|
|||
|
|
inlineConversationAutoScrollPinned,
|
|||
|
|
persistCurrentConversation,
|
|||
|
|
removeWorkbenchDateTag,
|
|||
|
|
replaceInlineMessage,
|
|||
|
|
resolveInlineThinkingEvents,
|
|||
|
|
scrollInlineConversationToBottom,
|
|||
|
|
searchConversationId,
|
|||
|
|
sending,
|
|||
|
|
stewardFlow
|
|||
|
|
} = options
|
|||
|
|
|
|||
|
|
function isModelPlannedReimbursementTask(modelPlan = {}) {
|
|||
|
|
const tasks = Array.isArray(modelPlan?.tasks) ? modelPlan.tasks : []
|
|||
|
|
return tasks.some((task) => {
|
|||
|
|
const taskType = String(task?.task_type || task?.taskType || '').trim()
|
|||
|
|
const assignedAgent = String(task?.assigned_agent || task?.assignedAgent || '').trim()
|
|||
|
|
return taskType === 'reimbursement' || assignedAgent === 'reimbursement_assistant'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateModelPlanningThinkingEvent(messageId, event) {
|
|||
|
|
const message = conversationMessages.value.find((item) => item.id === messageId)
|
|||
|
|
if (!message) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
const currentPlan = message.stewardPlan || {}
|
|||
|
|
message.stewardPlan = {
|
|||
|
|
...currentPlan,
|
|||
|
|
streamStatus: 'streaming',
|
|||
|
|
thinkingEvents: mergeWorkbenchAiThinkingEvents(resolveInlineThinkingEvents(message), [event])
|
|||
|
|
}
|
|||
|
|
persistCurrentConversation()
|
|||
|
|
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function startModelPlanningProgressUpdates(messageId) {
|
|||
|
|
const timerIds = buildModelPlanningProgressSchedule().map(({ delayMs, event }) => (
|
|||
|
|
globalThis.setTimeout(() => {
|
|||
|
|
updateModelPlanningThinkingEvent(messageId, event)
|
|||
|
|
}, delayMs)
|
|||
|
|
))
|
|||
|
|
return () => {
|
|||
|
|
timerIds.forEach((timerId) => globalThis.clearTimeout(timerId))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function startModelPlanningConversation(cleanPrompt, entry = {}) {
|
|||
|
|
if (conversationId.value === searchConversationId) {
|
|||
|
|
conversationId.value = ''
|
|||
|
|
conversationMessages.value = []
|
|||
|
|
activeConversationTitle.value = ''
|
|||
|
|
}
|
|||
|
|
activateInlineConversation({
|
|||
|
|
title: entry.label || cleanPrompt.slice(0, 18) || '新对话'
|
|||
|
|
})
|
|||
|
|
inlineConversationAutoScrollPinned.value = true
|
|||
|
|
conversationMessages.value.push(createInlineMessage('user', cleanPrompt))
|
|||
|
|
assistantDraft.value = ''
|
|||
|
|
removeWorkbenchDateTag()
|
|||
|
|
closeWorkbenchDatePicker()
|
|||
|
|
clearAiModeFiles()
|
|||
|
|
const pendingMessage = createInlineMessage('assistant', '正在识别意图,准备拆解申请、报销和附件任务。', {
|
|||
|
|
pending: true,
|
|||
|
|
stewardPlan: {
|
|||
|
|
streamStatus: 'streaming',
|
|||
|
|
thinkingEvents: buildInitialModelPlanningThinkingEvents()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
conversationMessages.value.push(pendingMessage)
|
|||
|
|
scrollInlineConversationToBottom()
|
|||
|
|
persistCurrentConversation()
|
|||
|
|
return pendingMessage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildModelPlannedNextTaskAction(remainingTasks = []) {
|
|||
|
|
const tasks = Array.isArray(remainingTasks) ? remainingTasks : []
|
|||
|
|
const nextTask = tasks[0]
|
|||
|
|
if (!nextTask || typeof nextTask !== 'object') {
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
const taskType = String(nextTask.task_type || nextTask.taskType || '').trim()
|
|||
|
|
const assignedAgent = String(nextTask.assigned_agent || nextTask.assignedAgent || '').trim()
|
|||
|
|
const isApplication = taskType === 'expense_application' || assignedAgent === 'application_assistant'
|
|||
|
|
const isReimbursement = taskType === 'reimbursement' || assignedAgent === 'reimbursement_assistant'
|
|||
|
|
if (!isApplication && !isReimbursement) {
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
const ontologyFields = nextTask.ontology_fields || nextTask.ontologyFields || {}
|
|||
|
|
const flowId = isApplication ? 'travel_application' : 'travel_reimbursement'
|
|||
|
|
const taskLabel = isApplication ? '出差申请' : '费用报销'
|
|||
|
|
return {
|
|||
|
|
label: `继续处理${taskLabel}`,
|
|||
|
|
action_type: 'steward_continue_next_task',
|
|||
|
|
payload: {
|
|||
|
|
steward_confirm_flow: true,
|
|||
|
|
flow_id: flowId,
|
|||
|
|
steward_current_task: nextTask,
|
|||
|
|
expense_type: String(ontologyFields.expense_type || 'travel').trim() || 'travel',
|
|||
|
|
expense_type_label: String(ontologyFields.expense_type_label || '差旅费').trim() || '差旅费',
|
|||
|
|
ontology_fields: ontologyFields,
|
|||
|
|
original_message: String(nextTask.summary || nextTask.title || `继续处理${taskLabel}`).trim(),
|
|||
|
|
steward_remaining_tasks: tasks.slice(1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function startModelPlannedNextTask(remainingTasks = []) {
|
|||
|
|
const nextTaskAction = buildModelPlannedNextTaskAction(remainingTasks)
|
|||
|
|
if (nextTaskAction) {
|
|||
|
|
actionRouter.handleInlineSuggestedAction(nextTaskAction)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function startModelPlannedApplicationPreview(travelApplicationRequest, plannerPendingMessage = null) {
|
|||
|
|
void applicationFlow.startAiApplicationPreview(
|
|||
|
|
travelApplicationRequest.expenseType,
|
|||
|
|
travelApplicationRequest.expenseTypeLabel,
|
|||
|
|
travelApplicationRequest.sourceText,
|
|||
|
|
{
|
|||
|
|
userMessage: travelApplicationRequest.sourceText,
|
|||
|
|
pushUserMessage: !plannerPendingMessage,
|
|||
|
|
pendingMessageId: plannerPendingMessage?.id,
|
|||
|
|
ontologyFields: travelApplicationRequest.ontologyFields,
|
|||
|
|
autoSubmit: travelApplicationRequest.autoSubmit,
|
|||
|
|
autoSaveDraft: travelApplicationRequest.autoSaveDraft,
|
|||
|
|
requestedSubmit: travelApplicationRequest.requestedSubmit,
|
|||
|
|
submitRequiresConfirmation: travelApplicationRequest.submitRequiresConfirmation,
|
|||
|
|
stewardRemainingTasks: travelApplicationRequest.stewardRemainingTasks,
|
|||
|
|
onPreviewReadyForNextTask: startModelPlannedNextTask,
|
|||
|
|
onApplicationActionCompleted: startModelPlannedNextTask
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildLowConfidenceTravelApplicationConfirmationText(request, plan) {
|
|||
|
|
const fields = request.ontologyFields || {}
|
|||
|
|
const summaryParts = []
|
|||
|
|
if (fields.time_range) summaryParts.push(`时间:${fields.time_range}`)
|
|||
|
|
if (fields.location) summaryParts.push(`地点:${fields.location}`)
|
|||
|
|
if (fields.reason) summaryParts.push(`事由:${fields.reason}`)
|
|||
|
|
if (fields.transport_mode) summaryParts.push(`交通:${fields.transport_mode}`)
|
|||
|
|
const summary = summaryParts.length ? `\n\n${summaryParts.join(';')}` : ''
|
|||
|
|
const confidenceNote = Number.isFinite(Number(plan?.confidence))
|
|||
|
|
? `(模型识别置信度较低,约 ${Math.round(Number(plan.confidence) * 100)}%)`
|
|||
|
|
: '(模型识别置信度较低)'
|
|||
|
|
return [
|
|||
|
|
'### 需要确认:您是要发起出差申请吗?',
|
|||
|
|
'',
|
|||
|
|
`小财管家把这句话理解成了“发起差旅申请”${confidenceNote},为避免误操作,先请您确认。`,
|
|||
|
|
summary,
|
|||
|
|
'',
|
|||
|
|
'点击下方「确认发起出差申请」即可继续;如果理解有误,请补充说明您的实际需求。'
|
|||
|
|
].filter(Boolean).join('\n')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function startModelPlannedTravelApplicationConfirmation(travelApplicationRequest, plan, plannerPendingMessage) {
|
|||
|
|
const confirmAction = {
|
|||
|
|
label: '确认发起出差申请',
|
|||
|
|
description: '根据上面识别到的信息生成出差申请预览。',
|
|||
|
|
icon: 'mdi mdi-check-circle-outline',
|
|||
|
|
action_type: 'ai_application_confirm_intent',
|
|||
|
|
payload: {
|
|||
|
|
ontologyFields: travelApplicationRequest.ontologyFields,
|
|||
|
|
sourceText: travelApplicationRequest.sourceText,
|
|||
|
|
autoSubmit: travelApplicationRequest.autoSubmit,
|
|||
|
|
autoSaveDraft: travelApplicationRequest.autoSaveDraft,
|
|||
|
|
requestedSubmit: travelApplicationRequest.requestedSubmit,
|
|||
|
|
submitRequiresConfirmation: travelApplicationRequest.submitRequiresConfirmation,
|
|||
|
|
stewardRemainingTasks: travelApplicationRequest.stewardRemainingTasks
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
replaceInlineMessage(plannerPendingMessage.id, createInlineMessage(
|
|||
|
|
'assistant',
|
|||
|
|
buildLowConfidenceTravelApplicationConfirmationText(travelApplicationRequest, plan),
|
|||
|
|
{
|
|||
|
|
id: plannerPendingMessage.id,
|
|||
|
|
suggestedActions: [confirmAction],
|
|||
|
|
stewardPlan: {
|
|||
|
|
streamStatus: 'completed',
|
|||
|
|
thinkingEvents: resolveInlineThinkingEvents(plannerPendingMessage)
|
|||
|
|
.map((item) => ({ ...item, status: 'completed' }))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
persistCurrentConversation()
|
|||
|
|
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function executeModelPlannedWorkbenchIntent(cleanPrompt, entry = {}, files = []) {
|
|||
|
|
let intentPlan = null
|
|||
|
|
let modelPlan = null
|
|||
|
|
const plannerPendingMessage = startModelPlanningConversation(cleanPrompt, entry)
|
|||
|
|
const stopPlanningProgressUpdates = startModelPlanningProgressUpdates(plannerPendingMessage.id)
|
|||
|
|
sending.value = true
|
|||
|
|
try {
|
|||
|
|
modelPlan = await stewardFlow.resolveInlineExecutionPlan(cleanPrompt, entry, files, {
|
|||
|
|
pendingMessageId: plannerPendingMessage.id
|
|||
|
|
})
|
|||
|
|
intentPlan = normalizeWorkbenchAiIntentPlan(modelPlan, { prompt: cleanPrompt })
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('AI mode intent planner failed, using local fallback:', error)
|
|||
|
|
const ruleRequest = resolveExecutableTravelApplicationPlan(
|
|||
|
|
buildRuleFallbackWorkbenchAiIntentPlan(cleanPrompt)
|
|||
|
|
)
|
|||
|
|
if (ruleRequest) {
|
|||
|
|
sending.value = false
|
|||
|
|
startModelPlannedApplicationPreview(ruleRequest, plannerPendingMessage)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
stopPlanningProgressUpdates()
|
|||
|
|
sending.value = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const travelApplicationRequest = resolveExecutableTravelApplicationPlan(intentPlan)
|
|||
|
|
if (travelApplicationRequest) {
|
|||
|
|
if (isLowConfidenceTravelApplicationPlan(intentPlan)) {
|
|||
|
|
startModelPlannedTravelApplicationConfirmation(travelApplicationRequest, intentPlan, plannerPendingMessage)
|
|||
|
|
} else {
|
|||
|
|
startModelPlannedApplicationPreview(travelApplicationRequest, plannerPendingMessage)
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isModelPlannedReimbursementTask(modelPlan) || isReimbursementCreationIntent(cleanPrompt)) {
|
|||
|
|
replaceInlineMessage(plannerPendingMessage.id, createInlineMessage(
|
|||
|
|
'assistant',
|
|||
|
|
'已识别为报销任务,正在进入报销流程。',
|
|||
|
|
{
|
|||
|
|
id: plannerPendingMessage.id,
|
|||
|
|
stewardPlan: {
|
|||
|
|
streamStatus: 'completed',
|
|||
|
|
thinkingEvents: resolveInlineThinkingEvents(plannerPendingMessage)
|
|||
|
|
.map((item) => ({ ...item, status: 'completed' }))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
void expenseFlow.startAiReimbursementAssociationGate(cleanPrompt, entry.label || cleanPrompt)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void stewardFlow.requestInlineAssistantReply(cleanPrompt, entry, files, {
|
|||
|
|
pendingMessageId: plannerPendingMessage.id
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
executeModelPlannedWorkbenchIntent,
|
|||
|
|
startModelPlannedNextTask
|
|||
|
|
}
|
|||
|
|
}
|