/* Classify page: submit a complaint and render the result. */ document.addEventListener("DOMContentLoaded", () => { const input = $("#complaint"); const btn = $("#btnClassify"); const spin = $("#spin"); const thr = $("#optThreshold"); // r=37 in the ring SVG -> circumference 2*pi*37 const RING_C = 2 * Math.PI * 37; thr.addEventListener("input", () => { $("#thrOut").value = (+thr.value).toFixed(2); }); $$(".chip").forEach((chip) => chip.addEventListener("click", () => { input.value = chip.dataset.sample; input.focus(); }) ); $("#btnClear").addEventListener("click", () => { input.value = ""; $("#result").classList.add("hidden"); $("#resultEmpty").classList.remove("hidden"); input.focus(); }); input.addEventListener("keydown", (e) => { if ((e.ctrlKey || e.metaKey) && e.key === "Enter") classify(); }); btn.addEventListener("click", classify); async function classify() { const text = input.value.trim(); if (!text) { toast("Type a complaint first.", true); input.focus(); return; } btn.disabled = true; spin.classList.remove("hidden"); try { const data = await api("/api/classify", { method: "POST", body: { text, translate: $("#optTranslate").checked, multi_label: $("#optMulti").checked, save: $("#optSave").checked, threshold: parseFloat(thr.value), }, }); render(data); toast(`Classified as "${data.predicted_label}" in ${data.took_ms} ms`); } catch (err) { toast(err.message, true); } finally { btn.disabled = false; spin.classList.add("hidden"); } } function render(d) { $("#resultEmpty").classList.add("hidden"); $("#result").classList.remove("hidden"); $("#rLabel").textContent = d.predicted_label; $("#rTime").textContent = d.took_ms + " ms"; $("#rEngine").textContent = d.engine; $("#rEngine").title = d.engine; // Confidence ring -- reset to empty first so the sweep animates every time. const ring = $("#rRing"); const arc = $(".ring-fg", ring); ring.classList.toggle("low", !d.confident); $("#rConf").textContent = Math.round(d.confidence * 100) + "%"; arc.style.strokeDasharray = RING_C; arc.style.strokeDashoffset = RING_C; requestAnimationFrame(() => requestAnimationFrame(() => { arc.style.strokeDashoffset = RING_C * (1 - Math.min(d.confidence, 1)); }) ); const t = d.translation; // Romanised text (Hindi typed in Latin letters, say) often detects with low // confidence even when the translation itself is fine -- say so rather than // presenting a shaky guess as fact. const shaky = t.detection_confidence > 0 && t.detection_confidence < 0.6 ? " · uncertain" : ""; const lang = `${t.source_lang_name} (${t.source_lang})${shaky}`; $("#rLang").textContent = lang + (t.was_translated ? " → EN" : ""); $("#rLang").title = t.was_translated ? `${lang}, translated to English` : lang; const warn = $("#rWarn"); if (!d.confident) { $("#rWarnText").textContent = `Low confidence — the top score of ${(d.confidence * 100).toFixed(1)}% is below the ` + `${(d.threshold * 100).toFixed(0)}% threshold. Treat this as unclassified, or add a ` + `label that covers this kind of complaint.`; warn.classList.remove("hidden"); } else { warn.classList.add("hidden"); } $("#rOriginal").textContent = t.original_text; $("#rTranslated").textContent = t.translated_text; const note = $("#rNote"); note.textContent = t.note || ""; note.classList.toggle("hidden", !t.note); $("#rTransBox").open = t.was_translated; // Bars are scaled to the top score so small differences stay readable. const max = Math.max(...d.scores.map((s) => s.score), 0.0001); $("#rScores").innerHTML = d.scores .map((s, i) => `