jimmy60504's picture
Update QML Classifier Explorer
7fab05e verified
import {
experimentMeta, loadingPanel, loadingLabel, loadingPercent, loadingBar,
exportStatus, runtimeSourceUrls, state,
} from "./dom.js";
function ensureTrailingSlash(value) {
if (!value) {
return "./";
}
return value.endsWith("/") ? value : `${value}/`;
}
function buildDatasetRuntimeRoot(repoId, revision = "main", pathPrefix = "") {
const safePrefix = pathPrefix.trim().replace(/^\/+|\/+$/g, "");
const suffix = safePrefix ? `${safePrefix}/` : "";
return `https://huggingface.co/datasets/${repoId}/resolve/${revision}/${suffix}`;
}
function isAbsoluteUrl(path) {
return /^(?:[a-z]+:)?\/\//i.test(path);
}
function resolveAgainstBase(path, baseUrl) {
if (!path) {
return path;
}
if (isAbsoluteUrl(path)) {
return path;
}
const normalizedBase = isAbsoluteUrl(baseUrl)
? baseUrl
: new URL(baseUrl, window.location.href).toString();
return new URL(path, normalizedBase).toString();
}
function shouldResolveFromRuntimeRoot(path) {
if (!path || isAbsoluteUrl(path)) {
return false;
}
return path.startsWith("runtime/") || path.startsWith("./runtime/");
}
function buildRuntimeRoot(runtimeSource) {
if (runtimeSource?.runtime_root_url) {
return ensureTrailingSlash(runtimeSource.runtime_root_url);
}
if (runtimeSource?.hf_dataset_repo) {
return buildDatasetRuntimeRoot(
runtimeSource.hf_dataset_repo,
runtimeSource.hf_dataset_revision || "main",
runtimeSource.hf_dataset_path_prefix || ""
);
}
return ensureTrailingSlash("./");
}
function normalizeRuntimeSource(runtimeSource) {
const normalized = {
manifest_path: "./runtime/viewer_manifest.json",
fallback_manifest_urls: ["./data/viewer_manifest.template.json"],
...runtimeSource,
};
normalized.runtime_root_url = buildRuntimeRoot(normalized);
return normalized;
}
function buildManifestUrls(runtimeSource) {
const urls = [];
if (runtimeSource?.manifest_path) {
urls.push(resolveAgainstBase(runtimeSource.manifest_path, runtimeSource.runtime_root_url));
}
for (const fallback of runtimeSource?.fallback_manifest_urls || []) {
urls.push(resolveAgainstBase(fallback, "./"));
}
return urls;
}
function resolveRuntimePath(path) {
if (!path) {
return path;
}
if (shouldResolveFromRuntimeRoot(path)) {
return resolveAgainstBase(path, state.runtimeSource?.runtime_root_url || "./");
}
return resolveAgainstBase(path, "./");
}
function normalizeManifest(manifest) {
return {
...manifest,
runs: (manifest.runs || []).map((run) => ({
...run,
path: resolveRuntimePath(run.path),
})),
};
}
function normalizeRunPayload(payload) {
const normalized = { ...payload };
if (normalized.assets) {
normalized.assets = {
...normalized.assets,
circuit: resolveRuntimePath(normalized.assets.circuit),
data_overview: resolveRuntimePath(normalized.assets.data_overview),
};
}
if (normalized.run?.path) {
normalized.run = {
...normalized.run,
path: resolveRuntimePath(normalized.run.path),
};
}
if (normalized.timeline_chunks) {
normalized.timeline_chunks = normalized.timeline_chunks.map((chunk) => ({
...chunk,
path: resolveRuntimePath(chunk.path),
}));
}
if (normalized.timeline_steps) {
normalized.timeline_steps = normalized.timeline_steps.map((step) => {
if (!step.chunk_path) {
return step;
}
return {
...step,
chunk_path: resolveRuntimePath(step.chunk_path),
};
});
}
return normalized;
}
function describePath(path) {
if (!path) {
return "runtime data";
}
try {
const url = new URL(path, window.location.href);
const parts = url.pathname.split("/").filter(Boolean);
return parts.slice(-2).join("/");
} catch {
return path.replace("./", "");
}
}
export function withCacheBust(path) {
const separator = path.includes("?") ? "&" : "?";
return `${path}${separator}t=${Date.now()}`;
}
export function formatMetric(value) {
if (value === undefined || value === null || Number.isNaN(Number(value))) {
return "—";
}
const numeric = Number(value);
const absValue = Math.abs(numeric);
if (absValue > 0 && absValue < 1e-3) {
return numeric.toExponential(2);
}
return numeric.toFixed(4);
}
export function formatInteger(value) {
if (value === undefined || value === null || Number.isNaN(Number(value))) {
return "—";
}
return String(Math.round(Number(value)));
}
export function appendMetaRow(label, value) {
const wrapper = document.createElement("div");
const dt = document.createElement("dt");
const dd = document.createElement("dd");
dt.textContent = label;
dd.textContent = value;
wrapper.append(dt, dd);
experimentMeta.appendChild(wrapper);
}
export function setLoadingState({ visible, label, percent, status }) {
if (loadingPanel) {
loadingPanel.hidden = !visible;
}
if (loadingLabel && label) {
loadingLabel.textContent = label;
}
if (typeof percent === "number") {
const clamped = Math.max(0, Math.min(100, Math.round(percent)));
if (loadingPercent) {
loadingPercent.textContent = `${clamped}%`;
}
if (loadingBar) {
loadingBar.style.width = `${clamped}%`;
}
}
if (status) {
exportStatus.textContent = status;
}
}
export async function loadRuntimeSource() {
for (const url of runtimeSourceUrls) {
const response = await fetch(withCacheBust(url), { cache: "no-store" });
if (response.ok) {
const runtimeSource = normalizeRuntimeSource(await response.json());
state.runtimeSource = runtimeSource;
return runtimeSource;
}
}
const fallback = normalizeRuntimeSource({});
state.runtimeSource = fallback;
return fallback;
}
export async function loadManifest() {
const runtimeSource = state.runtimeSource || (await loadRuntimeSource());
for (const url of buildManifestUrls(runtimeSource)) {
setLoadingState({
visible: true,
label: `Loading manifest from ${describePath(url)}`,
percent: 8,
status: "loading",
});
const response = await fetch(withCacheBust(url), { cache: "no-store" });
if (response.ok) {
const data = normalizeManifest(await response.json());
setLoadingState({
visible: true,
label: "Manifest ready",
percent: 18,
status: "loading",
});
return data;
}
}
throw new Error("No viewer manifest available.");
}
export function resolveRuntimeAssetPath(path) {
return resolveRuntimePath(path);
}
export async function loadRunData(path, loadToken) {
const response = await fetch(withCacheBust(path), { cache: "no-store" });
if (!response.ok) {
throw new Error(`Failed to load run data: ${path}`);
}
const contentLength = Number(response.headers.get("content-length") || 0);
if (!response.body || !contentLength) {
setLoadingState({
visible: true,
label: `Loading ${describePath(path)}`,
percent: 45,
status: "loading",
});
return normalizeRunPayload(await response.json());
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let received = 0;
let text = "";
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
if (loadToken !== state.activeLoadToken) {
throw new Error("Stale run load aborted.");
}
received += value.byteLength;
text += decoder.decode(value, { stream: true });
const progress = 20 + (received / contentLength) * 55;
setLoadingState({
visible: true,
label: `Downloading ${describePath(path)}`,
percent: progress,
status: "loading",
});
}
text += decoder.decode();
return normalizeRunPayload(JSON.parse(text));
}
export async function loadRunChunk(path, loadToken) {
const response = await fetch(withCacheBust(path), { cache: "no-store" });
if (!response.ok) {
throw new Error(`Failed to load run chunk: ${path}`);
}
if (loadToken !== state.activeLoadToken) {
throw new Error("Stale chunk load aborted.");
}
return normalizeRunPayload(await response.json());
}