import { useState, useEffect, useRef } from 'react' /** * Displays a live robot interaction panel with user input, conversation history, * and real-time updates from the robot backend via Server-Sent Events (SSE). * * @returns A React element rendering the interactive robot UI. */ export default function Robot() { /** The text message currently entered by the user. */ const [message, setMessage] = useState(''); /** Whether the robot’s microphone or listening mode is currently active. */ const [listening, setListening] = useState(false); /** The ongoing conversation history as a sequence of user/assistant messages. */ const [conversation, setConversation] = useState< {"role": "user" | "assistant", "content": string}[]>([]) /** Reference to the scrollable conversation container for auto-scrolling. */ const conversationRef = useRef(null); /** * Index used to force refresh the SSE connection or clear conversation. * Incrementing this value triggers a reset of the live data stream. */ const [conversationIndex, setConversationIndex] = useState(0); /** * Sends a message to the robot backend. * * Makes a POST request to `/message` with the user’s text. * The backend may respond with confirmation or error information. */ const sendMessage = async () => { try { const response = await fetch("http://localhost:8000/message", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message }), }); const data = await response.json(); console.log(data); } catch (error) { console.error("Error sending message: ", error); } }; /** * Establishes a persistent Server-Sent Events (SSE) connection * to receive real-time updates from the robot backend. * * Handles three event types: * - `voice_active`: whether the robot is currently listening. * - `speech`: recognized user speech input. * - `llm_response`: the robot’s language model-generated reply. * * The connection resets whenever `conversationIndex` changes. */ useEffect(() => { const eventSource = new EventSource("http://localhost:8000/sse"); eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); if ("voice_active" in data) setListening(data.voice_active); if ("speech" in data) setConversation(conversation => [...conversation, {"role": "user", "content": data.speech}]); if ("llm_response" in data) setConversation(conversation => [...conversation, {"role": "assistant", "content": data.llm_response}]); } catch { console.log("Unparsable SSE message:", event.data); } }; return () => { eventSource.close(); }; }, [conversationIndex]); /** * Automatically scrolls the conversation view to the bottom * whenever a new message is added. */ useEffect(() => { if (!conversationRef || !conversationRef.current) return; conversationRef.current.scrollTop = conversationRef.current.scrollHeight; }, [conversation]); return ( <>

Robot interaction

Force robot speech

setMessage(e.target.value)} onKeyDown={(e) => e.key === "Enter" && sendMessage().then(() => setMessage(""))} placeholder="Enter a message" />

Conversation

Listening {listening ? "🟢" : "🔴"}

{conversation.map((item, i) => (

{item["content"]}

))}
); }