Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>OpenX Rerun Viewer</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| background: #0d1117; | |
| color: #c9d1d9; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .header { | |
| padding: 12px 20px; | |
| background: #161b22; | |
| border-bottom: 1px solid #30363d; | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| flex-shrink: 0; | |
| } | |
| .header h1 { | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: #f0f6fc; | |
| white-space: nowrap; | |
| } | |
| .controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| flex-wrap: wrap; | |
| } | |
| .controls select, .controls input, .controls button { | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| border: 1px solid #30363d; | |
| background: #21262d; | |
| color: #c9d1d9; | |
| font-size: 13px; | |
| } | |
| .controls select:focus, .controls button:focus { | |
| outline: none; | |
| border-color: #58a6ff; | |
| } | |
| .controls button { | |
| cursor: pointer; | |
| background: #238636; | |
| border-color: #2ea043; | |
| font-weight: 600; | |
| } | |
| .controls button:hover { background: #2ea043; } | |
| .meta { | |
| font-size: 12px; | |
| color: #8b949e; | |
| display: flex; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| } | |
| .meta span { white-space: nowrap; } | |
| .viewer-container { | |
| flex: 1; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .viewer-container iframe { | |
| width: 100%; | |
| height: 100%; | |
| border: none; | |
| } | |
| .placeholder { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100%; | |
| color: #484f58; | |
| font-size: 16px; | |
| } | |
| .placeholder p { text-align: center; line-height: 1.6; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>OpenX Rerun Viewer</h1> | |
| <div class="controls"> | |
| <select id="dataset-select"> | |
| <option value="">-- Select Dataset --</option> | |
| </select> | |
| <select id="episode-select" disabled> | |
| <option value="">-- Select Episode --</option> | |
| </select> | |
| <button id="view-btn" disabled>View in Rerun</button> | |
| </div> | |
| <div class="meta" id="meta-display"></div> | |
| </div> | |
| <div class="viewer-container"> | |
| <div class="placeholder" id="placeholder"> | |
| <p>Select a dataset and episode above to visualize in Rerun.<br> | |
| <small>6 robot types · 18 episodes · OpenX-Embodiment</small></p> | |
| </div> | |
| <iframe id="rerun-frame" style="display:none" | |
| allow="cross-origin-isolated" | |
| allowfullscreen> | |
| </iframe> | |
| </div> | |
| <script> | |
| const BASE = document.querySelector('meta[content]')?.content || window.location.origin; | |
| let datasets = []; | |
| let currentSlug = null; | |
| async function init() { | |
| try { | |
| const resp = await fetch('/api/datasets'); | |
| datasets = await resp.json(); | |
| const sel = document.getElementById('dataset-select'); | |
| datasets.forEach(ds => { | |
| const opt = document.createElement('option'); | |
| opt.value = ds.slug; | |
| opt.textContent = `${ds.robot_type} (${ds.episodes_available.length} eps, ${ds.fps}fps, ${ds.camera_labels.length} cam)`; | |
| sel.appendChild(opt); | |
| }); | |
| } catch (e) { | |
| console.error('Failed to load datasets:', e); | |
| } | |
| } | |
| document.getElementById('dataset-select').addEventListener('change', async (e) => { | |
| const slug = e.target.value; | |
| currentSlug = slug; | |
| const epSel = document.getElementById('episode-select'); | |
| const viewBtn = document.getElementById('view-btn'); | |
| epSel.innerHTML = '<option value="">-- Select Episode --</option>'; | |
| epSel.disabled = !slug; | |
| viewBtn.disabled = true; | |
| if (!slug) { | |
| document.getElementById('meta-display').innerHTML = ''; | |
| return; | |
| } | |
| const ds = datasets.find(d => d.slug === slug); | |
| if (!ds) return; | |
| document.getElementById('meta-display').innerHTML = | |
| `<span>Robot: <strong>${ds.robot_type}</strong></span>` + | |
| `<span>FPS: ${ds.fps}</span>` + | |
| `<span>Cameras: ${ds.camera_labels.join(', ')}</span>` + | |
| `<span>State dim: ${ds.state_dim}</span>` + | |
| `<span>Action dim: ${ds.action_dim}</span>`; | |
| ds.episodes_available.forEach(ep => { | |
| const opt = document.createElement('option'); | |
| opt.value = ep.index; | |
| opt.textContent = `Episode ${ep.index} (${ep.frames} frames)`; | |
| epSel.appendChild(opt); | |
| }); | |
| epSel.disabled = false; | |
| }); | |
| document.getElementById('episode-select').addEventListener('change', () => { | |
| document.getElementById('view-btn').disabled = | |
| !document.getElementById('episode-select').value; | |
| }); | |
| document.getElementById('view-btn').addEventListener('click', () => { | |
| const slug = currentSlug; | |
| const epIdx = document.getElementById('episode-select').value; | |
| if (!slug || epIdx === '') return; | |
| const rrdUrl = `${window.location.origin}/data/${slug}/episode_${String(epIdx).padStart(6, '0')}.rrd`; | |
| const viewerBase = '{{VIEWER_BASE}}'; | |
| const viewerUrl = `${viewerBase}/index.html?url=${encodeURIComponent(rrdUrl)}`; | |
| document.getElementById('placeholder').style.display = 'none'; | |
| const frame = document.getElementById('rerun-frame'); | |
| frame.src = viewerUrl; | |
| frame.style.display = 'block'; | |
| }); | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |