feat: 新增风险图谱算法与系统仪表盘及操作反馈体系
后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL 校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计, 优化 agent 运行和编排执行链路,清理旧开发文档,前端新增 系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈 对话框和工作台日期选择器,优化报销创建和审批详情交互, 补充单元测试覆盖。
This commit is contained in:
@@ -3,12 +3,20 @@ import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import {
|
||||
buildOperationFeedbackPayload,
|
||||
normalizeOperationFeedbackContext
|
||||
} from '../src/composables/useOperationFeedback.js'
|
||||
|
||||
import {
|
||||
SESSION_TYPE_APPLICATION,
|
||||
SESSION_TYPE_EXPENSE,
|
||||
SESSION_TYPE_KNOWLEDGE,
|
||||
buildMessageMeta,
|
||||
buildWelcomeInsight,
|
||||
buildWelcomeMessage
|
||||
buildWelcomeMessage,
|
||||
createMessage,
|
||||
filterVisibleMessageMeta
|
||||
} from '../src/views/scripts/travelReimbursementConversationModel.js'
|
||||
|
||||
const appShellRouteView = readFileSync(
|
||||
@@ -23,10 +31,26 @@ const assistantScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/TravelReimbursementCreateView.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const assistantSubmitComposerScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const assistantTemplate = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/TravelReimbursementCreateView.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const messageItemTemplate = readFileSync(
|
||||
fileURLToPath(new URL('../src/components/travel/TravelReimbursementMessageItem.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const chatViewTemplate = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/ChatView.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const operationFeedbackInlineTemplate = readFileSync(
|
||||
fileURLToPath(new URL('../src/components/shared/OperationFeedbackInlineCard.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
test('application and reimbursement entries open the same financial assistant modal', () => {
|
||||
assert.match(appShellRouteView, /<TravelReimbursementCreateView[\s\S]*:entry-source="smartEntryContext\.source"/)
|
||||
@@ -45,7 +69,8 @@ test('application entry keeps its own assistant source without creating a separa
|
||||
})
|
||||
|
||||
test('financial assistant toolbar renders four isolated assistant sessions', () => {
|
||||
assert.match(assistantScript, /ASSISTANT_SESSION_MODE_OPTIONS\.map/)
|
||||
assert.match(assistantScript, /filterAssistantSessionModes\(ASSISTANT_SESSION_MODE_OPTIONS, currentUser\.value\)/)
|
||||
assert.match(assistantScript, /visibleModes\.map/)
|
||||
assert.match(assistantScript, /targetSessionType:\s*mode\.key/)
|
||||
assert.match(assistantScript, /active:\s*mode\.key === activeSessionType\.value/)
|
||||
assert.match(assistantTemplate, /:class="\{ active: shortcut\.active \}"/)
|
||||
@@ -79,3 +104,88 @@ test('financial assistant welcome copy differentiates application intent from re
|
||||
assert.equal(applicationInsight.metricValue, '申请助手')
|
||||
assert.equal(applicationInsight.title, '申请助手')
|
||||
})
|
||||
|
||||
test('assistant message meta hides internal routing and permission chips', () => {
|
||||
const meta = buildMessageMeta(
|
||||
{
|
||||
selected_agent: 'user_agent',
|
||||
permission_level: 'draft_write',
|
||||
run_id: 'run-001',
|
||||
trace_summary: {
|
||||
tool_count: 3,
|
||||
degraded: true
|
||||
},
|
||||
requires_confirmation: true
|
||||
},
|
||||
['invoice.pdf']
|
||||
)
|
||||
|
||||
assert.deepEqual(meta, ['已降级', '待确认', '附件: 1'])
|
||||
assert.deepEqual(
|
||||
filterVisibleMessageMeta(['Agent: user_agent', '权限: draft_write', 'Run: run-001', '工具: 3', '等待确认']),
|
||||
['等待确认']
|
||||
)
|
||||
assert.deepEqual(
|
||||
createMessage('assistant', '测试', [], { meta: ['Agent: user_agent', '权限: draft_write', '处理中'] }).meta,
|
||||
['处理中']
|
||||
)
|
||||
assert.doesNotMatch(messageItemTemplate, /message-meta-row|message-meta-chip/)
|
||||
assert.doesNotMatch(chatViewTemplate, /agent-meta-row|agent-meta-chip/)
|
||||
})
|
||||
|
||||
test('assistant operation feedback is inline and persists run context', () => {
|
||||
assert.doesNotMatch(appShellRouteView, /<OperationFeedbackDialog/)
|
||||
assert.doesNotMatch(appShellRouteView, /@operation-completed="handleOperationCompleted"/)
|
||||
assert.doesNotMatch(appShellComposable, /useOperationFeedback/)
|
||||
assert.match(messageItemTemplate, /<OperationFeedbackInlineCard/)
|
||||
assert.match(messageItemTemplate, /class="message-feedback-bubble"/)
|
||||
assert.match(messageItemTemplate, /:submitted="Boolean\(message\.operationFeedback\?\.submitted\)"/)
|
||||
assert.match(messageItemTemplate, /:submitted-rating="Number\(message\.operationFeedback\?\.rating \|\| 0\)"/)
|
||||
assert.match(assistantScript, /emits:\s*\['close', 'draft-saved', 'request-updated'\]/)
|
||||
assert.match(appShellRouteView, /@request-updated="handleRequestUpdated"/)
|
||||
assert.match(assistantScript, /function submitOperationFeedbackForMessage/)
|
||||
assert.match(assistantScript, /createOperationFeedback/)
|
||||
assert.match(assistantScript, /normalizeOperationFeedbackContext/)
|
||||
assert.match(assistantScript, /&& !feedback\.dismissed/)
|
||||
assert.doesNotMatch(assistantScript, /&& !feedback\.submitted/)
|
||||
assert.match(assistantScript, /submitted:\s*true/)
|
||||
assert.match(assistantScript, /dismissed:\s*false/)
|
||||
assert.doesNotMatch(assistantScript, /emit\('operation-completed'/)
|
||||
assert.match(assistantSubmitComposerScript, /emitOperationCompleted\?\.\(payload/)
|
||||
assert.match(assistantSubmitComposerScript, /operationFeedback:\s*buildOperationFeedbackState/)
|
||||
assert.match(assistantSubmitComposerScript, /rating:\s*0/)
|
||||
assert.match(operationFeedbackInlineTemplate, /v-for="option in ratingOptions"/)
|
||||
assert.match(operationFeedbackInlineTemplate, /is-submitted/)
|
||||
assert.match(operationFeedbackInlineTemplate, /submittedRating/)
|
||||
assert.match(operationFeedbackInlineTemplate, /感谢您的反馈。谢谢/)
|
||||
assert.match(operationFeedbackInlineTemplate, /busy \|\| submitted/)
|
||||
assert.match(operationFeedbackInlineTemplate, /role="radiogroup"/)
|
||||
assert.match(operationFeedbackInlineTemplate, /handleRatingKeydown/)
|
||||
assert.match(operationFeedbackInlineTemplate, /operation-feedback-stars/)
|
||||
assert.match(operationFeedbackInlineTemplate, /score > 3/)
|
||||
assert.match(operationFeedbackInlineTemplate, /v-if="showReasonInput"/)
|
||||
assert.match(operationFeedbackInlineTemplate, /稍后/)
|
||||
|
||||
const context = normalizeOperationFeedbackContext(
|
||||
{
|
||||
run_id: 'run-001',
|
||||
conversation_id: 'conv-001',
|
||||
selected_agent: 'user_agent',
|
||||
session_type: 'application',
|
||||
operation_status: 'succeeded',
|
||||
route_reason: 'model_route',
|
||||
result: { answer: '处理完成' }
|
||||
},
|
||||
{ username: 'wenjing.li' }
|
||||
)
|
||||
const payload = buildOperationFeedbackPayload(context, { rating: 2, reason: '识别错了' })
|
||||
|
||||
assert.equal(context.runId, 'run-001')
|
||||
assert.equal(context.userId, 'wenjing.li')
|
||||
assert.equal(payload.run_id, 'run-001')
|
||||
assert.equal(payload.conversation_id, 'conv-001')
|
||||
assert.equal(payload.agent, 'user_agent')
|
||||
assert.equal(payload.rating, 2)
|
||||
assert.equal(payload.reason, '识别错了')
|
||||
assert.equal(payload.context_json.low_rating, true)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user