Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 26,206 Bytes
da5cba1 6abb7be da5cba1 6abb7be da5cba1 6abb7be da5cba1 6abb7be da5cba1 6abb7be da5cba1 6abb7be da5cba1 6abb7be da5cba1 6abb7be da5cba1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | // A rollout on the MiMo release's own harness (app/mimo/runner), live: phases across the top, the agent's steps as a
// timeline, and the grade when it lands. The MiMo RL Environment Explorer's run page; run.js hands MiMo rollouts here.
import { $, api, esc, checkMsg, md, money, dur, ago, tokensShort, statusPill, toast, DOMAIN_NAME, LIVE_STATUSES, rewardClass, rewardText, sk, spinner, emptyState,
confetti, openDialog, closeModal, visibilityBadge, PRIVATE_NOTE, reportUrl } from "./util.js";
import { icon, DOMAIN_ICON } from "./icons.js";
import { refreshActive } from "./session.js";
import { taskHref, compareHref } from "./task.js";
const taskOf = (r) => taskHref(r.dataset || "XiaomiMiMo/MiMo-V2.6-RL-oss", r.path || r.task_id);
const PHASES = [["sandbox", "Sandbox"], ["setup", "Setup"], ["agent", "Agent"], ["verify", "Grading"], ["done", "Done"]];
const TOOL_ICON = { bash: "terminal", read: "file", write: "pencil", edit: "pencil", grep: "search", glob: "search", todowrite: "check", list: "list" };
let timer = null, tick = null, alive = true;
export function unmount() { alive = false; clearTimeout(timer); clearInterval(tick); }
const isLive = (r) => r && LIVE_STATUSES.includes(r.status);
function skeleton() {
return `<div class="wrap page"><div class="crumbs">${sk.line(18)}</div>
<div class="rp-head">${sk.line(22, 22)}${sk.line(55, 24)}${sk.line(45)}</div>
<div class="stepper">${PHASES.map(() => `<div class="step">${sk.box(20, "width:20px;border-radius:50%")}${sk.line(60)}</div>`).join("")}</div>
<div class="rp-grid"><div style="display:grid;gap:10px">${[70, 90, 55, 85, 60].map((w) => `<div style="display:grid;grid-template-columns:28px 1fr;gap:10px">${sk.box(28, "border-radius:8px")}${sk.box(30, `width:${w}%;border-radius:10px`)}</div>`).join("")}</div>
<div style="display:grid;gap:12px">${sk.card(sk.line(30, 14) + sk.line(25, 40) + sk.lines(90, 70))}${sk.card(sk.line(30, 14) + sk.line(35, 28) + sk.lines(80, 60))}</div></div></div>`;
}
export async function mount(el, id, first) {
alive = true;
const st = { id, after: 0, events: [], run: null, follow: true, steps: 0 };
if (!first) {
el.innerHTML = skeleton();
try { first = await api(`/api/runs/${encodeURIComponent(id)}`); }
catch (e) {
if (!alive) return;
el.innerHTML = `<div class="wrap page">${emptyState("search", "Rollout not found", e.status === 404 ? "It may be private, or the link is wrong." : esc(e.message),
`<a class="btn" href="/runs">${icon("arrowLeft")}Your rollouts</a>`)}</div>`;
return;
}
if (!alive) return;
}
el.innerHTML = `<div class="wrap page rp fade-in">
<nav class="crumbs" aria-label="Breadcrumb"><a href="/runs">Rollouts</a>${icon("chevronRight", 13)}<span>${esc(id)}</span></nav>
<header class="rp-head" id="rp-head"></header>
<div class="stepper" id="rp-phases"></div>
<div class="rp-grid">
<div class="rp-main"><div class="tl-bar"><h2>What the agent did</h2><span class="n" id="rp-n"></span>
<div class="tools-right"><button class="btn sm ghost" id="rp-expand" type="button">Expand all</button>
<label class="switch" id="rp-follow-wrap"><input type="checkbox" id="rp-follow" checked> Follow live</label></div></div>
<ol class="timeline" id="rp-tl"></ol><div id="rp-tail"></div></div>
<aside class="rp-side"><section class="panel grade" id="rp-grade"></section><section class="panel costcard" id="rp-cost"></section>
<section class="panel meta-card" id="rp-meta"></section></aside>
</div></div>`;
$("#rp-follow", el).addEventListener("change", (e) => (st.follow = e.target.checked));
$("#rp-expand", el).addEventListener("click", (e) => {
const open = e.target.textContent === "Expand all";
el.querySelectorAll("#rp-tl details").forEach((d) => (d.open = open));
e.target.textContent = open ? "Collapse all" : "Expand all";
});
el.addEventListener("click", onClick(st));
apply(el, st, first);
const poll = async () => {
if (!alive) return;
try { apply(el, st, await api(`/api/runs/${encodeURIComponent(id)}?after=${st.after}`)); } catch { /* retry next tick */ }
if (alive && isLive(st.run)) timer = setTimeout(poll, 1500);
else refreshActive();
};
if (isLive(st.run)) timer = setTimeout(poll, 1500);
tick = setInterval(() => { if (isLive(st.run)) { renderHead(el, st); renderTail(el, st); } }, 1000);
}
function apply(el, st, d) {
st.run = d.run;
st.stream = d.stream;
if (!st.navSet) { // someone else's public rollout belongs to Community, not to My rollouts
st.navSet = true;
const mine = !!d.run.is_owner;
document.querySelectorAll("[data-nav]").forEach((a) => a.classList.toggle("on", a.dataset.nav === (mine ? "runs" : "community")));
const c = el.querySelector(".crumbs a");
if (c && !mine) { c.href = "/community"; c.textContent = "Community"; }
}
const fresh = d.events.filter((e) => e.i >= st.after);
st.events.push(...fresh);
st.after = st.events.length ? st.events[st.events.length - 1].i + 1 : st.after;
renderHead(el, st);
renderPhases(el, st);
appendTimeline(el, st, fresh);
// history appears at once; only steps that arrive while you watch slide in
if (!st.primed) { el.querySelectorAll("#rp-tl .ev").forEach((li) => li.classList.add("still")); st.primed = true; }
renderGrade(el, st);
renderCost(el, st);
renderMeta(el, st);
renderTail(el, st);
$("#rp-follow-wrap", el).hidden = !isLive(st.run);
if (!st.run.is_owner) $("#rp-cost .fine", el)?.remove();
if (st.follow && fresh.length && isLive(st.run)) $("#rp-tail", el).scrollIntoView({ behavior: "smooth", block: "end" });
}
// Built once and then only its text changes: re-rendering every second restarted the spinner and made it flicker.
function renderTail(el, st) {
const t = $("#rp-tail", el);
if (!t) return;
if (!isLive(st.run)) { t.innerHTML = ""; return; }
let msg = $(".live-msg", t);
if (!msg) {
t.innerHTML = `<ol class="timeline"><li class="ev ev-live still"><span class="ei">${spinner()}</span><div class="eb"><span class="live-msg"></span></div><span class="ts"></span></li></ol>`;
msg = $(".live-msg", t);
}
const text = liveHint(st);
if (msg.textContent !== text) msg.textContent = text;
}
function liveHint(st) {
const s = st.run.status;
if (s === "queued") return "Queued…";
if (s === "starting") return "Starting a sandbox from the task's image. Big images take a minute or two.";
if (s === "setup") return "Preparing the environment…";
if (s === "verifying") return "Grading…";
const last = st.events[st.events.length - 1];
if (last?.kind === "partial") return `Writing… ${last.chars.toLocaleString()} characters${last.thinking_chars ? `, ${last.thinking_chars.toLocaleString()} thinking` : ""}`;
const sm = st.stream;
if (sm) {
const n = sm.text + sm.thinking + sm.tool;
if (!n) return `Waiting for the model's reply · ${dur(sm.age)}`;
const parts = [sm.thinking && `${sm.thinking.toLocaleString()} thinking`, sm.text && `${sm.text.toLocaleString()} message`,
sm.tool && `${sm.tool.toLocaleString()} in a tool call`].filter(Boolean);
return `The model is writing · ${n.toLocaleString()} characters so far (${parts.join(", ")}) · ${dur(sm.age)}`;
}
const since = last ? Math.max(0, (Date.now() / 1000 - (st.run.started_at || st.run.created_at)) - last.t) : 0;
return since > 45 ? `The model is working · ${dur(since)} since the last step. Long files take a while to write.` : "The model is working…";
}
function renderHead(el, st) {
const r = st.run;
if (!r) return;
const start = r.started_at || r.created_at, end = r.finished_at || Date.now() / 1000;
const owner = !!r.is_owner;
const action = !owner ? `<a class="btn sm primary" href="${taskOf(r)}">${icon("play", 13)}Run it yourself</a>`
: isLive(r)
? (st.stopping ? `<button class="btn danger sm" disabled>${spinner()}Stopping</button>` : `<button class="btn danger sm" id="rp-cancel">${icon("stop", 13)}Stop</button>`)
: `<a class="btn sm primary" href="${taskOf(r)}">${icon("refresh", 13)}Run again</a>`;
const vis = owner
? `<button class="btn sm" id="rp-vis" type="button" title="Change who can see this rollout">${icon(r.visibility === "public" ? "shield" : "globe", 13)}Make ${r.visibility === "public" ? "private" : "public"}</button>`
: "";
$("#rp-head", el).innerHTML = `
<div class="kick"><span class="badge" style="--dc:var(--c-${r.domain},var(--primary))">${icon(DOMAIN_ICON[r.domain] || "cpu", 13)}${esc(DOMAIN_NAME[r.domain] || (r.runner === "nemo-gym" ? "NeMo Gym" : r.domain))}</span>${statusPill(r.status)}${visibilityBadge(r.visibility)}
${owner ? "" : '<span class="when">shared anonymously</span>'}
<span class="when">${icon("clock", 13)}${dur(end - start)} · started ${ago(r.created_at)}</span></div>
<h1><a href="${taskOf(r)}">${esc(r.title)}</a></h1>
<div class="rp-bar"><div class="facts">
<span>${icon("cpu", 14)}<b>${esc(r.endpoint ? r.model : r.model.split("/")[1] || r.model)}</b> via ${r.endpoint ? `your endpoint <code>${esc(r.endpoint.host)}</code>` : esc(r.provider || "auto")}</span>
${paramsText(r.params) ? `<span>${icon("gauge", 14)}${esc(paramsText(r.params))}</span>` : ""}
${r.judge ? `<span>${icon("scale", 14)}judge <b>${esc(r.judge.split("/")[1] || r.judge)}</b></span>` : ""}
${r.runner === "nemo-gym" ? `<span>${icon("check", 14)}One prediction · NeMo Gym verifier</span>` : r.domain === "music" ? `<span>${icon("message", 14)}single model call</span>` : `<span>${icon("terminal", 14)}OpenCode on the MiMo harness</span>`}${r.flavor ? `<span>${icon("box", 14)}${esc(r.flavor)}</span>` : ""}</div>
<div class="rp-actions"><a class="btn sm ghost" href="${reportUrl({ run: r })}" target="_blank" rel="noopener" title="Report an issue with this rollout">${icon("flag", 13)}Report</a>${vis}
<a class="btn sm" href="${compareHref(r.dataset || "XiaomiMiMo/MiMo-V2.6-RL-oss", r.path || r.task_id, [r.id])}" title="Compare with other rollouts of this task">${icon("columns", 13)}Compare</a>
<a class="btn sm" href="${taskOf(r)}">${icon("file", 13)}View task</a>${action}</div></div>
${r.error ? `<div class="note-box err">${icon("alert")}<span>${esc(r.error)}</span></div>` : ""}`;
}
function renderPhases(el, st) {
const seen = {};
st.events.filter((e) => e.kind === "phase").forEach((e) => { (seen[e.name] ||= {})[e.status] = e; });
const failed = ["failed", "cancelled", "interrupted"].includes(st.run.status);
const noSandbox = st.run.domain === "music" || st.run.runner === "nemo-gym";
const list = PHASES.filter(([k]) => !(noSandbox && (k === "sandbox" || k === "setup")));
const box = $("#rp-phases", el);
box.style.setProperty("--n", list.length);
box.innerHTML = list.map(([k, label]) => {
const p = seen[k] || {};
const state = p.done ? (p.done.status === "error" ? "bad" : "ok") : p.start ? (failed ? "bad" : "on") : "off";
const t = p.start && p.done ? dur(p.done.t - p.start.t) : "";
const detail = (p.done || p.start)?.detail || "";
const mk = state === "ok" ? icon("check", 12) : state === "bad" ? icon("x", 12) : "";
return `<div class="step ${state}" title="${esc(detail)}"><span class="mk">${mk}</span><b>${label}</b><span class="t">${esc(t)}</span><em>${esc(detail)}</em></div>`;
}).join("");
}
function appendTimeline(el, st, events) {
const tl = $("#rp-tl", el);
for (const e of events) {
if (e.kind === "phase" && e.status === "start") st.phase = e.name;
const html = item(e, st);
if (html) { tl.insertAdjacentHTML("beforeend", html); if (e.kind === "tool" || e.kind === "text") st.steps++; }
}
$("#rp-n", el).textContent = st.steps ? `${st.steps} step${st.steps === 1 ? "" : "s"}` : "";
const empty = $(".tl-empty", el);
if (!tl.children.length && !isLive(st.run)) { if (!empty) tl.insertAdjacentHTML("afterend", `<p class="tl-empty">No agent steps were recorded.</p>`); }
else if (empty) empty.remove();
}
// A whole trace as timeline rows, for pages that show several at once (the comparison). `skip` leaves out kinds
// the page shows elsewhere, such as the prompt when every rollout got the same one.
export function traceHtml(id, events, { skip = [] } = {}) {
const st = { id, phase: null };
let steps = 0;
const html = events.map((e) => {
if (e.kind === "phase" && e.status === "start") st.phase = e.name;
if (skip.includes(e.kind)) return "";
const h = item(e, st);
if (h && (e.kind === "tool" || e.kind === "text")) steps++;
return h;
}).join("");
return { html, steps };
}
const row = (cls, ic, t, body) => `<li class="ev ${cls}"><span class="ei">${icon(ic, 14)}</span><div class="eb">${body}</div><span class="ts">${t}</span></li>`;
function item(e, st) {
const t = dur(e.t);
const summary = (inner) => `<summary>${icon("chevronRight", 13, "chev")}${inner}</summary>`;
switch (e.kind) {
case "prompt": return row("ev-prompt", "doc", t, `<details ${e.harness === "nemo-gym" || st.run.runner === "nemo-gym" ? "" : "open"}>${summary(`<b>Instructions to the model</b><span class="peek">${(e.text || "").length.toLocaleString()} characters</span>`)}
<div class="prompt-text">${esc(e.text)}</div><p class="prompt-note">${e.harness === "nemo-gym" || st.run.runner === "nemo-gym" ? "The native Responses request: conversation history and function definitions. The reference action is withheld." : e.harness === "opencode"
? "The task's instructions, sent as the first user message. OpenCode adds its own system prompt and tool definitions."
: "The whole request: one user message, no system prompt."}</p></details>`);
case "text": return row("ev-text", "message", t, `<div class="msg prose">${md(e.text)}</div>`);
case "thinking": return row("ev-think", "brain", t, `<details>${summary(`<b>Thinking</b><span class="peek">${esc(e.text.replace(/\s+/g, " ").slice(0, 220))}</span>`)}<div class="thought">${esc(e.text)}</div></details>`);
case "tool": {
const inp = e.input || {};
const head = inp.command || inp.filePath || inp.pattern || inp.path || e.title || Object.values(inp).map(String).join(" ").slice(0, 160);
const mcp = isMcpTool(e.tool);
const cls = `ev-tool${mcp ? " mcp" : ""}${e.status === "error" ? " bad" : ""}`;
return row(cls, mcp ? "plug" : TOOL_ICON[e.tool] || "terminal", t, `<details>${summary(`<b>${esc(mcp ? prettyTool(e.tool) : e.tool)}</b>
<code>${esc(String(head).split("\n")[0].slice(0, 200))}</code>${e.ms >= 1000 ? `<span class="ms">${dur(e.ms / 1000)}</span>` : ""}`)}
<div class="io">${Object.keys(inp).length ? `<h5>Input</h5><pre>${esc(JSON.stringify(inp, null, 2))}</pre>` : ""}
<h5>Output${e.truncated ? " (truncated)" : ""}</h5><pre>${esc(e.output || "(empty)")}</pre></div></details>`);
}
case "phase": return e.status === "error" && e.name !== "done" ? row("ev-err", "alert", t, `${esc(e.name)} failed: ${esc(e.detail)}`) : "";
case "error": return row("ev-err", "alert", t, `<b>Error</b> ${esc(e.text)}`);
case "log": return row("ev-log", "terminal", t, `<details>${summary(`<b>${({ sandbox: "Sandbox", setup: "Setup output", agent: "Agent output", verify: "Grader output" })[st.phase] || "Output"}</b><code>${esc((e.text || "").split("\n")[0].slice(0, 160))}</code>`)}<pre class="code-block">${esc(e.text)}</pre></details>`);
case "diff": return row("ev-diff", "diff", t, `<details open>${summary(`<b>The agent's change</b><code>${e.files.length} file${e.files.length === 1 ? "" : "s"}</code>`)}<pre class="code-block diff">${diffHtml(e.text)}</pre></details>`);
case "files": return row("ev-log", "folder", t, `<details>${summary(`<b>${esc(e.title)}</b><code>${e.files.length} files</code>`)}<pre class="code-block">${esc(e.files.join("\n"))}</pre></details>`);
case "image": {
const src = `/api/runs/${encodeURIComponent(st.id)}/artifacts/${encodeURIComponent(e.name)}`;
return row("ev-img", "image", t, `<b>Rendered page</b> <span class="muted sm">${e.width}×${e.height}</span>
<a href="${src}" target="_blank" rel="noopener"><img loading="lazy" src="${src}" alt="Full-page screenshot of what the agent built"></a>`);
}
default: return "";
}
}
const paramsText = (p) => !p ? "" : [p.thinking && p.thinking !== "default" && `thinking ${p.thinking === "none" ? "off" : p.thinking}`,
p.temperature != null && `temperature ${p.temperature}`, p.steps && `${p.steps} steps max`, p.timeout_min && `${p.timeout_min} min limit`,
p.max_tokens && `${p.max_tokens.toLocaleString()} max tokens`].filter(Boolean).join(" · ");
const humanize = (id) => { const t = String(id || "").replace(/[_-]+/g, " ").trim(); return t.charAt(0).toUpperCase() + t.slice(1); };
export const prettyTool = (name) => { const parts = name.split("_"); return parts.length > 3 ? parts.slice(-3).join("_") : name; };
export const isMcpTool = (name) => !(name in TOOL_ICON) && /_/.test(name || "");
export function diffHtml(text) {
return (text || "").split("\n").slice(0, 3000).map((l) => {
const c = l.startsWith("+++") || l.startsWith("---") || l.startsWith("diff ") ? "dh" : l.startsWith("+") ? "da" : l.startsWith("-") ? "dd" : l.startsWith("@@") ? "dc" : "";
return `<span class="${c}">${esc(l)}</span>`;
}).join("\n");
}
function renderGrade(el, st) {
const g = [...st.events].reverse().find((e) => e.kind === "checks") ||
(st.run.status === "done" && Number.isFinite(st.run.reward) ? { reward: st.run.reward, summary: "Recorded reward. Detailed grading events are unavailable." } : null);
const box = $("#rp-grade", el);
const h = `<div class="panel-h"><h3>${icon("scale", 14)}Grade</h3></div>`;
if (!g) {
box.innerHTML = `${h}<div class="panel-b"><p class="muted sm">${isLive(st.run) ? "Appears here once the agent finishes and the task's grader has run." : "This rollout was not graded."}</p></div>`;
return;
}
const rw = g.reward ?? st.run.reward;
const img = [...st.events].reverse().find((e) => e.kind === "image");
const src = img && `/api/runs/${encodeURIComponent(st.id)}/artifacts/${encodeURIComponent(img.name)}`;
const long = (g.summary || "").length > 180;
const zh = /[一-鿿]/.test(g.summary || "");
const cls = rewardClass(rw);
// General rubric checks: each one's share of the reward (verify.py counts a missing weight as 1)
const rubric = (g.checks || []).some((c) => c.method === "llm" || c.method === "rule");
const wt = (c) => (c.weight == null ? 1 : Number(c.weight));
const wsum = rubric ? g.checks.reduce((n, c) => n + wt(c), 0) || 1 : 1;
box.innerHTML = `${h}<div class="panel-b">
${src ? `<a class="shot" href="${src}" target="_blank" rel="noopener" title="Open the full-page render"><img src="${src}" alt="What the judge saw"></a>` : ""}
<div class="score"><span class="big ${cls}">${rewardText(rw)}</span><span class="of">${esc(st.run.reward_key || "reward")}</span></div>
${g.formula ? `<p class="muted xs">${esc(g.formula)}</p>` : ""}
${long || !g.summary ? "" : `<p class="gsum">${esc(g.summary)}</p>`}
${(g.checks || []).length ? `<ul class="gchecks">${g.checks.map((c) => `<li class="${c.passed === true ? "ok" : c.passed === false ? "bad" : "na"}">
<span class="mk">${icon(c.passed === true ? "check" : c.passed === false ? "x" : "more", 14)}</span>
<div><b>${esc(c.question || humanize(c.id))}</b>
${c.tier || c.method === "llm" ? `<span class="meta">${esc([c.tier, c.method === "llm" && "model-judged", rubric && `${Math.round((wt(c) / wsum) * 100)}% of the reward`].filter(Boolean).join(" · "))}</span>` : ""}
${c.message ? `<p>${esc(checkMsg(c.message).slice(0, 300))}</p>` : ""}</div>
${c.score != null && c.passed == null ? `<span class="sc">${Number(c.score).toFixed(2)}</span>` : "<span></span>"}</li>`).join("")}</ul>` : ""}
${(g.checks || []).some((c) => c.method === "llm") ? `<p class="xs faint" style="margin-top:8px">The task's grader keeps each check's verdict, not the judge's reasoning.</p>` : ""}
${long ? `<details class="notes"><summary class="disclose">${icon("chevronRight", 14, "chev")}Judge's notes</summary><p>${esc(g.summary)}</p>
${zh ? `<p class="xs faint">In Chinese because Xiaomi's grading rubric is written in Chinese; it is used unchanged so scores stay comparable with theirs.</p>` : ""}</details>` : ""}
</div>`;
}
function renderCost(el, st) {
const r = st.run, c = r.cost || {}, t = r.tokens || {};
const tot = (c.model || 0) + (c.sandbox || 0) || 1;
$("#rp-cost", el).innerHTML = `<div class="panel-h"><h3>${icon("coins", 14)}Cost</h3>${isLive(r) ? `<span class="aside"><i class="pulse" style="color:var(--live)"></i> live</span>` : ""}</div>
<div class="panel-b">
<div class="big-cost">${money(c.total)}</div>
<div class="cost-split"><i class="m" style="width:${((c.model || 0) / tot) * 100}%"></i><i class="s" style="width:${((c.sandbox || 0) / tot) * 100}%"></i></div>
<div class="legend">
<div><span class="sw" style="background:var(--c-code)"></span><span>Model tokens</span><b>${r.endpoint && r.endpoint.price_in == null && r.endpoint.price_out == null ? '<span class="faint">not metered</span>' : money(c.model)}</b></div>
<div><span class="sw" style="background:var(--c-cyber)"></span><span>Sandbox time</span><b>${money(c.sandbox)}</b></div>
${r.judge ? `<div><span class="sw" style="background:var(--surface-3)"></span><span>Judge calls</span><b class="faint">not metered</b></div>` : ""}
</div>
<dl class="kv sm"><dt>Tokens in</dt><dd class="num">${tokensShort((t.input || 0) + (t.cache_read || 0))}${t.cache_read ? ` <span class="faint">(${tokensShort(t.cache_read)} cached)</span>` : ""}</dd>
<dt>Tokens out</dt><dd class="num">${tokensShort(t.output)}${t.reasoning ? ` <span class="faint">+ ${tokensShort(t.reasoning)} thinking</span>` : ""}</dd></dl>
<p class="fine">${r.endpoint ? `Tokens are billed by your endpoint (${esc(r.endpoint.host)}); the sandbox by Hugging Face.` : `Billed to your Hugging Face account at ${esc(r.provider || "the provider")}'s price.`}</p></div>`;
}
async function setVisibility(el, st, to, btn) {
try {
await api(`/api/runs/${encodeURIComponent(st.id)}/visibility`, { method: "POST", body: { visibility: to } });
st.run.visibility = to;
renderHead(el, st);
if (to === "public") {
const r = (btn || $("#rp-vis", el))?.getBoundingClientRect();
confetti(r ? r.left + r.width / 2 : innerWidth / 2, r ? r.top : innerHeight / 3);
toast("Public: thank you for sharing with the community");
} else toast("Private: only you can see this rollout");
} catch (e) { toast(e.message); }
}
function renderMeta(el, st) {
const r = st.run, p = r.params || {}, pv = r.provenance || {};
const row = (k, v) => (v == null || v === "" ? "" : `<dt>${k}</dt><dd>${v}</dd>`);
const code = (v) => `<code>${esc(v)}</code>`;
const think = { none: "off", low: "low", medium: "medium", high: "high" }[p.thinking] || "model default";
$("#rp-meta", el).innerHTML = `<div class="panel-h"><h3>${icon("info", 14)}Run details</h3></div><div class="panel-b"><dl class="kv sm">
${row("Model", code(r.model))}
${row("Served by", r.endpoint ? "an OpenAI-compatible endpoint the runner brought" + (r.endpoint.host && r.is_owner ? ` (${code(r.endpoint.host)})` : "") : esc(r.provider || "HF router auto"))}
${row("Judge", r.judge ? code(r.judge) : "")}
${row("Thinking", esc(think))}
${row("Temperature", p.temperature != null ? esc(p.temperature) : "model default")}
${r.domain === "music" || r.runner === "nemo-gym" ? row("Max tokens", esc(p.max_tokens || p.max_tokens_used || 100000)) : row("Step cap", esc(p.steps || "task default")) + row("Time limit", p.timeout_min ? esc(p.timeout_min + " min") : "task default") + row("Max tokens / step", esc(p.max_tokens || "default"))}
${row("Harness", r.runner === "nemo-gym" ? "Single prediction; tool calls are not executed" : r.domain === "music" ? "single model call" : `OpenCode ${esc(pv.harness?.installed || pv.harness?.version || "")}`)}
${row("Verifier revision", r.runner === "nemo-gym" ? code(pv.commit || "unknown") : "")}
${row("Sandbox", r.flavor ? esc(r.flavor) : "")}
${row("Image", r.image ? code(r.image.replace("docker.io/", "")) : "")}
${row("Explorer", pv.app ? `v${esc(pv.app.version)} · ${code(pv.app.source)}` : "")}
${row("Dataset", pv.dataset ? `${esc(pv.dataset.repo)}${pv.dataset.revision ? " @ " + code(pv.dataset.revision.slice(0, 10)) : ""}` : "")}
${row("Reference", pv.reference ? Object.entries(pv.reference).map(([k, v]) => `${esc(k.split("/")[1])} ${code(v)}`).join(" · ") : "")}
${row("Rollout id", code(r.id))}
</dl></div>`;
}
function onClick(st) {
return async (ev) => {
const vb = ev.target.closest("#rp-vis");
if (vb) {
const el = vb.closest(".rp");
if (st.run.visibility === "public") {
const dlg = openDialog(`<div class="dialog-h"><div><h2>Make this rollout private?</h2><p>${esc(PRIVATE_NOTE)}</p></div>
<button class="icon-btn" type="button" data-close aria-label="Close">${icon("x", 18)}</button></div>
<div class="dialog-b"><div style="display:flex;gap:8px;justify-content:flex-end"><button class="btn" data-close>Keep it public</button>
<button class="btn primary" id="vis-go">Make private</button></div></div>`);
dlg.addEventListener("click", async (e) => {
if (e.target.closest("[data-close]")) closeModal();
if (e.target.closest("#vis-go")) { closeModal(); await setVisibility(el, st, "private"); }
});
} else await setVisibility(el, st, "public", vb);
return;
}
const b = ev.target.closest("#rp-cancel");
if (!b) return;
st.stopping = true;
b.disabled = true;
b.innerHTML = `${spinner()}Stopping`;
try { await api(`/api/runs/${encodeURIComponent(st.id)}/cancel`, { method: "POST" }); toast("Stopping the rollout…"); }
catch (e) { st.stopping = false; toast(e.message); b.disabled = false; b.innerHTML = `${icon("stop", 13)}Stop`; }
};
}
|