ReachyMaster
feat: initial Reachy conversation app with animations and Ollama
bfcd703
Raw History Blame Contribute Delete
2.86 kB
import { connectToHost } from "@pollen-robotics/reachy-mini-sdk/host/embed";
import { ConversationManager } from "./conversation.ts";
import { AnimationEngine } from "./animations.ts";
import { SpeechManager } from "./speech.ts";
import { UIManager } from "./ui.ts";
import type { ReachyMini } from "@pollen-robotics/reachy-mini-sdk";
// Simple safe return to home pose
async function safelyReturnToPose(reachy: ReachyMini) {
try {
reachy.setMotorMode("enabled");
reachy.setHeadRpyDeg(0, 0, 0);
reachy.setAntennasDeg(0, 0);
reachy.setBodyYawDeg(0);
} catch (e) {
console.error("Failed to return to safe pose:", e);
}
}
export async function main() {
const handle = await connectToHost();
const reachy = handle.reachy as ReachyMini;
document.documentElement.setAttribute("data-theme", handle.theme);
handle.onThemeChange((t) => document.documentElement.setAttribute("data-theme", t));
const animations = new AnimationEngine(reachy);
const speech = new SpeechManager();
const conversation = new ConversationManager();
const ui = new UIManager({
onSendMessage: (text) => conversation.sendMessage(text),
onStartListening: () => speech.startListening(),
onStopListening: () => speech.stopListening(),
onConnectOllama: (url, model) => conversation.connectOllama(url, model),
});
// Handle incoming speech-to-text results
speech.onResult((text) => {
conversation.sendMessage(text);
});
// When a new conversation message arrives
conversation.onMessage(async (msg) => {
ui.addMessage(msg.role, msg.content);
if (msg.role === "assistant") {
// Animate while speaking
await animations.animateSpeaking(msg.content);
// Speak the response
await speech.speak(msg.content);
animations.stopSpeaking();
}
});
// Conversation lull → dance
let lullTimer: ReturnType<typeof setTimeout> | null = null;
const LULL_TIMEOUT_MS = 8000; // 8 seconds of inactivity
function resetLullTimer() {
if (lullTimer) clearTimeout(lullTimer);
lullTimer = setTimeout(async () => {
ui.showStatus("dancing");
await animations.doRandomDance();
ui.showStatus("idle");
}, LULL_TIMEOUT_MS);
}
conversation.onActivity(resetLullTimer);
ui.onActivity(resetLullTimer);
// Safe teardown
handle.onLeave(() => {
if (lullTimer) clearTimeout(lullTimer);
speech.destroy();
safelyReturnToPose(reachy);
});
// Initial greeting
setTimeout(() => {
conversation.sendSystem("You are Reachy, a friendly and expressive robot. Keep responses short and engaging.");
}, 1000);
}
void main().catch((err) => {
console.error("[embed] Boot failed:", err);
window.parent.postMessage(
{ source: "reachy-mini", type: "embed:error", version: 1, message: String(err), fatal: true },
window.location.origin,
);
});