feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- 新增 PersonalWorkbenchAiMode 组件、AI 侧边栏与 orb 机器人视觉资源 - 新增 aiApplicationDraftModel / aiExpenseDraftModel / aiWorkbenchConversationStore 及业务准入 aiSidebarBusinessAccess,支撑 AI 模式下的申请与报销草稿 - 顶栏、侧边栏、工作台样式重构,适配 AI 模式切换与响应式布局 - 同步 steward plan/off_topic、差旅报销引导流、风险建议卡片等测试
This commit is contained in:
80
web/tests/workbench-ai-mode-expense-scene-action.test.mjs
Normal file
80
web/tests/workbench-ai-mode-expense-scene-action.test.mjs
Normal file
@@ -0,0 +1,80 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { buildExpenseSceneSelectionActions } from '../src/utils/expenseAssistantActions.js'
|
||||
import { buildExpenseSceneSelectionMessage } from '../src/views/scripts/travelReimbursementConversationModel.js'
|
||||
|
||||
const aiMode = readFileSync(
|
||||
fileURLToPath(new URL('../src/components/business/PersonalWorkbenchAiMode.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
test('expense scene selection message asks for type first and mentions application gate', () => {
|
||||
const text = buildExpenseSceneSelectionMessage('帮我发起一笔报销,并检查需要准备哪些票据材料。')
|
||||
|
||||
assert.match(text, /先选|选择.*报销类型|报销场景/)
|
||||
assert.match(text, /差旅|招待|申请单|关联申请单/)
|
||||
})
|
||||
|
||||
test('expense scene actions mark travel and meal as requiring application', () => {
|
||||
const actions = buildExpenseSceneSelectionActions('帮我发起一笔报销,并检查需要准备哪些票据材料。')
|
||||
const travel = actions.find((action) => action.payload.expense_type === 'travel')
|
||||
const meal = actions.find((action) => action.payload.expense_type === 'meal')
|
||||
const transport = actions.find((action) => action.payload.expense_type === 'transport')
|
||||
|
||||
assert.equal(travel.payload.requires_application_before_reimbursement, true)
|
||||
assert.equal(travel.payload.next_session_type, 'application')
|
||||
assert.equal(meal.payload.requires_application_before_reimbursement, true)
|
||||
assert.equal(meal.payload.next_session_type, 'application')
|
||||
assert.equal(transport.payload.requires_application_before_reimbursement, false)
|
||||
assert.equal(transport.payload.next_session_type, 'expense')
|
||||
})
|
||||
|
||||
test('AI mode quick reimbursement card opens scene selection before steward plan', () => {
|
||||
assert.match(
|
||||
aiMode,
|
||||
/function runAiModeAction\(item\) {[\s\S]{0,220}pushInlineExpenseSceneSelectionPrompt\(item\.prompt, item\.label\)/
|
||||
)
|
||||
})
|
||||
|
||||
test('AI mode expense scene selection stays in the inline conversation without opening the create view', () => {
|
||||
assert.match(aiMode, /actionType === 'select_expense_type'/)
|
||||
assert.doesNotMatch(aiMode, /emit\('open-assistant'/)
|
||||
})
|
||||
|
||||
test('AI mode offers an inline application shortcut when no candidate application exists', () => {
|
||||
assert.match(aiMode, /!candidates\.length/)
|
||||
assert.match(aiMode, /ai_application_start_inline/)
|
||||
assert.match(aiMode, /buildRequiredApplicationMissingText/)
|
||||
assert.match(aiMode, /function startAiApplicationDraft/)
|
||||
})
|
||||
|
||||
test('AI mode steward reimbursement action opens expense scene selection locally', () => {
|
||||
assert.match(aiMode, /buildExpenseSceneSelectionMessage/)
|
||||
assert.match(aiMode, /buildExpenseSceneSelectionActions/)
|
||||
assert.match(aiMode, /SESSION_TYPE_EXPENSE/)
|
||||
assert.match(aiMode, /function pushInlineExpenseSceneSelectionPrompt/)
|
||||
assert.match(aiMode, /payload\?\.session_type[\s\S]*SESSION_TYPE_EXPENSE/)
|
||||
assert.match(aiMode, /pushInlineExpenseSceneSelectionPrompt\(carryText, action\.label\)/)
|
||||
assert.match(
|
||||
aiMode,
|
||||
/SESSION_TYPE_EXPENSE[\s\S]{0,140}pushInlineExpenseSceneSelectionPrompt\(carryText, action\.label\)[\s\S]{0,40}return/
|
||||
)
|
||||
})
|
||||
|
||||
test('AI mode attaches required application lookup result before steward planning', () => {
|
||||
assert.match(aiMode, /async function attachAiRequiredApplicationGate\(planRequest, prompt\)/)
|
||||
assert.match(aiMode, /fetchExpenseClaims\(\)/)
|
||||
assert.match(aiMode, /filterRequiredApplicationCandidates\(claims, 'travel', currentUser\.value \|\| \{\}\)/)
|
||||
assert.match(aiMode, /required_application_gate/)
|
||||
assert.match(aiMode, /await attachAiRequiredApplicationGate\(planRequest, prompt\)/)
|
||||
})
|
||||
|
||||
test('AI mode automatically continues required application gate decisions from steward plan', () => {
|
||||
assert.match(aiMode, /function continueAiRequiredApplicationGateFromPlan\(normalizedPlan\)/)
|
||||
assert.match(aiMode, /flow\.flowId === 'travel_application'[\s\S]*startAiApplicationDraft\('travel', '差旅费'/)
|
||||
assert.match(aiMode, /flow\.flowId === 'travel_reimbursement'[\s\S]*startAiExpenseDraft\('travel', '差旅费', true/)
|
||||
assert.match(aiMode, /continueAiRequiredApplicationGateFromPlan\(normalizedPlan\)/)
|
||||
})
|
||||
Reference in New Issue
Block a user