import { useState, useEffect, useRef } from "react"; import { ChevronLeft } from "lucide-react"; import { Header } from "@/components/header"; import { Footer } from "@/components/footer"; import { EditRobotDialog } from "@/components/edit-robot-dialog"; import { DeviceDashboard } from "@/components/device-dashboard"; import { CalibrationView } from "@/components/calibration-view"; import { TeleoperationView } from "@/components/teleoperation-view"; import { SetupCards } from "@/components/setup-cards"; import { DocsSection } from "@/components/docs-section"; import { RoadmapSection } from "@/components/roadmap-section"; import { HardwareSupportSection } from "@/components/hardware-support-section"; import { useToast } from "@/hooks/use-toast"; import { Toaster } from "@/components/ui/toaster"; import { findPort, isWebSerialSupported, type RobotConnection, type RobotConfig, } from "@lerobot/web"; import { getAllSavedRobots, getUnifiedRobotData, saveDeviceInfo, removeRobotData, type DeviceInfo, } from "@/lib/unified-storage"; function App() { const [view, setView] = useState< "dashboard" | "calibrating" | "teleoperating" >("dashboard"); const [robots, setRobots] = useState([]); const [selectedRobot, setSelectedRobot] = useState( null ); const [editingRobot, setEditingRobot] = useState( null ); const [isConnecting, setIsConnecting] = useState(false); const hardwareSectionRef = useRef(null); const { toast } = useToast(); // Check browser support const isSupported = isWebSerialSupported(); useEffect(() => { if (!isSupported) { toast({ title: "Browser Not Supported", description: "WebSerial API is not supported. Please use Chrome, Edge, or another Chromium-based browser.", variant: "destructive", }); } }, [isSupported, toast]); useEffect(() => { const loadSavedRobots = async () => { if (!isSupported) return; try { setIsConnecting(true); // Get saved robot configurations const savedRobots = getAllSavedRobots(); if (savedRobots.length > 0) { const robotConfigs: RobotConfig[] = savedRobots.map((device) => ({ robotType: device.robotType as "so100_follower" | "so100_leader", robotId: device.robotId, serialNumber: device.serialNumber, })); // Auto-connect to saved robots const findPortProcess = await findPort({ robotConfigs, onMessage: (msg: string) => { console.log("Connection message:", msg); }, }); const reconnectedRobots = await findPortProcess.result; // Merge saved device info (names, etc.) with fresh connection data const robotsWithSavedInfo = reconnectedRobots.map((robot) => { const savedData = getUnifiedRobotData(robot.serialNumber || ""); if (savedData?.device_info) { return { ...robot, robotId: savedData.device_info.robotId, name: savedData.device_info.robotId, // Use the saved custom name robotType: savedData.device_info.robotType as | "so100_follower" | "so100_leader", }; } return robot; }); setRobots(robotsWithSavedInfo); } } catch (error) { console.error("Failed to load saved robots:", error); toast({ title: "Connection Error", description: "Failed to reconnect to saved robots", variant: "destructive", }); } finally { setIsConnecting(false); } }; loadSavedRobots(); }, [isSupported, toast]); const handleFindNewRobots = async () => { if (!isSupported) { toast({ title: "Browser Not Supported", description: "WebSerial API is required for robot connection", variant: "destructive", }); return; } try { setIsConnecting(true); // Interactive mode - show browser dialog const findPortProcess = await findPort({ onMessage: (msg: string) => { console.log("Find port message:", msg); }, }); const newRobots = await findPortProcess.result; if (newRobots.length > 0) { setRobots((prev: RobotConnection[]) => { const existingSerialNumbers = new Set( prev.map((r: RobotConnection) => r.serialNumber) ); const uniqueNewRobots = newRobots.filter( (r: RobotConnection) => !existingSerialNumbers.has(r.serialNumber) ); // Auto-edit first new robot for configuration if (uniqueNewRobots.length > 0) { setEditingRobot(uniqueNewRobots[0]); } return [...prev, ...uniqueNewRobots]; }); toast({ title: "Robots Found", description: `Found ${newRobots.length} robot(s)`, }); } else { toast({ title: "No Robots Found", description: "No compatible devices detected", }); } } catch (error) { console.error("Failed to find robots:", error); toast({ title: "Connection Error", description: "Failed to find robots. Please try again.", variant: "destructive", }); } finally { setIsConnecting(false); } }; const handleUpdateRobot = (updatedRobot: RobotConnection) => { // Save device info to unified storage if (updatedRobot.serialNumber && updatedRobot.robotId) { const deviceInfo: DeviceInfo = { serialNumber: updatedRobot.serialNumber, robotType: updatedRobot.robotType || "so100_follower", robotId: updatedRobot.robotId, usbMetadata: updatedRobot.usbMetadata ? { vendorId: parseInt(updatedRobot.usbMetadata.vendorId || "0", 16), productId: parseInt( updatedRobot.usbMetadata.productId || "0", 16 ), serialNumber: updatedRobot.usbMetadata.serialNumber, manufacturer: updatedRobot.usbMetadata.manufacturerName, product: updatedRobot.usbMetadata.productName, } : undefined, }; saveDeviceInfo(updatedRobot.serialNumber, deviceInfo); } setRobots((prev) => prev.map((r) => r.serialNumber === updatedRobot.serialNumber ? updatedRobot : r ) ); setEditingRobot(null); }; const handleRemoveRobot = (robotId: string) => { const robot = robots.find((r) => r.robotId === robotId); if (robot?.serialNumber) { removeRobotData(robot.serialNumber); } setRobots((prev) => prev.filter((r) => r.robotId !== robotId)); toast({ title: "Robot Removed", description: `${robotId} has been removed from the registry`, }); }; const handleCalibrate = (robot: RobotConnection) => { if (!robot.isConnected) { toast({ title: "Robot Not Connected", description: "Please connect the robot before calibrating", variant: "destructive", }); return; } setSelectedRobot(robot); setView("calibrating"); }; const handleTeleoperate = (robot: RobotConnection) => { if (!robot.isConnected) { toast({ title: "Robot Not Connected", description: "Please connect the robot before teleoperating", variant: "destructive", }); return; } setSelectedRobot(robot); setView("teleoperating"); }; const handleCloseSubView = () => { setSelectedRobot(null); setView("dashboard"); }; const scrollToHardware = () => { hardwareSectionRef.current?.scrollIntoView({ behavior: "smooth" }); }; const renderView = () => { switch (view) { case "calibrating": return selectedRobot && ; case "teleoperating": return selectedRobot && ; case "dashboard": default: return (

install

Choose your preferred development environment

); } }; const PageHeader = () => { return (
{view === "calibrating" && selectedRobot ? (

calibrate: {" "} {selectedRobot.robotId?.toUpperCase()}

) : view === "teleoperating" && selectedRobot ? (

teleoperate: {" "} {selectedRobot.robotId?.toUpperCase()}

) : (

DASHBOARD

)}
{view !== "dashboard" ? ( ) : (

{""}

)}
); }; return (
{renderView()} !open && setEditingRobot(null)} onSave={handleUpdateRobot} />
); } export default App;