emotions / index.html
RemiFabre
Update app for official version
af83c92
raw
history blame
12.7 kB
<!doctype html>
<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>