SampleViewer / teleopSample /lib /enhance.js
darren.zhang
Claude Opus 5 (1M context)
teleopSample: one shared step UI for all 22 episode pages
81299b2
Raw History Blame Contribute Delete
3.46 kB
/* Adapter that gives the older sample pages the same step UI as the pick-and-place pages.
* All the drawing and the measurements live in steps-ui.js — this file only reads each
* page's own JSON blocks, supplies the task description, and hands StepsUI its containers. */
(function () {
const J = id => { const e = document.getElementById(id); return e ? JSON.parse(e.textContent) : null; };
const S = J('series'), STEPS = J('spans') || [], META = J('meta');
if (!S || !STEPS.length || !window.StepsUI) return;
const slug = (META && META.slug) || (location.pathname.match(/\/(ep\d+)\//) || [])[1] || '';
const family = slug.replace(/_?ep\d+$/, '') || 'legacy';
// object / target / scene, written from each recording's own description
const TASK = {
mem: { object: 'DDR memory module', target: 'motherboard slot',
scene: 'workbench with an open motherboard, dual-arm grippers' },
drink: { object: 'bottled green beverage', target: 'coaster on the countertop',
scene: 'countertop with the drink and a coaster set apart' },
sort: { object: 'packed garment bags (pink / white / beige / green)',
target: 'left side of the table', scene: 'table with a material bin of packed garment bags' },
wipe: { object: 'sponge', target: 'stains on the tabletop', scene: 'tabletop with stains' },
legacy: { object: 'water bottle', target: 'basket',
scene: 'tabletop with a water bottle and a basket' },
}[family] || {};
const sceneLine = (document.querySelector('.hilevel') || {}).textContent || '';
const task = {
instruction: (META && META.high_level_instruction) ||
(document.querySelector('header h1') || {}).textContent || '',
object: TASK.object, target: TASK.target,
scene: sceneLine.replace(/^\s*scene\s*/i, '').trim() || TASK.scene || '',
};
const header = document.querySelector('header');
const controls = document.querySelector('.panel.controls');
const videos = [...document.querySelectorAll('video')];
const ui = window.StepsUI.mount({
series: S, steps: STEPS, task, numbering: 'id',
listItems: document.querySelectorAll('.span-item'),
seek: t => videos.forEach(v => { try { v.currentTime = t; } catch (e) {} }),
hosts: { instrAfter: header, timelineBefore: controls, panelAfter: controls },
describe: st => { // the recorded instruction for that segment
const segs = META && META.instruction_segments;
if (!segs) return null;
const m = segs.find(x => Math.abs(x.start_frame_index / S.fps - st.start_s) < 0.2);
return m ? m.instruction : null;
},
});
if (!ui) return;
// the chip row is redundant now that the timeline carries the colour key
const chips = document.getElementById('chips') || document.querySelector('.chips');
if (chips) chips.remove();
// wording: spans / subtask -> steps
document.querySelectorAll('.spans-head').forEach(e => {
e.textContent = e.textContent.replace(/temporal spans/i, 'annotated steps').replace(/spans/gi, 'steps');
});
document.querySelectorAll('.ro .k, .rgrid .k').forEach(e => {
const t = e.textContent.trim().toLowerCase();
if (t === 'subtask') e.textContent = 'step';
if (t === 'span progress') e.textContent = 'step progress';
});
const master = videos[0];
(function tick() { if (master) ui.update(master.currentTime); requestAnimationFrame(tick); })();
})();