diff --git a/unredact/static/dom.js b/unredact/static/dom.js
index 7f26a2f..9c7383d 100644
--- a/unredact/static/dom.js
+++ b/unredact/static/dom.js
@@ -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");
diff --git a/unredact/static/index.html b/unredact/static/index.html
index 634f0c4..70aa01a 100644
--- a/unredact/static/index.html
+++ b/unredact/static/index.html
@@ -34,6 +34,7 @@
diff --git a/unredact/static/main.js b/unredact/static/main.js
index 4a72fc1..6027f0c 100644
--- a/unredact/static/main.js
+++ b/unredact/static/main.js
@@ -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();
});
}
diff --git a/unredact/static/state.js b/unredact/static/state.js
index 312a031..48ca08b 100644
--- a/unredact/static/state.js
+++ b/unredact/static/state.js
@@ -10,6 +10,7 @@ export const state = {
activeRedaction: null,
fonts: [],
fontsReady: false,
+ ocrReady: false,
zoom: 1,
panX: 0,
panY: 0,
diff --git a/unredact/static/style.css b/unredact/static/style.css
index ee3d8aa..55f74dd 100644
--- a/unredact/static/style.css
+++ b/unredact/static/style.css
@@ -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;