import { parseAiDocumentDetailHref } from '../../utils/aiDocumentDetailReference.js' 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', '审批中', '已审批', '已完成', '已付款', '已归档', '已删除', '已驳回', '已退回' ]) const DOCUMENT_DETAIL_LINK_RE = /]*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)) { 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 } 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)) { 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 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() 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 } }] } } 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 } } }) } }