File size: 12,732 Bytes
03317af
d4694ba
03317af
 
af83c92
 
 
 
03317af
 
 
af83c92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03317af
 
d4694ba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
<!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>