import assert from 'node:assert/strict' import test from 'node:test' import { useWorkbenchAiApplicationPreviewFlow } from '../src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js' function createRef(value) { return { value } } function createInlineMessage(role, content, options = {}) { return { id: options.id || `msg-${Math.random().toString(16).slice(2)}`, role, content, text: content, paragraphs: String(content || '').split(/\n+/).filter(Boolean), pending: Boolean(options.pending), ...options } } function buildApplicationPreviewFlowHarness(messages, options = {}) { const conversationMessages = createRef(messages) const applicationSubmitConfirmOpen = createRef(false) const applicationSubmitConfirmContext = createRef(null) const persisted = createRef(0) const flow = useWorkbenchAiApplicationPreviewFlow({ activateInlineConversation: () => {}, applicationPreviewEditor: createRef({}), applicationSubmitConfirmContext, applicationSubmitConfirmOpen, assistantDraft: createRef(''), cancelApplicationPreviewEditor: () => {}, clearAiModeFiles: () => {}, closeWorkbenchDatePicker: () => {}, commitApplicationPreviewEditor: async () => true, conversationId: createRef('conversation-context-submit'), conversationMessages, conversationStarted: createRef(true), createInlineMessage, currentUser: createRef({ username: 'zhangsan@example.com', name: '张三' }), handleApplicationPreviewEditorKeydown: () => {}, inlineConversationAutoScrollPinned: createRef(true), isApplicationPreviewEditing: createRef(false), openApplicationPreviewEditor: () => {}, persistCurrentConversation: () => { persisted.value += 1 }, pushInlineApplicationActionUserMessage: (text) => { conversationMessages.value.push(createInlineMessage('user', text)) }, pushInlineUserMessage: (text) => { conversationMessages.value.push(createInlineMessage('user', text)) }, refreshApplicationPreviewEstimate: async (preview) => preview, removeWorkbenchDateTag: () => {}, replaceInlineMessage: (id, nextMessage) => { const index = conversationMessages.value.findIndex((item) => item.id === id) if (index >= 0) { conversationMessages.value.splice(index, 1, nextMessage) } else { conversationMessages.value.push(nextMessage) } }, resolveApplicationPreviewEditorDateMax: () => '', resolveApplicationPreviewEditorDateMin: () => '', resolveApplicationPreviewEditorControl: () => null, resolveApplicationPreviewEditorOptions: () => [], resolveInlineThinkingEvents: (message) => message?.stewardPlan?.thinkingEvents || [], resolveLatestInlineUserPrompt: () => '2026-02-20 至 2026-02-23,去上海出差,交通火车,保存草稿', scrollInlineConversationToBottom: () => {}, sending: createRef(false), toast: () => {}, onApplicationActionCompleted: options.onApplicationActionCompleted }) return { applicationSubmitConfirmOpen, conversationMessages, flow, persisted } } test('workbench auto-saved application draft continues remaining steward task', async () => { const originalFetch = globalThis.fetch const requests = [] globalThis.fetch = async (url, options = {}) => { const normalizedUrl = String(url) if (normalizedUrl.includes('/reimbursements/application-preview-action')) { const body = JSON.parse(String(options.body || '{}')) requests.push({ url: normalizedUrl, body }) return { ok: true, async json() { return { status: 'succeeded', result: { draft_payload: { claim_id: 'claim-auto-saved-draft', claim_no: 'AEW2DDAFL', status: 'draft' } } } } } } throw new Error(`unexpected request: ${normalizedUrl}`) } try { const continuedTasks = [] const remainingTasks = [{ task_id: 'task-reimbursement-2', task_type: 'reimbursement', assigned_agent: 'reimbursement_assistant', summary: '报销昨天的业务招待费 2000 元', ontology_fields: { expense_type: 'entertainment', amount: '2000元', time_range: '2026-06-25', reason: '业务招待费报销' } }] const harness = buildApplicationPreviewFlowHarness([], { onApplicationActionCompleted: (tasks, sourceMessage) => { continuedTasks.push({ tasks, sourceMessage }) } }) await harness.flow.startAiApplicationPreview('travel', '差旅费', '2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元', { autoSaveDraft: true, stewardRemainingTasks: remainingTasks, onApplicationActionCompleted: (tasks, sourceMessage) => { continuedTasks.push({ tasks, sourceMessage, fromOptions: true }) } }) assert.equal(requests.length, 1) assert.equal(continuedTasks.length, 1) assert.deepEqual(continuedTasks[0].tasks, remainingTasks) assert.equal(continuedTasks[0].sourceMessage.stewardRemainingTasks, remainingTasks) assert.equal(continuedTasks[0].fromOptions, true) assert.doesNotMatch(harness.conversationMessages.value.at(-1).content, /继续处理费用报销/) } finally { globalThis.fetch = originalFetch } }) test('workbench saved application draft can be submitted by contextual text without re-planning', async () => { const originalFetch = globalThis.fetch const requests = [] globalThis.fetch = async (url, options = {}) => { const normalizedUrl = String(url) if (normalizedUrl.includes('/reimbursements/claims')) { return { ok: true, async json() { return { items: [] } } } } if (normalizedUrl.includes('/reimbursements/application-preview-action')) { const body = JSON.parse(String(options.body || '{}')) requests.push({ url: normalizedUrl, body }) return { ok: true, async json() { return { status: 'succeeded', result: { draft_payload: { claim_id: 'claim-saved-draft', claim_no: 'A20260220', status: 'submitted', approval_stage: '直属领导审批' } } } } } } throw new Error(`unexpected request: ${normalizedUrl}`) } try { const previewMessage = createInlineMessage('assistant', '申请核对表', { id: 'application-preview-1', applicationPreview: { readyToSubmit: true, fields: { applicationType: '差旅费用申请', time: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', days: '4天', transportMode: '火车', amount: '1200元' }, missingFields: [], validationIssues: [] }, draftPayload: { claim_id: 'claim-saved-draft', claim_no: 'A20260220', status: 'draft' } }) const harness = buildApplicationPreviewFlowHarness([ createInlineMessage('user', '2026-02-20 至 2026-02-23,去上海出差,交通火车,保存草稿'), previewMessage, createInlineMessage('assistant', '### 申请草稿已保存', { draftPayload: previewMessage.draftPayload }) ]) const handled = harness.flow.handleInlineApplicationPreviewTextAction( '提交这个单据', createRef(false) ) assert.equal(handled, true) await new Promise((resolve) => setTimeout(resolve, 0)) assert.equal(harness.applicationSubmitConfirmOpen.value, false) assert.equal(requests.length, 1) assert.equal(requests[0].body.context_json.application_edit_claim_id, 'claim-saved-draft') assert.equal(requests[0].body.context_json.application_edit_mode, true) assert.match(harness.conversationMessages.value.at(-1).content, /申请单据已生成/) assert.ok(harness.persisted.value > 0) } finally { globalThis.fetch = originalFetch } })