const $ = (selector) => document.querySelector(selector); const $$ = (selector) => [...document.querySelectorAll(selector)]; const verdictClass = { "SUPPORTED BY PROVIDED LABEL": "supported", "CONTRADICTED BY PROVIDED LABEL": "contradicted", "TECHNICALLY TRUE, CONTEXT MISSING": "context", "CANNOT VERIFY": "unknown", }; let currentAudit = null; let feedbackVerdict = ""; function setMode(mode) { $$(".mode-switch button").forEach((button) => button.classList.toggle("active", button.dataset.mode === mode)); $$(".mode-panel").forEach((panel) => panel.classList.toggle("active", panel.id === `mode-${mode}`)); } $$(".mode-switch button").forEach((button) => button.addEventListener("click", () => setMode(button.dataset.mode))); $$("[data-scroll]").forEach((button) => button.addEventListener("click", () => $(`#${button.dataset.scroll}`).scrollIntoView())); const photoSets = { front: [], back: [] }; function preview(input, target, side) { input.addEventListener("change", () => { const incoming = [...input.files]; if (!incoming.length) return; const available = Math.max(0, 6 - photoSets[side].length); photoSets[side].push(...incoming.slice(0, available)); const file = photoSets[side][photoSets[side].length - 1]; target.style.backgroundImage = `url(${URL.createObjectURL(file)})`; target.innerHTML = ""; $(`#${side}-count`).textContent = `${photoSets[side].length} photo${photoSets[side].length === 1 ? "" : "s"} · add more`; input.value = ""; }); } preview($("#front-file"), $("#front-preview"), "front"); preview($("#back-file"), $("#back-preview"), "back"); $("#clear-photos").addEventListener("click", () => { photoSets.front.length = 0; photoSets.back.length = 0; [["front", "F"], ["back", "B"]].forEach(([side, label]) => { $(`#${side}-preview`).style.backgroundImage = ""; $(`#${side}-preview`).innerHTML = `${label}`; $(`#${side}-count`).textContent = "Add photos"; }); $("#ocr-status").textContent = "Selected photos cleared."; }); async function runAudit(frontText, backText) { const response = await fetch("api/audit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ front_text: frontText, back_text: backText }), }); if (!response.ok) throw new Error("The evidence engine could not open this case."); render(await response.json()); } function evidenceHtml(item) { return `
${escapeHtml(item.source)}${escapeHtml(item.text)}
`; } function escapeHtml(value = "") { return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); } function render(data) { currentAudit = data; $("#claim-count").textContent = data.claims.length; $("#router-model").textContent = data.investigation.router_model; $("#agent-steps").innerHTML = data.investigation.steps.map((step, index) => `
${String(index + 1).padStart(2, "0")}
${escapeHtml(step.tool.replaceAll("_", " "))}

${escapeHtml(step.reason)}

${escapeHtml(step.source)} · ${escapeHtml(step.status)}
`).join(""); $("#stop-reason").textContent = data.investigation.stop_reason; $("#missing-evidence").textContent = data.investigation.missing_evidence.length ? data.investigation.missing_evidence.join(" · ") : "None. The required evidence path completed."; const review = data.agent_review; $("#nemotron-review").innerHTML = review.status === "NOT_REQUESTED" ? "" : `NVIDIA NEMOTRON INDEPENDENT REVIEW · ${escapeHtml(review.status)} ${escapeHtml(review.priority || "No additional action requested.")} ${escapeHtml(review.evidence_request || review.rationale)}`; $("#claim-grid").innerHTML = data.claims.length ? data.claims.map((claim) => `
${escapeHtml(claim.claim)}${escapeHtml(claim.verdict)}
Evidence confidence · ${escapeHtml(claim.confidence)}

${escapeHtml(claim.summary)}

${claim.evidence.map(evidenceHtml).join("")} ${claim.caveat ? `

${escapeHtml(claim.caveat)}

` : ""}
`).join("") : `
No supported claim detected

Try High Protein, No Added Sugar, Multigrain, 100% Natural, FSSAI Approved, or No Preservatives.

`; $("#gap-grid").innerHTML = data.persuasion_gap.length ? data.persuasion_gap.map((finding) => `
${escapeHtml(finding.severity)} context gap

${escapeHtml(finding.headline)}

FRONT IMPRESSION${escapeHtml(finding.front_impression)}

QUIET CONTEXT${escapeHtml(finding.quiet_context)}

${finding.evidence.map(evidenceHtml).join("")}
`).join("") : `
No persuasion gap calculatedMore complete nutrition, ingredient, or package-size evidence may be required.
`; const packet = data.whole_packet; const facts = [ ["Declared basis", data.nutrition.basis], ["Packet size", data.nutrition.package_size_g == null ? "Not found" : `${data.nutrition.package_size_g}g`], ["Whole-packet protein", packet.protein_g == null ? "Not calculable" : `${packet.protein_g}g`], ["Whole-packet total sugar", packet.total_sugar_g == null ? "Not calculable" : `${packet.total_sugar_g}g`], ["Sugar equivalent", packet.sugar_teaspoons == null ? "Not calculable" : `≈ ${packet.sugar_teaspoons} tsp`], ["Whole-packet sodium", packet.sodium_mg == null ? "Not calculable" : `${packet.sodium_mg}mg`], ]; $("#nutrition-grid").innerHTML = facts.map(([key, value]) => `
${key}${value}
`).join(""); $("#nutrition-explanation").textContent = packet.explanation; $("#expiry-status").textContent = data.expiry.status; $("#opening-status").textContent = data.expiry.after_opening_instruction ? `After opening: ${data.expiry.after_opening_instruction}` : "No after-opening deadline found."; $("#raw-json").textContent = JSON.stringify(data, null, 2); $("#results").classList.remove("hidden"); $("#results").scrollIntoView({ behavior: "smooth" }); } $$("[data-feedback]").forEach((button) => button.addEventListener("click", () => { feedbackVerdict = button.dataset.feedback; $$("[data-feedback]").forEach((choice) => choice.classList.toggle("active", choice === button)); })); $("#submit-feedback").addEventListener("click", async () => { if (!currentAudit || !feedbackVerdict) { $("#feedback-status").textContent = "Run an audit and choose a review outcome first."; return; } $("#feedback-status").textContent = "Packaging evidence review..."; const response = await fetch("api/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ verdict: feedbackVerdict, correction: $("#feedback-correction").value, audit: currentAudit, }), }); const result = await response.json(); $("#feedback-status").textContent = result.message; }); $("#audit-text").addEventListener("click", async () => { $("#audit-text").textContent = "Examining evidence..."; try { await runAudit($("#front-text").value, $("#back-text").value); } catch (error) { alert(error.message); } finally { $("#audit-text").innerHTML = "Put this packet on trial "; } }); $("#read-photos").addEventListener("click", async () => { const form = new FormData(); photoSets.front.forEach((file) => form.append("fronts", file)); photoSets.back.forEach((file) => form.append("backs", file)); $("#ocr-status").textContent = `Reading ${photoSets.front.length + photoSets.back.length} packet photos...`; try { const response = await fetch("api/ocr", { method: "POST", body: form }); const result = await response.json(); $("#front-text").value = result.front.text; $("#back-text").value = result.back.text; $("#ocr-status").textContent = `${result.front.status} ${result.back.status} Review merged evidence before trial.`; setMode("text"); } catch (error) { $("#ocr-status").textContent = "OCR failed. Paste the label text to continue."; } }); async function loadSamples() { const samples = await fetch("api/samples").then((response) => response.json()); $("#sample-grid").innerHTML = Object.entries(samples).map(([name, value]) => ` `).join(""); $$(".sample-card").forEach((button) => button.addEventListener("click", async () => { const sample = samples[button.dataset.name]; $("#front-text").value = sample.front; $("#back-text").value = sample.back; await runAudit(sample.front, sample.back); })); $("#hero-sample").addEventListener("click", () => { setMode("samples"); $("#workspace").scrollIntoView(); }); } loadSamples(); fetch("api/model").then((response) => response.json()).then((status) => { $("#model-mode").textContent = status.enabled ? "MiniCPM-V evidence engine online" : "Deterministic evidence engine online"; }).catch(() => {});