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:
caoxiaozhu
2026-06-24 21:58:46 +08:00
parent 5311c99d69
commit bc560145a4
18 changed files with 1914 additions and 38 deletions

View File

@@ -27,6 +27,10 @@ import {
extractInlineApplicationDraftPayload
} from './workbenchAiApplicationPreviewModel.js'
import { AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION } from './workbenchAiMessageModel.js'
import {
completeWorkbenchAiThinkingEvents,
mergeWorkbenchAiThinkingEvents
} from './workbenchAiPlanningThinkingModel.js'
import {
isOrphanInlineApplicationPreviewMessage,
resolveInlineApplicationPreviewTextAction,
@@ -202,6 +206,26 @@ export function useWorkbenchAiApplicationPreviewFlow({
].join('\n\n')
}
function hasSavedInlineApplicationDraft(message = {}, options = {}) {
const draftPayload = message?.draftPayload || options.draftPayload || null
if (!draftPayload || typeof draftPayload !== 'object') {
return false
}
const claimId = String(draftPayload.claim_id || draftPayload.claimId || '').trim()
const claimNo = String(draftPayload.claim_no || draftPayload.claimNo || '').trim()
const status = String(draftPayload.status || '').trim().toLowerCase()
return Boolean((claimId || claimNo) && status !== 'submitted')
}
function isContextualInlineApplicationSubmitText(text = '') {
const normalized = String(text || '').replace(/\s+/g, '').trim()
return Boolean(
normalized &&
/提交/.test(normalized) &&
/(这个|这张|当前|上面|上述|刚才|申请单|单据|草稿|领导|审核|审批)/.test(normalized)
)
}
function resolveLatestInlineApplicationPreviewMessage() {
return resolveLatestApplicationPreviewMessage(conversationMessages.value)
}
@@ -334,7 +358,12 @@ export function useWorkbenchAiApplicationPreviewFlow({
return true
}
if (isSubmit && !options.confirmed) {
const shouldSubmitSavedDraftDirectly = isSubmit &&
!options.confirmed &&
hasSavedInlineApplicationDraft(targetMessage, options) &&
isContextualInlineApplicationSubmitText(userText)
if (isSubmit && !options.confirmed && !shouldSubmitSavedDraftDirectly) {
requestInlineApplicationSubmitConfirmation(targetMessage, { ...options, userText })
return true
}
@@ -474,11 +503,16 @@ export function useWorkbenchAiApplicationPreviewFlow({
pushInlineUserMessage(options.userMessage || '确认发起出差申请')
}
const existingPendingMessage = options.pendingMessageId
? conversationMessages.value.find((item) => item.id === options.pendingMessageId)
: null
const previousThinkingEvents = completeWorkbenchAiThinkingEvents(resolveInlineThinkingEvents(existingPendingMessage))
const pendingMessage = createInlineMessage('assistant', '正在生成申请核对表,请稍等...', {
id: options.pendingMessageId,
pending: true,
stewardPlan: {
streamStatus: 'streaming',
thinkingEvents: [
thinkingEvents: mergeWorkbenchAiThinkingEvents(previousThinkingEvents, [
{
eventId: 'application-preview-build',
title: '整理申请表字段',
@@ -491,19 +525,25 @@ export function useWorkbenchAiApplicationPreviewFlow({
content: '正在刷新差旅规则和费用测算,完成后会直接展示核对表。',
status: 'pending'
}
]
])
}
})
conversationMessages.value.push(pendingMessage)
if (options.pendingMessageId) {
replaceInlineMessage(options.pendingMessageId, pendingMessage)
} else {
conversationMessages.value.push(pendingMessage)
}
persistCurrentConversation()
scrollInlineConversationToBottom()
try {
const preview = await refreshApplicationPreviewEstimate(
buildInlineApplicationPreview(expenseTypeLabel || expenseType, previewSourceText, currentUser.value || {})
buildInlineApplicationPreview(expenseTypeLabel || expenseType, previewSourceText, currentUser.value || {}, {
ontologyFields: options.ontologyFields
})
)
const content = buildLocalApplicationPreviewMessage(preview)
replaceInlineMessage(pendingMessage.id, createInlineMessage('assistant', content, {
const previewMessage = createInlineMessage('assistant', content, {
id: pendingMessage.id,
applicationPreview: preview,
suggestedActions: buildInlineApplicationPreviewSuggestedActions(preview),
@@ -512,7 +552,20 @@ export function useWorkbenchAiApplicationPreviewFlow({
thinkingEvents: completeInlineThinkingEvents(resolveInlineThinkingEvents(pendingMessage))
},
text: content
}))
})
replaceInlineMessage(pendingMessage.id, previewMessage)
if (options.autoSubmit && normalizeApplicationPreview(preview).readyToSubmit) {
await executeInlineApplicationPreviewAction(AI_APPLICATION_ACTION_SUBMIT, previewMessage, {
confirmed: true,
skipUserMessage: true,
userText: options.userMessage || '直接提交'
})
} else if (options.autoSaveDraft) {
await executeInlineApplicationPreviewAction(AI_APPLICATION_ACTION_SAVE_DRAFT, previewMessage, {
skipUserMessage: true,
userText: options.userMessage || '保存草稿'
})
}
} catch (error) {
replaceInlineMessage(pendingMessage.id, createInlineMessage('assistant', error?.message || '申请核对表生成失败,请稍后重试。', {
id: pendingMessage.id,