2026-06-22 11:58:53 +08:00
|
|
|
|
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,
|
2026-06-23 11:21:18 +08:00
|
|
|
|
extractInlineApplicationDraftPayload
|
2026-06-22 11:58:53 +08:00
|
|
|
|
} from './workbenchAiApplicationPreviewModel.js'
|
|
|
|
|
|
import { AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION } from './workbenchAiMessageModel.js'
|
2026-06-23 11:21:18 +08:00
|
|
|
|
import {
|
|
|
|
|
|
isOrphanInlineApplicationPreviewMessage,
|
|
|
|
|
|
resolveInlineApplicationPreviewTextAction,
|
|
|
|
|
|
resolveLatestApplicationPreviewMessage,
|
|
|
|
|
|
resolveLatestOrphanApplicationPreviewMessage
|
|
|
|
|
|
} from './workbenchAiApplicationGateModel.js'
|
2026-06-22 11:58:53 +08:00
|
|
|
|
|
|
|
|
|
|
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 = '') {
|
2026-06-22 15:56:06 +08:00
|
|
|
|
return ['transportMode', 'time', 'time_return', 'location', 'days'].includes(String(fieldKey || '').trim())
|
2026-06-22 11:58:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-06-23 09:42:13 +08:00
|
|
|
|
resolveApplicationPreviewEditorDateMax,
|
|
|
|
|
|
resolveApplicationPreviewEditorDateMin,
|
2026-06-22 11:58:53 +08:00
|
|
|
|
resolveApplicationPreviewEditorControl,
|
|
|
|
|
|
resolveApplicationPreviewEditorOptions,
|
|
|
|
|
|
resolveInlineThinkingEvents,
|
|
|
|
|
|
resolveLatestInlineUserPrompt,
|
|
|
|
|
|
scrollInlineConversationToBottom,
|
|
|
|
|
|
sending,
|
|
|
|
|
|
toast
|
|
|
|
|
|
}) {
|
|
|
|
|
|
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) {
|
2026-06-23 09:42:13 +08:00
|
|
|
|
return resolveApplicationPreviewEditorControl(fieldKey)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveInlineApplicationPreviewEditorDateMin(message, fieldKey) {
|
|
|
|
|
|
return resolveApplicationPreviewEditorDateMin?.(message, fieldKey) || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveInlineApplicationPreviewEditorDateMax(message, fieldKey) {
|
|
|
|
|
|
return resolveApplicationPreviewEditorDateMax?.(message, fieldKey) || ''
|
2026-06-22 11:58:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
return '申请核对表已补齐,费用测算已同步。你仍可点击表格继续修改;确认无误后,可以点击下方按钮保存草稿或直接提交,也可以直接回复“保存草稿”或“提交”。'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 09:42:13 +08:00
|
|
|
|
function buildInlineApplicationActionFailureText(error, isSubmit) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
isSubmit ? '### 申请提交失败' : '### 申请草稿保存失败',
|
|
|
|
|
|
error?.message || (isSubmit ? '申请提交失败,请稍后重试。' : '申请草稿保存失败,请稍后重试。'),
|
|
|
|
|
|
'我已保留当前申请核对表,您可以修改后重试,也可以稍后再次保存。'
|
|
|
|
|
|
].join('\n\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 11:21:18 +08:00
|
|
|
|
function resolveLatestInlineApplicationPreviewMessage() {
|
|
|
|
|
|
return resolveLatestApplicationPreviewMessage(conversationMessages.value)
|
2026-06-23 09:43:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 11:21:18 +08:00
|
|
|
|
function resolveLatestOrphanInlineApplicationPreviewMessage() {
|
|
|
|
|
|
return resolveLatestOrphanApplicationPreviewMessage(conversationMessages.value)
|
2026-06-23 09:43:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function executeInlineApplicationPreviewAction(actionType, sourceMessage = null, options = {}) {
|
2026-06-23 11:21:18 +08:00
|
|
|
|
const targetMessage = sourceMessage?.applicationPreview ? sourceMessage : resolveLatestInlineApplicationPreviewMessage()
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isSubmit && !options.confirmed) {
|
|
|
|
|
|
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 = []
|
|
|
|
|
|
replaceInlineMessage(
|
|
|
|
|
|
pendingMessage.id,
|
|
|
|
|
|
createInlineMessage('assistant', buildInlineApplicationPreviewActionResultText(actionType, payload), {
|
|
|
|
|
|
id: pendingMessage.id,
|
|
|
|
|
|
stewardPlan: {
|
|
|
|
|
|
streamStatus: 'completed',
|
|
|
|
|
|
thinkingEvents: completeInlineThinkingEvents(resolveInlineThinkingEvents(pendingMessage))
|
|
|
|
|
|
},
|
|
|
|
|
|
suggestedActions: isSubmit ? buildInlineApplicationDetailAction(draftPayload) : []
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
persistCurrentConversation()
|
|
|
|
|
|
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
|
|
|
|
|
|
return true
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
replaceInlineMessage(
|
|
|
|
|
|
pendingMessage.id,
|
2026-06-23 09:42:13 +08:00
|
|
|
|
createInlineMessage('assistant', buildInlineApplicationActionFailureText(error, isSubmit), {
|
2026-06-22 11:58:53 +08:00
|
|
|
|
id: pendingMessage.id,
|
2026-06-23 09:42:13 +08:00
|
|
|
|
applicationPreview: targetMessage.applicationPreview,
|
|
|
|
|
|
draftPayload: targetMessage.draftPayload || options.draftPayload || null,
|
|
|
|
|
|
suggestedActions: buildInlineApplicationPreviewSuggestedActions(
|
|
|
|
|
|
targetMessage.applicationPreview,
|
|
|
|
|
|
targetMessage.draftPayload || options.draftPayload || null
|
|
|
|
|
|
),
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-06-23 11:21:18 +08:00
|
|
|
|
const actionType = resolveInlineApplicationPreviewTextAction(prompt)
|
2026-06-23 09:43:29 +08:00
|
|
|
|
if (!actionType) {
|
2026-06-22 11:58:53 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-06-23 11:21:18 +08:00
|
|
|
|
if (!resolveLatestInlineApplicationPreviewMessage()) {
|
|
|
|
|
|
const orphanPreviewMessage = resolveLatestOrphanInlineApplicationPreviewMessage()
|
2026-06-23 09:43:29 +08:00
|
|
|
|
if (!orphanPreviewMessage) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
const previewSourceText = resolveLatestInlineUserPrompt()
|
|
|
|
|
|
pushInlineApplicationActionUserMessage(prompt)
|
|
|
|
|
|
toast('当前申请核对表状态不完整,我先重新生成可编辑表格。')
|
|
|
|
|
|
void startAiApplicationPreview('travel', '差旅费', previewSourceText, {
|
|
|
|
|
|
pushUserMessage: false
|
|
|
|
|
|
})
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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 pendingMessage = createInlineMessage('assistant', '正在生成申请核对表,请稍等...', {
|
|
|
|
|
|
pending: true,
|
|
|
|
|
|
stewardPlan: {
|
|
|
|
|
|
streamStatus: 'streaming',
|
|
|
|
|
|
thinkingEvents: [
|
|
|
|
|
|
{
|
|
|
|
|
|
eventId: 'application-preview-build',
|
|
|
|
|
|
title: '整理申请表字段',
|
|
|
|
|
|
content: '正在把已识别的时间、地点、事由和差旅类型整理成可编辑表格。',
|
|
|
|
|
|
status: 'running'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
eventId: 'application-preview-estimate',
|
|
|
|
|
|
title: '同步费用测算',
|
|
|
|
|
|
content: '正在刷新差旅规则和费用测算,完成后会直接展示核对表。',
|
|
|
|
|
|
status: 'pending'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
conversationMessages.value.push(pendingMessage)
|
|
|
|
|
|
persistCurrentConversation()
|
|
|
|
|
|
scrollInlineConversationToBottom()
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const preview = await refreshApplicationPreviewEstimate(
|
|
|
|
|
|
buildInlineApplicationPreview(expenseTypeLabel || expenseType, previewSourceText, currentUser.value || {})
|
|
|
|
|
|
)
|
|
|
|
|
|
const content = buildLocalApplicationPreviewMessage(preview)
|
|
|
|
|
|
replaceInlineMessage(pendingMessage.id, createInlineMessage('assistant', content, {
|
|
|
|
|
|
id: pendingMessage.id,
|
|
|
|
|
|
applicationPreview: preview,
|
|
|
|
|
|
suggestedActions: buildInlineApplicationPreviewSuggestedActions(preview),
|
|
|
|
|
|
stewardPlan: {
|
|
|
|
|
|
streamStatus: 'completed',
|
|
|
|
|
|
thinkingEvents: completeInlineThinkingEvents(resolveInlineThinkingEvents(pendingMessage))
|
|
|
|
|
|
},
|
|
|
|
|
|
text: content
|
|
|
|
|
|
}))
|
|
|
|
|
|
} 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,
|
2026-06-23 09:42:13 +08:00
|
|
|
|
resolveInlineApplicationPreviewEditorDateMax,
|
|
|
|
|
|
resolveInlineApplicationPreviewEditorDateMin,
|
2026-06-22 11:58:53 +08:00
|
|
|
|
resolveInlineApplicationPreviewMissingFields,
|
|
|
|
|
|
resolveInlineApplicationPreviewRows,
|
|
|
|
|
|
startAiApplicationPreview
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|