diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrAsyncRunner.java b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrAsyncRunner.java index a3090a62..610c3e2d 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrAsyncRunner.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrAsyncRunner.java @@ -39,16 +39,32 @@ public class OcrAsyncRunner { job.setStatus(OcrJobStatus.RUNNING); ocrJobRepository.save(job); + OcrJobDocument jobDoc = ocrJobDocumentRepository.findByJobIdAndDocumentId(jobId, documentId) + .orElse(null); + if (jobDoc != null) { + jobDoc.setStatus(OcrDocumentStatus.RUNNING); + ocrJobDocumentRepository.save(jobDoc); + } + Document doc = documentService.getDocumentById(documentId); try { processDocument(documentId, doc, userId); job.setStatus(OcrJobStatus.DONE); job.setProcessedDocuments(1); + if (jobDoc != null) { + jobDoc.setStatus(OcrDocumentStatus.DONE); + ocrJobDocumentRepository.save(jobDoc); + } } catch (Exception e) { log.error("OCR processing failed for document {}", documentId, e); job.setStatus(OcrJobStatus.FAILED); job.setErrorCount(1); + if (jobDoc != null) { + jobDoc.setStatus(OcrDocumentStatus.FAILED); + jobDoc.setErrorMessage(e.getMessage()); + ocrJobDocumentRepository.save(jobDoc); + } } ocrJobRepository.save(job); diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrService.java index 3812db4e..75ade1c7 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrService.java @@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.*; +import org.raddatz.familienarchiv.repository.OcrJobDocumentRepository; import org.raddatz.familienarchiv.repository.OcrJobRepository; import org.springframework.stereotype.Service; @@ -18,6 +19,7 @@ public class OcrService { private final OcrHealthClient ocrHealthClient; private final DocumentService documentService; private final OcrJobRepository ocrJobRepository; + private final OcrJobDocumentRepository ocrJobDocumentRepository; private final OcrAsyncRunner ocrAsyncRunner; public UUID startOcr(UUID documentId, ScriptType scriptTypeOverride, UUID userId) { @@ -44,6 +46,13 @@ public class OcrService { .build(); job = ocrJobRepository.save(job); + OcrJobDocument jobDoc = OcrJobDocument.builder() + .jobId(job.getId()) + .documentId(documentId) + .status(OcrDocumentStatus.PENDING) + .build(); + ocrJobDocumentRepository.save(jobDoc); + ocrAsyncRunner.runSingleDocument(job.getId(), documentId, userId); return job.getId(); } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/OcrServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/OcrServiceTest.java index fe66287f..a94958a3 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/OcrServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/OcrServiceTest.java @@ -8,6 +8,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.*; +import org.raddatz.familienarchiv.repository.OcrJobDocumentRepository; import org.raddatz.familienarchiv.repository.OcrJobRepository; import java.util.UUID; @@ -24,6 +25,7 @@ class OcrServiceTest { @Mock OcrHealthClient ocrHealthClient; @Mock DocumentService documentService; @Mock OcrJobRepository ocrJobRepository; + @Mock OcrJobDocumentRepository ocrJobDocumentRepository; @Mock OcrAsyncRunner ocrAsyncRunner; @InjectMocks OcrService ocrService; diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index 46eafbd3..23d6a232 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -251,12 +251,28 @@ function handleParagraphClick(annotationId: string) { ); } -// Load blocks when transcribe mode is entered and set default panel mode +async function checkOcrStatus() { + if (!doc?.id) return; + try { + const res = await fetch(`/api/documents/${doc.id}/ocr-status`); + if (!res.ok) return; + const status = await res.json(); + if ((status.status === 'PENDING' || status.status === 'RUNNING') && status.jobId) { + ocrRunning = true; + pollOcrJob(status.jobId); + } + } catch { + // best-effort + } +} + +// Load blocks and check OCR status when transcribe mode is entered $effect(() => { if (transcribeMode) { loadTranscriptionBlocks().then(() => { panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit'; }); + checkOcrStatus(); } });