Files
X-Financial/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js
caoxiaozhu 43779f8f2c fix(web): 多 task 串行推进 task2 不再被预览生成时提前触发
onPreviewReadyForNextTask 在 task1 申请核对表刚生成、用户还没操作时就
提前拉起 task2,与用户后续在 task1 上的保存草稿/提交操作互相打架,导致
task2 完全无反应。移除该提前推进回调,统一由 onApplicationActionCompleted
在 task1 真正完成后再推进 task2。

- useWorkbenchAiApplicationPreviewFlow: 删除预览生成时的提前推进分支
- usePersonalWorkbenchAiMode: startModelPlannedApplicationPreview 不再传 onPreviewReadyForNextTask
- useWorkbenchAiActionRouter: 低置信确认按钮分支同步删除该回调
- 新增时序回归测试:预览生成不提前推进、保存草稿后才推进
- 更新两处源码正则断言为 doesNotMatch
2026-06-30 11:40:31 +08:00

667 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
buildApplicationPreviewFooterMessage,
buildApplicationPreviewRows,
buildLocalApplicationPreviewMessage,
normalizeApplicationPreview
} from '../../utils/expenseApplicationPreview.js'
import {
buildAiApplicationPrecheck,
buildAiApplicationSubmitConflictMessage,
isAiApplicationPrecheckBlocking
} from '../../utils/aiApplicationPrecheckModel.js'
import { fetchExpenseClaims } from '../../services/reimbursements.js'
import {
AI_APPLICATION_ACTION_SAVE_DRAFT,
AI_APPLICATION_ACTION_SUBMIT,
runAiApplicationPreviewAction
} from '../../services/aiApplicationPreviewActions.js'
import {
buildFailedInlineApplicationSubmitThinkingEvents,
buildInitialInlineApplicationSubmitThinkingEvents,
buildInlineApplicationDetailAction,
buildInlineApplicationPreview,
buildInlineApplicationPreviewActionResultText,
buildInlineApplicationSubmitPrecheckPayload,
buildInlineApplicationSubmitThinkingEvents,
completeInlineThinkingEvents,
extractInlineApplicationDraftPayload
} from './workbenchAiApplicationPreviewModel.js'
import { AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION } from './workbenchAiMessageModel.js'
import {
completeWorkbenchAiThinkingEvents,
mergeWorkbenchAiThinkingEvents
} from './workbenchAiPlanningThinkingModel.js'
import {
isOrphanInlineApplicationPreviewMessage,
resolveInlineApplicationPreviewTextAction,
resolveLatestApplicationPreviewMessage,
resolveLatestOrphanApplicationPreviewMessage
} from './workbenchAiApplicationGateModel.js'
function isApplicationPreviewEstimatePendingPreview(applicationPreview = {}) {
const fields = normalizeApplicationPreview(applicationPreview).fields || {}
return [
fields.transportPolicy,
fields.policyEstimate,
fields.transportEstimatedAmount,
fields.amount
].some((value) => /正在|查询中/.test(String(value || '')))
}
function shouldRefreshInlineApplicationPreviewEstimate(fieldKey = '') {
return ['transportMode', 'time', 'time_return', 'location', 'days'].includes(String(fieldKey || '').trim())
}
export function useWorkbenchAiApplicationPreviewFlow({
activateInlineConversation,
applicationPreviewEditor,
applicationSubmitConfirmContext,
applicationSubmitConfirmOpen,
assistantDraft,
cancelApplicationPreviewEditor,
clearAiModeFiles,
closeWorkbenchDatePicker,
commitApplicationPreviewEditor: commitBaseApplicationPreviewEditor,
conversationId,
conversationMessages,
conversationStarted,
createInlineMessage,
currentUser,
handleApplicationPreviewEditorKeydown,
inlineConversationAutoScrollPinned,
isApplicationPreviewEditing,
openApplicationPreviewEditor,
persistCurrentConversation,
pushInlineApplicationActionUserMessage,
pushInlineUserMessage,
refreshApplicationPreviewEstimate,
removeWorkbenchDateTag,
replaceInlineMessage,
resolveApplicationPreviewEditorDateMax,
resolveApplicationPreviewEditorDateMin,
resolveApplicationPreviewEditorControl,
resolveApplicationPreviewEditorOptions,
resolveInlineThinkingEvents,
resolveLatestInlineUserPrompt,
scrollInlineConversationToBottom,
sending,
toast,
onApplicationActionCompleted = null
}) {
function isApplicationPreviewEstimatePending(message = {}) {
return Boolean(message?.applicationPreview && isApplicationPreviewEstimatePendingPreview(message.applicationPreview))
}
function canShowInlineSuggestedActions(message = {}) {
return Boolean(message?.suggestedActions?.length) && !isApplicationPreviewEstimatePending(message)
}
function isInlineSuggestedActionDisabled(action = {}, message = {}) {
const actionType = String(action?.action_type || '').trim()
return (
Boolean(action?.disabled) ||
(actionType === AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION && sending.value) ||
(
[AI_APPLICATION_ACTION_SAVE_DRAFT, AI_APPLICATION_ACTION_SUBMIT].includes(actionType) &&
isApplicationPreviewEstimatePending(message)
)
)
}
function resolveInlineApplicationPreviewRows(message) {
return buildApplicationPreviewRows(message?.applicationPreview || {})
}
function resolveInlineApplicationPreviewMissingFields(message) {
return normalizeApplicationPreview(message?.applicationPreview || {}).missingFields || []
}
function resolveInlineApplicationPreviewEditorControl(fieldKey) {
return resolveApplicationPreviewEditorControl(fieldKey)
}
function resolveInlineApplicationPreviewEditorDateMin(message, fieldKey) {
return resolveApplicationPreviewEditorDateMin?.(message, fieldKey) || ''
}
function resolveInlineApplicationPreviewEditorDateMax(message, fieldKey) {
return resolveApplicationPreviewEditorDateMax?.(message, fieldKey) || ''
}
function buildInlineApplicationPreviewSuggestedActions(applicationPreview = {}, draftPayload = null) {
if (isApplicationPreviewEstimatePendingPreview(applicationPreview)) {
return []
}
const normalized = normalizeApplicationPreview(applicationPreview)
const actions = [{
label: '保存草稿',
description: '先保存当前申请表,后续可以继续补充或提交。',
icon: 'mdi mdi-content-save-outline',
action_type: AI_APPLICATION_ACTION_SAVE_DRAFT,
payload: { draftPayload }
}]
if (normalized.readyToSubmit) {
actions.push({
label: '直接提交',
description: '提交前先核查相同日期申请单,确认通过后进入审批流程。',
icon: 'mdi mdi-send-check-outline',
action_type: AI_APPLICATION_ACTION_SUBMIT,
payload: { draftPayload }
})
}
return actions
}
function syncInlineApplicationPreviewMessageContent(message) {
if (!message?.applicationPreview) {
return
}
const nextContent = buildLocalApplicationPreviewMessage(message.applicationPreview)
message.content = nextContent
message.text = nextContent
message.suggestedActions = buildInlineApplicationPreviewSuggestedActions(message.applicationPreview, message.draftPayload)
}
async function commitInlineApplicationPreviewEditor(message) {
const shouldLockForEstimate = shouldRefreshInlineApplicationPreviewEstimate(applicationPreviewEditor.value.fieldKey)
if (shouldLockForEstimate) {
message.suggestedActions = []
persistCurrentConversation()
}
const committed = await commitBaseApplicationPreviewEditor(message)
syncInlineApplicationPreviewMessageContent(message)
persistCurrentConversation()
return committed
}
function handleInlineApplicationPreviewEditorKeydown(event, message) {
if (event.key === 'Enter') {
event.preventDefault()
void commitInlineApplicationPreviewEditor(message)
return
}
if (event.key === 'Escape') {
event.preventDefault()
cancelApplicationPreviewEditor()
return
}
handleApplicationPreviewEditorKeydown(event, message)
}
function buildInlineApplicationPreviewFooterText(message) {
const normalized = normalizeApplicationPreview(message?.applicationPreview || {})
if (isApplicationPreviewEstimatePending(message)) {
return '费用测算正在同步,请稍等,完成后才能保存草稿或直接提交。'
}
if (normalized.validationIssues?.length || normalized.missingFields?.length) {
return buildApplicationPreviewFooterMessage(normalized)
}
if (message?.submitRequiresConfirmation) {
return '已识别到您希望直接提交。系统不会自动提交申请,请先核对申请核对表;确认无误后,点击下方“直接提交”按钮再进入提交确认。'
}
return '申请核对表已补齐,费用测算已同步。您仍可点击表格继续修改;确认无误后,可以点击下方按钮保存草稿或直接提交,也可以直接回复“保存草稿”或“提交”。'
}
function buildInlineApplicationActionFailureText(error, isSubmit) {
return [
isSubmit ? '### 申请提交失败' : '### 申请草稿保存失败',
error?.message || (isSubmit ? '申请提交失败,请稍后重试。' : '申请草稿保存失败,请稍后重试。'),
'我已保留当前申请核对表,您可以修改后重试,也可以稍后再次保存。'
].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)
}
function resolveLatestOrphanInlineApplicationPreviewMessage() {
return resolveLatestOrphanApplicationPreviewMessage(conversationMessages.value)
}
function requestInlineApplicationSubmitConfirmation(targetMessage, options = {}) {
applicationSubmitConfirmContext.value = {
messageId: String(targetMessage?.id || '').trim(),
draftPayload: targetMessage?.draftPayload || options.draftPayload || null,
userText: String(options.userText || '直接提交').trim() || '直接提交'
}
applicationSubmitConfirmOpen.value = true
persistCurrentConversation()
}
function cancelInlineApplicationSubmitConfirm() {
applicationSubmitConfirmOpen.value = false
applicationSubmitConfirmContext.value = null
}
function confirmInlineApplicationSubmit() {
const context = applicationSubmitConfirmContext.value || {}
applicationSubmitConfirmOpen.value = false
applicationSubmitConfirmContext.value = null
const sourceMessage = conversationMessages.value.find((message) => message.id === context.messageId)
if (!sourceMessage?.applicationPreview) {
toast('当前申请表已变化,请重新点击直接提交。')
return
}
void executeInlineApplicationPreviewAction(AI_APPLICATION_ACTION_SUBMIT, sourceMessage, {
confirmed: true,
skipUserMessage: false,
draftPayload: context.draftPayload || null,
userText: context.userText || '直接提交'
})
}
async function runInlineApplicationSubmitPrecheck(targetMessage, pendingMessage, normalizedPreview, options = {}) {
try {
const claimsPayload = await fetchExpenseClaims({ page: 1, pageSize: 100 })
const precheck = buildAiApplicationPrecheck(normalizedPreview, {
claimsPayload: buildInlineApplicationSubmitPrecheckPayload(
claimsPayload,
targetMessage.draftPayload || options.draftPayload || null
),
currentUser: currentUser.value || {},
expenseType: 'travel'
})
const thinkingEvents = buildInlineApplicationSubmitThinkingEvents(precheck)
const blocked = isAiApplicationPrecheckBlocking(precheck)
if (blocked) {
replaceInlineMessage(
pendingMessage.id,
createInlineMessage('assistant', buildAiApplicationSubmitConflictMessage(normalizedPreview, precheck), {
id: pendingMessage.id,
stewardPlan: {
streamStatus: 'completed',
thinkingEvents
}
})
)
persistCurrentConversation()
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
return false
}
const message = conversationMessages.value.find((item) => item.id === pendingMessage.id) || pendingMessage
message.content = '提交前核查通过,正在提交申请并进入审批流程...'
message.paragraphs = ['提交前核查通过,正在提交申请并进入审批流程...']
message.stewardPlan = {
...(message.stewardPlan || {}),
streamStatus: 'streaming',
thinkingEvents
}
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
return true
} catch (error) {
replaceInlineMessage(
pendingMessage.id,
createInlineMessage('assistant', [
'### 提交前核查失败',
'系统未能完成相同日期申请单查询,所以本次申请没有提交。',
'请稍后重试;如果仍然失败,请先到单据中心核对是否已有同日期申请单。'
].join('\n\n'), {
id: pendingMessage.id,
stewardPlan: {
streamStatus: 'failed',
thinkingEvents: buildFailedInlineApplicationSubmitThinkingEvents(error)
}
})
)
toast('提交前核查失败,已暂停提交。')
persistCurrentConversation()
return false
}
}
function buildApplicationPreviewNextTaskAction(targetMessage) {
// 多 task 串行推进:申请草稿保存/提交成功后,检查是否有剩余 task(如报销),
// 有则生成"继续处理下一个任务"按钮,让用户一键推进。
const remainingTasks = Array.isArray(targetMessage?.stewardRemainingTasks)
? targetMessage.stewardRemainingTasks
: []
const nextTask = remainingTasks[0]
if (!nextTask || !nextTask.task_type) {
return null
}
const taskType = String(nextTask.task_type || '').trim()
const isApplication = taskType === 'expense_application'
const flowId = isApplication ? 'travel_application' : 'travel_reimbursement'
const taskLabel = isApplication ? '出差申请' : '费用报销'
const ontologyFields = nextTask.ontology_fields || nextTask.ontologyFields || {}
// 透传去掉当前 nextTask 之后的剩余 task 列表,保证 task2 完成后 task3 也能继续推进,
// 避免 3+ task 场景在 task2 处断链。
const furtherRemainingTasks = remainingTasks.slice(1)
return {
label: `继续处理${taskLabel}`,
description: `接下来处理${taskLabel}${String(nextTask.summary || nextTask.title || '').slice(0, 40)}`,
icon: isApplication ? 'mdi mdi-file-plus-outline' : 'mdi mdi-receipt-text-plus-outline',
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: furtherRemainingTasks
}
}
}
async function executeInlineApplicationPreviewAction(actionType, sourceMessage = null, options = {}) {
const targetMessage = sourceMessage?.applicationPreview ? sourceMessage : resolveLatestInlineApplicationPreviewMessage()
if (!targetMessage?.applicationPreview) {
toast('当前没有可提交的申请表。')
return false
}
const normalizedPreview = normalizeApplicationPreview(targetMessage.applicationPreview)
const isSubmit = actionType === AI_APPLICATION_ACTION_SUBMIT
const userText = String(options.userText || (isSubmit ? '直接提交' : '保存草稿')).trim()
if (isSubmit && !normalizedPreview.readyToSubmit) {
if (!options.skipUserMessage) {
pushInlineApplicationActionUserMessage(userText)
}
const missingText = normalizedPreview.missingFields?.length
? `当前还缺少:${normalizedPreview.missingFields.join('、')}`
: ''
const validationText = normalizedPreview.validationIssues?.length
? normalizedPreview.validationIssues.map((item) => item.message).join('')
: ''
conversationMessages.value.push(createInlineMessage('assistant', [
'### 暂不能提交申请',
missingText || validationText || '当前申请表还未通过提交校验。',
'请先点击表格中的字段补充或修正,补齐后我会开放“直接提交”入口。'
].filter(Boolean).join('\n\n')))
persistCurrentConversation()
scrollInlineConversationToBottom()
return true
}
const shouldSubmitSavedDraftDirectly = isSubmit &&
!options.confirmed &&
hasSavedInlineApplicationDraft(targetMessage, options) &&
isContextualInlineApplicationSubmitText(userText)
if (isSubmit && !options.confirmed && !shouldSubmitSavedDraftDirectly) {
requestInlineApplicationSubmitConfirmation(targetMessage, { ...options, userText })
return true
}
if (!options.skipUserMessage) {
pushInlineApplicationActionUserMessage(userText)
}
sending.value = true
const pendingMessage = createInlineMessage(
'assistant',
isSubmit ? '正在提交前核查相同日期申请单...' : '正在保存申请草稿...',
{
pending: true,
stewardPlan: {
streamStatus: 'streaming',
thinkingEvents: isSubmit
? buildInitialInlineApplicationSubmitThinkingEvents()
: [
{
eventId: 'application-save-draft',
title: '保存申请草稿',
content: '正在按当前申请表内容保存草稿。',
status: 'running'
}
]
}
}
)
conversationMessages.value.push(pendingMessage)
scrollInlineConversationToBottom()
try {
if (isSubmit) {
const precheckPassed = await runInlineApplicationSubmitPrecheck(
targetMessage,
pendingMessage,
normalizedPreview,
options
)
if (!precheckPassed) {
return true
}
}
const payload = await runAiApplicationPreviewAction({
actionType,
applicationPreview: normalizedPreview,
currentUser: currentUser.value || {},
conversationId: conversationId.value,
draftPayload: targetMessage.draftPayload || options.draftPayload || null
})
const draftPayload = extractInlineApplicationDraftPayload(payload)
if (draftPayload) {
targetMessage.draftPayload = draftPayload
}
targetMessage.suggestedActions = []
const detailActions = buildInlineApplicationDetailAction(draftPayload)
const nextTaskAction = buildApplicationPreviewNextTaskAction(targetMessage)
const actionCompletedHandler = typeof options.onApplicationActionCompleted === 'function'
? options.onApplicationActionCompleted
: onApplicationActionCompleted
const shouldAutoContinueNextTask = Boolean(
nextTaskAction &&
typeof actionCompletedHandler === 'function' &&
Array.isArray(targetMessage.stewardRemainingTasks) &&
targetMessage.stewardRemainingTasks.length
)
replaceInlineMessage(
pendingMessage.id,
createInlineMessage('assistant', buildInlineApplicationPreviewActionResultText(actionType, payload), {
id: pendingMessage.id,
stewardPlan: {
streamStatus: 'completed',
thinkingEvents: completeInlineThinkingEvents(resolveInlineThinkingEvents(pendingMessage))
},
suggestedActions: shouldAutoContinueNextTask
? detailActions
: (nextTaskAction ? [...detailActions, nextTaskAction] : detailActions)
})
)
persistCurrentConversation()
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
if (shouldAutoContinueNextTask) {
actionCompletedHandler(targetMessage.stewardRemainingTasks, targetMessage)
}
return true
} catch (error) {
replaceInlineMessage(
pendingMessage.id,
createInlineMessage('assistant', buildInlineApplicationActionFailureText(error, isSubmit), {
id: pendingMessage.id,
applicationPreview: targetMessage.applicationPreview,
draftPayload: targetMessage.draftPayload || options.draftPayload || null,
suggestedActions: buildInlineApplicationPreviewSuggestedActions(
targetMessage.applicationPreview,
targetMessage.draftPayload || options.draftPayload || null
),
stewardPlan: {
streamStatus: 'failed',
thinkingEvents: resolveInlineThinkingEvents(pendingMessage).map((item) => ({
...item,
status: 'failed'
}))
}
})
)
toast(error?.message || (isSubmit ? '申请提交失败。' : '申请草稿保存失败。'))
persistCurrentConversation()
return true
} finally {
sending.value = false
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
}
}
function handleInlineApplicationPreviewTextAction(prompt, applicationPreviewEstimatePending) {
if (applicationPreviewEstimatePending.value) {
toast('请等待费用测算完成后再继续操作。')
return true
}
const actionType = resolveInlineApplicationPreviewTextAction(prompt)
if (!actionType) {
return false
}
if (!resolveLatestInlineApplicationPreviewMessage()) {
const orphanPreviewMessage = resolveLatestOrphanInlineApplicationPreviewMessage()
if (!orphanPreviewMessage) {
return false
}
const previewSourceText = resolveLatestInlineUserPrompt()
pushInlineApplicationActionUserMessage(prompt)
toast('当前申请核对表状态不完整,我先重新生成可编辑表格。')
void startAiApplicationPreview('travel', '差旅费', previewSourceText, {
pushUserMessage: false
})
return true
}
void executeInlineApplicationPreviewAction(actionType, null, { userText: prompt })
return true
}
async function startAiApplicationPreview(expenseType, expenseTypeLabel, sourceText = '', options = {}) {
if (!conversationStarted.value) {
activateInlineConversation({ title: String(expenseTypeLabel || expenseType || '申请').trim().slice(0, 18) || '申请' })
}
const previewSourceText = String(sourceText || resolveLatestInlineUserPrompt()).trim()
assistantDraft.value = ''
removeWorkbenchDateTag()
closeWorkbenchDatePicker()
clearAiModeFiles()
if (options.pushUserMessage !== false) {
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: mergeWorkbenchAiThinkingEvents(previousThinkingEvents, [
{
eventId: 'application-preview-build',
title: '整理申请表字段',
content: '正在把已识别的时间、地点、事由和差旅类型整理成可编辑表格。',
status: 'running'
},
{
eventId: 'application-preview-estimate',
title: '同步费用测算',
content: '正在刷新差旅规则和费用测算,完成后会直接展示核对表。',
status: 'pending'
}
])
}
})
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 || {}, {
ontologyFields: options.ontologyFields
})
)
const content = buildLocalApplicationPreviewMessage(preview)
const previewMessage = createInlineMessage('assistant', content, {
id: pendingMessage.id,
applicationPreview: preview,
suggestedActions: buildInlineApplicationPreviewSuggestedActions(preview),
requestedSubmit: Boolean(options.requestedSubmit),
submitRequiresConfirmation: Boolean(options.submitRequiresConfirmation),
stewardRemainingTasks: Array.isArray(options.stewardRemainingTasks) ? options.stewardRemainingTasks : [],
stewardPlan: {
streamStatus: 'completed',
thinkingEvents: completeWorkbenchAiThinkingEvents(resolveInlineThinkingEvents(pendingMessage))
},
text: content
})
replaceInlineMessage(pendingMessage.id, previewMessage)
if (options.autoSaveDraft) {
await executeInlineApplicationPreviewAction(AI_APPLICATION_ACTION_SAVE_DRAFT, previewMessage, {
skipUserMessage: true,
userText: options.userMessage || '保存草稿',
onApplicationActionCompleted: options.onApplicationActionCompleted
})
}
// 多 task 串行推进:预览生成后不提前拉起下一个 task(避免和用户在 task1 核对表上的
// 保存草稿/提交操作互相打架,导致 task2 状态错乱)。task2 的推进统一交给
// onApplicationActionCompleted,在 task1 真正完成(保存草稿/提交成功)后再触发。
} catch (error) {
replaceInlineMessage(pendingMessage.id, createInlineMessage('assistant', error?.message || '申请核对表生成失败,请稍后重试。', {
id: pendingMessage.id,
stewardPlan: {
streamStatus: 'failed',
thinkingEvents: resolveInlineThinkingEvents(pendingMessage).map((item) => ({
...item,
status: 'failed'
}))
}
}))
toast(error?.message || '申请核对表生成失败。')
} finally {
persistCurrentConversation()
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
}
}
return {
buildInlineApplicationPreviewFooterText,
buildInlineApplicationPreviewSuggestedActions,
canShowInlineSuggestedActions,
cancelInlineApplicationSubmitConfirm,
commitInlineApplicationPreviewEditor,
confirmInlineApplicationSubmit,
executeInlineApplicationPreviewAction,
handleInlineApplicationPreviewEditorKeydown,
handleInlineApplicationPreviewTextAction,
isApplicationPreviewEditing,
isApplicationPreviewEstimatePending,
isInlineSuggestedActionDisabled,
openApplicationPreviewEditor,
resolveApplicationPreviewEditorOptions,
resolveInlineApplicationPreviewEditorControl,
resolveInlineApplicationPreviewEditorDateMax,
resolveInlineApplicationPreviewEditorDateMin,
resolveInlineApplicationPreviewMissingFields,
resolveInlineApplicationPreviewRows,
startAiApplicationPreview
}
}