"use client"; import { useState, useEffect, useCallback } from "react"; import { useConversation } from "@/hooks/useConversation"; import { useListening } from "@/hooks/useListening"; import { useSession } from "@/hooks/useSession"; import { useConsent } from "@/hooks/useConsent"; import { SettingsPanel } from "@/components/SettingsPanel"; import { ReportsPanel } from "@/components/ReportsPanel"; import { ConsentModal } from "@/components/ConsentModal"; import { DashboardGrid } from "@/components/DashboardGrid"; import { ComponentOverlay } from "@/components/ComponentOverlay"; import { ObservabilityPanel } from "@/components/ObservabilityPanel"; import { Loader2, Mic, MicOff, Power, Square, Video, VideoOff } from "lucide-react"; interface ChatInterfaceProps { wsUrl?: string; } export function ChatInterface({ wsUrl = "ws://localhost:8000/api/stream/ws" }: ChatInterfaceProps) { const { messages, isConnected, latestCameraFrame, latestComponent, dismissLatestComponent, pendingTools } = useConversation({ wsUrl, }); const { isListening, toggle: toggleListening, setListening } = useListening(); const { isActive: isSessionActive, isToggling: isSessionToggling, toggle: baseToggleSession } = useSession(); const { hasConsented, isLoading: isConsentLoading, giveConsent } = useConsent(); // Camera view visibility state const [showCameraView, setShowCameraView] = useState(false); // Lifted panel state for voice control const [showSettings, setShowSettings] = useState(false); const [showReports, setShowReports] = useState(false); const [settingsSection, setSettingsSection] = useState(undefined); const toggleSettings = useCallback(() => { setShowSettings((prev) => { if (prev) setSettingsSection(undefined); // clear section when closing return !prev; }); }, []); const toggleReports = useCallback(() => setShowReports((prev) => !prev), []); // Observability panel state const [showObservability, setShowObservability] = useState(false); const toggleObservability = useCallback(() => setShowObservability((prev) => !prev), []); // Wrap session toggle to sync listening state const toggleSession = async () => { await baseToggleSession(); // If session is becoming active, the backend auto-enables listening if (!isSessionActive) { setListening(true); } }; // Listen for voice-triggered UI navigation events useEffect(() => { const handleNavigate = (e: Event) => { const { target, section } = (e as CustomEvent).detail; switch (target) { case "camera": setShowCameraView((prev) => !prev); break; case "settings": if (section) { // When a section is specified, always open settings to that tab setSettingsSection(section); setShowSettings(true); } else { setShowSettings((prev) => { if (prev) setSettingsSection(undefined); return !prev; }); } break; case "reports": setShowReports((prev) => !prev); break; case "observability": setShowObservability((prev) => !prev); break; } }; window.addEventListener("ui-navigate", handleNavigate); return () => window.removeEventListener("ui-navigate", handleNavigate); }, []); // Show loading state while checking consent if (isConsentLoading) { return (
); } return (
{/* Consent Modal - shown if not consented */} {!hasConsented && } {/* Header - Card style matching mockup */}
Reachy Mini

Reachy Mini Minder

Always here for you

{/* Only show listen toggle when session is active */} {isSessionActive && ( )}
{/* Dashboard Grid - Bento Layout */} {hasConsented && ( )} {/* Component overlay - prominently displays the latest GenUI component */} {latestComponent && ( )} {/* Disconnected Overlay */} {!isConnected && (
{/* Image banner - full width, no margin */}
No connection
{/* Text content */}

System Offline

Lost connection to the Reachy Mini Minder backend. Controls are disabled until the connection is restored.

Attempting to reconnect...
)} {/* Sticky Bottom Navigation */}
); } interface ListenToggleProps { isListening: boolean; onToggle: () => void; disabled?: boolean; } function ListenToggle({ isListening, onToggle, disabled }: ListenToggleProps) { return ( ); } interface SessionToggleProps { isActive: boolean; isToggling: boolean; onToggle: () => void; } function SessionToggle({ isActive, isToggling, onToggle }: SessionToggleProps) { return ( ); }