Spaces:
Running
Running
Download checker.js from landtml/rag-verifier: direct link, hf CLI and curl.
- Browser
- Download file 7.23 kB
-
https://huggingface.co/spaces/landtml/rag-verifier/resolve/main/checker.js
- Command line
-
hf download hf://spaces/landtml/rag-verifier/checker.js
-
curl -L -o checker.js https://huggingface.co/spaces/landtml/rag-verifier/resolve/main/checker.js
7.23 kB
| // The checker: fills the form from an example, submits to the API named in <meta name="api-base">, follows the | |
| // job through RunPod's real stages and shows the verdict only after the model has answered. With no API set, unedited examples show the score the model gave | |
| // them ahead of time. Cut-offs are the ones chosen on the development split. | |
| const PASS = 0.88, BLOCK = 0.12, FLAG = 0.56; | |
| const $ = (id) => document.getElementById(id); | |
| const form = $("ck-form"), list = $("ck-list"), status = $("ck-status"), out = $("ck-out"), go = $("ck-go"); | |
| const fields = { source: $("ck-source"), request: $("ck-request"), answer: $("ck-answer") }; | |
| const API = (document.querySelector('meta[name="api-base"]')?.content || "").replace(/\/$/, ""); | |
| let current = null; | |
| // Progress follows RunPod's real state. Each stage owns a stretch of the bar and eases across it while | |
| // it lasts, so the bar always moves; a later stage jumps ahead the moment RunPod reports it, and the bar | |
| // never goes back. While the page waits a soft sweep runs over the fill. | |
| const bar = $("ck-progress"), fill = bar.querySelector("span"); | |
| const STAGES = { | |
| sending: { lo: 0, hi: 12, tau: 0.6, text: () => "Sending the answer to the model." }, | |
| starting: { lo: 12, hi: 72, tau: 30, text: (s) => `Starting a model server 路 ${s} s. After a quiet spell this takes up to two minutes; your check keeps its place.` }, | |
| queued: { lo: 72, hi: 82, tau: 3, text: (s) => `The server is up. Your check is next 路 ${s} s.` }, | |
| checking: { lo: 82, hi: 96, tau: 0.8, text: () => "Reading the source and the answer." }, | |
| }; | |
| let stage = null, stageAt = 0, shown = 0, startedAt = 0, tick = null; | |
| function set(p) { | |
| shown = Math.max(shown, p); | |
| fill.style.width = `${shown.toFixed(1)}%`; | |
| bar.setAttribute("aria-valuenow", String(Math.round(shown))); | |
| } | |
| function render() { | |
| const st = STAGES[stage]; if (!st) return; | |
| const t = (performance.now() - stageAt) / 1000, secs = Math.round((performance.now() - startedAt) / 1000); | |
| set(st.lo + (st.hi - st.lo) * (1 - Math.exp(-t / st.tau))); | |
| status.textContent = st.text(secs); | |
| } | |
| function enter(next) { | |
| if (!STAGES[next] || next === stage) return; | |
| if (stage && STAGES[next].lo < STAGES[stage].lo) return; // RunPod's view can lag; never step back | |
| stage = next; stageAt = performance.now(); | |
| set(STAGES[next].lo); | |
| render(); | |
| } | |
| function progressStart() { | |
| shown = 0; stage = null; startedAt = performance.now(); | |
| bar.hidden = false; bar.classList.add("is-waiting"); fill.style.width = "0%"; | |
| enter("sending"); | |
| tick = setInterval(render, 200); | |
| } | |
| function progressEnd(done) { | |
| clearInterval(tick); stage = null; bar.classList.remove("is-waiting"); | |
| if (done) set(100); | |
| setTimeout(() => { bar.hidden = true; shown = 0; fill.style.width = "0%"; }, done ? 450 : 0); | |
| } | |
| function showExample(ex, btn) { | |
| fields.source.value = ex.source; fields.request.value = ex.request; fields.answer.value = ex.answer; | |
| list.querySelectorAll(".ck-item").forEach((b) => b.setAttribute("aria-pressed", String(b === btn))); | |
| out.hidden = true; status.textContent = ""; current = ex; | |
| } | |
| async function loadExamples() { | |
| let examples = []; | |
| try { examples = await (await fetch("examples.json")).json(); } catch { return; } | |
| for (const ex of examples) { | |
| const li = document.createElement("li"); | |
| const b = document.createElement("button"); | |
| b.type = "button"; b.className = "ck-item"; b.setAttribute("aria-pressed", "false"); | |
| b.innerHTML = `<span class="ck-item__t"></span><span class="ck-item__s"></span><span class="ck-item__l"></span>`; | |
| b.querySelector(".ck-item__t").textContent = ex.title; | |
| b.querySelector(".ck-item__s").textContent = `${ex.task} 路 ${ex.label}`; | |
| b.querySelector(".ck-item__l").textContent = ex.app; | |
| b.addEventListener("click", () => showExample(ex, b)); | |
| li.append(b); list.append(li); | |
| } | |
| if (examples.length) showExample(examples[0], list.querySelector(".ck-item")); | |
| } | |
| function show(p) { | |
| $("ck-verdict").textContent = p >= FLAG ? "Supported" : "Not supported"; | |
| $("ck-score").textContent = p.toFixed(3); | |
| $("ck-route").textContent = p >= PASS ? "Pass" : p <= BLOCK ? "Block" : "Send to review"; | |
| out.hidden = false; | |
| } | |
| function invalid(el, on) { | |
| const err = $(el.getAttribute("aria-describedby")); | |
| el.setAttribute("aria-invalid", String(on)); if (err) err.hidden = !on; | |
| } | |
| form.addEventListener("submit", async (e) => { | |
| e.preventDefault(); | |
| const missing = ["source", "answer"].filter((k) => !fields[k].value.trim()); | |
| ["source", "answer"].forEach((k) => invalid(fields[k], missing.includes(k))); | |
| if (missing.length) { | |
| fields[missing[0]].focus(); | |
| status.textContent = missing.length === 1 ? "One field needs text." : "Two fields need text."; | |
| return; | |
| } | |
| out.hidden = true; | |
| if (!API) { | |
| const same = current && ["source", "request", "answer"].every((k) => fields[k].value === current[k]); | |
| if (same && typeof current.score === "number") { | |
| show(current.score); | |
| status.textContent = `Stored score from the model. Live checking of your own text is offline. Scores below ${FLAG} count as not supported.`; | |
| } else status.textContent = "Live checking is offline right now. The examples on the left show stored scores."; | |
| return; | |
| } | |
| go.disabled = true; | |
| progressStart(); | |
| let ok = false; | |
| try { | |
| const noul = await runCheck(); | |
| const secs = ((performance.now() - startedAt) / 1000).toFixed(1); | |
| ok = true; progressEnd(true); | |
| show(noul); | |
| status.textContent = `Checked in ${secs} s. Scores below ${FLAG} count as not supported.`; | |
| } catch (err) { | |
| status.textContent = `${err.message} Your text is still in the fields.`; | |
| } finally { if (!ok) progressEnd(false); go.disabled = false; } | |
| }); | |
| const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | |
| // Submit, then follow the job until RunPod finishes it. A warm server answers in the first call; a cold | |
| // start is followed for up to six minutes, and a lost poll is simply asked again. | |
| async function runCheck() { | |
| const r = await fetch(`${API}/api/check`, { method: "POST", headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ source: fields.source.value, request: fields.request.value, answer: fields.answer.value }) }); | |
| const j = await r.json().catch(() => ({})); | |
| if (!r.ok) throw new Error(j.detail || "The check failed."); | |
| if (j.stage === "done") { enter("checking"); return j.noul; } | |
| enter(j.stage); | |
| let misses = 0; | |
| while ((performance.now() - startedAt) / 1000 < 360) { | |
| await sleep((performance.now() - startedAt) / 1000 < 30 ? 1000 : 1500); | |
| let s; | |
| try { | |
| const q = await fetch(`${API}/api/status?id=${encodeURIComponent(j.id)}`); | |
| s = await q.json().catch(() => ({})); | |
| if (!q.ok) throw Object.assign(new Error(s.detail || "The check failed."), { final: q.status !== 429 && q.status < 500 }); | |
| misses = 0; | |
| } catch (err) { | |
| if (err.final) throw err; | |
| if (++misses >= 6) throw new Error("Lost contact with the model server."); | |
| continue; | |
| } | |
| if (s.stage === "done") { enter("checking"); return s.noul; } | |
| enter(s.stage); | |
| } | |
| throw new Error("The model server is taking unusually long. Try again in a minute."); | |
| } | |
| loadExamples(); | |