feat: add OCR-on-upload and Detect Redactions button

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Alex-GilbertandClaude Opus 4.6 committed 2026-02-20 12:43:09 -06:00
1 parent 2783ed2615
commit 7331fc5936
5 files changed
+64 -3

No files matched your search

+1
View File
@@ -12,6 +12,7 @@ export const pageInfo = document.getElementById("page-info");
export const prevBtn = /** @type {HTMLButtonElement} */ (document.getElementById("prev-page"));
export const nextBtn = /** @type {HTMLButtonElement} */ (document.getElementById("next-page"));
export const redactionListEl = document.getElementById("redaction-list");
export const detectBtn = /** @type {HTMLButtonElement} */ (document.getElementById("detect-btn"));
export const zoomInBtn = document.getElementById("zoom-in");
export const zoomOutBtn = document.getElementById("zoom-out");
export const zoomFitBtn = document.getElementById("zoom-fit");
+1
View File
@@ -34,6 +34,7 @@
<div id="workspace">
<div id="left-panel">
<button id="detect-btn" disabled>Detect Redactions</button>
<div id="redaction-list"></div>
</div>
+42 -3
View File
@@ -4,7 +4,7 @@
import { state } from './state.js';
import {
dropZone, fileInput, uploadSection, viewerSection, docImage,
canvas, pageInfo, prevBtn, nextBtn, redactionListEl,
canvas, pageInfo, prevBtn, nextBtn, redactionListEl, detectBtn,
rightPanel, fontSelect,
solveAccept, gapValue, showToast,
} from './dom.js';
@@ -96,8 +96,8 @@ async function uploadFile(file) {
// Load the first page image and controls immediately
await loadPage(1);
// Start background analysis via SSE
startAnalysisSSE();
// Start background OCR via SSE
startOcrSSE();
}
/**
@@ -120,12 +120,51 @@ function startAnalysisSSE() {
} else if (data.event === "done") {
es.close();
showToast("Analysis complete");
if (detectBtn) {
detectBtn.disabled = false;
detectBtn.textContent = "Detect Redactions";
}
}
});
es.addEventListener("error", () => {
es.close();
showToast("Analysis connection lost", "error");
if (detectBtn) {
detectBtn.disabled = false;
detectBtn.textContent = "Detect Redactions";
}
});
}
function startOcrSSE() {
const es = new EventSource(`/api/doc/${state.docId}/ocr`);
showToast("Running OCR...", "info");
es.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.event === "page_ocr_complete") {
// OCR cached for this page on the backend
} else if (data.event === "ocr_complete") {
es.close();
state.ocrReady = true;
showToast("OCR complete — ready to detect redactions", "success");
if (detectBtn) detectBtn.disabled = false;
} else if (data.event === "error") {
showToast(`OCR error on page ${data.page}: ${data.message}`, "error");
}
};
es.onerror = () => {
es.close();
showToast("OCR connection lost", "error");
};
}
if (detectBtn) {
detectBtn.addEventListener("click", () => {
detectBtn.disabled = true;
detectBtn.textContent = "Detecting...";
startAnalysisSSE();
});
}
+1
View File
@@ -10,6 +10,7 @@ export const state = {
activeRedaction: null,
fonts: [],
fontsReady: false,
ocrReady: false,
zoom: 1,
panX: 0,
panY: 0,
+19
View File
@@ -147,6 +147,25 @@ main {
padding: 0.5rem 0;
}
#detect-btn {
width: 100%;
padding: 8px 12px;
margin-bottom: 8px;
background: #2563eb;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
#detect-btn:hover:not(:disabled) {
background: #1d4ed8;
}
#detect-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Redaction list items */
.redaction-item {
padding: 0.5rem 0.75rem;