feat(ui): hide/exclude models from the dashboard (persisted) #1
@@ -159,6 +159,12 @@ Its mirror, **solo-error penalty ×** (default `1.5`), multiplies the FP penalty
|
||||
was made by **only that model** — a unique wrong claim is noisier than a shared mistake. So a
|
||||
Blocking-claimed solo FP costs `high(8) × -0.5 × 1.5 = -6` vs `-4` for a shared one. Set to `1` to disable.
|
||||
|
||||
**Hiding models.** Each scoreboard row has a small **×** to hide that model — handy for retired ones
|
||||
(e.g. `m1`) you no longer want cluttering the view. Hidden models drop out of the table, the totals,
|
||||
and the findings drill-down (but **not** from solo-ness, which stays computed against all models — hiding
|
||||
is a view filter, not a rescoring). The hidden set persists in `localStorage` across reloads; a
|
||||
**hidden (N): …** bar lists them as click-to-restore chips, with a **show all** to clear.
|
||||
|
||||
Auth: the `/ui` shell is public (it holds no data); paste the store token into its **connect** box,
|
||||
or open `/ui?token=<token>` once (remembered in `localStorage`). Prefer your own dashboard? Point
|
||||
Grafana/Metabase/etc. at the SQLite file or the same `/export` + `/scoreboard` + `/runs` JSON.
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
|
||||
<div class="panel">
|
||||
<div id="summary" class="small mut" style="margin-bottom:8px"></div>
|
||||
<div id="hidden" class="small mut" style="margin-bottom:8px;display:none"></div>
|
||||
<table id="models">
|
||||
<thead><tr id="mhead"></tr></thead>
|
||||
<tbody id="mbody"></tbody>
|
||||
@@ -115,6 +116,17 @@ const SEVCOLOR = { trivial:"#3b4252", small:"#2e4d3a", medium:"#4d4a2e", high:"#
|
||||
let RUNS = [], ROWS = [];
|
||||
let sortKey = "ptsPerMin", sortAsc = false, selModel = null;
|
||||
|
||||
// Persistently-excluded models (e.g. retired ones like m1). Hidden from the
|
||||
// scoreboard, totals, and drill-down; persisted in localStorage across reloads.
|
||||
// Solo-ness is still computed against ALL models (hiding is a view filter, not a
|
||||
// rescoring), so hiding one model never fakes another's solo finds.
|
||||
function loadHidden(){ try { return new Set(JSON.parse(localStorage.getItem("grt-hidden") || "[]")); } catch { return new Set(); } }
|
||||
let HIDDEN = loadHidden();
|
||||
function saveHidden(){ localStorage.setItem("grt-hidden", JSON.stringify([...HIDDEN].sort())); }
|
||||
function hideModel(m){ HIDDEN.add(m); if (selModel===m) selModel=null; saveHidden(); render(); }
|
||||
function showModel(m){ HIDDEN.delete(m); saveHidden(); render(); }
|
||||
function showAllModels(){ HIDDEN.clear(); saveHidden(); render(); }
|
||||
|
||||
function token(){
|
||||
const q = new URL(location.href).searchParams.get("token");
|
||||
if (q) { localStorage.setItem("grt", q); return q; }
|
||||
@@ -229,7 +241,7 @@ function aggregate(f){
|
||||
for (const r of RUNS){ if(!runMatch(r,f)) continue; const m=get(r.model); m.runs++; m.minutes += (r.duration_secs||0)/60;
|
||||
m.inTok += r.input_tokens||0; m.outTok += r.output_tokens||0; if(r.provider) m.provider=r.provider; }
|
||||
|
||||
const rows = ROWS.filter(r => rowMatch(r, f));
|
||||
const rows = ROWS.filter(r => rowMatch(r, f) && !HIDDEN.has(r.model));
|
||||
for (const r of rows){ const m=get(r.model); if(r.provider) m.provider=m.provider||r.provider;
|
||||
m.findings.add(r.finding_id);
|
||||
if (r.graded && r.is_real === true){ m.confirmed.set(r.finding_id, r.severity || ""); }
|
||||
@@ -256,7 +268,7 @@ function aggregate(f){
|
||||
ptsPerMin: m.minutes>0 ? points/m.minutes : null,
|
||||
ptsPerRun: m.runs>0 ? points/m.runs : null,
|
||||
confirmedPct: findings>0 ? confirmed/findings*100 : null };
|
||||
}).filter(m => m.runs>0 || m.findings>0);
|
||||
}).filter(m => (m.runs>0 || m.findings>0) && !HIDDEN.has(m.model));
|
||||
return { models: out, rows };
|
||||
}
|
||||
|
||||
@@ -297,7 +309,16 @@ function render(){
|
||||
for (const col of COLS){
|
||||
const td = document.createElement("td"); if (col.l) td.className="l";
|
||||
const v = m[col.k];
|
||||
if (col.k==="model"){
|
||||
// model name + a hide control (× pill) — injection-safe via JS handler.
|
||||
td.textContent = (v==null?"—":v) + " ";
|
||||
const x = document.createElement("span");
|
||||
x.className = "pill"; x.textContent = "×"; x.title = "hide this model (persists)";
|
||||
x.onclick = (e)=>{ e.stopPropagation(); hideModel(m.model); };
|
||||
td.appendChild(x);
|
||||
} else {
|
||||
td.innerHTML = col.fmt ? col.fmt(v) : (v==null?"—":v);
|
||||
}
|
||||
if ((col.k==="ptsPerMin" || col.k==="ptsPerRun" || col.k==="points") && v!=null) td.classList.add(v<0 ? "bad" : "good");
|
||||
if (col.k==="fpPen" && v<0) td.classList.add("bad");
|
||||
if (col.k==="solo" && v>0) td.classList.add("good");
|
||||
@@ -306,6 +327,24 @@ function render(){
|
||||
}
|
||||
mb.appendChild(tr);
|
||||
}
|
||||
// hidden-models panel: click a model to restore it
|
||||
const hid = document.getElementById("hidden");
|
||||
if (HIDDEN.size){
|
||||
hid.innerHTML = "";
|
||||
const lab = document.createElement("span"); lab.textContent = "hidden ("+HIDDEN.size+"): "; hid.appendChild(lab);
|
||||
for (const m of [...HIDDEN].sort()){
|
||||
const p = document.createElement("span"); p.className="pill"; p.textContent = "+ "+m;
|
||||
p.title = "show this model again"; p.style.marginRight="6px";
|
||||
p.onclick = ()=> showModel(m);
|
||||
hid.appendChild(p);
|
||||
}
|
||||
const all = document.createElement("button"); all.className="link"; all.textContent="show all";
|
||||
all.onclick = showAllModels; hid.appendChild(all);
|
||||
hid.style.display = "";
|
||||
} else {
|
||||
hid.style.display = "none"; hid.innerHTML = "";
|
||||
}
|
||||
|
||||
const tot = models.reduce((a,m)=>({runs:a.runs+m.runs, min:a.min+m.minutes, find:a.find+m.findings, conf:a.conf+m.confirmed, pts:a.pts+m.points}), {runs:0,min:0,find:0,conf:0,pts:0});
|
||||
document.getElementById("summary").innerHTML =
|
||||
`${models.length} models · ${tot.runs} runs · ${tot.min.toFixed(0)} min · ${tot.find} findings · ${tot.conf} confirmed · ${tot.pts.toFixed(0)} pts` +
|
||||
|
||||
Reference in New Issue
Block a user