|
@@ -31,6 +31,7 @@
|
|
|
td.recipe:hover .copy { visibility: visible; }
|
|
td.recipe:hover .copy { visibility: visible; }
|
|
|
td.recipe .copy:hover { background: color-mix(in srgb, currentColor 12%, canvas); }
|
|
td.recipe .copy:hover { background: color-mix(in srgb, currentColor 12%, canvas); }
|
|
|
td.recipe .copied { color: green; font-weight: 600; }
|
|
td.recipe .copied { color: green; font-weight: 600; }
|
|
|
|
|
+ td.recipe .medals { letter-spacing: 1px; }
|
|
|
.half { -webkit-background-clip: text; background-clip: text; color: transparent; }
|
|
.half { -webkit-background-clip: text; background-clip: text; color: transparent; }
|
|
|
td.num { text-align: right; white-space: nowrap; }
|
|
td.num { text-align: right; white-space: nowrap; }
|
|
|
#count { color: gray; }
|
|
#count { color: gray; }
|
|
@@ -58,6 +59,8 @@
|
|
|
<input id="q" type="search" placeholder="Search recipes, models, images, labels…">
|
|
<input id="q" type="search" placeholder="Search recipes, models, images, labels…">
|
|
|
<label>Group:
|
|
<label>Group:
|
|
|
<select id="group">
|
|
<select id="group">
|
|
|
|
|
+ <option value="pm">Parent → Model</option>
|
|
|
|
|
+ <option value="parent">Parent</option>
|
|
|
<option value="mq">Model → Quant</option>
|
|
<option value="mq">Model → Quant</option>
|
|
|
<option value="model">Model</option>
|
|
<option value="model">Model</option>
|
|
|
<option value="quant">Quant</option>
|
|
<option value="quant">Quant</option>
|
|
@@ -103,7 +106,15 @@ const COLS = [
|
|
|
["tps1", "Tok/s c1"], ["tps2", "Tok/s c2"], ["parent", "Parent"],
|
|
["tps1", "Tok/s c1"], ["tps2", "Tok/s c2"], ["parent", "Parent"],
|
|
|
];
|
|
];
|
|
|
const NUM = new Set(["nodes", "tp", "port", "n", "pct", "tps1", "tps2", "stars", "state", "uptime", "runs"]);
|
|
const NUM = new Set(["nodes", "tp", "port", "n", "pct", "tps1", "tps2", "stars", "state", "uptime", "runs"]);
|
|
|
-const MEDAL = { 1: "🥇 ", 2: "🥈 ", 3: "🥉 " };
|
|
|
|
|
|
|
+const MEDAL = { 1: "🥇", 2: "🥈", 3: "🥉" };
|
|
|
|
|
+// three-tier medal badge: family | model | tp (slots shown only when earned)
|
|
|
|
|
+function medalBadge(r) {
|
|
|
|
|
+ const M = r.medals || {};
|
|
|
|
|
+ const g = v => MEDAL[v] || "";
|
|
|
|
|
+ const parts = [g(M.p), g(M.m), g(M.t)].filter(Boolean);
|
|
|
|
|
+ if (!parts.length) return "";
|
|
|
|
|
+ return `<span class="medals" title="family ${g(M.p) || "–"} · model ${g(M.m) || "–"} · tp${r.tp} ${g(M.t) || "–"}">${parts.join("|")}</span> `;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
// run-ledger helpers: who is up right now, for how long (live — a 30 s tick
|
|
// run-ledger helpers: who is up right now, for how long (live — a 30 s tick
|
|
|
// re-renders while anything is running)
|
|
// re-renders while anything is running)
|
|
@@ -342,13 +353,14 @@ function filtered() {
|
|
|
const q = S.q.toLowerCase();
|
|
const q = S.q.toLowerCase();
|
|
|
return DATA.filter(r => (!S.tp || String(r.tp) === S.tp) &&
|
|
return DATA.filter(r => (!S.tp || String(r.tp) === S.tp) &&
|
|
|
(!S.uponly || r.up != null) &&
|
|
(!S.uponly || r.up != null) &&
|
|
|
- (!S.medal || (S.medal === "m" ? r.medal != null : String(r.medal) === S.medal)) &&
|
|
|
|
|
|
|
+ (!S.medal || (S.medal === "m" ? (r.medals && (r.medals.p || r.medals.m || r.medals.t)) : r.medals && String(r.medals.p) === S.medal)) &&
|
|
|
(!q || (r.name + " " + r.title + " " + r.model + " " + r.parent + " " + r.quant + " " +
|
|
(!q || (r.name + " " + r.title + " " + r.model + " " + r.parent + " " + r.quant + " " +
|
|
|
r.engine + " " + r.image + " " + r.deploy + " " +
|
|
r.engine + " " + r.image + " " + r.deploy + " " +
|
|
|
r.reports.map(e => e.date + " " + e.label).join(" ")).toLowerCase().includes(q)));
|
|
r.reports.map(e => e.date + " " + e.label).join(" ")).toLowerCase().includes(q)));
|
|
|
}
|
|
}
|
|
|
function sorted(rows) {
|
|
function sorted(rows) {
|
|
|
- const gk = { mq: r => r.parent + "\0" + r.quant, model: r => r.parent,
|
|
|
|
|
|
|
+ const gk = { mq: r => r.parent + "\0" + r.quant, pm: r => r.parent + "\0" + r.model,
|
|
|
|
|
+ parent: r => r.parent, model: r => r.parent,
|
|
|
quant: r => r.quant, engine: r => r.engine }[S.group];
|
|
quant: r => r.quant, engine: r => r.engine }[S.group];
|
|
|
const k = S.sort, d = S.dir;
|
|
const k = S.sort, d = S.dir;
|
|
|
const val = r => {
|
|
const val = r => {
|
|
@@ -365,6 +377,8 @@ function sorted(rows) {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
function gkey(r, level) {
|
|
function gkey(r, level) {
|
|
|
|
|
+ if (S.group === "pm") return level === 1 ? r.parent : r.parent + "\u0001" + r.model;
|
|
|
|
|
+ if (S.group === "parent") return level === 1 ? r.parent : null;
|
|
|
if (S.group === "mq") return level === 1 ? r.model : r.model + "\u0001" + r.quant;
|
|
if (S.group === "mq") return level === 1 ? r.model : r.model + "\u0001" + r.quant;
|
|
|
const f = { model: "model", quant: "quant", engine: "engine" }[S.group];
|
|
const f = { model: "model", quant: "quant", engine: "engine" }[S.group];
|
|
|
return level === 1 ? r[f] : null;
|
|
return level === 1 ? r[f] : null;
|
|
@@ -404,7 +418,7 @@ function render() {
|
|
|
if (k2 !== g2) {
|
|
if (k2 !== g2) {
|
|
|
g2 = k2;
|
|
g2 = k2;
|
|
|
const closed = S.closed.has("2\0" + k2);
|
|
const closed = S.closed.has("2\0" + k2);
|
|
|
- out.push(`<tr class="g2" data-g="${encodeURIComponent("2\0" + k2)}"><td colspan="${vis.length}">${closed ? "▶" : "▼"} ${esc(r.quant)} <span class="dim">(${all.filter(x => gkey(x, 2) === k2).length})</span></td></tr>`);
|
|
|
|
|
|
|
+ out.push(`<tr class="g2" data-g="${encodeURIComponent("2\0" + k2)}"><td colspan="${vis.length}">${closed ? "▶" : "▼"} ${esc(S.group === "pm" ? r.model : r.quant)} <span class="dim">(${all.filter(x => gkey(x, 2) === k2).length})</span></td></tr>`);
|
|
|
}
|
|
}
|
|
|
if (S.closed.has("2\0" + k2)) continue;
|
|
if (S.closed.has("2\0" + k2)) continue;
|
|
|
}
|
|
}
|
|
@@ -435,7 +449,7 @@ function render() {
|
|
|
} else if (v === "" || v == null) {
|
|
} else if (v === "" || v == null) {
|
|
|
body = '<span class="dim">—</span>';
|
|
body = '<span class="dim">—</span>';
|
|
|
} else if (c[0] === "name") {
|
|
} else if (c[0] === "name") {
|
|
|
- body = (MEDAL[r.medal] || "") + esc(v) +
|
|
|
|
|
|
|
+ body = medalBadge(r) + esc(v) +
|
|
|
`<button class="copy" data-copy="${esc(r.name)}" title="copy recipe name">⧉</button>`;
|
|
`<button class="copy" data-copy="${esc(r.name)}" title="copy recipe name">⧉</button>`;
|
|
|
} else {
|
|
} else {
|
|
|
body = esc(v);
|
|
body = esc(v);
|