mirror of
https://github.com/PR0M3TH3AN/unredact.git
synced 2026-10-27 08:40:29 +00:00
feat: add LLM validation UI with score badges and context panel
Wire up Validate button in solver.js with showValidatePanel, runValidation, and renderScoredResult functions. Add CSS for score badges (green/yellow/red) and validate panel styling. Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
1 parent
f7dd5a342a
commit
fa6709cca4
4 files changed
+157
-1
No files matched your search
@@ -39,6 +39,11 @@ 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 solveValidate = /** @type {HTMLButtonElement} */ (document.getElementById("solve-validate"));
|
||||
export const validatePanel = document.getElementById("validate-panel");
|
||||
export const validateLeft = /** @type {HTMLInputElement} */ (document.getElementById("validate-left"));
|
||||
export const validateRight = /** @type {HTMLInputElement} */ (document.getElementById("validate-right"));
|
||||
export const validateRun = /** @type {HTMLButtonElement} */ (document.getElementById("validate-run"));
|
||||
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"));
|
||||
|
||||
@@ -150,8 +150,14 @@
|
||||
<button id="solve-start" class="solve-btn">Solve</button>
|
||||
<button id="solve-stop" class="solve-btn" hidden>Stop</button>
|
||||
<button id="solve-accept" class="solve-btn accept" hidden>Accept</button>
|
||||
<button id="solve-validate" class="solve-btn" hidden>Validate</button>
|
||||
<span id="solve-status"></span>
|
||||
</div>
|
||||
<div id="validate-panel" hidden>
|
||||
<label>Left context <input type="text" id="validate-left" spellcheck="false" autocomplete="off"></label>
|
||||
<label>Right context <input type="text" id="validate-right" spellcheck="false" autocomplete="off"></label>
|
||||
<button id="validate-run" class="solve-btn">Run</button>
|
||||
</div>
|
||||
<div id="solve-results"></div>
|
||||
<button id="solve-load-more" class="solve-btn" hidden>Load more</button>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
solveCharset, solveTolerance, solveMode, solveFilter,
|
||||
solveKnownStart, solveKnownEnd, solvePlural, solveVocab,
|
||||
solveResults, solveStatus, solveStart, solveStop,
|
||||
solveAccept, solveLoadMore, redactionMarker, escapeHtml,
|
||||
solveAccept, solveLoadMore, solveValidate,
|
||||
validatePanel, validateLeft, validateRight, validateRun,
|
||||
redactionMarker, escapeHtml,
|
||||
} from './dom.js';
|
||||
import { renderCanvas } from './canvas.js';
|
||||
import { matchAssociates, tierBadgeClass, tierLabel, isVictimMatch, showAssocDetail } from './associates.js';
|
||||
@@ -45,6 +47,8 @@ export function startSolve() {
|
||||
solveStart.hidden = true;
|
||||
solveStop.hidden = false;
|
||||
solveAccept.hidden = true;
|
||||
solveValidate.hidden = true;
|
||||
validatePanel.hidden = true;
|
||||
|
||||
const body = {
|
||||
font_id: fontId,
|
||||
@@ -210,6 +214,9 @@ function handleSolveEvent(data, redactionId) {
|
||||
solveStart.hidden = false;
|
||||
solveStop.hidden = true;
|
||||
activeEventSource = null;
|
||||
if (totalFound > 0) {
|
||||
solveValidate.hidden = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,9 +285,88 @@ async function loadMore() {
|
||||
}
|
||||
}
|
||||
|
||||
function showValidatePanel() {
|
||||
const id = state.activeRedaction;
|
||||
if (!id) return;
|
||||
const r = state.redactions[id];
|
||||
if (!r || !r.analysis) return;
|
||||
|
||||
const a = r.analysis;
|
||||
const o = r.overrides || {};
|
||||
const leftText = o.leftText ?? (a.segments.length > 0 ? a.segments[0].text : "");
|
||||
const rightText = o.rightText ?? (a.segments.length > 1 ? a.segments[1].text : "");
|
||||
|
||||
validateLeft.value = leftText;
|
||||
validateRight.value = rightText;
|
||||
validatePanel.hidden = false;
|
||||
}
|
||||
|
||||
async function runValidation() {
|
||||
if (!currentSolveId) return;
|
||||
validateRun.disabled = true;
|
||||
validateRun.textContent = "Validating...";
|
||||
solveStatus.textContent = `Validating ${totalFound} results...`;
|
||||
|
||||
try {
|
||||
const resp = await fetch(`/api/solve/${currentSolveId}/validate`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
left_context: validateLeft.value,
|
||||
right_context: validateRight.value,
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) throw new Error("Validation failed");
|
||||
const data = await resp.json();
|
||||
|
||||
// Replace results with scored list
|
||||
solveResults.innerHTML = "";
|
||||
displayedCount = 0;
|
||||
solveLoadMore.hidden = true;
|
||||
|
||||
const redactionId = state.activeRedaction;
|
||||
for (const item of data.results) {
|
||||
renderScoredResult(item, redactionId);
|
||||
}
|
||||
|
||||
solveStatus.textContent = `Validated. ${data.total} results scored.`;
|
||||
validatePanel.hidden = true;
|
||||
} catch (err) {
|
||||
solveStatus.textContent = "Validation error: " + err.message;
|
||||
} finally {
|
||||
validateRun.disabled = false;
|
||||
validateRun.textContent = "Run";
|
||||
}
|
||||
}
|
||||
|
||||
function renderScoredResult(data, redactionId) {
|
||||
handleSolveEvent({ status: "match", ...data }, redactionId);
|
||||
|
||||
if (data.llm_score !== undefined) {
|
||||
const results = solveResults.children;
|
||||
for (const el of results) {
|
||||
const textEl = el.querySelector(".result-text");
|
||||
if (textEl && textEl.textContent === data.text && !el.querySelector(".llm-score")) {
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "llm-score";
|
||||
const score = data.llm_score;
|
||||
if (score >= 70) badge.classList.add("score-high");
|
||||
else if (score >= 30) badge.classList.add("score-mid");
|
||||
else badge.classList.add("score-low");
|
||||
badge.textContent = String(score);
|
||||
badge.title = "LLM contextual fit score";
|
||||
el.prepend(badge);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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);
|
||||
solveValidate.addEventListener("click", showValidatePanel);
|
||||
validateRun.addEventListener("click", runValidation);
|
||||
}
|
||||
@@ -827,6 +827,65 @@ main {
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/* LLM validation score badges */
|
||||
.llm-score {
|
||||
flex-shrink: 0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.llm-score.score-high {
|
||||
background: #2d8a4e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.llm-score.score-mid {
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.llm-score.score-low {
|
||||
background: var(--red);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Validate panel */
|
||||
#validate-panel {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#validate-panel label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
#validate-panel input {
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-subtle);
|
||||
color: var(--text);
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
#validate-panel .solve-btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
/* ── Mobile (≤768px) ── */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
Reference in new issue
Block a user