From 035be110b61c45ae329f3e0de390a4f1c5fd8f8d Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Tue, 12 May 2026 03:05:51 +0000 Subject: [PATCH] feat(web): add OCR service and update travel reimbursement view - web/src/services/ocr.js: add OCR service API client - web/src/views/scripts/TravelReimbursementCreateView.js: update travel form script with OCR integration --- web/src/services/ocr.js | 14 ++++++ .../scripts/TravelReimbursementCreateView.js | 47 +++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 web/src/services/ocr.js diff --git a/web/src/services/ocr.js b/web/src/services/ocr.js new file mode 100644 index 0000000..e69be2f --- /dev/null +++ b/web/src/services/ocr.js @@ -0,0 +1,14 @@ +import { apiRequest } from './api.js' + +export function recognizeOcrFiles(files) { + const formData = new FormData() + for (const file of files) { + formData.append('files', file) + } + + return apiRequest('/ocr/recognize', { + method: 'POST', + body: formData, + contentType: null + }) +} diff --git a/web/src/views/scripts/TravelReimbursementCreateView.js b/web/src/views/scripts/TravelReimbursementCreateView.js index 336d8ad..e5e2bc4 100644 --- a/web/src/views/scripts/TravelReimbursementCreateView.js +++ b/web/src/views/scripts/TravelReimbursementCreateView.js @@ -1,6 +1,7 @@ import { computed, nextTick, onMounted, ref } from 'vue' import { useSystemState } from '../../composables/useSystemState.js' +import { recognizeOcrFiles } from '../../services/ocr.js' import { runOrchestrator } from '../../services/orchestrator.js' const DEFAULT_REQUEST = { @@ -128,6 +129,26 @@ function buildMessageMeta(payload, fileNames = []) { return items } +function normalizeOcrDocuments(payload) { + const documents = Array.isArray(payload?.documents) ? payload.documents : [] + return documents.slice(0, 5).map((item) => ({ + filename: item.filename, + summary: item.summary, + text: String(item.text || '').slice(0, 240), + avg_score: Number(item.avg_score || 0), + line_count: Number(item.line_count || 0), + warnings: Array.isArray(item.warnings) ? item.warnings : [] + })) +} + +function buildOcrSummary(payload) { + const parts = normalizeOcrDocuments(payload) + .map((item) => `${item.filename}:${item.summary || item.text}`) + .filter(Boolean) + + return parts.join(';') +} + function buildWelcomeInsight(entrySource, linkedRequest) { return { intent: 'welcome', @@ -360,7 +381,7 @@ export default { submitComposer() } - function buildBackendMessage(rawText, fileNames) { + function buildBackendMessage(rawText, fileNames, ocrSummary = '') { const parts = [] const normalizedText = String(rawText || '').trim() @@ -374,6 +395,10 @@ export default { parts.push(`附件名称:${fileNames.join('、')}`) } + if (ocrSummary) { + parts.push(`OCR摘要:${ocrSummary}`) + } + if (props.entrySource === 'detail') { parts.push(`关联单号:${linkedRequest.value.id}`) } @@ -389,7 +414,6 @@ export default { const fileNames = files.map((file) => file.name) const userText = rawText || `我上传了 ${fileNames.length} 份票据,请帮我识别并给出报销建议。` - const backendMessage = buildBackendMessage(rawText, fileNames) messages.value.push(createMessage('user', userText, fileNames)) @@ -409,6 +433,21 @@ export default { try { const user = currentUser.value || {} + let ocrPayload = null + let ocrSummary = '' + let ocrDocuments = [] + + if (files.length) { + try { + ocrPayload = await recognizeOcrFiles(files) + ocrSummary = buildOcrSummary(ocrPayload) + ocrDocuments = normalizeOcrDocuments(ocrPayload) + } catch (error) { + console.warn('OCR request failed:', error) + } + } + + const backendMessage = buildBackendMessage(rawText, fileNames, ocrSummary) const payload = await runOrchestrator({ source: 'user_message', user_id: user.username || user.name || 'anonymous', @@ -421,7 +460,9 @@ export default { entry_source: props.entrySource, request_context: linkedRequest.value, attachment_names: fileNames, - attachment_count: fileNames.length + attachment_count: fileNames.length, + ocr_summary: ocrSummary, + ocr_documents: ocrDocuments } })