Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Reachy Mini • Emotions Wheel</title> | |
| <link rel="stylesheet" href="style.css" /> | |
| </head> | |
| <body> | |
| <div class="bg-gradient"></div> | |
| <main class="page"> | |
| <header class="hero"> | |
| <div class="hero-text"> | |
| <p class="eyebrow">Reachy Mini App</p> | |
| <h1>Emotions Wheel</h1> | |
| <p class="lede"> | |
| The landing page now mirrors the production UI: two synchronized rings (families outside, behaviors inside), | |
| tooltips straight from the dataset metadata, manual layout controls, and coherence diagnostics so demos feel | |
| the same as the robot runtime. | |
| </p> | |
| <div class="hero-meta"> | |
| <span class="chip">Linked wheels</span> | |
| <span class="chip">Manual layout mode</span> | |
| <span class="chip">Dataset-aware tooltips</span> | |
| </div> | |
| <div class="hero-cta"> | |
| <div> | |
| <p class="label">Dataset</p> | |
| <p class="value"><a href="https://huggingface.co/datasets/pollen-robotics/reachy-mini-emotions-library">pollen-robotics/reachy-mini-emotions-library</a></p> | |
| </div> | |
| <div> | |
| <p class="label">Behaviors catalogued</p> | |
| <p class="value">138 motions · 8 families</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="hero-preview"> | |
| <div class="preview-wheel"> | |
| <div class="ring outer"></div> | |
| <div class="ring inner"></div> | |
| <div class="preview-label"> | |
| <span>emotion</span> | |
| <span>behavior</span> | |
| </div> | |
| </div> | |
| <div class="preview-controls"> | |
| <span class="chip accent">Manual layout</span> | |
| <span class="chip soft">Save layout</span> | |
| <span class="chip success">Reachy idle</span> | |
| </div> | |
| </div> | |
| </header> | |
| <section class="feature-grid"> | |
| <article class="feature-card"> | |
| <h2>Linked wheels + live tooltips</h2> | |
| <p> | |
| Hover the outer emotion ring to highlight its behaviors, then click a behavior badge to play the move on | |
| Reachy Mini. Tooltips quote the dataset entry name, intensity, and duration so the wheel always stays in | |
| sync with the YAML catalog. | |
| </p> | |
| </article> | |
| <article class="feature-card"> | |
| <h2>Manual layout mode</h2> | |
| <p> | |
| Tapping <em>Manual layout</em> displays draggable handles for every move. Drop badges anywhere on the wheel, | |
| press save, and the layout persists to disk so your museum demos, lessons, and videos stay consistent. | |
| </p> | |
| </article> | |
| <article class="feature-card"> | |
| <h2>Duration & intensity cues</h2> | |
| <p> | |
| Distance from the center encodes emotion intensity, while the colored crescents show the duration bucket. | |
| It is the same visual legend the runtime uses, so observers instantly grasp what Reachy is about to do. | |
| </p> | |
| </article> | |
| <article class="feature-card"> | |
| <h2>Sync diagnostics panel</h2> | |
| <p> | |
| A small panel below the wheels compares the dataset, YAML spec, and recorded files. If something drifts, | |
| the warning badge catches it before a visitor clicks a missing behavior. | |
| </p> | |
| </article> | |
| </section> | |
| <section class="details-grid"> | |
| <article class="details-card"> | |
| <h3>Color language</h3> | |
| <p>Plutchik-inspired colors span the outer wheel. Labels stay inside tooltips so the UI remains minimal.</p> | |
| <ul class="palette"> | |
| <li><span class="swatch" style="--swatch:#FF9F66"></span><div><strong>Joy</strong><small>warm coral</small></div></li> | |
| <li><span class="swatch" style="--swatch:#5CC8D7"></span><div><strong>Trust</strong><small>lagoon teal</small></div></li> | |
| <li><span class="swatch" style="--swatch:#4D7C8A"></span><div><strong>Fear</strong><small>noctilucent blue</small></div></li> | |
| <li><span class="swatch" style="--swatch:#C084FC"></span><div><strong>Surprise</strong><small>lilac flare</small></div></li> | |
| <li><span class="swatch" style="--swatch:#4F6DF5"></span><div><strong>Sadness</strong><small>dusk indigo</small></div></li> | |
| <li><span class="swatch" style="--swatch:#58B368"></span><div><strong>Disgust</strong><small>mossy green</small></div></li> | |
| <li><span class="swatch" style="--swatch:#E94F37"></span><div><strong>Anger</strong><small>ember red</small></div></li> | |
| <li><span class="swatch" style="--swatch:#FFB347"></span><div><strong>Anticipation</strong><small>amber sunrise</small></div></li> | |
| </ul> | |
| </article> | |
| <article class="details-card"> | |
| <h3>Duration crescents</h3> | |
| <p>Badges keep the same three crescents the runtime overlays on every node.</p> | |
| <ul class="duration-list"> | |
| <li><span class="swatch small" style="--swatch:#73E0A9"></span><div><strong>Seafoam</strong><small>short · ≤ 4 seconds</small></div></li> | |
| <li><span class="swatch small" style="--swatch:#FFC857"></span><div><strong>Amber</strong><small>medium · 4–8 seconds</small></div></li> | |
| <li><span class="swatch small" style="--swatch:#FF6B6B"></span><div><strong>Rose</strong><small>long · > 8 seconds</small></div></li> | |
| </ul> | |
| </article> | |
| <article class="details-card"> | |
| <h3>Operator niceties</h3> | |
| <ul class="feature-list"> | |
| <li>Status chip locks clicks while Reachy finishes a previous behavior.</li> | |
| <li>Toast + tooltip copy include the raw move name so debugging stays easy.</li> | |
| <li>Aside from layout saves, everything runs client-side, so the wheel feels instant.</li> | |
| </ul> | |
| </article> | |
| </section> | |
| <section class="install-card"> | |
| <div> | |
| <h2>Install on your Reachy Mini</h2> | |
| <p> | |
| Point the installer at the dashboard that runs on your robot (default is | |
| <code>http://localhost:8000</code>). We ping its health endpoint first, then call <code>/api/install</code> | |
| with the Space URL so the dashboard clones this repository. | |
| </p> | |
| </div> | |
| <div class="install-form"> | |
| <label for="dashboardUrl">Dashboard URL</label> | |
| <input type="url" id="dashboardUrl" value="http://localhost:8000" | |
| placeholder="http://reachy-mini:8000" /> | |
| <button id="installBtn" class="install-btn"> | |
| <span>📥</span> | |
| Install Emotions Wheel | |
| </button> | |
| <div id="installStatus" class="install-status"></div> | |
| </div> | |
| </section> | |
| </main> | |
| <footer class="footer"> | |
| <p>Built with ❤️ by Pollen Robotics · Browse more Reachy Mini apps on Hugging Face Spaces.</p> | |
| </footer> | |
| <script> | |
| function getCurrentSpaceUrl() { | |
| const currentUrl = window.location.href; | |
| return currentUrl.split('?')[0].replace(/\/$/, ''); | |
| } | |
| function parseTomlProjectName(tomlContent) { | |
| try { | |
| const lines = tomlContent.split('\n'); | |
| let inProjectSection = false; | |
| for (const line of lines) { | |
| const trimmedLine = line.trim(); | |
| if (trimmedLine === '[project]') { | |
| inProjectSection = true; | |
| continue; | |
| } | |
| if (trimmedLine.startsWith('[') && trimmedLine !== '[project]') { | |
| inProjectSection = false; | |
| continue; | |
| } | |
| if (inProjectSection && trimmedLine.startsWith('name')) { | |
| const match = trimmedLine.match(/name\s*=\s*["']([^"']+)["']/); | |
| if (match) { | |
| return match[1].toLowerCase().replace(/[^a-z0-9-_]/g, '-'); | |
| } | |
| } | |
| } | |
| throw new Error('Project name not found in pyproject.toml'); | |
| } catch (error) { | |
| console.error('Error parsing pyproject.toml:', error); | |
| return 'unknown-app'; | |
| } | |
| } | |
| async function getAppNameFromCurrentSpace() { | |
| try { | |
| const response = await fetch('./pyproject.toml'); | |
| if (!response.ok) { | |
| throw new Error(`Failed to fetch pyproject.toml: ${response.status}`); | |
| } | |
| const tomlContent = await response.text(); | |
| return parseTomlProjectName(tomlContent); | |
| } catch (error) { | |
| console.error('Error fetching app name from current space:', error); | |
| const url = getCurrentSpaceUrl(); | |
| const parts = url.split('/'); | |
| const spaceName = parts[parts.length - 1]; | |
| return spaceName.toLowerCase().replace(/[^a-z0-9-_]/g, '-'); | |
| } | |
| } | |
| function showStatus(type, message) { | |
| const statusDiv = document.getElementById('installStatus'); | |
| statusDiv.textContent = message; | |
| statusDiv.className = `install-status ${type}`; | |
| } | |
| async function installToReachy() { | |
| const dashboardUrl = document.getElementById('dashboardUrl').value.trim(); | |
| const installBtn = document.getElementById('installBtn'); | |
| if (!dashboardUrl) { | |
| showStatus('error', 'Please enter your Reachy dashboard URL'); | |
| return; | |
| } | |
| try { | |
| installBtn.disabled = true; | |
| installBtn.textContent = 'Installing…'; | |
| showStatus('loading', 'Connecting to your Reachy dashboard…'); | |
| const testResponse = await fetch(`${dashboardUrl}/api/status`, { | |
| method: 'GET', | |
| mode: 'cors', | |
| }); | |
| if (!testResponse.ok) { | |
| throw new Error('Cannot connect to dashboard. Check the URL and make sure the dashboard is running.'); | |
| } | |
| showStatus('loading', 'Reading app configuration…'); | |
| const appName = await getAppNameFromCurrentSpace(); | |
| const repoUrl = getCurrentSpaceUrl(); | |
| showStatus('loading', `Starting installation of "${appName}"…`); | |
| const installResponse = await fetch(`${dashboardUrl}/api/install`, { | |
| method: 'POST', | |
| mode: 'cors', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| url: repoUrl, | |
| name: appName | |
| }) | |
| }); | |
| const result = await installResponse.json(); | |
| if (installResponse.ok) { | |
| showStatus('success', `Installation started for "${appName}". Check your dashboard for progress.`); | |
| } else { | |
| throw new Error(result.detail || 'Installation failed'); | |
| } | |
| } catch (error) { | |
| console.error('Installation error:', error); | |
| showStatus('error', `❌ ${error.message}`); | |
| } finally { | |
| installBtn.disabled = false; | |
| installBtn.textContent = 'Install Emotions Wheel'; | |
| } | |
| } | |
| document.getElementById('installBtn').addEventListener('click', installToReachy); | |
| </script> | |
| </body> | |
| </html> | |