- 新增 aiApplicationPrecheckModel/aiDocumentQueryModel/aiApplicationPreviewActions/aiConversationHtmlRenderer 四个独立模型与服务,按职责从主组件拆出 - PersonalWorkbenchAiMode 接入拆分后的预审、文档查询与 HTML 渲染逻辑,配合 markdown 工具增强结构化展示 - 文档中心与归档筛选、风险可见性、申请预览等工具同步适配,补充对应单元测试 - 新增 AI 文档卡片背景资源
106 lines
5.7 KiB
JavaScript
106 lines
5.7 KiB
JavaScript
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 startAiApplicationPreview/)
|
|
assert.match(aiMode, /buildLocalApplicationPreview/)
|
|
assert.match(aiMode, /buildLocalApplicationPreviewMessage/)
|
|
assert.match(aiMode, /refreshApplicationPreviewEstimate/)
|
|
assert.match(aiMode, /applicationPreview:\s*preview/)
|
|
assert.doesNotMatch(aiMode, /function startAiApplicationDraft/)
|
|
assert.doesNotMatch(aiMode, /buildAiApplicationStepPrompt/)
|
|
})
|
|
|
|
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 handles document query prompts locally before steward planning', () => {
|
|
assert.match(aiMode, /resolveAiDocumentQueryIntent\(prompt/)
|
|
assert.match(aiMode, /async function handleAiDocumentQueryIntent/)
|
|
assert.match(aiMode, /buildAiDocumentQueryConditionSummary/)
|
|
assert.match(aiMode, /filterAiDocumentQueryRecords\(payload, intent\)/)
|
|
assert.match(aiMode, /fetchApprovalExpenseClaims/)
|
|
assert.match(aiMode, /buildAiDocumentQueryMessage/)
|
|
assert.match(aiMode, /AI_DOCUMENT_QUERY_STEP_DELAY_MS/)
|
|
assert.match(aiMode, /async function updateAiDocumentQueryThinking/)
|
|
assert.match(aiMode, /解析自然语言筛选条件/)
|
|
assert.match(aiMode, /查询业务单据接口/)
|
|
assert.match(aiMode, /组合筛选单据/)
|
|
assert.match(aiMode, /if \(await handleAiDocumentQueryIntent\(prompt, pendingMessage\)\) \{[\s\S]*return[\s\S]*\}/)
|
|
assert.match(aiMode, /emit\('open-document', buildAiDocumentDetailRequest\(detailReference\)\)/)
|
|
})
|
|
|
|
test('AI mode continues required application gate decisions into table preview from steward plan', () => {
|
|
assert.match(aiMode, /function continueAiRequiredApplicationGateFromPlan\(normalizedPlan, prompt = ''\)/)
|
|
assert.match(aiMode, /flow\.flowId === 'travel_application'[\s\S]*void startAiApplicationPreview\('travel', '差旅费', prompt/)
|
|
assert.match(aiMode, /flow\.flowId === 'travel_reimbursement'[\s\S]*startAiExpenseDraft\('travel', '差旅费', true/)
|
|
assert.match(aiMode, /continueAiRequiredApplicationGateFromPlan\(normalizedPlan, prompt\)/)
|
|
assert.match(aiMode, /class="workbench-ai-application-preview application-preview-shell"/)
|
|
assert.match(aiMode, /resolveInlineApplicationPreviewRows\(message\)/)
|
|
assert.match(aiMode, /commitInlineApplicationPreviewEditor\(message\)/)
|
|
})
|