Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 13,860 Bytes
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 | // App shell: router, account menu and sign-in, theme. Every page has a real path (the server answers each with the
// page and its own title, description and structured data, app/seo.py); in-app links move with the History API.
// / explore: every RL environment on the Hub
// /d/<org>/<name> an environment dataset, any format (app/envs): how it's read and graded, and its tasks
// /t/<org>/<name>/<ref> a task, any format: what the agent gets, how it is graded, what it runs in, its files;
// run it (ref is the adapter's: a Harbor task's folder, a row's [config/]split/i)
// /s/<org>/<name> an environment Space: live status, its app, a playground, its tasks, MCP
// /compare/<org>/<name>/<ref>?r=a,b rollouts of one task side by side
// /run/<id> a rollout: its trajectory as it runs, then its reward
// /runs your rollouts
// /community everyone's public rollouts, by model
// Older addresses still open: #/β¦ (the hash router this app had), and the MiMo RL Environment Explorer's #/task/<id>,
// #/compare/<id> and #/rewards.
import { $, api, esc, storage, closeModal, openDialog, progress, spinner, toast, nav, setMeta, emptyState } from "./util.js";
import { icon } from "./icons.js";
import { getSession, setSession, refreshActive } from "./session.js";
import { wireTopSearch } from "./topsearch.js";
import { forgetMine } from "./data.js";
let current = null;
const VIEWS = { home: () => import("./home.js"), dataset: () => import("./env.js"), task: () => import("./task.js"),
run: () => import("./run.js"), runs: () => import("./runs.js"), community: () => import("./community.js"),
space: () => import("./space.js"), compare: () => import("./compare.js") };
const MIMO = "XiaomiMiMo/MiMo-V2.6-RL-oss";
const framed = window.top !== window.self;
const decodePath = (s) => { try { return decodeURIComponent(s); } catch { return s; } };
function parse(url) {
const [path, qs] = url.split("?");
let m = path.match(/^\/run\/([\w-]+)$/);
if (m) return { name: "run", arg: { id: m[1] }, qs };
if (/^\/runs\/?$/.test(path)) return { name: "runs", arg: {}, qs };
if (/^\/community\/?$/.test(path)) return { name: "community", arg: {}, qs };
m = path.match(/^\/s\/([^/]+)\/([^/]+)\/?$/);
if (m) return { name: "space", arg: { spec: `${decodePath(m[1])}/${decodePath(m[2])}` }, qs };
m = path.match(/^\/compare\/([^/]+)\/([^/]+)\/(.+)$/);
if (m) return { name: "compare", arg: { spec: `${decodePath(m[1])}/${decodePath(m[2])}`, ref: decodePath(m[3]) }, qs };
m = path.match(/^\/(task|compare)\/([^/]+)$/); // the MiMo explorer's addresses
if (m) return { redirect: `/${m[1] === "task" ? "t" : "compare"}/${MIMO}/${m[2]}${qs ? `?${qs}` : ""}` };
if (/^\/rewards\/?$/.test(path)) return { redirect: `/d/${MIMO}?rewards=1` };
m = path.match(/^\/r\/([^/]+)\/([^/]+)\/?$/);
if (m) { // a row's old address: its task page now, like every other task
const q = new URLSearchParams(qs || ""), c = q.get("c") || "default", sp = q.get("s") || "train", i = Math.max(0, +q.get("i") || 0);
["c", "s", "i"].forEach((k) => q.delete(k));
return { redirect: `/t/${m[1]}/${m[2]}/${c === "default" ? "" : `${encodeURIComponent(c)}/`}${encodeURIComponent(sp)}/${i}${q.toString() ? `?${q}` : ""}` };
}
m = path.match(/^\/d\/([^/]+)\/([^/]+)\/?$/);
if (m) return { name: "dataset", arg: { spec: `${decodePath(m[1])}/${decodePath(m[2])}` }, qs };
m = path.match(/^\/t\/([^/]+)\/([^/]+)\/?(.*)$/);
if (m) return { name: "task", arg: { spec: `${decodePath(m[1])}/${decodePath(m[2])}`, path: decodePath(m[3] || "") }, qs };
return { name: "home", arg: {}, qs };
}
let routing = 0;
async function route() {
const token = ++routing;
if (location.hash.startsWith("#/")) { history.replaceState(null, "", location.hash.slice(1)); } // an address from the hash router
const { name, arg, qs, redirect } = parse(location.pathname + location.search);
if (redirect) { history.replaceState(null, "", redirect); return route(); }
if (current?.unmount) current.unmount();
current = null;
closeModal();
const tip = document.getElementById("tip");
if (tip) tip.hidden = true;
document.body.dataset.view = name; // the Explore page has its own search, so the header's hides there
setMeta({ title: { runs: "My rollouts", community: "Community rollouts", run: "Rollout", compare: "Compare rollouts" }[name] });
document.querySelectorAll("[data-nav]").forEach((a) => a.classList.toggle("on", a.dataset.nav === ({ runs: "runs", run: "runs", community: "community", compare: "community" }[name] || "explore")));
window.scrollTo(0, 0);
progress.start();
try {
const mod = await VIEWS[name]();
if (token !== routing) return;
current = mod;
// a fresh container per page: views attach their listeners to it, so none survive into the next page
const old = $("#view"), view = old.cloneNode(false);
old.replaceWith(view);
await mod.mount(view, { ...arg, qs: qs ? new URLSearchParams(qs) : new URLSearchParams() });
} catch (error) {
if (token !== routing) return;
current?.unmount?.();
current = null;
$("#view").innerHTML = `<div class="wrap page">${emptyState("alert", "Couldn't load this page", esc(error.message || "Please try again."),
'<button class="btn" type="button" id="page-retry">Try again</button><a class="btn" href="/">Explore environments</a>')}</div>`;
$("#page-retry").addEventListener("click", route);
} finally { progress.done(); }
}
// ββ account ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function loadSession() {
try { setSession(await api("/api/me")); } catch { setSession({ user: null }); }
forgetMine(); // whose datasets "yours" means may have changed
const v = getSession();
const note = $("#announce");
note.hidden = !v.announcement;
note.querySelector("span").textContent = v.announcement || "";
if (v.version) $("#foot-version").innerHTML = `v${esc(v.version)} Β· <code title="source hash">${esc(v.source || "")}</code>`;
renderAccount();
}
function renderAccount() {
const el = $("#account");
const s = getSession(), u = s.user;
if (!u) {
el.innerHTML = `<button class="btn primary signin" type="button" data-signin aria-label="Sign in">${icon("user", 15)}<span class="long">Sign in</span></button>`;
return;
}
const via = { oauth: "Signed in with Hugging Face", token: "Signed in with an access token", local: "Using this machine's HF token" }[u.via] || "";
el.innerHTML = `<div class="acct">
<button class="acct-btn" type="button" aria-haspopup="menu" aria-expanded="false">
${u.avatar ? `<img src="${esc(u.avatar)}" alt="">` : `<span class="avatar"></span>`}<span class="name">${esc(u.name)}</span>${icon("chevronDown", 14)}</button>
<div class="menu" role="menu" hidden>
<div class="menu-h"><b>${esc(u.name)}</b><span>${esc(via)}</span></div>
<a href="https://huggingface.co/settings/billing" target="_blank" rel="noopener" role="menuitem">${icon("coins")}Billing on Hugging Face</a>
<button type="button" data-signin role="menuitem">${icon("key")}Use a different token</button>
${u.via === "local" ? "" : `<button type="button" data-signout role="menuitem">${icon("logout")}Sign out</button>`}
</div></div>`;
const btn = $(".acct-btn", el), menu = $(".menu", el);
btn.addEventListener("click", (e) => { e.stopPropagation(); menu.hidden = !menu.hidden; btn.setAttribute("aria-expanded", String(!menu.hidden)); });
document.addEventListener("click", (e) => { if (!menu.hidden && !e.composedPath().includes(el)) { menu.hidden = true; btn.setAttribute("aria-expanded", "false"); } });
}
export function openSignIn() {
const s = getSession();
const dlg = openDialog(`
<div class="dialog-h"><div><h2>${s.user ? "Use a different token" : "Sign in"}</h2>
<p>Rollouts run on your Hugging Face account: an HF Sandbox plus the model's tokens at the provider's price. Signing in also
opens your private Harbor datasets.</p></div>
<button class="icon-btn" type="button" data-close aria-label="Close">${icon("x", 18)}</button></div>
<div class="dialog-b">
${s.oauth ? `<a class="btn primary lg block" href="/login" ${framed ? 'target="_blank" rel="noopener"' : ""}>
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="" width="18" height="18">Continue with Hugging Face</a>
<div class="or">or use an access token</div>` : ""}
<form class="field" id="tok-form" autocomplete="off">
<span>Hugging Face access token <em>write, or fine-grained with Inference Providers and Jobs</em></span>
<input class="input mono" id="tok" type="password" placeholder="hf_β¦" spellcheck="false" autocomplete="off" aria-label="Hugging Face access token" required>
<div id="tok-err"></div>
<button class="btn ${s.oauth ? "" : "primary"} block" type="submit" id="tok-go">Sign in with token</button>
</form>
<p class="fine">The token is checked with Hugging Face, then kept in an encrypted cookie on this browser for 7 days. It is never
written to disk or into a trace.</p>
</div>`);
const form = $("#tok-form", dlg), input = $("#tok", dlg), go = $("#tok-go", dlg);
if (!s.oauth) input.focus();
dlg.addEventListener("click", (e) => { if (e.target.closest("[data-close]")) closeModal(); });
form.addEventListener("submit", async (e) => {
e.preventDefault();
$("#tok-err", dlg).innerHTML = "";
go.disabled = true; go.innerHTML = `${spinner()}Checking with Hugging Faceβ¦`;
try {
const r = await api("/api/login/token", { method: "POST", body: { token: input.value } });
input.value = "";
closeModal();
await loadSession();
toast(`Signed in as ${r.user.name}`, 3000);
route();
} catch (err) {
$("#tok-err", dlg).innerHTML = `<div class="note-box err">${icon("alert")}<span>${esc(err.message)}</span></div>`;
go.disabled = false; go.textContent = "Sign in with token";
}
});
}
async function signOut() {
try { await api("/api/logout", { method: "POST" }); } catch { /* the cookie is gone either way */ }
await loadSession();
toast("Signed out");
route();
}
// ββ theme ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const isDark = () => (document.documentElement.dataset.theme ? document.documentElement.dataset.theme === "dark" : matchMedia("(prefers-color-scheme: dark)").matches);
function paintTheme() { $("#theme").innerHTML = icon(isDark() ? "sun" : "moon", 17); }
function initTheme() {
paintTheme();
$("#theme").addEventListener("click", () => {
document.documentElement.dataset.theme = isDark() ? "light" : "dark";
storage.set("theme", document.documentElement.dataset.theme);
paintTheme();
});
matchMedia("(prefers-color-scheme: dark)").addEventListener("change", paintTheme);
}
function wireGlobal() {
$("#modal-close").innerHTML = icon("x", 18);
$("#modal-close").addEventListener("click", closeModal);
$("#scrim").addEventListener("click", closeModal);
document.addEventListener("click", (e) => {
if (e.target.closest(".menu a, .menu button")) { const m = $(".menu"); if (m) m.hidden = true; }
if (e.target.closest("[data-signin]")) { e.preventDefault(); openSignIn(); }
else if (e.target.closest("[data-signout]")) { e.preventDefault(); signOut(); }
});
document.addEventListener("keydown", (ev) => {
if (ev.key === "Escape") closeModal();
if (ev.key === "/" && !/INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName)) {
const s = document.querySelector("[data-search]") || $("#top-q");
if (s) { ev.preventDefault(); s.focus(); }
}
});
addEventListener("popstate", route);
addEventListener("hashchange", () => { if (location.hash.startsWith("#/")) route(); }); // an old #/ link pasted in
// in-app links: the History API, not a page load (new tab, modified clicks, downloads and other origins as usual)
document.addEventListener("click", (e) => {
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
const a = e.target.closest("a[href]");
if (!a || a.target || a.hasAttribute("download") || a.origin !== location.origin) return;
const href = a.getAttribute("href");
if (!href.startsWith("/") || /^\/(api|mcp|capture|login|logout|oauth)\b/.test(href)) return;
e.preventDefault();
if (href === location.pathname + location.search) return;
nav(href);
});
wireTopSearch();
// a Space preview that won't load (no thumbnail yet) leaves, rather than showing a broken image (no inline handlers: CSP)
document.addEventListener("error", (e) => { if (e.target.matches?.(".sp-thumb, .fe-thumb, .sp-shot")) e.target.remove(); }, true);
}
// the FineEnvs banner: shown until it's closed, then not again on this browser for a week
function wireBanner() {
const b = $("#fe-banner");
const closed = Number(storage.get("fe-banner-closed") || 0);
if (Date.now() - closed < 7 * 864e5) return;
b.hidden = false;
$("#fe-banner-close").addEventListener("click", () => { b.classList.add("closing"); storage.set("fe-banner-closed", String(Date.now())); setTimeout(() => (b.hidden = true), 220); });
}
initTheme();
wireBanner();
wireGlobal();
loadSession().then(() => { route(); refreshActive(); });
setInterval(refreshActive, 8000);
|