feat(web): AI 工作台意图规划与规划思考模型
- 新增 workbenchAiIntentPlannerModel,基于 LLM function_call 解析建单/草稿/提交意图,区分 model 与 rule_fallback 来源 - 新增 workbenchAiPlanningThinkingModel 合并规划思考事件流,按 eventId 去重合并 - application gate/preview 模型接入意图规划,usePersonalWorkbenchAiMode/useWorkbenchAiStewardFlow/useWorkbenchAiActionRouter 链路适配,支持上下文提交 - steward 服务与 stewardPlanModel 适配新动作结构,receipt-folder-view 微调样式 - 新增 intent-planner-model/application-context-submit/steward-actions-service 测试,更新 gate-model/action-router/plan-message-copy/fast-preview 测试
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
AI_APPLICATION_ACTION_SAVE_DRAFT,
|
||||
AI_APPLICATION_ACTION_SUBMIT
|
||||
} from '../../services/aiApplicationPreviewActions.js'
|
||||
import { executeStewardAction } from '../../services/steward.js'
|
||||
import { buildAiDocumentDetailRequest } from '../../utils/aiDocumentDetailReference.js'
|
||||
import {
|
||||
mergeComposerPrefill,
|
||||
@@ -25,11 +26,16 @@ export function useWorkbenchAiActionRouter({
|
||||
applicationFlow,
|
||||
assistantDraft,
|
||||
attachmentFlow,
|
||||
conversationMessages,
|
||||
createInlineMessage,
|
||||
emit,
|
||||
expenseFlow,
|
||||
focusAiModeInput,
|
||||
hasInlineAttachmentOcrDetails,
|
||||
persistCurrentConversation,
|
||||
replaceInlineMessage,
|
||||
resolveLatestInlineUserPrompt,
|
||||
scrollInlineConversationToBottom,
|
||||
selectedFiles,
|
||||
startInlineConversation,
|
||||
toast,
|
||||
@@ -45,6 +51,9 @@ export function useWorkbenchAiActionRouter({
|
||||
|
||||
const actionType = String(action?.action_type || '').trim()
|
||||
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
||||
if (actionPayload.steward_execute_action) {
|
||||
return executeInlineStewardAction(action, sourceMessage)
|
||||
}
|
||||
if (actionType === AI_ATTACHMENT_OCR_DETAIL_ACTION) {
|
||||
if (!hasInlineAttachmentOcrDetails(sourceMessage)) {
|
||||
toast('当前消息没有可查看的附件识别明细。')
|
||||
@@ -162,6 +171,191 @@ export function useWorkbenchAiActionRouter({
|
||||
}, action?.payload?.carry_files ? Array.from(selectedFiles.value) : [])
|
||||
}
|
||||
|
||||
async function executeInlineStewardAction(action = {}, sourceMessage = null) {
|
||||
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
||||
const actionType = String(actionPayload.steward_action_type || '').trim()
|
||||
if (!actionType || !actionPayload.steward_current_task) {
|
||||
toast('当前动作缺少可执行任务快照,请重新发起规划。')
|
||||
return false
|
||||
}
|
||||
if (sourceMessage?.suggestedActionsLocked) {
|
||||
return true
|
||||
}
|
||||
if (sourceMessage) {
|
||||
sourceMessage.suggestedActionsLocked = true
|
||||
}
|
||||
|
||||
const pendingMessage = appendStewardActionMessage(
|
||||
`正在执行:${String(action.label || '小财管家动作').trim() || '小财管家动作'}...`,
|
||||
{ pending: true }
|
||||
)
|
||||
|
||||
try {
|
||||
let precheckResult = null
|
||||
if (actionType === 'submit_application') {
|
||||
precheckResult = await executeStewardAction(
|
||||
buildStewardActionExecutePayload(action, 'run_duplicate_precheck')
|
||||
)
|
||||
if (!isSuccessfulStewardPrecheck(precheckResult)) {
|
||||
finalizeStewardActionMessage(pendingMessage, buildStewardActionResultText(precheckResult), {
|
||||
suggestedActions: []
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const contextJson = precheckResult
|
||||
? { precheck_result: precheckResult.result_payload || precheckResult.resultPayload || {} }
|
||||
: {}
|
||||
const result = await executeStewardAction(
|
||||
buildStewardActionExecutePayload(action, actionType, contextJson)
|
||||
)
|
||||
finalizeStewardActionMessage(pendingMessage, buildStewardActionResultText(result), {
|
||||
suggestedActions: buildStewardActionResultActions(result)
|
||||
})
|
||||
return true
|
||||
} catch (error) {
|
||||
if (sourceMessage) {
|
||||
sourceMessage.suggestedActionsLocked = false
|
||||
}
|
||||
finalizeStewardActionMessage(
|
||||
pendingMessage,
|
||||
`动作执行失败:${String(error?.message || '请稍后重试。').trim()}`
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function buildStewardActionExecutePayload(action = {}, actionType = '', contextJson = {}) {
|
||||
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
||||
const currentTask = actionPayload.steward_current_task || null
|
||||
return {
|
||||
action_type: actionType,
|
||||
message: resolveStewardActionMessage(action),
|
||||
plan_id: String(actionPayload.steward_plan_id || '').trim(),
|
||||
conversation_id: String(actionPayload.conversation_id || actionPayload.conversationId || '').trim() || null,
|
||||
task: currentTask,
|
||||
action_step: resolveStewardActionStep(actionPayload, actionType),
|
||||
confirmed: true,
|
||||
context_json: contextJson,
|
||||
client_trace_id: buildStewardActionClientTraceId(actionPayload, actionType)
|
||||
}
|
||||
}
|
||||
|
||||
function resolveStewardActionMessage(action = {}) {
|
||||
const actionPayload = action?.payload && typeof action.payload === 'object' ? action.payload : {}
|
||||
return String(
|
||||
actionPayload.carry_text ||
|
||||
actionPayload.original_message ||
|
||||
resolveLatestInlineUserPrompt?.() ||
|
||||
action.label ||
|
||||
''
|
||||
).trim()
|
||||
}
|
||||
|
||||
function resolveStewardActionStep(actionPayload = {}, actionType = '') {
|
||||
if (String(actionPayload.steward_action_step?.action_type || '').trim() === actionType) {
|
||||
return actionPayload.steward_action_step
|
||||
}
|
||||
const steps = Array.isArray(actionPayload.steward_current_task?.action_steps)
|
||||
? actionPayload.steward_current_task.action_steps
|
||||
: []
|
||||
return steps.find((step) => String(step?.action_type || '').trim() === actionType) || null
|
||||
}
|
||||
|
||||
function buildStewardActionClientTraceId(actionPayload = {}, actionType = '') {
|
||||
const planId = String(actionPayload.steward_plan_id || 'steward').trim() || 'steward'
|
||||
const taskId = String(actionPayload.steward_action_task_id || actionPayload.steward_current_task?.task_id || 'task').trim() || 'task'
|
||||
return `${planId}:${taskId}:${actionType}:${Date.now()}`
|
||||
}
|
||||
|
||||
function isSuccessfulStewardPrecheck(result = {}) {
|
||||
const status = String(result?.status || '').trim()
|
||||
const payload = result?.result_payload || result?.resultPayload || {}
|
||||
return status === 'succeeded' && payload?.blocking !== true
|
||||
}
|
||||
|
||||
function appendStewardActionMessage(content, options = {}) {
|
||||
if (!conversationMessages?.value || typeof createInlineMessage !== 'function') {
|
||||
toast(String(content || '').trim())
|
||||
return null
|
||||
}
|
||||
const message = createInlineMessage('assistant', content, options)
|
||||
conversationMessages.value.push(message)
|
||||
persistStewardActionConversation()
|
||||
return message
|
||||
}
|
||||
|
||||
function finalizeStewardActionMessage(pendingMessage, content, options = {}) {
|
||||
if (!conversationMessages?.value || typeof createInlineMessage !== 'function') {
|
||||
toast(String(content || '').trim())
|
||||
return
|
||||
}
|
||||
const finalMessage = createInlineMessage('assistant', content, {
|
||||
...options,
|
||||
id: pendingMessage?.id
|
||||
})
|
||||
if (pendingMessage?.id && typeof replaceInlineMessage === 'function') {
|
||||
replaceInlineMessage(pendingMessage.id, finalMessage)
|
||||
} else if (pendingMessage?.id) {
|
||||
const index = conversationMessages.value.findIndex((item) => item.id === pendingMessage.id)
|
||||
if (index >= 0) {
|
||||
conversationMessages.value.splice(index, 1, finalMessage)
|
||||
} else {
|
||||
conversationMessages.value.push(finalMessage)
|
||||
}
|
||||
} else {
|
||||
conversationMessages.value.push(finalMessage)
|
||||
}
|
||||
persistStewardActionConversation()
|
||||
}
|
||||
|
||||
function persistStewardActionConversation() {
|
||||
persistCurrentConversation?.()
|
||||
scrollInlineConversationToBottom?.({ force: true })
|
||||
}
|
||||
|
||||
function buildStewardActionResultText(result = {}) {
|
||||
const status = String(result?.status || '').trim()
|
||||
const message = String(result?.message || '').trim()
|
||||
if (status === 'succeeded') {
|
||||
return message || '动作已完成。'
|
||||
}
|
||||
if (status === 'needs_confirmation') {
|
||||
return message || '这一步还需要您确认后才能继续。'
|
||||
}
|
||||
if (status === 'blocked') {
|
||||
return ['这一步暂时不能继续。', message].filter(Boolean).join('\n\n')
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return `动作执行失败:${message || '请稍后重试。'}`
|
||||
}
|
||||
return message || '动作已返回结果。'
|
||||
}
|
||||
|
||||
function buildStewardActionResultActions(result = {}) {
|
||||
if (String(result?.status || '').trim() !== 'succeeded') {
|
||||
return []
|
||||
}
|
||||
const resultPayload = result?.result_payload || result?.resultPayload || {}
|
||||
const draftPayload = resultPayload?.draft_payload || resultPayload?.draftPayload || resultPayload
|
||||
const claimNo = String(draftPayload?.claim_no || draftPayload?.claimNo || '').trim()
|
||||
const claimId = String(draftPayload?.claim_id || draftPayload?.claimId || '').trim()
|
||||
if (!claimNo && !claimId) {
|
||||
return []
|
||||
}
|
||||
return [{
|
||||
label: claimNo ? `查看单据 ${claimNo}` : '查看单据',
|
||||
description: '打开刚生成的单据详情。',
|
||||
icon: 'mdi mdi-open-in-new',
|
||||
action_type: 'open_application_detail',
|
||||
payload: {
|
||||
claim_id: claimId,
|
||||
claim_no: claimNo
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
return {
|
||||
handleInlineSuggestedAction
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user