From d8dcba1a713bf7d7e76919c4d07634e67ecd7b71 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 12 Apr 2026 23:50:39 +0200 Subject: [PATCH] fix(ocr): unblock event loop during OCR and show errors in UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OCR engines are CPU-bound and were blocking Uvicorn's single async event loop, making /health unresponsive during processing. This caused new OCR requests to fail silently (health check failure → no DB record → UI shows NONE). Wrap engine calls in asyncio.to_thread() to keep the event loop free. Also surface OCR trigger errors in the frontend instead of silently resetting the spinner. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/routes/documents/[id]/+page.svelte | 15 +++++++++++++++ ocr-service/main.py | 6 ++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index b9e0e994..5a524328 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -7,6 +7,7 @@ import TranscriptionEditView from '$lib/components/TranscriptionEditView.svelte' import TranscriptionReadView from '$lib/components/TranscriptionReadView.svelte'; import TranscriptionPanelHeader from '$lib/components/TranscriptionPanelHeader.svelte'; import type { TranscriptionBlockData } from '$lib/types'; +import { getErrorMessage } from '$lib/errors'; let { data } = $props(); @@ -129,6 +130,7 @@ async function reviewToggle(blockId: string) { let ocrRunning = $state(false); let ocrProgressMessage = $state(''); +let ocrErrorMessage = $state(''); let ocrPollTimer = $state | null>(null); function translateOcrProgress(code: string): string { @@ -154,6 +156,7 @@ function translateOcrProgress(code: string): string { async function triggerOcr(scriptType: string) { ocrRunning = true; + ocrErrorMessage = ''; try { const res = await fetch(`/api/documents/${doc.id}/ocr`, { method: 'POST', @@ -165,10 +168,14 @@ async function triggerOcr(scriptType: string) { pollOcrJob(data.jobId); } else { ocrRunning = false; + const body = await res.json().catch(() => null); + const code = (body as { code?: string } | null)?.code; + ocrErrorMessage = code ? getErrorMessage(code) : m.ocr_status_error(); } } catch (e) { console.error('Failed to trigger OCR:', e); ocrRunning = false; + ocrErrorMessage = m.ocr_status_error(); } } @@ -185,6 +192,9 @@ function pollOcrJob(jobId: string) { ocrPollTimer = null; ocrRunning = false; ocrProgressMessage = ''; + if (job.status === 'FAILED') { + ocrErrorMessage = m.ocr_status_error(); + } await loadTranscriptionBlocks(); annotationReloadKey++; panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit'; @@ -399,6 +409,11 @@ onMount(() => { onClose={() => (transcribeMode = false)} />
+ {#if ocrErrorMessage} +
+

{ocrErrorMessage}

+
+ {/if} {#if ocrRunning}