PanGalactic Claude Opus 4.6 commited on
Commit
78b0f5a
·
1 Parent(s): 1e98f5f

Add HuggingFace Spaces live demo infrastructure

Browse files

Docker-based demo that runs the full dashboard with reachy-mini-daemon
in simulation mode. Supervisor manages daemon + app lifecycle. Includes
graceful fallbacks for missing hardware (GStreamer, Bluetooth) so the
app runs cleanly in containers without audio/BT hardware.

New files: Dockerfile.demo, supervisord.demo.conf, demo-start.sh,
README.demo.md, .dockerignore

Modified: audio_output.py (GStreamer availability check),
bluetooth.py (bluetoothctl availability check),
index.html (Try Live Demo button)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

.dockerignore ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Landing page assets (not needed in demo container)
2
+ demo_video.mp4
3
+ frames/
4
+ screenshots/
5
+ docs.html
6
+ style.css
7
+ README.md
8
+ index.html
9
+ README.demo.md
10
+
11
+ # Git
12
+ .git/
13
+ .gitattributes
14
+ .gitignore
15
+
16
+ # Python
17
+ __pycache__/
18
+ *.pyc
19
+ *.pyo
20
+ *.egg-info/
21
+
22
+ # Media (runtime-generated, not needed in image)
23
+ media/
24
+
25
+ # Settings (runtime-generated on Reachy, not needed in image)
26
+ settings.json
Dockerfile.demo ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace Spaces demo — runs the full dashboard with a simulated robot.
2
+ #
3
+ # Build: docker build -f Dockerfile.demo -t hw-demo .
4
+ # Run: docker run -p 8042:8042 hw-demo
5
+ # Browse: http://localhost:8042
6
+
7
+ FROM python:3.12-slim
8
+
9
+ # ── System dependencies ──────────────────────────────────────────
10
+ # libosmesa6-dev — headless OpenGL for MuJoCo (no GPU needed)
11
+ # libgl1-mesa-glx — GL runtime (MuJoCo shared libs expect it)
12
+ # tmux — shell tab uses shared tmux sessions
13
+ # curl — startup script health checks
14
+ # supervisor — manages daemon + app lifecycle
15
+ RUN apt-get update && apt-get install -y --no-install-recommends \
16
+ libosmesa6-dev \
17
+ libgl1-mesa-glx \
18
+ tmux \
19
+ curl \
20
+ supervisor \
21
+ && rm -rf /var/lib/apt/lists/*
22
+
23
+ # MuJoCo headless rendering — OSMesa software renderer, no GPU required
24
+ ENV MUJOCO_GL=osmesa
25
+
26
+ # HuggingFace Spaces requires non-root user with uid 1000
27
+ RUN useradd -m -u 1000 user
28
+ USER user
29
+ ENV PATH="/home/user/.local/bin:${PATH}"
30
+
31
+ WORKDIR /home/user/app
32
+
33
+ # Install MuJoCo + reachy-mini SDK (provides reachy-mini-daemon command)
34
+ RUN pip install --no-cache-dir --user mujoco "reachy-mini"
35
+
36
+ # Copy the hello_world app and install as editable package
37
+ # (editable so the daemon discovers it via entry points)
38
+ COPY --chown=user:user . .
39
+ RUN pip install --no-cache-dir --user -e .
40
+
41
+ # Make startup script executable
42
+ RUN chmod +x demo-start.sh
43
+
44
+ # The app serves on port 8042 (HF proxy forwards to this)
45
+ EXPOSE 8042
46
+
47
+ # Supervisor manages: (1) daemon in sim mode, (2) startup script
48
+ CMD ["supervisord", "-n", "-c", "/home/user/app/supervisord.demo.conf"]
README.demo.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Hello World Demo
3
+ emoji: 🤖
4
+ colorFrom: purple
5
+ colorTo: cyan
6
+ sdk: docker
7
+ app_port: 8042
8
+ pinned: false
9
+ ---
10
+
11
+ # Hello World Demo
12
+
13
+ Live interactive demo of the **Hello World** dashboard running with a simulated Reachy Mini robot. No physical robot needed — the daemon runs in simulation mode with full MuJoCo kinematics.
14
+
15
+ **[View documentation and source code on the main space](https://huggingface.co/spaces/panny247/hello_world)**
16
+
17
+ ## What works
18
+
19
+ - 3D MuJoCo simulation with 20 scenes and 21 skins
20
+ - Joystick head control (moves the simulated robot)
21
+ - 81 emotions and 20 dances with 3D preview
22
+ - 6 oscillation patterns (sway, nod, circle, breathe, figure-8, look around)
23
+ - AI conversation (bring your own API key in Settings)
24
+ - YOLO WebGPU vision (uses your browser camera)
25
+ - System telemetry charts
26
+ - Settings persistence
27
+ - Scratchpad (generative UI)
28
+ - Timers and alarms
29
+ - Shell terminal
30
+
31
+ ## Hardware-only features (unavailable in demo)
32
+
33
+ - Robot speaker and microphone (no USB audio hardware)
34
+ - Bluetooth A2DP (no BlueZ stack)
35
+ - AuraBox LED display (no peripheral)
36
+ - Smart home integrations (no local network access)
demo-start.sh ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Startup script for HuggingFace Spaces demo.
3
+ #
4
+ # Waits for the daemon to be healthy, wakes it up (transitions from
5
+ # not_initialized → ready), then starts the hello_world app.
6
+
7
+ set -e
8
+
9
+ DAEMON_URL="http://localhost:8000"
10
+ APP_URL="http://localhost:8042"
11
+
12
+ # ── Wait for daemon health endpoint ──────────────────────────────
13
+ echo "[demo] Waiting for daemon to start..."
14
+ for i in $(seq 1 60); do
15
+ if curl -sf "${DAEMON_URL}/api/daemon/status" > /dev/null 2>&1; then
16
+ echo "[demo] Daemon is up (took ${i}s)"
17
+ break
18
+ fi
19
+ if [ "$i" -eq 60 ]; then
20
+ echo "[demo] ERROR: Daemon did not start within 60s"
21
+ exit 1
22
+ fi
23
+ sleep 1
24
+ done
25
+
26
+ # ── Wake up the daemon (not_initialized → ready) ─────────────────
27
+ echo "[demo] Waking up daemon..."
28
+ curl -sf -X POST "${DAEMON_URL}/api/daemon/start?wake_up=true" || true
29
+ sleep 3
30
+
31
+ # ── Start the hello_world app ────────────────────────────────────
32
+ echo "[demo] Starting Hello World app..."
33
+ curl -sf -X POST "${DAEMON_URL}/api/apps/start-app/hello_world" || true
34
+
35
+ # ── Wait for the app to be serving ────────────────────────────────
36
+ echo "[demo] Waiting for app to respond..."
37
+ for i in $(seq 1 30); do
38
+ if curl -sf "${APP_URL}/api/settings" > /dev/null 2>&1; then
39
+ echo "[demo] Hello World is live on port 8042"
40
+ break
41
+ fi
42
+ if [ "$i" -eq 30 ]; then
43
+ echo "[demo] WARNING: App did not respond within 30s (may still be starting)"
44
+ fi
45
+ sleep 1
46
+ done
47
+
48
+ echo "[demo] Startup complete."
hello_world/api/audio_output.py CHANGED
@@ -36,11 +36,15 @@ __all__ = ['AudioRouter']
36
  import asyncio
37
  import logging
38
  import os
 
39
  import subprocess
40
  import tempfile
41
 
42
  import numpy as np
43
 
 
 
 
44
  logger = logging.getLogger("reachy_mini.app.hello_world.audio_output")
45
 
46
  # Bluetooth A2DP sample rate (full quality)
@@ -158,6 +162,9 @@ class AudioRouter:
158
  hardware with the daemon. This is the same approach as the reference
159
  app and ensures the XMOS DSP sees the reference signal for AEC.
160
  """
 
 
 
161
  cmd = [
162
  "gst-launch-1.0", "-q",
163
  "filesrc", f"location={filepath}", "!",
 
36
  import asyncio
37
  import logging
38
  import os
39
+ import shutil
40
  import subprocess
41
  import tempfile
42
 
43
  import numpy as np
44
 
45
+ # Check once at import time — avoids noisy error logs in containers without GStreamer
46
+ _HAS_GSTREAMER = shutil.which("gst-launch-1.0") is not None
47
+
48
  logger = logging.getLogger("reachy_mini.app.hello_world.audio_output")
49
 
50
  # Bluetooth A2DP sample rate (full quality)
 
162
  hardware with the daemon. This is the same approach as the reference
163
  app and ensures the XMOS DSP sees the reference signal for AEC.
164
  """
165
+ if not _HAS_GSTREAMER:
166
+ logger.debug("GStreamer not available, skipping robot audio playback")
167
+ return
168
  cmd = [
169
  "gst-launch-1.0", "-q",
170
  "filesrc", f"location={filepath}", "!",
hello_world/api/bluetooth.py CHANGED
@@ -22,6 +22,7 @@ import json
22
  import logging
23
  import math
24
  import re
 
25
  import subprocess
26
  import urllib.request
27
 
@@ -44,6 +45,8 @@ _UUID_CAPABILITIES = {
44
 
45
  def _run_bluetoothctl(args: list, timeout: int = 10) -> subprocess.CompletedProcess:
46
  """Run a bluetoothctl command with timeout."""
 
 
47
  return subprocess.run(
48
  ["bluetoothctl"] + args,
49
  capture_output=True, text=True, timeout=timeout
 
22
  import logging
23
  import math
24
  import re
25
+ import shutil
26
  import subprocess
27
  import urllib.request
28
 
 
45
 
46
  def _run_bluetoothctl(args: list, timeout: int = 10) -> subprocess.CompletedProcess:
47
  """Run a bluetoothctl command with timeout."""
48
+ if not shutil.which("bluetoothctl"):
49
+ raise FileNotFoundError("bluetoothctl not found")
50
  return subprocess.run(
51
  ["bluetoothctl"] + args,
52
  capture_output=True, text=True, timeout=timeout
index.html CHANGED
@@ -524,7 +524,10 @@
524
  <div class="stat-label">Robot Skins</div>
525
  </div>
526
  </div>
527
- <a href="docs.html" class="hero-btn">Read the Full Documentation <span class="arrow">&rarr;</span></a>
 
 
 
528
  </div>
529
  </section>
530
 
 
524
  <div class="stat-label">Robot Skins</div>
525
  </div>
526
  </div>
527
+ <div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
528
+ <a href="https://huggingface.co/spaces/panny247/hello_world-demo" class="hero-btn" style="background: linear-gradient(135deg, rgba(124, 92, 252, 0.25), rgba(92, 224, 216, 0.15));">Try Live Demo <span class="arrow">&rarr;</span></a>
529
+ <a href="docs.html" class="hero-btn">Read the Full Documentation <span class="arrow">&rarr;</span></a>
530
+ </div>
531
  </div>
532
  </section>
533
 
supervisord.demo.conf ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ; Supervisor config for HuggingFace Spaces demo.
2
+ ;
3
+ ; Manages two processes:
4
+ ; 1. reachy-mini-daemon --sim — simulated robot server on port 8000
5
+ ; 2. demo-start.sh — waits for daemon, wakes it, starts the app
6
+
7
+ [supervisord]
8
+ nodaemon=true
9
+ logfile=/tmp/supervisord.log
10
+ pidfile=/tmp/supervisord.pid
11
+ childlogdir=/tmp
12
+
13
+ [program:daemon]
14
+ command=reachy-mini-daemon --sim
15
+ autostart=true
16
+ autorestart=true
17
+ stdout_logfile=/dev/stdout
18
+ stdout_logfile_maxbytes=0
19
+ stderr_logfile=/dev/stderr
20
+ stderr_logfile_maxbytes=0
21
+
22
+ [program:startup]
23
+ command=/home/user/app/demo-start.sh
24
+ autostart=true
25
+ autorestart=false
26
+ startsecs=0
27
+ exitcodes=0
28
+ stdout_logfile=/dev/stdout
29
+ stdout_logfile_maxbytes=0
30
+ stderr_logfile=/dev/stderr
31
+ stderr_logfile_maxbytes=0