feat: 完善文档中心与报销申请交互及侧边栏重构
后端优化编排器报销查询和本体检测精度,增强报销单草稿保 存和附件回填逻辑,前端重构侧边栏组件支持折叠和图标导 航,完善文档中心状态筛选和详情提示,报销创建和审批详情 页优化会话管理和费用明细交互,新增助手应用服务和预设动 作工具函数,补充单元测试覆盖。
This commit is contained in:
173
web/src/utils/assistantSessionScope.js
Normal file
173
web/src/utils/assistantSessionScope.js
Normal file
@@ -0,0 +1,173 @@
|
||||
export const ASSISTANT_SCOPE_ACTION_SWITCH = 'switch_assistant_session'
|
||||
|
||||
export const ASSISTANT_SCOPE_SESSION_APPLICATION = 'application'
|
||||
export const ASSISTANT_SCOPE_SESSION_EXPENSE = 'expense'
|
||||
export const ASSISTANT_SCOPE_SESSION_APPROVAL = 'approval'
|
||||
export const ASSISTANT_SCOPE_SESSION_KNOWLEDGE = 'knowledge'
|
||||
|
||||
const SESSION_SCOPE_CONFIG = {
|
||||
[ASSISTANT_SCOPE_SESSION_APPLICATION]: {
|
||||
label: '申请助手',
|
||||
icon: 'mdi mdi-file-plus-outline',
|
||||
scope: '费用申请、事前审批、申请材料清单、申请单状态查询'
|
||||
},
|
||||
[ASSISTANT_SCOPE_SESSION_EXPENSE]: {
|
||||
label: '报销助手',
|
||||
icon: 'mdi mdi-receipt-text-plus-outline',
|
||||
scope: '发起报销、票据识别、草稿归集、报销单状态查询和报销信息核对'
|
||||
},
|
||||
[ASSISTANT_SCOPE_SESSION_APPROVAL]: {
|
||||
label: '审核助手',
|
||||
icon: 'mdi mdi-clipboard-check-outline',
|
||||
scope: '待审单据查询、审批动作、风险解释和审核意见草稿'
|
||||
},
|
||||
[ASSISTANT_SCOPE_SESSION_KNOWLEDGE]: {
|
||||
label: '财务知识助手',
|
||||
icon: 'mdi mdi-book-open-page-variant-outline',
|
||||
scope: '财务制度、报销标准、票据要求、流程规则和政策口径解释'
|
||||
}
|
||||
}
|
||||
|
||||
const SESSION_SCOPE_TYPES = Object.keys(SESSION_SCOPE_CONFIG)
|
||||
|
||||
const APPLICATION_PATTERN =
|
||||
/费用申请|发起申请|申请单|事前申请|事前审批|前置审批|出差申请|采购申请|用款申请|预算申请|申请材料|材料清单|先申请|立项申请/
|
||||
const EXPENSE_PATTERN =
|
||||
/报销|报销单|票据|发票|火车票|高铁票|机票|飞机票|的士票|出租车|网约车|酒店票|住宿票|住宿单据|保存草稿|草稿|费用明细|归集|上传.*票|关联单据|继续下一步/
|
||||
const APPROVAL_PATTERN =
|
||||
/待我审核|待审|审核|审批|审核意见|审批意见|审批通过|审批驳回|驳回|退回|审核中心|审批中心|领导审批|财务审核|处理意见/
|
||||
const KNOWLEDGE_PATTERN =
|
||||
/制度|政策|标准|规则|规定|流程|口径|依据|上限|额度|补贴|住宿标准|差旅标准|报销标准|票据要求|可不可以|能不能|怎么规定|如何计算|怎么算/
|
||||
const EXPENSE_OPERATION_PATTERN = /发起报销|报销单|票据|发票|火车票|高铁票|机票|的士票|草稿|归集|上传|关联单据|继续下一步/
|
||||
const CURRENT_CLAIM_RISK_PATTERN = /这张|当前|本单|该单|单据|风险|超标|异常|重复|待补/
|
||||
|
||||
function normalizeSessionType(sessionType) {
|
||||
const normalized = String(sessionType || '').trim()
|
||||
return SESSION_SCOPE_TYPES.includes(normalized) ? normalized : ASSISTANT_SCOPE_SESSION_EXPENSE
|
||||
}
|
||||
|
||||
function normalizeText(rawText) {
|
||||
return String(rawText || '')
|
||||
.replace(/\s+/g, '')
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function resolveScopeConfig(sessionType) {
|
||||
return SESSION_SCOPE_CONFIG[normalizeSessionType(sessionType)] || SESSION_SCOPE_CONFIG[ASSISTANT_SCOPE_SESSION_EXPENSE]
|
||||
}
|
||||
|
||||
export function inferAssistantScopeTarget(rawText, options = {}) {
|
||||
const text = normalizeText(rawText)
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const applicationMatched = APPLICATION_PATTERN.test(text)
|
||||
const expenseMatched = EXPENSE_PATTERN.test(text)
|
||||
const approvalMatched = APPROVAL_PATTERN.test(text)
|
||||
const knowledgeMatched = KNOWLEDGE_PATTERN.test(text)
|
||||
|
||||
if (approvalMatched && /(待我审核|待审|审核意见|审批意见|审批通过|审批驳回|驳回|退回|审核中心|审批中心|处理意见)/.test(text)) {
|
||||
return ASSISTANT_SCOPE_SESSION_APPROVAL
|
||||
}
|
||||
|
||||
if (knowledgeMatched && !options.hasActiveReviewPayload && !EXPENSE_OPERATION_PATTERN.test(text)) {
|
||||
return ASSISTANT_SCOPE_SESSION_KNOWLEDGE
|
||||
}
|
||||
|
||||
if (expenseMatched && !applicationMatched) {
|
||||
return ASSISTANT_SCOPE_SESSION_EXPENSE
|
||||
}
|
||||
|
||||
if (applicationMatched && !expenseMatched) {
|
||||
return ASSISTANT_SCOPE_SESSION_APPLICATION
|
||||
}
|
||||
|
||||
if (knowledgeMatched && !expenseMatched && !approvalMatched && !applicationMatched) {
|
||||
return ASSISTANT_SCOPE_SESSION_KNOWLEDGE
|
||||
}
|
||||
|
||||
if (knowledgeMatched && !options.hasActiveReviewPayload) {
|
||||
return ASSISTANT_SCOPE_SESSION_KNOWLEDGE
|
||||
}
|
||||
|
||||
if (approvalMatched) {
|
||||
return ASSISTANT_SCOPE_SESSION_APPROVAL
|
||||
}
|
||||
|
||||
if (expenseMatched) {
|
||||
return ASSISTANT_SCOPE_SESSION_EXPENSE
|
||||
}
|
||||
|
||||
if (applicationMatched) {
|
||||
return ASSISTANT_SCOPE_SESSION_APPLICATION
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function shouldAllowCurrentExpensePolicyQuestion(rawText, currentSessionType, targetSessionType, options = {}) {
|
||||
if (
|
||||
normalizeSessionType(currentSessionType) !== ASSISTANT_SCOPE_SESSION_EXPENSE ||
|
||||
targetSessionType !== ASSISTANT_SCOPE_SESSION_KNOWLEDGE ||
|
||||
!options.hasActiveReviewPayload
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return CURRENT_CLAIM_RISK_PATTERN.test(normalizeText(rawText))
|
||||
}
|
||||
|
||||
function buildScopeSwitchAction(targetSessionType, rawText, options = {}) {
|
||||
const target = resolveScopeConfig(targetSessionType)
|
||||
const carryText = String(rawText || '').trim()
|
||||
|
||||
return {
|
||||
label: `切换到${target.label}`,
|
||||
description: `带着这条内容进入${target.label}继续处理`,
|
||||
icon: target.icon,
|
||||
action_type: ASSISTANT_SCOPE_ACTION_SWITCH,
|
||||
payload: {
|
||||
session_type: normalizeSessionType(targetSessionType),
|
||||
carry_text: carryText,
|
||||
carry_files: Boolean(options.attachmentCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildScopeBoundaryText(currentSessionType, targetSessionType) {
|
||||
const current = resolveScopeConfig(currentSessionType)
|
||||
const target = resolveScopeConfig(targetSessionType)
|
||||
|
||||
return [
|
||||
`我先暂停在「${current.label}」里继续处理这条消息。`,
|
||||
'',
|
||||
`当前助手的业务范围是:${current.scope}。`,
|
||||
'',
|
||||
`您这条内容更适合交给「${target.label}」处理;它的业务范围是:${target.scope}。`,
|
||||
'',
|
||||
`建议切换到「${target.label}」后继续,我会尽量把这条内容带过去,避免在错误的会话里把流程跑偏。`
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
export function resolveAssistantScopeGuard(rawText, currentSessionType, options = {}) {
|
||||
const normalizedCurrent = normalizeSessionType(currentSessionType)
|
||||
const targetSessionType = inferAssistantScopeTarget(rawText, options)
|
||||
if (!targetSessionType || targetSessionType === normalizedCurrent) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (shouldAllowCurrentExpensePolicyQuestion(rawText, normalizedCurrent, targetSessionType, options)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const target = resolveScopeConfig(targetSessionType)
|
||||
|
||||
return {
|
||||
targetSessionType,
|
||||
targetLabel: target.label,
|
||||
text: buildScopeBoundaryText(normalizedCurrent, targetSessionType),
|
||||
meta: [`建议切换至${target.label}`],
|
||||
suggestedActions: [buildScopeSwitchAction(targetSessionType, rawText, options)]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user