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:
166
web/tests/workbench-ai-application-context-submit.test.mjs
Normal file
166
web/tests/workbench-ai-application-context-submit.test.mjs
Normal file
@@ -0,0 +1,166 @@
|
||||
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) {
|
||||
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: () => {}
|
||||
})
|
||||
|
||||
return {
|
||||
applicationSubmitConfirmOpen,
|
||||
conversationMessages,
|
||||
flow,
|
||||
persisted
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user