feat(web): AI 工作台多 task 串行推进与会话适配

- useWorkbenchAiApplicationPreviewFlow/useWorkbenchAiActionRouter/useWorkbenchAiCommandIntents 支持 task1 完成后自动推进 task2,确认按钮直接拉起申请预览,草稿/提交成功后继续推进下一 task
- workbenchAiIntentPlannerModel/workbenchAiMessageModel/workbenchAiCommandIntentModel 适配多 task 意图规划与消息结构
- aiApplicationPreviewActions/aiApplicationPrecheckModel/aiExpenseDraftModel/aiWorkbenchConversationStore 草稿与会话存储适配
- PersonalWorkbenchAiMode 与样式适配,更新 preview-actions/expense-draft/conversation-store/fast-preview/action-router/command-intent/intent-planner 测试
This commit is contained in:
caoxiaozhu
2026-06-26 22:42:23 +08:00
parent 5753899eb3
commit c4b5fcc067
22 changed files with 1171 additions and 144 deletions

View File

@@ -1,3 +1,7 @@
import {
parseAiDocumentDetailHref
} from '../../utils/aiDocumentDetailReference.js'
const DRAFT_DELETION_ACTION_PATTERN = /删除|删掉|删了|移除|作废|撤销/
const DRAFT_DELETION_TARGET_PATTERN = (
/草稿|这个单据|这张单据|当前单据|当前申请|当前报销|刚才保存的草稿|刚才的草稿|上面的单据|最近的单据|申请单|报销单/
@@ -22,11 +26,26 @@ const SUBMITTED_OR_FINAL_STATUS = new Set([
'已驳回',
'已退回'
])
const DOCUMENT_DETAIL_LINK_RE = /<a\b[^>]*href="([^"]*#ai-open-document-detail:[^"]+)"[^>]*>(.*?)<\/a>/g
const DOCUMENT_COMMAND_ACTION_LABELS = {
delete: '删除',
approve: '审核通过',
reject: '驳回/退回'
}
const DOCUMENT_COMMAND_DETAIL_LABELS = {
delete: '进入详情确认删除',
approve: '进入详情确认审核',
reject: '进入详情确认驳回'
}
function normalizeCompactText(value = '') {
return String(value || '').replace(/\s+/g, '').trim()
}
function normalizeText(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)) {
@@ -77,6 +96,60 @@ function extractDraftPayloadFromSuggestedActions(message = {}) {
return null
}
function stripHtml(value = '') {
return normalizeText(String(value || '').replace(/<[^>]*>/g, ''))
}
function normalizeDocumentCommandCandidate(detailReference = null, rawLabel = '') {
if (!detailReference || typeof detailReference !== 'object') {
return null
}
const claimId = String(detailReference.claimId || detailReference.claim_id || '').trim()
const claimNo = String(detailReference.claimNo || detailReference.claim_no || detailReference.reference || '').trim()
if (!claimId && !claimNo) {
return null
}
return {
claimId,
claimNo,
documentType: normalizeDraftDocumentType(detailReference, claimNo),
actionLabel: stripHtml(rawLabel) || '查看详情'
}
}
function extractDocumentCommandCandidatesFromContent(content = '') {
const text = String(content || '')
const candidates = []
const seen = new Set()
for (const match of text.matchAll(DOCUMENT_DETAIL_LINK_RE)) {
const candidate = normalizeDocumentCommandCandidate(
parseAiDocumentDetailHref(match[1]),
match[2]
)
if (!candidate) {
continue
}
const key = `${candidate.claimId || ''}:${candidate.claimNo || ''}`
if (seen.has(key)) {
continue
}
seen.add(key)
candidates.push(candidate)
}
return candidates
}
function canReuseDocumentCommandContext(content = '', commandFrame = {}) {
const action = String(commandFrame?.action || '').trim()
if (!['approve', 'reject'].includes(action)) {
return false
}
if (String(commandFrame?.safetyLevel || '').trim() !== 'confirm_required') {
return false
}
return /ai-document-card--approval-task|待我审核|待审|待审批|待审核|确认审核|进入详情确认审核/.test(String(content || ''))
}
export function isWorkbenchDraftDeletionIntent(prompt = '') {
const compact = normalizeCompactText(prompt)
if (!compact || !DRAFT_DELETION_ACTION_PATTERN.test(compact)) {
@@ -104,6 +177,29 @@ export function resolveLatestWorkbenchDraftPayload(messages = []) {
return null
}
export function resolveLatestWorkbenchDocumentCommandContext(messages = [], commandFrame = {}) {
const safeMessages = Array.isArray(messages) ? messages : []
for (const message of [...safeMessages].reverse()) {
if (String(message?.role || '').trim() !== 'assistant') {
continue
}
const content = String(message?.content || message?.text || '')
if (!canReuseDocumentCommandContext(content, commandFrame)) {
continue
}
const candidates = extractDocumentCommandCandidatesFromContent(content)
if (!candidates.length) {
continue
}
return {
sourceMessageId: String(message?.id || '').trim(),
action: String(commandFrame?.action || '').trim(),
candidates
}
}
return null
}
export function buildWorkbenchDraftDeletionGuidance(draftPayload = {}) {
const claimNo = String(draftPayload.claimNo || draftPayload.claim_no || '').trim()
const claimId = String(draftPayload.claimId || draftPayload.claim_id || '').trim()
@@ -128,3 +224,42 @@ export function buildWorkbenchDraftDeletionGuidance(draftPayload = {}) {
}]
}
}
export function buildWorkbenchDocumentCommandFollowupGuidance(context = {}, commandFrame = {}) {
const action = String(commandFrame?.action || context?.action || '').trim()
const actionLabel = DOCUMENT_COMMAND_ACTION_LABELS[action] || '处理'
const detailLabel = DOCUMENT_COMMAND_DETAIL_LABELS[action] || '进入详情确认'
const candidates = Array.isArray(context?.candidates) ? context.candidates : []
const visibleCandidates = candidates.slice(0, 8)
const candidateLines = visibleCandidates.map((candidate, index) => {
const reference = candidate.claimNo || candidate.claimId || `候选 ${index + 1}`
return `${index + 1}. ${reference}`
})
const overflowText = candidates.length > visibleCandidates.length
? `\n\n还有 ${candidates.length - visibleCandidates.length} 张候选未展示,请先补充更具体条件。`
: ''
return {
content: [
'### 已接上刚才查询到的待审单据',
`您想继续执行 **${actionLabel}**。这属于高风险审批动作,我不会直接替您通过或驳回。`,
'请先从刚才的候选单据中选择一张,进入详情页核对风险、金额和审批节点后再确认。',
candidateLines.length ? candidateLines.join('\n') : '',
overflowText
].filter(Boolean).join('\n\n'),
suggestedActions: visibleCandidates.map((candidate) => {
const reference = candidate.claimNo || candidate.claimId || '单据'
return {
label: `${detailLabel} ${reference}`,
description: '打开详情页核对后,再完成审批确认。',
icon: 'mdi mdi-open-in-new',
action_type: 'open_application_detail',
payload: {
claim_id: candidate.claimId,
claim_no: candidate.claimNo,
document_type: candidate.documentType,
command_action: action
}
}
})
}
}