feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- 新增 PersonalWorkbenchAiMode 组件、AI 侧边栏与 orb 机器人视觉资源 - 新增 aiApplicationDraftModel / aiExpenseDraftModel / aiWorkbenchConversationStore 及业务准入 aiSidebarBusinessAccess,支撑 AI 模式下的申请与报销草稿 - 顶栏、侧边栏、工作台样式重构,适配 AI 模式切换与响应式布局 - 同步 steward plan/off_topic、差旅报销引导流、风险建议卡片等测试
This commit is contained in:
@@ -74,6 +74,10 @@ export function normalizeOcrDocuments(payload) {
|
||||
preview_kind: String(item.preview_kind || '').trim(),
|
||||
preview_data_url: String(item.preview_data_url || '').trim(),
|
||||
preview_url: String(item.preview_url || '').trim(),
|
||||
receipt_id: String(item.receipt_id || item.receiptId || '').trim(),
|
||||
receipt_status: String(item.receipt_status || item.receiptStatus || '').trim(),
|
||||
receipt_preview_url: String(item.receipt_preview_url || item.receiptPreviewUrl || '').trim(),
|
||||
receipt_source_url: String(item.receipt_source_url || item.receiptSourceUrl || '').trim(),
|
||||
document_fields: Array.isArray(item.document_fields)
|
||||
? item.document_fields
|
||||
.map((field) => ({
|
||||
@@ -87,6 +91,87 @@ export function normalizeOcrDocuments(payload) {
|
||||
}))
|
||||
}
|
||||
|
||||
function defineFileReceiptId(file, receiptId) {
|
||||
const normalizedReceiptId = String(receiptId || '').trim()
|
||||
if (!file || !normalizedReceiptId) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
Object.defineProperty(file, 'receiptId', {
|
||||
value: normalizedReceiptId,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
})
|
||||
return true
|
||||
} catch {
|
||||
try {
|
||||
file.receiptId = normalizedReceiptId
|
||||
return String(file.receiptId || '').trim() === normalizedReceiptId
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function attachReceiptFolderIdsToFiles(files = [], payload = null) {
|
||||
const safeFiles = Array.isArray(files) ? files : []
|
||||
const documents = Array.isArray(payload?.documents) ? payload.documents : []
|
||||
let attachedCount = 0
|
||||
|
||||
safeFiles.slice(0, documents.length).forEach((file, index) => {
|
||||
const document = documents[index] || {}
|
||||
const receiptId = String(document.receipt_id || document.receiptId || '').trim()
|
||||
if (receiptId && defineFileReceiptId(file, receiptId)) {
|
||||
attachedCount += 1
|
||||
}
|
||||
})
|
||||
|
||||
return attachedCount
|
||||
}
|
||||
|
||||
export async function collectReceiptFiles({
|
||||
files = [],
|
||||
recognizedAttachmentData = null,
|
||||
recognizeOcrFiles,
|
||||
timeoutMs = 90000,
|
||||
timeoutMessage = '票据 OCR 识别超时,已继续使用附件名称处理。'
|
||||
} = {}) {
|
||||
const safeFiles = Array.isArray(files) ? files : []
|
||||
const reusedData = recognizedAttachmentData && typeof recognizedAttachmentData === 'object'
|
||||
? recognizedAttachmentData
|
||||
: null
|
||||
|
||||
if (reusedData) {
|
||||
const ocrDocuments = Array.isArray(reusedData.ocrDocuments) ? [...reusedData.ocrDocuments] : []
|
||||
const ocrPayload = reusedData.ocrPayload || { documents: ocrDocuments }
|
||||
attachReceiptFolderIdsToFiles(safeFiles, ocrPayload)
|
||||
return {
|
||||
ocrPayload,
|
||||
ocrSummary: String(reusedData.ocrSummary || '').trim() || buildOcrSummaryFromDocuments(ocrDocuments),
|
||||
ocrDocuments,
|
||||
ocrFilePreviews: Array.isArray(reusedData.ocrFilePreviews) ? [...reusedData.ocrFilePreviews] : []
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof recognizeOcrFiles !== 'function') {
|
||||
throw new Error('票据采集服务未配置。')
|
||||
}
|
||||
|
||||
const ocrPayload = await recognizeOcrFiles(safeFiles, {
|
||||
timeoutMs,
|
||||
timeoutMessage
|
||||
})
|
||||
attachReceiptFolderIdsToFiles(safeFiles, ocrPayload)
|
||||
|
||||
return {
|
||||
ocrPayload,
|
||||
ocrSummary: buildOcrSummary(ocrPayload),
|
||||
ocrDocuments: normalizeOcrDocuments(ocrPayload),
|
||||
ocrFilePreviews: buildOcrFilePreviews(ocrPayload)
|
||||
}
|
||||
}
|
||||
|
||||
export function buildOcrSummary(payload) {
|
||||
return buildOcrSummaryFromDocuments(normalizeOcrDocuments(payload))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user