feat(web): AI 工作台命令意图解析与动作策略
- 新增 workbenchIntentFrameModel,解析删除/审批/查询/咨询政策等意图帧,含风险等级、目标模式(当前上下文/筛选候选)与日期归一化 - 新增 workbenchIntentActionPolicy,按意图帧路由下一步(直通/拦截批量/上下文确认/查询候选) - 新增 workbenchAiCommandIntentModel 与 useWorkbenchAiCommandIntents,识别草稿删除意图并解析最近草稿载荷生成引导 - usePersonalWorkbenchAiMode 接入命令意图处理,personal-workbench-ai-mode.css 适配 - 新增 command-intent-model/intent-frame-model 测试
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
const DRAFT_DELETION_ACTION_PATTERN = /删除|删掉|删了|移除|作废|撤销/
|
||||
const DRAFT_DELETION_TARGET_PATTERN = (
|
||||
/草稿|这个单据|这张单据|当前单据|当前申请|当前报销|刚才保存的草稿|刚才的草稿|上面的单据|最近的单据|申请单|报销单/
|
||||
)
|
||||
const NON_DRAFT_DELETE_TARGET_PATTERN = /附件|票据|发票|图片|文件|明细|费用行/
|
||||
const DELETABLE_DRAFT_STATUS = new Set(['', 'draft', 'pending', '待提交', '草稿'])
|
||||
const SUBMITTED_OR_FINAL_STATUS = new Set([
|
||||
'submitted',
|
||||
'approved',
|
||||
'completed',
|
||||
'paid',
|
||||
'archived',
|
||||
'deleted',
|
||||
'rejected',
|
||||
'returned',
|
||||
'审批中',
|
||||
'已审批',
|
||||
'已完成',
|
||||
'已付款',
|
||||
'已归档',
|
||||
'已删除',
|
||||
'已驳回',
|
||||
'已退回'
|
||||
])
|
||||
|
||||
function normalizeCompactText(value = '') {
|
||||
return String(value || '').replace(/\s+/g, '').trim()
|
||||
}
|
||||
|
||||
function normalizeDraftDocumentType(payload = {}, claimNo = '') {
|
||||
const rawType = String(payload.document_type || payload.documentType || payload.draft_type || payload.draftType || '').trim()
|
||||
if (/application|expense_application|申请/.test(rawType)) {
|
||||
return 'application'
|
||||
}
|
||||
if (/reimbursement|expense|报销/.test(rawType)) {
|
||||
return 'reimbursement'
|
||||
}
|
||||
return /^A/i.test(String(claimNo || '').trim()) ? 'application' : 'reimbursement'
|
||||
}
|
||||
|
||||
function normalizeDraftPayload(payload = null, sourceText = '') {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return null
|
||||
}
|
||||
const claimId = String(payload.claim_id || payload.claimId || payload.id || '').trim()
|
||||
const claimNo = String(payload.claim_no || payload.claimNo || payload.document_no || payload.documentNo || '').trim()
|
||||
if (!claimId && !claimNo) {
|
||||
return null
|
||||
}
|
||||
const status = String(payload.status || payload.status_label || payload.statusLabel || '').trim()
|
||||
if (SUBMITTED_OR_FINAL_STATUS.has(status.toLowerCase()) || SUBMITTED_OR_FINAL_STATUS.has(status)) {
|
||||
return null
|
||||
}
|
||||
if (!DELETABLE_DRAFT_STATUS.has(status.toLowerCase()) && !DELETABLE_DRAFT_STATUS.has(status) && !/草稿/.test(sourceText)) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
claimId,
|
||||
claimNo,
|
||||
status: status || 'draft',
|
||||
documentType: normalizeDraftDocumentType(payload, claimNo)
|
||||
}
|
||||
}
|
||||
|
||||
function extractDraftPayloadFromSuggestedActions(message = {}) {
|
||||
const actions = Array.isArray(message?.suggestedActions) ? message.suggestedActions : []
|
||||
for (const action of [...actions].reverse()) {
|
||||
const actionType = String(action?.action_type || action?.actionType || '').trim()
|
||||
if (actionType !== 'open_application_detail') {
|
||||
continue
|
||||
}
|
||||
const payload = normalizeDraftPayload(action.payload, String(message.content || message.text || ''))
|
||||
if (payload) {
|
||||
return payload
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function isWorkbenchDraftDeletionIntent(prompt = '') {
|
||||
const compact = normalizeCompactText(prompt)
|
||||
if (!compact || !DRAFT_DELETION_ACTION_PATTERN.test(compact)) {
|
||||
return false
|
||||
}
|
||||
if (NON_DRAFT_DELETE_TARGET_PATTERN.test(compact) && !/草稿|单据|申请单|报销单/.test(compact)) {
|
||||
return false
|
||||
}
|
||||
return DRAFT_DELETION_TARGET_PATTERN.test(compact)
|
||||
}
|
||||
|
||||
export function resolveLatestWorkbenchDraftPayload(messages = []) {
|
||||
const safeMessages = Array.isArray(messages) ? messages : []
|
||||
for (const message of [...safeMessages].reverse()) {
|
||||
const sourceText = String(message?.content || message?.text || '')
|
||||
const actionPayload = extractDraftPayloadFromSuggestedActions(message)
|
||||
if (actionPayload) {
|
||||
return actionPayload
|
||||
}
|
||||
const draftPayload = normalizeDraftPayload(message?.draftPayload, sourceText)
|
||||
if (draftPayload) {
|
||||
return draftPayload
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function buildWorkbenchDraftDeletionGuidance(draftPayload = {}) {
|
||||
const claimNo = String(draftPayload.claimNo || draftPayload.claim_no || '').trim()
|
||||
const claimId = String(draftPayload.claimId || draftPayload.claim_id || '').trim()
|
||||
const documentType = String(draftPayload.documentType || draftPayload.document_type || 'reimbursement').trim()
|
||||
const reference = claimNo || claimId || '最近这张草稿'
|
||||
return {
|
||||
content: [
|
||||
'### 已识别到您想删除草稿',
|
||||
`我找到了最近这张草稿:**${reference}**。`,
|
||||
'删除草稿会影响单据和附件关联,我不会直接替您删除。请先打开详情页,在详情页点击 **删除草稿** 并完成二次确认。'
|
||||
].join('\n\n'),
|
||||
suggestedActions: [{
|
||||
label: claimNo ? `查看草稿 ${claimNo}` : '查看草稿详情',
|
||||
description: '打开详情页后可点击删除草稿并二次确认。',
|
||||
icon: 'mdi mdi-open-in-new',
|
||||
action_type: 'open_application_detail',
|
||||
payload: {
|
||||
claim_id: claimId,
|
||||
claim_no: claimNo,
|
||||
document_type: documentType
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user