fix(ocr): resume polling on page reload + track single-doc job status
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:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user