diff --git a/unredact/static/dom.js b/unredact/static/dom.js
index 0b2b169..1317b95 100644
--- a/unredact/static/dom.js
+++ b/unredact/static/dom.js
@@ -38,6 +38,7 @@ export const solveStop = document.getElementById("solve-stop");
export const solveAccept = document.getElementById("solve-accept");
export const solveStatus = document.getElementById("solve-status");
export const solveResults = document.getElementById("solve-results");
+export const solveLoadMore = /** @type {HTMLButtonElement} */ (document.getElementById("solve-load-more"));
export const fontToolbar = document.getElementById("font-toolbar");
export const fontSelect = /** @type {HTMLSelectElement} */ (document.getElementById("font-select"));
export const sizeSlider = /** @type {HTMLInputElement} */ (document.getElementById("size-slider"));
diff --git a/unredact/static/index.html b/unredact/static/index.html
index d86bd15..f3d5c50 100644
--- a/unredact/static/index.html
+++ b/unredact/static/index.html
@@ -153,6 +153,7 @@
![Document page]()
diff --git a/unredact/static/solver.js b/unredact/static/solver.js
index 408df55..4740e6b 100644
--- a/unredact/static/solver.js
+++ b/unredact/static/solver.js
@@ -6,7 +6,7 @@ import {
solveCharset, solveTolerance, solveMode, solveFilter,
solveKnownStart, solveKnownEnd, solvePlural, solveVocab,
solveResults, solveStatus, solveStart, solveStop,
- solveAccept, redactionMarker, escapeHtml,
+ solveAccept, solveLoadMore, redactionMarker, escapeHtml,
} from './dom.js';
import { renderCanvas } from './canvas.js';
import { matchAssociates, tierBadgeClass, tierLabel, isVictimMatch, showAssocDetail } from './associates.js';
@@ -14,6 +14,11 @@ import { matchAssociates, tierBadgeClass, tierLabel, isVictimMatch, showAssocDet
/** @type {AbortController|null} */
let activeEventSource = null;
+/** @type {string|null} */
+let currentSolveId = null;
+let displayedCount = 0;
+let totalFound = 0;
+
export function startSolve() {
const id = state.activeRedaction;
if (!id) return;
@@ -32,6 +37,10 @@ export function startSolve() {
const rightCtx = rightText.length > 0 ? rightText[0] : "";
solveResults.innerHTML = "";
+ solveLoadMore.hidden = true;
+ currentSolveId = null;
+ displayedCount = 0;
+ totalFound = 0;
solveStatus.textContent = "Starting...";
solveStart.hidden = true;
solveStop.hidden = false;
@@ -184,10 +193,19 @@ function handleSolveEvent(data, redactionId) {
solveResults.appendChild(div);
}
- solveStatus.textContent = `Found ${solveResults.children.length} matches`;
+ displayedCount++;
+ solveStatus.textContent = `Found ${displayedCount} matches`;
} else if (data.status === "running") {
solveStatus.textContent = `Checked ${data.checked}, found ${data.found}...`;
+ } else if (data.status === "page_complete") {
+ currentSolveId = data.solve_id;
+ solveStatus.textContent = `Found ${displayedCount} matches, searching for more...`;
} else if (data.status === "done") {
+ totalFound = data.total_found;
+ if (totalFound > displayedCount) {
+ solveLoadMore.textContent = `Load more (showing ${displayedCount} of ${totalFound})`;
+ solveLoadMore.hidden = false;
+ }
solveStatus.textContent = `Done. ${data.total_found} total matches.`;
solveStart.hidden = false;
solveStop.hidden = true;
@@ -231,8 +249,38 @@ export function acceptSolution() {
r.solveFullText = null;
}
+async function loadMore() {
+ if (!currentSolveId) return;
+ solveLoadMore.disabled = true;
+ solveLoadMore.textContent = "Loading...";
+
+ try {
+ const resp = await fetch(
+ `/api/solve/${currentSolveId}/results?offset=${displayedCount}&limit=200`
+ );
+ if (!resp.ok) throw new Error("Failed to load results");
+ const data = await resp.json();
+
+ const redactionId = state.activeRedaction;
+ for (const item of data.results) {
+ handleSolveEvent({ status: "match", ...item }, redactionId);
+ }
+
+ if (displayedCount >= totalFound) {
+ solveLoadMore.hidden = true;
+ } else {
+ solveLoadMore.textContent = `Load more (showing ${displayedCount} of ${totalFound})`;
+ solveLoadMore.disabled = false;
+ }
+ } catch (err) {
+ solveLoadMore.textContent = "Error loading — click to retry";
+ solveLoadMore.disabled = false;
+ }
+}
+
/** Set up solver button listeners. Call once from main.js. */
export function initSolver() {
solveStart.addEventListener("click", startSolve);
solveStop.addEventListener("click", stopSolve);
+ solveLoadMore.addEventListener("click", loadMore);
}