Skip to content

Commit bfcfdad

Browse files
committed
feat: add interactive AgiBot and LIBERO demos
1 parent d055545 commit bfcfdad

283 files changed

Lines changed: 3110 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 8 additions & 0 deletions

docs/interactive-demo.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
(() => {
2+
"use strict";
3+
4+
const root = document.getElementById("interactive-demo");
5+
if (!root) return;
6+
7+
const data = window.A2WORLD_INTERACTIVE_DEMO;
8+
const sceneTabs = document.getElementById("scene-tabs");
9+
const sceneDataset = document.getElementById("scene-dataset");
10+
const sceneTitle = document.getElementById("scene-title");
11+
const sceneDescription = document.getElementById("scene-description");
12+
const stageList = document.getElementById("action-stage-list");
13+
const video = document.getElementById("interactive-video");
14+
const videoFrame = video.closest(".focus-video");
15+
const focusTitle = document.getElementById("focus-title");
16+
const focusMeta = document.getElementById("focus-meta");
17+
const focusPath = document.getElementById("focus-path");
18+
const generateButton = document.getElementById("generate-rollout");
19+
const resetButton = document.getElementById("reset-rollout");
20+
const assetRoot = "docs/resources/interactive-demo/";
21+
22+
if (!data || !Array.isArray(data.scenes) || data.scenes.length < 1) {
23+
root.innerHTML = '<p class="demo-error">Interactive demo assets are unavailable.</p>';
24+
return;
25+
}
26+
27+
let sceneIndex = 0;
28+
let scene = data.scenes[0];
29+
let selected = [];
30+
31+
const asset = (path) => `${assetRoot}${path}`;
32+
const optionById = (stage, id) => stage.options.find((option) => option.id === id);
33+
34+
function resetSelections() {
35+
selected = scene.stages.map((stage, index) =>
36+
optionById(stage, scene.preferred?.[index]) ? scene.preferred[index] : stage.options[0].id,
37+
);
38+
}
39+
40+
function selectedLabels() {
41+
return selected.map((choice, index) => optionById(scene.stages[index], choice).label);
42+
}
43+
44+
function renderPath(labels = []) {
45+
focusPath.textContent = labels.length ? labels.join(" → ") : "Initial";
46+
}
47+
48+
function resetViewer() {
49+
video.pause();
50+
video.removeAttribute("src");
51+
video.poster = asset(scene.initial);
52+
video.load();
53+
focusTitle.textContent = "Initial observation";
54+
focusMeta.textContent = `${scene.views} views`;
55+
renderPath();
56+
}
57+
58+
function renderTabs() {
59+
sceneTabs.replaceChildren();
60+
data.scenes.forEach((item, index) => {
61+
const button = document.createElement("button");
62+
button.type = "button";
63+
button.className = "scene-tab";
64+
button.textContent = item.dataset;
65+
button.setAttribute("aria-pressed", String(index === sceneIndex));
66+
button.addEventListener("click", () => loadScene(index));
67+
sceneTabs.append(button);
68+
});
69+
}
70+
71+
function select(stageIndex, optionId) {
72+
selected[stageIndex] = optionId;
73+
renderControls();
74+
const labels = selectedLabels();
75+
video.pause();
76+
video.removeAttribute("src");
77+
video.poster = asset(scene.initial);
78+
video.load();
79+
focusTitle.textContent = "Ready";
80+
focusMeta.textContent = `${labels.length} selected action${labels.length === 1 ? "" : "s"}`;
81+
renderPath(labels);
82+
}
83+
84+
function renderControls() {
85+
stageList.replaceChildren();
86+
stageList.dataset.stageCount = String(scene.stages.length);
87+
scene.stages.forEach((stage, stageIndex) => {
88+
const section = document.createElement("section");
89+
section.className = "action-stage";
90+
91+
const heading = document.createElement("div");
92+
heading.className = "action-stage-label";
93+
heading.textContent = stage.title;
94+
95+
const options = document.createElement("div");
96+
options.className = "action-option-list";
97+
stage.options.forEach((option) => {
98+
const button = document.createElement("button");
99+
button.type = "button";
100+
button.className = "action-option";
101+
button.textContent = option.label;
102+
if (option.detail) button.title = option.detail;
103+
button.setAttribute("aria-pressed", String(selected[stageIndex] === option.id));
104+
button.addEventListener("click", () => select(stageIndex, option.id));
105+
options.append(button);
106+
});
107+
108+
section.append(heading, options);
109+
stageList.append(section);
110+
});
111+
}
112+
113+
function playSelected() {
114+
const sequenceId = selected.join("__");
115+
const sequence = scene.sequences[sequenceId];
116+
if (!sequence) {
117+
focusMeta.textContent = "This rollout is unavailable in the static manifest.";
118+
return;
119+
}
120+
const labels = selectedLabels();
121+
video.removeAttribute("poster");
122+
video.src = asset(sequence.video);
123+
video.load();
124+
focusTitle.textContent = labels.join(" → ");
125+
const duration = Number(sequence.duration_seconds || 0).toFixed(1);
126+
focusMeta.textContent = `${duration} s`;
127+
renderPath(labels);
128+
video.play().catch(() => {
129+
focusMeta.textContent = `${duration} s · press play`;
130+
});
131+
}
132+
133+
function loadScene(index) {
134+
sceneIndex = index;
135+
scene = data.scenes[index];
136+
resetSelections();
137+
sceneDataset.textContent = scene.dataset;
138+
sceneTitle.textContent = scene.task;
139+
sceneDescription.textContent = scene.description;
140+
sceneDescription.hidden = !scene.description;
141+
videoFrame.style.aspectRatio = scene.video_aspect_ratio;
142+
renderTabs();
143+
renderControls();
144+
resetViewer();
145+
}
146+
147+
generateButton.addEventListener("click", playSelected);
148+
resetButton.addEventListener("click", () => {
149+
resetSelections();
150+
renderControls();
151+
resetViewer();
152+
});
153+
154+
loadScene(0);
155+
})();
Lines changed: 13 additions & 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)