fix(ocr): resume polling on page reload + track single-doc job status
Some checks failed
CI / Unit & Component Tests (push) Failing after 1s
CI / Backend Unit Tests (push) Failing after 1s
CI / Unit & Component Tests (pull_request) Failing after 0s
CI / Backend Unit Tests (pull_request) Failing after 1s

Single-document OCR now creates an OcrJobDocument row so
GET /api/documents/{id}/ocr-status can find running jobs.
OcrAsyncRunner updates the job document status (RUNNING → DONE/FAILED).

Frontend checks OCR status when entering transcription mode —
if a job is running, resumes polling and shows the spinner.

Refs #226

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-12 23:16:59 +02:00
parent 2db1b73d5d
commit c1befd3fa3
4 changed files with 44 additions and 1 deletions

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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();
}
});