AIMoCap / assets /runtime /hfspace-results-runtime.html
animtex's picture
Publish compiled HF Space artifact
41c0a8c
Raw
History Blame Contribute Delete
26.9 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AIMoCap HF Space Result Viewer</title>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: linear-gradient(180deg, #f8fafc, #e2e8f0);
color: #0f172a;
font-family: Arial, sans-serif;
}
#app {
height: 100%;
min-height: 100%;
box-sizing: border-box;
padding: 14px;
display: flex;
flex-direction: column;
gap: 12px;
}
#header {
display: flex;
align-items: center;
justify-content: center;
}
#empty {
flex: 1;
border: 1px solid #cbd5e1;
border-radius: 18px;
background: rgba(255, 255, 255, 0.82);
display: none;
align-items: center;
justify-content: center;
text-align: center;
padding: 24px;
color: #475569;
}
#tabs {
display: flex;
gap: 8px;
justify-content: center;
}
.tab {
border: 1px solid #cbd5e1;
background: #ffffff;
border-radius: 999px;
padding: 8px 14px;
cursor: pointer;
font-size: 13px;
color: #0f172a;
}
.tab.is-active {
background: #0f172a;
color: #f8fafc;
border-color: #0f172a;
}
#footer {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
}
#controls {
display: flex;
gap: 12px;
align-items: center;
padding: 10px 12px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.82);
border: 1px solid rgba(203, 213, 225, 0.95);
flex: 1;
}
#play {
border: 0;
border-radius: 999px;
padding: 8px 14px;
background: #0f172a;
color: #f8fafc;
cursor: pointer;
min-width: 86px;
}
#play:disabled {
opacity: 0.45;
cursor: not-allowed;
}
#seek {
flex: 1;
}
#time {
min-width: 104px;
text-align: right;
color: #334155;
font-size: 12px;
}
#views {
flex: 1;
min-height: 0;
position: relative;
min-height: clamp(320px, 58vh, 610px);
}
.view {
position: absolute;
inset: 0;
height: 100%;
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.view.is-active {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.viewer-shell {
position: relative;
height: 100%;
min-height: 0;
}
.viewer-frame {
width: 100%;
height: 100%;
border: 0;
border-radius: 16px;
overflow: hidden;
background: #020617;
}
.viewer-fallback {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
background: linear-gradient(180deg, #0f172a, #020617 58%);
border-radius: 16px;
display: none;
}
.viewer-fallback.is-visible {
display: block;
}
.viewer-overlay {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
padding: 22px;
border-radius: 16px;
background: linear-gradient(180deg, rgba(2, 6, 23, 0.76), rgba(15, 23, 42, 0.68));
color: #e2e8f0;
text-align: center;
pointer-events: none;
font: 600 14px/1.7 Arial, sans-serif;
}
.viewer-overlay.is-visible {
display: flex;
}
.viewer-overlay.is-actionable {
pointer-events: auto;
}
.viewer-overlay-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
max-width: 420px;
}
.viewer-overlay-action {
display: none;
border: 0;
border-radius: 999px;
padding: 10px 16px;
background: #f8fafc;
color: #0f172a;
cursor: pointer;
font: 700 13px/1 Arial, sans-serif;
}
.viewer-overlay-action.is-visible {
display: inline-flex;
align-items: center;
justify-content: center;
}
#download-action {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 180px;
min-height: 44px;
padding: 0 16px;
border-radius: 999px;
background: #0f172a;
color: #f8fafc;
text-decoration: none;
font: 700 13px/1 Arial, sans-serif;
white-space: nowrap;
}
#download-action.is-hidden {
visibility: hidden;
}
@media (max-width: 768px) {
html,
body {
overflow: auto;
}
#app {
padding: 10px;
gap: 10px;
}
#header {
justify-content: flex-start;
}
#tabs {
width: 100%;
justify-content: flex-start;
overflow-x: auto;
padding-bottom: 2px;
}
#footer {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
#controls {
flex-wrap: wrap;
padding: 10px;
}
#play {
min-width: 96px;
}
#seek {
order: 3;
flex: 1 1 100%;
width: 100%;
}
#time {
min-width: 0;
margin-left: auto;
}
#download-action {
width: 100%;
min-width: 0;
}
#views {
min-height: min(42vh, 320px);
}
}
</style>
</head>
<body>
<div id="app">
<div id="header">
<div id="tabs"></div>
</div>
<video id="clock" style="display:none" preload="metadata" playsinline></video>
<div id="empty">Waiting for result preview...</div>
<div id="views"></div>
<div id="footer">
<div id="controls">
<button id="play" type="button" disabled>Play</button>
<input id="seek" type="range" min="0" max="0" step="0.01" value="0" />
<div id="time">0.0s / 0.0s</div>
</div>
<a id="download-action" class="is-hidden" href="#" target="_blank" rel="noopener noreferrer">Download</a>
</div>
</div>
<script type="module">
const TARGET_LABELS = {
default: "Default",
"robot:unitree_g1": "Unitree G1",
};
function decodePayload() {
const token = window.location.hash.replace(/^#/, "").trim();
if (!token) return null;
const padded = token + "=".repeat((4 - (token.length % 4 || 4)) % 4);
const json = atob(padded.replace(/-/g, "+").replace(/_/g, "/"));
return JSON.parse(json);
}
const payload = decodePayload();
const tabsRoot = document.getElementById("tabs");
const viewsRoot = document.getElementById("views");
const emptyRoot = document.getElementById("empty");
const controlsRoot = document.getElementById("controls");
const playButton = document.getElementById("play");
const seek = document.getElementById("seek");
const timeLabel = document.getElementById("time");
const clockVideo = document.getElementById("clock");
const downloadAction = document.getElementById("download-action");
if (!payload || !Array.isArray(payload.targetIds) || !payload.targetIds.length) {
emptyRoot.style.display = "flex";
tabsRoot.style.display = "none";
viewsRoot.style.display = "none";
controlsRoot.style.display = "none";
throw new Error("Missing HF Space result payload.");
}
const targetIds = payload.targetIds.filter((item) => item && payload.resultsByTarget?.[item]);
let activeTargetId = targetIds.includes(payload.activeTargetId) ? payload.activeTargetId : targetIds[0];
const shouldAutoplay = !!payload.autoplay;
const runtimeNonce = Number(payload.nonce || 0);
const assetVersion = String(payload.assetVersion || "").trim();
const frames = {};
const viewerStates = {};
const loadTimeouts = {};
const viewerSessions = {};
const fallbackLoaded = {};
let playing = false;
const masterTargetId = targetIds.includes("default") ? "default" : targetIds[0];
const masterVideoUrl = payload.resultsByTarget?.[masterTargetId]?.resultVideoUrl || "";
if (masterVideoUrl) {
clockVideo.src = masterVideoUrl;
}
function formatTime(value) {
const safe = Number.isFinite(value) ? Math.max(0, value) : 0;
return `${safe.toFixed(1)}s`;
}
function updateTimeLabel() {
const duration = Number.isFinite(clockVideo.duration) ? clockVideo.duration : 0;
timeLabel.textContent = `${formatTime(clockVideo.currentTime)} / ${formatTime(duration)}`;
seek.max = duration > 0 ? String(duration) : "0";
seek.value = String(clockVideo.currentTime || 0);
playButton.disabled = !masterVideoUrl;
}
function getDownloadAction(targetId) {
const result = payload.resultsByTarget?.[targetId] || {};
if (targetId === "default" && result.resultFbxUrl) {
return { href: result.downloadFbxUrl || result.resultFbxUrl, label: "Download FBX" };
}
if (targetId === "robot:unitree_g1" && result.motionJsonUrl) {
return { href: result.downloadMotionJsonUrl || result.motionJsonUrl, label: "Download mujoco JSON" };
}
return null;
}
function clearLoadTimeout(targetId) {
if (loadTimeouts[targetId]) {
window.clearTimeout(loadTimeouts[targetId]);
delete loadTimeouts[targetId];
}
}
function shouldShowFallbackVideo(targetId) {
return targetId === "default" && !!payload.resultsByTarget?.[targetId]?.resultVideoUrl;
}
function formatTargetFailureMessage(targetId, fallbackMessage = "") {
if (targetId === "robot:unitree_g1") {
return (viewerStates.default?.phase || "idle") === "active-ready"
? "Unitree G1 preview failed. Default result is still available."
: "Unitree G1 preview failed.";
}
const explicit = String(fallbackMessage || "").trim();
return explicit || `Failed to load ${TARGET_LABELS[targetId] || targetId}.`;
}
function getTimeoutMs(targetId) {
return targetId === "default" ? 30000 : 60000;
}
function renderViewerState(targetId) {
const state = viewerStates[targetId];
viewsRoot.querySelectorAll(".view").forEach((view) => {
if (view.dataset.targetId !== targetId) return;
const overlay = view.querySelector(".viewer-overlay");
const overlayMessage = view.querySelector(".viewer-overlay-message");
const overlayAction = view.querySelector(".viewer-overlay-action");
const fallbackVideo = view.querySelector(".viewer-fallback");
if (!(overlay instanceof HTMLElement)) return;
if (overlayMessage instanceof HTMLElement) {
overlayMessage.textContent = state.message || "Loading preview...";
} else {
overlay.textContent = state.message || "Loading preview...";
}
const showBackToDefault =
targetId === "robot:unitree_g1" &&
activeTargetId === targetId &&
(state.phase === "error" || state.phase === "timeout") &&
(viewerStates.default?.phase === "active-ready");
overlay.classList.toggle("is-actionable", showBackToDefault);
overlay.classList.toggle("is-visible", state.phase !== "active-ready");
if (overlayAction instanceof HTMLButtonElement) {
overlayAction.classList.toggle("is-visible", showBackToDefault);
}
if (fallbackVideo instanceof HTMLVideoElement) {
fallbackVideo.classList.toggle("is-visible", !!state.showFallback);
}
});
}
function emitAggregateStatus() {
const phasesByTarget = {};
targetIds.forEach((targetId) => {
phasesByTarget[targetId] = viewerStates[targetId]?.phase || "idle";
});
const activePhase = viewerStates[activeTargetId]?.phase || "idle";
const activeMessage = viewerStates[activeTargetId]?.message || "";
const activeReady = activePhase === "active-ready";
const defaultReady = (viewerStates.default?.phase || "idle") === "active-ready";
const defaultAvailable = !!payload.resultsByTarget?.default;
const failedTargets = targetIds.filter((targetId) => {
const phase = viewerStates[targetId]?.phase || "idle";
return phase === "timeout" || phase === "error";
});
const allReady = targetIds.every((targetId) => {
const phase = viewerStates[targetId]?.phase || "idle";
return phase === "idle" || phase === "active-ready";
});
const anyTimeout = targetIds.some((targetId) => viewerStates[targetId]?.phase === "timeout");
const anyError = targetIds.some((targetId) => viewerStates[targetId]?.phase === "error");
window.parent.postMessage(
{
source: "hfspace-results-runtime",
type: "aggregate-status",
nonce: runtimeNonce,
targetIds,
activeTargetId,
phasesByTarget,
activePhase,
activeMessage,
activeError: activePhase === "timeout" || activePhase === "error",
activeTargetFailed: activePhase === "timeout" || activePhase === "error",
activeReady,
defaultReady,
defaultAvailable,
failedTargets,
allReady,
anyTimeout,
anyError,
},
"*"
);
}
function setViewerState(targetId, nextState) {
const previous = viewerStates[targetId] || {};
viewerStates[targetId] = {
phase: "runtime-loading",
message: `Loading ${TARGET_LABELS[targetId] || targetId} viewer`,
showFallback: false,
...previous,
...(nextState || {}),
};
if (
viewerStates[targetId].phase === "active-ready" ||
viewerStates[targetId].phase === "error" ||
viewerStates[targetId].phase === "timeout"
) {
clearLoadTimeout(targetId);
}
renderViewerState(targetId);
emitAggregateStatus();
}
function buildChildViewerUrl(targetId) {
const basePath =
targetId === "default"
? "/hf-assets/runtime/hfspace-fbx-viewer-runtime.html"
: "/hf-assets/runtime/robot-motion-viewer-runtime.html";
const params = new URLSearchParams();
params.set("viewerNonce", viewerSessions[targetId]);
params.set("targetId", targetId);
if (assetVersion) {
params.set("v", assetVersion);
}
return `${basePath}?${params.toString()}`;
}
function ensureFallbackVideoLoaded(targetId) {
if (fallbackLoaded[targetId]) return;
const fallbackVideo = viewsRoot.querySelector(`.view[data-target-id="${targetId}"] .viewer-fallback`);
if (!(fallbackVideo instanceof HTMLVideoElement)) return;
const result = payload.resultsByTarget?.[targetId];
if (!result?.resultVideoUrl) return;
fallbackVideo.src = result.resultVideoUrl;
fallbackLoaded[targetId] = true;
}
function armLoadTimeout(targetId) {
clearLoadTimeout(targetId);
loadTimeouts[targetId] = window.setTimeout(() => {
const state = viewerStates[targetId];
const hasFallback = shouldShowFallbackVideo(targetId);
if (!state || !["runtime-loading", "child-boot", "child-loading"].includes(state.phase)) return;
setViewerState(targetId, {
phase: "timeout",
message:
targetId === "default"
? "Default viewer is taking longer than expected. Downloads stay available below."
: (viewerStates.default?.phase || "idle") === "active-ready"
? "Unitree G1 viewer is taking longer than expected. Default result is still available below."
: "Unitree G1 viewer is taking longer than expected. Motion JSON stays available below.",
showFallback: hasFallback,
});
}, getTimeoutMs(targetId));
}
function postLoad(targetId) {
const frame = frames[targetId];
const result = payload.resultsByTarget?.[targetId];
if (!frame?.contentWindow || !result) return;
if (targetId === "default" && result.resultFbxUrl) {
frame.contentWindow.postMessage(
{
type: "fbx-load",
src: result.resultFbxUrl,
videoSrc: result.resultVideoUrl || "",
autoplay: false,
time: clockVideo.currentTime || 0,
cameraFit: "wider",
viewerNonce: viewerSessions[targetId],
},
"*"
);
} else if (targetId === "robot:unitree_g1" && result.motionJsonUrl) {
frame.contentWindow.postMessage(
{
type: "robot-load",
src: result.motionJsonUrl,
videoSrc: result.resultVideoUrl || "",
modelSrc: `/hf-assets/system-targets/robot/unitree_g1/meta.json`,
viewerNonce: viewerSessions[targetId],
},
"*"
);
}
}
function syncViewers() {
const time = Number(clockVideo.currentTime || 0);
Object.entries(frames).forEach(([targetId, frame]) => {
if (!frame?.contentWindow || !payload.resultsByTarget?.[targetId]) return;
frame.contentWindow.postMessage(
{
type: targetId === "default" ? "fbx-sync" : "robot-sync",
time,
playing,
viewerNonce: viewerSessions[targetId],
},
"*"
);
});
}
function mountViewerFrame(targetId) {
if (frames[targetId]) return;
const view = viewsRoot.querySelector(`.view[data-target-id="${targetId}"]`);
const shell = view?.querySelector(".viewer-shell");
const overlay = view?.querySelector(".viewer-overlay");
if (!(shell instanceof HTMLElement)) return;
const iframe = document.createElement("iframe");
iframe.className = "viewer-frame";
iframe.loading = "eager";
iframe.referrerPolicy = "no-referrer";
viewerSessions[targetId] = `${runtimeNonce}:${targetId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
iframe.src = buildChildViewerUrl(targetId);
iframe.addEventListener("load", () => {
setViewerState(targetId, {
phase: "child-boot",
message: `Connecting ${TARGET_LABELS[targetId] || targetId} viewer`,
showFallback: false,
});
armLoadTimeout(targetId);
});
frames[targetId] = iframe;
shell.insertBefore(iframe, overlay || null);
setViewerState(targetId, {
phase: "runtime-loading",
message: `Loading ${TARGET_LABELS[targetId] || targetId} viewer`,
showFallback: false,
});
}
function setActiveTarget(targetId) {
activeTargetId = targetId;
tabsRoot.querySelectorAll(".tab").forEach((button) => {
button.classList.toggle("is-active", button.dataset.targetId === targetId);
});
viewsRoot.querySelectorAll(".view").forEach((view) => {
view.classList.toggle("is-active", view.dataset.targetId === targetId);
});
const action = getDownloadAction(targetId);
if (downloadAction) {
if (action) {
downloadAction.href = action.href;
downloadAction.textContent = action.label;
downloadAction.classList.remove("is-hidden");
} else {
downloadAction.href = "#";
downloadAction.textContent = "Download";
downloadAction.classList.add("is-hidden");
}
}
ensureFallbackVideoLoaded(targetId);
mountViewerFrame(targetId);
renderViewerState(targetId);
syncViewers();
}
window.addEventListener("message", (event) => {
const data = event.data || {};
if (!data?.source) return;
const source = Object.entries(frames).find(([, frame]) => frame?.contentWindow === event.source)?.[0] || "";
if (!source) return;
if (String(data.viewerNonce || "") !== String(viewerSessions[source] || "")) {
return;
}
if (data.type === "error") {
setViewerState(source, {
phase: "error",
message: formatTargetFailureMessage(source, data.message || ""),
showFallback: shouldShowFallbackVideo(source),
});
return;
}
if (data.type === "boot") {
setViewerState(source, {
phase: "child-loading",
message: `Loading ${TARGET_LABELS[source] || source} viewer`,
showFallback: false,
});
postLoad(source);
syncViewers();
return;
}
if (data.type === "ready" || data.type === "model-ready") {
setViewerState(source, {
phase: "active-ready",
message: `${TARGET_LABELS[source] || source} preview is ready.`,
showFallback: false,
});
return;
}
if (data.type === "status") {
const incomingPhase = String(data.phase || "loading");
const phase = incomingPhase === "ready" ? "active-ready" : incomingPhase === "boot" ? "child-boot" : "child-loading";
const message =
String(data.message || "").trim() ||
(phase === "active-ready"
? `${TARGET_LABELS[source] || source} preview is ready.`
: `Loading ${TARGET_LABELS[source] || source} viewer`);
setViewerState(source, {
phase,
message,
showFallback: (phase === "timeout" || phase === "error") && shouldShowFallbackVideo(source),
});
}
});
targetIds.forEach((targetId) => {
const result = payload.resultsByTarget[targetId];
const tab = document.createElement("button");
tab.type = "button";
tab.dataset.targetId = targetId;
tab.className = `tab${targetId === activeTargetId ? " is-active" : ""}`;
tab.textContent = TARGET_LABELS[targetId] || targetId;
tab.addEventListener("click", () => setActiveTarget(targetId));
tabsRoot.appendChild(tab);
const view = document.createElement("div");
view.className = `view${targetId === activeTargetId ? " is-active" : ""}`;
view.dataset.targetId = targetId;
const shell = document.createElement("div");
shell.className = "viewer-shell";
const fallbackVideo = document.createElement("video");
fallbackVideo.className = "viewer-fallback";
fallbackVideo.preload = "metadata";
fallbackVideo.playsInline = true;
fallbackVideo.muted = true;
fallbackVideo.controls = true;
const overlay = document.createElement("div");
overlay.className = "viewer-overlay is-visible";
const overlayContent = document.createElement("div");
overlayContent.className = "viewer-overlay-content";
const overlayMessage = document.createElement("div");
overlayMessage.className = "viewer-overlay-message";
overlayMessage.textContent =
targetId === activeTargetId
? `Loading ${TARGET_LABELS[targetId] || targetId} viewer`
: "Select this result to load the preview.";
const overlayAction = document.createElement("button");
overlayAction.type = "button";
overlayAction.className = "viewer-overlay-action";
overlayAction.textContent = "Back to Default";
overlayAction.addEventListener("click", () => setActiveTarget("default"));
overlayContent.appendChild(overlayMessage);
overlayContent.appendChild(overlayAction);
overlay.appendChild(overlayContent);
shell.appendChild(fallbackVideo);
shell.appendChild(overlay);
view.appendChild(shell);
viewsRoot.appendChild(view);
setViewerState(targetId, {
phase: targetId === activeTargetId ? "runtime-loading" : "idle",
message:
targetId === activeTargetId
? `Loading ${TARGET_LABELS[targetId] || targetId} viewer`
: "Select this result to load the preview.",
showFallback: false,
});
});
playButton.addEventListener("click", async () => {
if (!clockVideo.src) return;
if (clockVideo.paused) {
try {
await clockVideo.play();
} catch (_error) {
return;
}
} else {
clockVideo.pause();
}
});
seek.addEventListener("input", () => {
const nextTime = Number(seek.value || 0);
if (Number.isFinite(nextTime)) {
clockVideo.currentTime = nextTime;
updateTimeLabel();
syncViewers();
}
});
clockVideo.addEventListener("loadedmetadata", updateTimeLabel);
clockVideo.addEventListener("canplay", () => {
updateTimeLabel();
if (shouldAutoplay && clockVideo.paused) {
clockVideo.play().catch(() => null);
}
});
clockVideo.addEventListener("timeupdate", () => {
updateTimeLabel();
syncViewers();
});
clockVideo.addEventListener("play", () => {
playing = true;
playButton.textContent = "Pause";
syncViewers();
});
clockVideo.addEventListener("pause", () => {
playing = false;
playButton.textContent = "Play";
syncViewers();
});
clockVideo.addEventListener("ended", () => {
playing = false;
playButton.textContent = "Play";
syncViewers();
});
updateTimeLabel();
if (!masterVideoUrl) {
playButton.disabled = true;
}
setActiveTarget(activeTargetId);
</script>
</body>
</html>