useWorkbenchAiApplicationPreviewFlow 在自动保存草稿分支透传 onApplicationActionCompleted, 确保草稿/提交完成后仍能按 remaining tasks 推进,修复多 task 报销场景后续步骤不启动。 更新 application-context-submit/intent-planner-model 测试,补充 bug 日志与开发日志。
234 lines
8.1 KiB
JavaScript
234 lines
8.1 KiB
JavaScript
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
|
||
}
|
||
})
|