Spaces:
Running
Running
feat(example): improved everything to make it release rdy
Browse files- examples/cyberpunk-standalone/src/App.tsx +3 -3
- examples/cyberpunk-standalone/src/components/calibration-view.tsx +138 -56
- examples/cyberpunk-standalone/src/components/device-dashboard.tsx +216 -162
- examples/cyberpunk-standalone/src/components/docs-section.tsx +127 -55
- examples/cyberpunk-standalone/src/components/footer.tsx +3 -3
- examples/cyberpunk-standalone/src/components/hardware-support-section.tsx +14 -27
- examples/cyberpunk-standalone/src/components/header.tsx +3 -3
- examples/cyberpunk-standalone/src/components/roadmap-section.tsx +61 -35
- examples/cyberpunk-standalone/src/components/setup-cards.tsx +46 -30
examples/cyberpunk-standalone/src/App.tsx
CHANGED
|
@@ -267,7 +267,7 @@ function App() {
|
|
| 267 |
case "dashboard":
|
| 268 |
default:
|
| 269 |
return (
|
| 270 |
-
<div className="space-y-
|
| 271 |
<DeviceDashboard
|
| 272 |
robots={robots}
|
| 273 |
onCalibrate={handleCalibrate}
|
|
@@ -280,7 +280,7 @@ function App() {
|
|
| 280 |
/>
|
| 281 |
<div>
|
| 282 |
<div className="mb-6">
|
| 283 |
-
<h2 className="text-
|
| 284 |
install
|
| 285 |
</h2>
|
| 286 |
<p className="text-sm text-muted-foreground font-mono">
|
|
@@ -356,7 +356,7 @@ function App() {
|
|
| 356 |
};
|
| 357 |
|
| 358 |
return (
|
| 359 |
-
<div className="flex flex-col min-h-screen font-sans">
|
| 360 |
<Header />
|
| 361 |
<main className="flex-grow container mx-auto py-12 px-4 md:px-6">
|
| 362 |
<PageHeader />
|
|
|
|
| 267 |
case "dashboard":
|
| 268 |
default:
|
| 269 |
return (
|
| 270 |
+
<div className="space-y-20">
|
| 271 |
<DeviceDashboard
|
| 272 |
robots={robots}
|
| 273 |
onCalibrate={handleCalibrate}
|
|
|
|
| 280 |
/>
|
| 281 |
<div>
|
| 282 |
<div className="mb-6">
|
| 283 |
+
<h2 className="text-3xl font-bold font-mono tracking-wider mb-2 uppercase">
|
| 284 |
install
|
| 285 |
</h2>
|
| 286 |
<p className="text-sm text-muted-foreground font-mono">
|
|
|
|
| 356 |
};
|
| 357 |
|
| 358 |
return (
|
| 359 |
+
<div className="flex flex-col min-h-screen font-sans bg-gray-200 dark:bg-background">
|
| 360 |
<Header />
|
| 361 |
<main className="flex-grow container mx-auto py-12 px-4 md:px-6">
|
| 362 |
<PageHeader />
|
examples/cyberpunk-standalone/src/components/calibration-view.tsx
CHANGED
|
@@ -3,6 +3,16 @@ import { useState, useMemo, useEffect, useCallback } from "react";
|
|
| 3 |
import { Download } from "lucide-react";
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import { Card } from "@/components/ui/card";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
import { useToast } from "@/hooks/use-toast";
|
| 7 |
import {
|
| 8 |
calibrate,
|
|
@@ -28,6 +38,7 @@ export function CalibrationView({ robot }: CalibrationViewProps) {
|
|
| 28 |
const [liveData, setLiveData] = useState<LiveCalibrationData | null>(null);
|
| 29 |
const [isCalibrating, setIsCalibrating] = useState(false);
|
| 30 |
const [isPreparing, setIsPreparing] = useState(false);
|
|
|
|
| 31 |
const [calibrationProcess, setCalibrationProcess] =
|
| 32 |
useState<CalibrationProcess | null>(null);
|
| 33 |
const [calibrationResults, setCalibrationResults] =
|
|
@@ -86,12 +97,38 @@ export function CalibrationView({ robot }: CalibrationViewProps) {
|
|
| 86 |
|
| 87 |
const handleStart = async () => {
|
| 88 |
try {
|
| 89 |
-
|
| 90 |
-
setStatus("🤖
|
| 91 |
|
| 92 |
// Release motors first
|
| 93 |
await releaseMotorTorque();
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
// Start calibration process
|
| 96 |
const process = await calibrate({
|
| 97 |
robot,
|
|
@@ -162,6 +199,12 @@ export function CalibrationView({ robot }: CalibrationViewProps) {
|
|
| 162 |
}
|
| 163 |
};
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
const handleFinish = async () => {
|
| 166 |
if (calibrationProcess) {
|
| 167 |
try {
|
|
@@ -224,67 +267,106 @@ export function CalibrationView({ robot }: CalibrationViewProps) {
|
|
| 224 |
);
|
| 225 |
|
| 226 |
return (
|
| 227 |
-
|
| 228 |
-
<
|
| 229 |
-
<div className="flex items-center
|
| 230 |
-
<div className="
|
| 231 |
-
|
| 232 |
-
<
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
</div>
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
<Button
|
| 241 |
-
onClick={
|
|
|
|
| 242 |
size="lg"
|
| 243 |
-
disabled={
|
| 244 |
>
|
| 245 |
-
|
| 246 |
</Button>
|
| 247 |
-
|
| 248 |
-
<Button onClick={handleFinish} variant="destructive" size="lg">
|
| 249 |
-
Finish Recording
|
| 250 |
-
</Button>
|
| 251 |
-
)}
|
| 252 |
-
<Button
|
| 253 |
-
onClick={downloadJson}
|
| 254 |
-
variant="outline"
|
| 255 |
-
size="lg"
|
| 256 |
-
disabled={!calibrationResults}
|
| 257 |
-
>
|
| 258 |
-
<Download className="w-4 h-4 mr-2" /> Download JSON
|
| 259 |
-
</Button>
|
| 260 |
-
</div>
|
| 261 |
-
</div>
|
| 262 |
-
<div className="pt-6 p-6">
|
| 263 |
-
<div className="flex items-center gap-4 py-2 px-4 text-sm font-sans text-muted-foreground">
|
| 264 |
-
<div className="w-40">Motor Name</div>
|
| 265 |
-
<div className="flex-1">Visual Range</div>
|
| 266 |
-
<div className="w-16 text-right">Current</div>
|
| 267 |
-
<div className="w-16 text-right">Min</div>
|
| 268 |
-
<div className="w-16 text-right">Max</div>
|
| 269 |
-
<div className="w-16 text-right">Range</div>
|
| 270 |
</div>
|
| 271 |
-
<div className="
|
| 272 |
-
|
| 273 |
-
<
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
}
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
</div>
|
| 287 |
-
</
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
);
|
| 290 |
}
|
|
|
|
| 3 |
import { Download } from "lucide-react";
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import { Card } from "@/components/ui/card";
|
| 6 |
+
import {
|
| 7 |
+
AlertDialog,
|
| 8 |
+
AlertDialogAction,
|
| 9 |
+
AlertDialogCancel,
|
| 10 |
+
AlertDialogContent,
|
| 11 |
+
AlertDialogDescription,
|
| 12 |
+
AlertDialogFooter,
|
| 13 |
+
AlertDialogHeader,
|
| 14 |
+
AlertDialogTitle,
|
| 15 |
+
} from "@/components/ui/alert-dialog";
|
| 16 |
import { useToast } from "@/hooks/use-toast";
|
| 17 |
import {
|
| 18 |
calibrate,
|
|
|
|
| 38 |
const [liveData, setLiveData] = useState<LiveCalibrationData | null>(null);
|
| 39 |
const [isCalibrating, setIsCalibrating] = useState(false);
|
| 40 |
const [isPreparing, setIsPreparing] = useState(false);
|
| 41 |
+
const [showHomingDialog, setShowHomingDialog] = useState(false);
|
| 42 |
const [calibrationProcess, setCalibrationProcess] =
|
| 43 |
useState<CalibrationProcess | null>(null);
|
| 44 |
const [calibrationResults, setCalibrationResults] =
|
|
|
|
| 97 |
|
| 98 |
const handleStart = async () => {
|
| 99 |
try {
|
| 100 |
+
setIsPreparing(true);
|
| 101 |
+
setStatus("🤖 Preparing for calibration...");
|
| 102 |
|
| 103 |
// Release motors first
|
| 104 |
await releaseMotorTorque();
|
| 105 |
|
| 106 |
+
// Show dialog asking user to put robot in homing position
|
| 107 |
+
setShowHomingDialog(true);
|
| 108 |
+
} catch (error) {
|
| 109 |
+
console.error("Failed to prepare for calibration:", error);
|
| 110 |
+
setStatus(
|
| 111 |
+
`❌ Preparation failed: ${
|
| 112 |
+
error instanceof Error ? error.message : error
|
| 113 |
+
}`
|
| 114 |
+
);
|
| 115 |
+
toast({
|
| 116 |
+
title: "Preparation Failed",
|
| 117 |
+
description:
|
| 118 |
+
error instanceof Error ? error.message : "An unknown error occurred",
|
| 119 |
+
variant: "destructive",
|
| 120 |
+
});
|
| 121 |
+
setIsPreparing(false);
|
| 122 |
+
}
|
| 123 |
+
};
|
| 124 |
+
|
| 125 |
+
const handleStartCalibration = async () => {
|
| 126 |
+
try {
|
| 127 |
+
setShowHomingDialog(false);
|
| 128 |
+
setIsCalibrating(true);
|
| 129 |
+
setIsPreparing(false);
|
| 130 |
+
setStatus("🤖 Starting calibration process...");
|
| 131 |
+
|
| 132 |
// Start calibration process
|
| 133 |
const process = await calibrate({
|
| 134 |
robot,
|
|
|
|
| 199 |
}
|
| 200 |
};
|
| 201 |
|
| 202 |
+
const handleCancelCalibration = () => {
|
| 203 |
+
setShowHomingDialog(false);
|
| 204 |
+
setIsPreparing(false);
|
| 205 |
+
setStatus("Ready to calibrate.");
|
| 206 |
+
};
|
| 207 |
+
|
| 208 |
const handleFinish = async () => {
|
| 209 |
if (calibrationProcess) {
|
| 210 |
try {
|
|
|
|
| 267 |
);
|
| 268 |
|
| 269 |
return (
|
| 270 |
+
<>
|
| 271 |
+
<Card className="border-0 rounded-none">
|
| 272 |
+
<div className="p-4 border-b border-white/10 flex items-center justify-between">
|
| 273 |
+
<div className="flex items-center gap-4">
|
| 274 |
+
<div className="w-1 h-8 bg-primary"></div>
|
| 275 |
+
<div>
|
| 276 |
+
<h3 className="text-xl font-bold text-foreground font-mono tracking-wider uppercase">
|
| 277 |
+
motor calibration
|
| 278 |
+
</h3>
|
| 279 |
+
<p className="text-sm text-muted-foreground font-mono">
|
| 280 |
+
{status}
|
| 281 |
+
</p>
|
| 282 |
+
</div>
|
| 283 |
</div>
|
| 284 |
+
<div className="flex gap-4">
|
| 285 |
+
{!isCalibrating ? (
|
| 286 |
+
<Button
|
| 287 |
+
onClick={handleStart}
|
| 288 |
+
size="lg"
|
| 289 |
+
disabled={isPreparing || !robot.isConnected}
|
| 290 |
+
>
|
| 291 |
+
{isPreparing
|
| 292 |
+
? "Preparing..."
|
| 293 |
+
: calibrationResults
|
| 294 |
+
? "Re-calibrate"
|
| 295 |
+
: "Start Calibration"}
|
| 296 |
+
</Button>
|
| 297 |
+
) : (
|
| 298 |
+
<Button onClick={handleFinish} variant="destructive" size="lg">
|
| 299 |
+
Finish Recording
|
| 300 |
+
</Button>
|
| 301 |
+
)}
|
| 302 |
<Button
|
| 303 |
+
onClick={downloadJson}
|
| 304 |
+
variant="outline"
|
| 305 |
size="lg"
|
| 306 |
+
disabled={!calibrationResults}
|
| 307 |
>
|
| 308 |
+
<Download className="w-4 h-4 mr-2" /> Download JSON
|
| 309 |
</Button>
|
| 310 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
</div>
|
| 312 |
+
<div className="pt-6 p-6">
|
| 313 |
+
<div className="flex items-center gap-4 py-2 px-4 text-sm font-sans text-muted-foreground">
|
| 314 |
+
<div className="w-40">Motor Name</div>
|
| 315 |
+
<div className="flex-1">Visual Range</div>
|
| 316 |
+
<div className="w-16 text-right">Current</div>
|
| 317 |
+
<div className="w-16 text-right">Min</div>
|
| 318 |
+
<div className="w-16 text-right">Max</div>
|
| 319 |
+
<div className="w-16 text-right">Range</div>
|
| 320 |
+
</div>
|
| 321 |
+
<div className="border-t border-white/10">
|
| 322 |
+
{motorData.map(([name, data]) => (
|
| 323 |
+
<MotorCalibrationVisual
|
| 324 |
+
key={name as string}
|
| 325 |
+
name={name as string}
|
| 326 |
+
data={
|
| 327 |
+
data as {
|
| 328 |
+
current: number;
|
| 329 |
+
min: number;
|
| 330 |
+
max: number;
|
| 331 |
+
range: number;
|
| 332 |
+
}
|
| 333 |
}
|
| 334 |
+
/>
|
| 335 |
+
))}
|
| 336 |
+
</div>
|
| 337 |
</div>
|
| 338 |
+
</Card>
|
| 339 |
+
|
| 340 |
+
<AlertDialog open={showHomingDialog} onOpenChange={setShowHomingDialog}>
|
| 341 |
+
<AlertDialogContent>
|
| 342 |
+
<AlertDialogHeader>
|
| 343 |
+
<AlertDialogTitle>Position Robot for Calibration</AlertDialogTitle>
|
| 344 |
+
<AlertDialogDescription>
|
| 345 |
+
The motors have been released and are now free to move. Please
|
| 346 |
+
position the robot in its homing position:
|
| 347 |
+
<br />
|
| 348 |
+
<br />
|
| 349 |
+
• Move all joints to their center/neutral position
|
| 350 |
+
<br />
|
| 351 |
+
• Ensure the robot is in a comfortable, balanced pose
|
| 352 |
+
<br />
|
| 353 |
+
• Make sure all motors can move freely through their full range
|
| 354 |
+
<br />
|
| 355 |
+
<br />
|
| 356 |
+
Once the robot is positioned correctly, click "Start Calibration"
|
| 357 |
+
to begin recording joint ranges.
|
| 358 |
+
</AlertDialogDescription>
|
| 359 |
+
</AlertDialogHeader>
|
| 360 |
+
<AlertDialogFooter>
|
| 361 |
+
<AlertDialogCancel onClick={handleCancelCalibration}>
|
| 362 |
+
Cancel
|
| 363 |
+
</AlertDialogCancel>
|
| 364 |
+
<AlertDialogAction onClick={handleStartCalibration}>
|
| 365 |
+
Start Calibration
|
| 366 |
+
</AlertDialogAction>
|
| 367 |
+
</AlertDialogFooter>
|
| 368 |
+
</AlertDialogContent>
|
| 369 |
+
</AlertDialog>
|
| 370 |
+
</>
|
| 371 |
);
|
| 372 |
}
|
examples/cyberpunk-standalone/src/components/device-dashboard.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
|
|
|
|
| 3 |
import {
|
| 4 |
Settings,
|
| 5 |
Gamepad2,
|
|
@@ -11,8 +12,19 @@ import {
|
|
| 11 |
import { Button } from "@/components/ui/button";
|
| 12 |
import { Card, CardFooter } from "@/components/ui/card";
|
| 13 |
import { Badge } from "@/components/ui/badge";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
import { cn } from "@/lib/utils";
|
| 15 |
import HudCorners from "@/components/hud-corners";
|
|
|
|
| 16 |
import type { RobotConnection } from "@/types/robot";
|
| 17 |
|
| 18 |
interface DeviceDashboardProps {
|
|
@@ -36,183 +48,225 @@ export function DeviceDashboard({
|
|
| 36 |
isConnecting,
|
| 37 |
onScrollToHardware,
|
| 38 |
}: DeviceDashboardProps) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
return (
|
| 40 |
-
|
| 41 |
-
<
|
| 42 |
-
<div className="flex items-center
|
| 43 |
-
<div className="
|
| 44 |
-
|
| 45 |
-
<
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
<
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
</div>
|
| 62 |
-
{robots.length > 0 && (
|
| 63 |
-
<Button
|
| 64 |
-
onClick={onFindNew}
|
| 65 |
-
disabled={isConnecting}
|
| 66 |
-
size="lg"
|
| 67 |
-
className="font-mono uppercase"
|
| 68 |
-
>
|
| 69 |
-
<Plus className="w-4 h-4 mr-2" />
|
| 70 |
-
add unit
|
| 71 |
-
</Button>
|
| 72 |
-
)}
|
| 73 |
-
</div>
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
|
|
|
| 112 |
</div>
|
| 113 |
-
</
|
| 114 |
-
</
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
</h4>
|
| 127 |
-
<div className="flex items-center gap-2 mt-1">
|
| 128 |
-
<span className="text-xs text-muted-foreground font-mono">
|
| 129 |
-
{robot.serialNumber}
|
| 130 |
-
</span>
|
| 131 |
-
<span className="text-xs text-muted-foreground">
|
| 132 |
-
•
|
| 133 |
-
</span>
|
| 134 |
-
<span className="text-xs font-mono uppercase text-muted-foreground">
|
| 135 |
-
{robot.robotType}
|
| 136 |
-
</span>
|
| 137 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
</div>
|
| 139 |
-
<Badge
|
| 140 |
-
variant="outline"
|
| 141 |
-
className={cn(
|
| 142 |
-
"border-primary/50 bg-primary/20 text-primary font-mono text-xs",
|
| 143 |
-
robot.isConnected && "animate-pulse-slow"
|
| 144 |
-
)}
|
| 145 |
-
>
|
| 146 |
-
{robot.isConnected ? "ONLINE" : "OFFLINE"}
|
| 147 |
-
</Badge>
|
| 148 |
</div>
|
| 149 |
-
</div>
|
| 150 |
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
|
|
|
| 159 |
</div>
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
</div>
|
| 168 |
</div>
|
| 169 |
</div>
|
| 170 |
-
</div>
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
);
|
| 218 |
}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { useState } from "react";
|
| 4 |
import {
|
| 5 |
Settings,
|
| 6 |
Gamepad2,
|
|
|
|
| 12 |
import { Button } from "@/components/ui/button";
|
| 13 |
import { Card, CardFooter } from "@/components/ui/card";
|
| 14 |
import { Badge } from "@/components/ui/badge";
|
| 15 |
+
import {
|
| 16 |
+
AlertDialog,
|
| 17 |
+
AlertDialogAction,
|
| 18 |
+
AlertDialogCancel,
|
| 19 |
+
AlertDialogContent,
|
| 20 |
+
AlertDialogDescription,
|
| 21 |
+
AlertDialogFooter,
|
| 22 |
+
AlertDialogHeader,
|
| 23 |
+
AlertDialogTitle,
|
| 24 |
+
} from "@/components/ui/alert-dialog";
|
| 25 |
import { cn } from "@/lib/utils";
|
| 26 |
import HudCorners from "@/components/hud-corners";
|
| 27 |
+
import { getUnifiedRobotData } from "@/lib/unified-storage";
|
| 28 |
import type { RobotConnection } from "@/types/robot";
|
| 29 |
|
| 30 |
interface DeviceDashboardProps {
|
|
|
|
| 48 |
isConnecting,
|
| 49 |
onScrollToHardware,
|
| 50 |
}: DeviceDashboardProps) {
|
| 51 |
+
const [robotToRemove, setRobotToRemove] = useState<RobotConnection | null>(
|
| 52 |
+
null
|
| 53 |
+
);
|
| 54 |
+
|
| 55 |
+
const handleRemoveClick = (robot: RobotConnection) => {
|
| 56 |
+
setRobotToRemove(robot);
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
+
const handleConfirmRemove = () => {
|
| 60 |
+
if (robotToRemove) {
|
| 61 |
+
onRemove(
|
| 62 |
+
robotToRemove.robotId || robotToRemove.serialNumber || "unknown"
|
| 63 |
+
);
|
| 64 |
+
setRobotToRemove(null);
|
| 65 |
+
}
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
+
const handleCancelRemove = () => {
|
| 69 |
+
setRobotToRemove(null);
|
| 70 |
+
};
|
| 71 |
return (
|
| 72 |
+
<>
|
| 73 |
+
<Card className="border-0 rounded-none">
|
| 74 |
+
<div className="p-4 border-b border-white/10 flex items-center justify-between">
|
| 75 |
+
<div className="flex items-center gap-4">
|
| 76 |
+
<div className="w-1 h-8 bg-primary"></div>
|
| 77 |
+
<div>
|
| 78 |
+
<h3 className="text-xl font-bold text-foreground font-mono tracking-wider uppercase">
|
| 79 |
+
device registry
|
| 80 |
+
</h3>
|
| 81 |
+
<div className="flex items-center gap-2 mt-1">
|
| 82 |
+
<span className="text-xs text-muted-foreground font-mono">
|
| 83 |
+
currently supports SO-100{" "}
|
| 84 |
+
</span>
|
| 85 |
+
<button
|
| 86 |
+
onClick={onScrollToHardware}
|
| 87 |
+
className="text-xs text-primary hover:text-accent transition-colors underline font-mono flex items-center gap-1"
|
| 88 |
+
>
|
| 89 |
+
<ExternalLink className="w-3 h-3" />
|
| 90 |
+
add more devices
|
| 91 |
+
</button>
|
| 92 |
+
</div>
|
| 93 |
</div>
|
| 94 |
</div>
|
| 95 |
+
{robots.length > 0 && (
|
| 96 |
+
<Button
|
| 97 |
+
onClick={onFindNew}
|
| 98 |
+
disabled={isConnecting}
|
| 99 |
+
size="lg"
|
| 100 |
+
className="font-mono uppercase"
|
| 101 |
+
>
|
| 102 |
+
<Plus className="w-4 h-4 mr-2" />
|
| 103 |
+
add unit
|
| 104 |
+
</Button>
|
| 105 |
+
)}
|
| 106 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
+
<div className="pt-6 p-6">
|
| 109 |
+
{robots.length === 0 ? (
|
| 110 |
+
<div className="relative">
|
| 111 |
+
<HudCorners className="p-16">
|
| 112 |
+
<div className="text-center font-mono">
|
| 113 |
+
<div className="mb-6">
|
| 114 |
+
{isConnecting ? (
|
| 115 |
+
<>
|
| 116 |
+
<div className="w-16 h-16 mx-auto mb-4 border-2 border-primary/50 rounded-lg flex items-center justify-center animate-pulse">
|
| 117 |
+
<Plus className="w-8 h-8 text-primary animate-spin" />
|
| 118 |
+
</div>
|
| 119 |
+
<h4 className="text-xl text-primary mb-2 tracking-wider uppercase">
|
| 120 |
+
scanning for units
|
| 121 |
+
</h4>
|
| 122 |
+
<p className="text-sm text-muted-foreground mb-8">
|
| 123 |
+
searching for available devices...
|
| 124 |
+
</p>
|
| 125 |
+
</>
|
| 126 |
+
) : (
|
| 127 |
+
<>
|
| 128 |
+
<div className="w-16 h-16 mx-auto mb-4 border-2 border-dashed border-primary/50 rounded-lg flex items-center justify-center">
|
| 129 |
+
<Plus className="w-8 h-8 text-primary/50" />
|
| 130 |
+
</div>
|
| 131 |
+
<h4 className="text-xl text-primary mb-2 tracking-wider uppercase">
|
| 132 |
+
no units detected
|
| 133 |
+
</h4>
|
| 134 |
|
| 135 |
+
<Button
|
| 136 |
+
onClick={onFindNew}
|
| 137 |
+
size="lg"
|
| 138 |
+
className="font-mono uppercase"
|
| 139 |
+
>
|
| 140 |
+
<Plus className="w-4 h-4 mr-2" />
|
| 141 |
+
add unit
|
| 142 |
+
</Button>
|
| 143 |
+
</>
|
| 144 |
+
)}
|
| 145 |
+
</div>
|
| 146 |
</div>
|
| 147 |
+
</HudCorners>
|
| 148 |
+
</div>
|
| 149 |
+
) : (
|
| 150 |
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
| 151 |
+
{robots.map((robot) => (
|
| 152 |
+
<HudCorners key={robot.robotId}>
|
| 153 |
+
<Card className="flex flex-col h-full">
|
| 154 |
+
<div className="p-4 border-b border-white/10">
|
| 155 |
+
<div className="flex items-center justify-between">
|
| 156 |
+
<div className="flex-1">
|
| 157 |
+
<h4 className="text-2xl font-bold text-primary font-mono tracking-wider">
|
| 158 |
+
{robot.name}
|
| 159 |
+
</h4>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
</div>
|
| 161 |
+
<Badge
|
| 162 |
+
variant="outline"
|
| 163 |
+
className={cn(
|
| 164 |
+
"border-primary/50 bg-primary/20 text-primary font-mono text-xs",
|
| 165 |
+
robot.isConnected && "animate-pulse-slow"
|
| 166 |
+
)}
|
| 167 |
+
>
|
| 168 |
+
{robot.isConnected ? "ONLINE" : "OFFLINE"}
|
| 169 |
+
</Badge>
|
| 170 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
</div>
|
|
|
|
| 172 |
|
| 173 |
+
<div className="flex-grow p-4">
|
| 174 |
+
<div className="grid grid-cols-2 gap-4 text-xs font-mono">
|
| 175 |
+
<div>
|
| 176 |
+
<span className="text-muted-foreground/40 uppercase">
|
| 177 |
+
serial number
|
| 178 |
+
</span>
|
| 179 |
+
<div className="text-muted-foreground uppercase/70">
|
| 180 |
+
{robot.serialNumber}
|
| 181 |
+
</div>
|
| 182 |
</div>
|
| 183 |
+
<div>
|
| 184 |
+
<span className="text-muted-foreground/40 uppercase">
|
| 185 |
+
type
|
| 186 |
+
</span>
|
| 187 |
+
<div className="text-muted-foreground uppercase/70">
|
| 188 |
+
{robot.robotType}
|
| 189 |
+
</div>
|
| 190 |
</div>
|
| 191 |
</div>
|
| 192 |
</div>
|
|
|
|
| 193 |
|
| 194 |
+
<CardFooter className="p-4 border-t border-white/10 flex flex-wrap gap-2">
|
| 195 |
+
<Button
|
| 196 |
+
variant="outline"
|
| 197 |
+
size="sm"
|
| 198 |
+
onClick={() => onEdit(robot)}
|
| 199 |
+
className="font-mono text-xs uppercase px-2"
|
| 200 |
+
>
|
| 201 |
+
<Pencil className="w-3 h-3 mr-0.5" /> edit
|
| 202 |
+
</Button>
|
| 203 |
+
<Button
|
| 204 |
+
variant="outline"
|
| 205 |
+
size="sm"
|
| 206 |
+
onClick={() => onCalibrate(robot)}
|
| 207 |
+
className="font-mono text-xs uppercase px-2"
|
| 208 |
+
>
|
| 209 |
+
<Settings className="w-3 h-3 mr-0.5" />
|
| 210 |
+
{robot.serialNumber &&
|
| 211 |
+
getUnifiedRobotData(robot.serialNumber)?.calibration
|
| 212 |
+
? "re-calibrate"
|
| 213 |
+
: "calibrate"}
|
| 214 |
+
</Button>
|
| 215 |
+
<Button
|
| 216 |
+
variant="outline"
|
| 217 |
+
size="sm"
|
| 218 |
+
onClick={() => onTeleoperate(robot)}
|
| 219 |
+
className="font-mono text-xs uppercase px-2"
|
| 220 |
+
>
|
| 221 |
+
<Gamepad2 className="w-3 h-3 mr-0.5" /> control
|
| 222 |
+
</Button>
|
| 223 |
+
<Button
|
| 224 |
+
variant="destructive"
|
| 225 |
+
size="sm"
|
| 226 |
+
onClick={() => handleRemoveClick(robot)}
|
| 227 |
+
className="font-mono text-xs uppercase px-2"
|
| 228 |
+
>
|
| 229 |
+
<Trash2 className="w-3 h-3 mr-0.5" /> remove
|
| 230 |
+
</Button>
|
| 231 |
+
</CardFooter>
|
| 232 |
+
</Card>
|
| 233 |
+
</HudCorners>
|
| 234 |
+
))}
|
| 235 |
+
</div>
|
| 236 |
+
)}
|
| 237 |
+
</div>
|
| 238 |
+
</Card>
|
| 239 |
+
|
| 240 |
+
<AlertDialog
|
| 241 |
+
open={!!robotToRemove}
|
| 242 |
+
onOpenChange={() => setRobotToRemove(null)}
|
| 243 |
+
>
|
| 244 |
+
<AlertDialogContent>
|
| 245 |
+
<AlertDialogHeader>
|
| 246 |
+
<AlertDialogTitle>Remove Robot</AlertDialogTitle>
|
| 247 |
+
<AlertDialogDescription>
|
| 248 |
+
Are you sure you want to remove{" "}
|
| 249 |
+
<strong>{robotToRemove?.name || robotToRemove?.robotId}</strong>{" "}
|
| 250 |
+
from the device registry?
|
| 251 |
+
<br />
|
| 252 |
+
<br />
|
| 253 |
+
This will permanently delete all stored calibration data and
|
| 254 |
+
settings for this robot. This action cannot be undone.
|
| 255 |
+
</AlertDialogDescription>
|
| 256 |
+
</AlertDialogHeader>
|
| 257 |
+
<AlertDialogFooter>
|
| 258 |
+
<AlertDialogCancel onClick={handleCancelRemove}>
|
| 259 |
+
Cancel
|
| 260 |
+
</AlertDialogCancel>
|
| 261 |
+
<AlertDialogAction
|
| 262 |
+
onClick={handleConfirmRemove}
|
| 263 |
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
| 264 |
+
>
|
| 265 |
+
Remove Robot
|
| 266 |
+
</AlertDialogAction>
|
| 267 |
+
</AlertDialogFooter>
|
| 268 |
+
</AlertDialogContent>
|
| 269 |
+
</AlertDialog>
|
| 270 |
+
</>
|
| 271 |
);
|
| 272 |
}
|
examples/cyberpunk-standalone/src/components/docs-section.tsx
CHANGED
|
@@ -30,7 +30,7 @@ const CodeBlock = ({
|
|
| 30 |
typeof children === "string" ? children : children?.toString() || "";
|
| 31 |
|
| 32 |
return (
|
| 33 |
-
<div className="bg-
|
| 34 |
<SyntaxHighlighter
|
| 35 |
language={language}
|
| 36 |
style={oneDark}
|
|
@@ -40,6 +40,8 @@ const CodeBlock = ({
|
|
| 40 |
fontSize: "0.875rem",
|
| 41 |
background: "transparent",
|
| 42 |
backgroundColor: "transparent",
|
|
|
|
|
|
|
| 43 |
}}
|
| 44 |
wrapLines={true}
|
| 45 |
wrapLongLines={true}
|
|
@@ -68,16 +70,36 @@ export function DocsSection() {
|
|
| 68 |
return (
|
| 69 |
<div className="font-mono">
|
| 70 |
<div className="mb-6">
|
| 71 |
-
<h2 className="text-
|
| 72 |
<Book className="w-6 h-6" />
|
| 73 |
Docs
|
| 74 |
</h2>
|
| 75 |
<p className="text-sm text-muted-foreground">
|
| 76 |
-
Complete API reference for @lerobot/web
|
| 77 |
</p>
|
| 78 |
</div>
|
| 79 |
|
| 80 |
-
<div className="bg-muted/40 dark:bg-black/30 border
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
{/* Getting Started */}
|
| 82 |
<div>
|
| 83 |
<h3 className="text-xl font-bold text-cyan-600 dark:text-accent-cyan tracking-wider uppercase flex items-center gap-2">
|
|
@@ -115,28 +137,11 @@ const teleop = await teleoperate({
|
|
| 115 |
calibrationData,
|
| 116 |
teleop: { type: "keyboard" },
|
| 117 |
});
|
| 118 |
-
teleop.start()
|
| 119 |
-
</CodeBlock>
|
| 120 |
-
</div>
|
| 121 |
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
Browser Requirements
|
| 126 |
-
</h3>
|
| 127 |
-
<div className="mt-4 space-y-2 text-sm">
|
| 128 |
-
<div>
|
| 129 |
-
• <strong>Chromium 89+</strong> with WebSerial and WebUSB API
|
| 130 |
-
support
|
| 131 |
-
</div>
|
| 132 |
-
<div>
|
| 133 |
-
• <strong>HTTPS or localhost</strong>
|
| 134 |
-
</div>
|
| 135 |
-
<div>
|
| 136 |
-
• <strong>User gesture</strong> required for initial port
|
| 137 |
-
selection
|
| 138 |
-
</div>
|
| 139 |
-
</div>
|
| 140 |
</div>
|
| 141 |
|
| 142 |
{/* API Reference */}
|
|
@@ -145,7 +150,7 @@ teleop.start();`}
|
|
| 145 |
<Code2 className="w-5 h-5" />
|
| 146 |
API Reference
|
| 147 |
</h3>
|
| 148 |
-
<div className="space-y-
|
| 149 |
{/* findPort */}
|
| 150 |
<div>
|
| 151 |
<h4 className="font-bold text-primary">findPort(config?)</h4>
|
|
@@ -163,14 +168,40 @@ const findProcess = await findPort({
|
|
| 163 |
{ robotType: "so100_follower", robotId: "left_arm", serialNumber: "USB123" }
|
| 164 |
],
|
| 165 |
onMessage: (msg) => console.log(msg),
|
| 166 |
-
})
|
| 167 |
-
|
| 168 |
-
// Returns: FindPortProcess
|
| 169 |
-
{
|
| 170 |
-
result: Promise<RobotConnection[]>, // Array of robot connections
|
| 171 |
-
stop(): void // Cancel discovery process
|
| 172 |
-
}`}
|
| 173 |
</CodeBlock>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
</div>
|
| 175 |
|
| 176 |
{/* calibrate */}
|
|
@@ -195,14 +226,25 @@ const findProcess = await findPort({
|
|
| 195 |
|
| 196 |
// Move robot through full range of motion...
|
| 197 |
calibrationProcess.stop(); // Stop range recording
|
| 198 |
-
const calibrationData = await calibrationProcess.result
|
| 199 |
-
|
| 200 |
-
// Returns: CalibrationProcess
|
| 201 |
-
{
|
| 202 |
-
result: Promise<WebCalibrationResults>, // Python-compatible format
|
| 203 |
-
stop(): void // Stop calibration process
|
| 204 |
-
}`}
|
| 205 |
</CodeBlock>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
</div>
|
| 207 |
|
| 208 |
{/* teleoperate */}
|
|
@@ -228,17 +270,37 @@ const directTeleop = await teleoperate({
|
|
| 228 |
robot,
|
| 229 |
calibrationData: savedCalibrationData,
|
| 230 |
teleop: { type: "direct" },
|
| 231 |
-
})
|
| 232 |
-
|
| 233 |
-
// Returns: TeleoperationProcess
|
| 234 |
-
{
|
| 235 |
-
start(): void, // Begin teleoperation
|
| 236 |
-
stop(): void, // Stop teleoperation and clear states
|
| 237 |
-
getState(): TeleoperationState, // Current state and motor positions
|
| 238 |
-
teleoperator: BaseWebTeleoperator, // Access teleoperator-specific methods
|
| 239 |
-
disconnect(): Promise<void> // Stop and disconnect
|
| 240 |
-
}`}
|
| 241 |
</CodeBlock>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
</div>
|
| 243 |
|
| 244 |
{/* releaseMotors */}
|
|
@@ -254,12 +316,22 @@ const directTeleop = await teleoperate({
|
|
| 254 |
await releaseMotors(robot);
|
| 255 |
|
| 256 |
// Release specific motors only
|
| 257 |
-
await releaseMotors(robot, [1, 2, 3])
|
| 258 |
-
|
| 259 |
-
// Parameters
|
| 260 |
-
robot: RobotConnection // Connected robot
|
| 261 |
-
motorIds?: number[] // Specific motor IDs (default: all motors for robot type)`}
|
| 262 |
</CodeBlock>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
</div>
|
| 264 |
</div>
|
| 265 |
</div>
|
|
|
|
| 30 |
typeof children === "string" ? children : children?.toString() || "";
|
| 31 |
|
| 32 |
return (
|
| 33 |
+
<div className="bg-slate-800 dark:bg-black/40 border border-border dark:border-white/10 rounded-md overflow-hidden my-4 relative">
|
| 34 |
<SyntaxHighlighter
|
| 35 |
language={language}
|
| 36 |
style={oneDark}
|
|
|
|
| 40 |
fontSize: "0.875rem",
|
| 41 |
background: "transparent",
|
| 42 |
backgroundColor: "transparent",
|
| 43 |
+
fontFamily:
|
| 44 |
+
"Geist Mono, ui-monospace, SFMono-Regular, 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
|
| 45 |
}}
|
| 46 |
wrapLines={true}
|
| 47 |
wrapLongLines={true}
|
|
|
|
| 70 |
return (
|
| 71 |
<div className="font-mono">
|
| 72 |
<div className="mb-6">
|
| 73 |
+
<h2 className="text-3xl font-bold tracking-wider mb-2 uppercase flex items-center gap-3">
|
| 74 |
<Book className="w-6 h-6" />
|
| 75 |
Docs
|
| 76 |
</h2>
|
| 77 |
<p className="text-sm text-muted-foreground">
|
| 78 |
+
Complete API reference for @lerobot/web
|
| 79 |
</p>
|
| 80 |
</div>
|
| 81 |
|
| 82 |
+
<div className="bg-muted/40 dark:bg-black/30 border border-border dark:border-white/10 p-6 md:p-8 rounded-lg space-y-14">
|
| 83 |
+
{/* Browser Requirements */}
|
| 84 |
+
<div>
|
| 85 |
+
<h3 className="text-xl font-bold text-cyan-600 dark:text-accent-cyan tracking-wider uppercase">
|
| 86 |
+
Browser Requirements
|
| 87 |
+
</h3>
|
| 88 |
+
<div className="mt-4 space-y-2 text-sm">
|
| 89 |
+
<div>
|
| 90 |
+
• <strong>Chromium 89+</strong> with WebSerial and WebUSB API
|
| 91 |
+
support
|
| 92 |
+
</div>
|
| 93 |
+
<div>
|
| 94 |
+
• <strong>HTTPS or localhost</strong>
|
| 95 |
+
</div>
|
| 96 |
+
<div>
|
| 97 |
+
• <strong>User gesture</strong> required for initial port
|
| 98 |
+
selection
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
|
| 103 |
{/* Getting Started */}
|
| 104 |
<div>
|
| 105 |
<h3 className="text-xl font-bold text-cyan-600 dark:text-accent-cyan tracking-wider uppercase flex items-center gap-2">
|
|
|
|
| 137 |
calibrationData,
|
| 138 |
teleop: { type: "keyboard" },
|
| 139 |
});
|
| 140 |
+
teleop.start();
|
|
|
|
|
|
|
| 141 |
|
| 142 |
+
// 5. stop control (run this when you're done)
|
| 143 |
+
teleop.stop();`}
|
| 144 |
+
</CodeBlock>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
</div>
|
| 146 |
|
| 147 |
{/* API Reference */}
|
|
|
|
| 150 |
<Code2 className="w-5 h-5" />
|
| 151 |
API Reference
|
| 152 |
</h3>
|
| 153 |
+
<div className="space-y-12 mt-4">
|
| 154 |
{/* findPort */}
|
| 155 |
<div>
|
| 156 |
<h4 className="font-bold text-primary">findPort(config?)</h4>
|
|
|
|
| 168 |
{ robotType: "so100_follower", robotId: "left_arm", serialNumber: "USB123" }
|
| 169 |
],
|
| 170 |
onMessage: (msg) => console.log(msg),
|
| 171 |
+
});`}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
</CodeBlock>
|
| 173 |
+
<div className="mt-3">
|
| 174 |
+
<h5 className="font-bold text-sm text-muted-foreground tracking-wider">
|
| 175 |
+
Options
|
| 176 |
+
</h5>
|
| 177 |
+
<ul className="mt-1 ml-4 space-y-1 text-sm text-muted-foreground">
|
| 178 |
+
<li>
|
| 179 |
+
• <code>robotConfigs?: RobotConfig[]</code> - Auto-connect
|
| 180 |
+
to these known robots
|
| 181 |
+
</li>
|
| 182 |
+
<li>
|
| 183 |
+
• <code>onMessage?: (message: string) => void</code> -
|
| 184 |
+
Progress messages callback
|
| 185 |
+
</li>
|
| 186 |
+
</ul>
|
| 187 |
+
</div>
|
| 188 |
+
<div className="mt-3">
|
| 189 |
+
<h5 className="font-bold text-sm text-muted-foreground tracking-wider">
|
| 190 |
+
Returns:{" "}
|
| 191 |
+
<code className="bg-muted/50 px-1 rounded">
|
| 192 |
+
FindPortProcess
|
| 193 |
+
</code>
|
| 194 |
+
</h5>
|
| 195 |
+
<ul className="mt-1 ml-4 space-y-1 text-sm text-muted-foreground">
|
| 196 |
+
<li>
|
| 197 |
+
• <code>result: Promise<RobotConnection[]></code> -
|
| 198 |
+
Array of robot connections
|
| 199 |
+
</li>
|
| 200 |
+
<li>
|
| 201 |
+
• <code>stop(): void</code> - Cancel discovery process
|
| 202 |
+
</li>
|
| 203 |
+
</ul>
|
| 204 |
+
</div>
|
| 205 |
</div>
|
| 206 |
|
| 207 |
{/* calibrate */}
|
|
|
|
| 226 |
|
| 227 |
// Move robot through full range of motion...
|
| 228 |
calibrationProcess.stop(); // Stop range recording
|
| 229 |
+
const calibrationData = await calibrationProcess.result;`}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
</CodeBlock>
|
| 231 |
+
<div className="mt-3">
|
| 232 |
+
<h5 className="font-bold text-sm text-muted-foreground tracking-wider">
|
| 233 |
+
Returns:{" "}
|
| 234 |
+
<code className="bg-muted/50 px-1 rounded">
|
| 235 |
+
CalibrationProcess
|
| 236 |
+
</code>
|
| 237 |
+
</h5>
|
| 238 |
+
<ul className="mt-1 ml-4 space-y-1 text-sm text-muted-foreground">
|
| 239 |
+
<li>
|
| 240 |
+
• <code>result: Promise<WebCalibrationResults></code>{" "}
|
| 241 |
+
- Python-compatible format
|
| 242 |
+
</li>
|
| 243 |
+
<li>
|
| 244 |
+
• <code>stop(): void</code> - Stop calibration process
|
| 245 |
+
</li>
|
| 246 |
+
</ul>
|
| 247 |
+
</div>
|
| 248 |
</div>
|
| 249 |
|
| 250 |
{/* teleoperate */}
|
|
|
|
| 270 |
robot,
|
| 271 |
calibrationData: savedCalibrationData,
|
| 272 |
teleop: { type: "direct" },
|
| 273 |
+
});`}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
</CodeBlock>
|
| 275 |
+
<div className="mt-3">
|
| 276 |
+
<h5 className="font-bold text-sm text-muted-foreground tracking-wider">
|
| 277 |
+
Returns:{" "}
|
| 278 |
+
<code className="bg-muted/50 px-1 rounded">
|
| 279 |
+
TeleoperationProcess
|
| 280 |
+
</code>
|
| 281 |
+
</h5>
|
| 282 |
+
<ul className="mt-1 ml-4 space-y-1 text-sm text-muted-foreground">
|
| 283 |
+
<li>
|
| 284 |
+
• <code>start(): void</code> - Begin teleoperation
|
| 285 |
+
</li>
|
| 286 |
+
<li>
|
| 287 |
+
• <code>stop(): void</code> - Stop teleoperation and clear
|
| 288 |
+
states
|
| 289 |
+
</li>
|
| 290 |
+
<li>
|
| 291 |
+
• <code>getState(): TeleoperationState</code> - Current
|
| 292 |
+
state and motor positions
|
| 293 |
+
</li>
|
| 294 |
+
<li>
|
| 295 |
+
• <code>teleoperator: BaseWebTeleoperator</code> - Access
|
| 296 |
+
teleoperator-specific methods
|
| 297 |
+
</li>
|
| 298 |
+
<li>
|
| 299 |
+
• <code>disconnect(): Promise<void></code> - Stop and
|
| 300 |
+
disconnect
|
| 301 |
+
</li>
|
| 302 |
+
</ul>
|
| 303 |
+
</div>
|
| 304 |
</div>
|
| 305 |
|
| 306 |
{/* releaseMotors */}
|
|
|
|
| 316 |
await releaseMotors(robot);
|
| 317 |
|
| 318 |
// Release specific motors only
|
| 319 |
+
await releaseMotors(robot, [1, 2, 3]);`}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
</CodeBlock>
|
| 321 |
+
<div className="mt-3">
|
| 322 |
+
<h5 className="font-bold text-sm text-muted-foreground tracking-wider">
|
| 323 |
+
Parameters
|
| 324 |
+
</h5>
|
| 325 |
+
<ul className="mt-1 ml-4 space-y-1 text-sm text-muted-foreground">
|
| 326 |
+
<li>
|
| 327 |
+
• <code>robot: RobotConnection</code> - Connected robot
|
| 328 |
+
</li>
|
| 329 |
+
<li>
|
| 330 |
+
• <code>motorIds?: number[]</code> - Specific motor IDs
|
| 331 |
+
(default: all motors for robot type)
|
| 332 |
+
</li>
|
| 333 |
+
</ul>
|
| 334 |
+
</div>
|
| 335 |
</div>
|
| 336 |
</div>
|
| 337 |
</div>
|
examples/cyberpunk-standalone/src/components/footer.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
import { Github } from "lucide-react"
|
| 2 |
|
| 3 |
export function Footer() {
|
| 4 |
return (
|
| 5 |
-
<footer className="w-full border-t">
|
| 6 |
<div className="container mx-auto flex h-16 items-center justify-center px-4 md:px-6">
|
| 7 |
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
| 8 |
<span>
|
|
@@ -37,5 +37,5 @@ export function Footer() {
|
|
| 37 |
</div>
|
| 38 |
</div>
|
| 39 |
</footer>
|
| 40 |
-
)
|
| 41 |
}
|
|
|
|
| 1 |
+
import { Github } from "lucide-react";
|
| 2 |
|
| 3 |
export function Footer() {
|
| 4 |
return (
|
| 5 |
+
<footer className="w-full border-t border-gray-300 dark:border-border">
|
| 6 |
<div className="container mx-auto flex h-16 items-center justify-center px-4 md:px-6">
|
| 7 |
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
| 8 |
<span>
|
|
|
|
| 37 |
</div>
|
| 38 |
</div>
|
| 39 |
</footer>
|
| 40 |
+
);
|
| 41 |
}
|
examples/cyberpunk-standalone/src/components/hardware-support-section.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { Cpu,
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import HudCorners from "@/components/hud-corners";
|
| 6 |
|
|
@@ -17,7 +17,7 @@ export function HardwareSupportSection() {
|
|
| 17 |
return (
|
| 18 |
<div className="font-mono">
|
| 19 |
<div className="mb-6">
|
| 20 |
-
<h2 className="text-
|
| 21 |
<Cpu className="w-6 h-6" />
|
| 22 |
Hardware Support
|
| 23 |
</h2>
|
|
@@ -88,7 +88,8 @@ export function HardwareSupportSection() {
|
|
| 88 |
<div>
|
| 89 |
<p className="text-muted-foreground mb-4">
|
| 90 |
please provide us with access to different robot hardware, so
|
| 91 |
-
we can add them to lerobot.js
|
|
|
|
| 92 |
</p>
|
| 93 |
|
| 94 |
<div className="bg-muted/60 dark:bg-black/40 border border-border dark:border-white/10 rounded-lg p-4 mb-4">
|
|
@@ -104,30 +105,16 @@ export function HardwareSupportSection() {
|
|
| 104 |
</div>
|
| 105 |
|
| 106 |
<div className="flex flex-col sm:flex-row gap-4">
|
| 107 |
-
<Button
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
Contact Tim
|
| 118 |
-
</Button>
|
| 119 |
-
<Button
|
| 120 |
-
variant="outline"
|
| 121 |
-
className="font-mono uppercase flex items-center gap-2 bg-transparent"
|
| 122 |
-
onClick={() =>
|
| 123 |
-
window.open(
|
| 124 |
-
"https://github.com/timpietrusky/lerobot.js",
|
| 125 |
-
"_blank"
|
| 126 |
-
)
|
| 127 |
-
}
|
| 128 |
-
>
|
| 129 |
-
<ExternalLink className="w-4 h-4" />
|
| 130 |
-
GitHub
|
| 131 |
</Button>
|
| 132 |
</div>
|
| 133 |
</div>
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { Cpu, MessageCircle, Plus } from "lucide-react";
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import HudCorners from "@/components/hud-corners";
|
| 6 |
|
|
|
|
| 17 |
return (
|
| 18 |
<div className="font-mono">
|
| 19 |
<div className="mb-6">
|
| 20 |
+
<h2 className="text-3xl font-bold tracking-wider mb-2 uppercase flex items-center gap-3">
|
| 21 |
<Cpu className="w-6 h-6" />
|
| 22 |
Hardware Support
|
| 23 |
</h2>
|
|
|
|
| 88 |
<div>
|
| 89 |
<p className="text-muted-foreground mb-4">
|
| 90 |
please provide us with access to different robot hardware, so
|
| 91 |
+
we can add them to lerobot.js. join the LeRobot Discord and DM
|
| 92 |
+
me!
|
| 93 |
</p>
|
| 94 |
|
| 95 |
<div className="bg-muted/60 dark:bg-black/40 border border-border dark:border-white/10 rounded-lg p-4 mb-4">
|
|
|
|
| 105 |
</div>
|
| 106 |
|
| 107 |
<div className="flex flex-col sm:flex-row gap-4">
|
| 108 |
+
<Button asChild className="font-mono">
|
| 109 |
+
<a
|
| 110 |
+
href="https://discord.gg/s3KuuzsPFb"
|
| 111 |
+
target="_blank"
|
| 112 |
+
rel="noopener noreferrer"
|
| 113 |
+
className="flex items-center gap-2"
|
| 114 |
+
>
|
| 115 |
+
<MessageCircle className="w-4 h-4" />
|
| 116 |
+
LeRobot Discord → dm @NERDDISCO
|
| 117 |
+
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
</Button>
|
| 119 |
</div>
|
| 120 |
</div>
|
examples/cyberpunk-standalone/src/components/header.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
import { ThemeToggle } from "./theme-toggle"
|
| 2 |
|
| 3 |
export function Header() {
|
| 4 |
return (
|
| 5 |
-
<header className="w-full border-b">
|
| 6 |
<div className="container mx-auto flex h-16 items-center justify-between px-4 md:px-6">
|
| 7 |
<div className="flex items-center gap-6">
|
| 8 |
<h1 className="text-2xl font-bold">LeRobot.js</h1>
|
|
@@ -23,5 +23,5 @@ export function Header() {
|
|
| 23 |
<ThemeToggle />
|
| 24 |
</div>
|
| 25 |
</header>
|
| 26 |
-
)
|
| 27 |
}
|
|
|
|
| 1 |
+
import { ThemeToggle } from "./theme-toggle";
|
| 2 |
|
| 3 |
export function Header() {
|
| 4 |
return (
|
| 5 |
+
<header className="w-full border-b border-gray-300 dark:border-border">
|
| 6 |
<div className="container mx-auto flex h-16 items-center justify-between px-4 md:px-6">
|
| 7 |
<div className="flex items-center gap-6">
|
| 8 |
<h1 className="text-2xl font-bold">LeRobot.js</h1>
|
|
|
|
| 23 |
<ThemeToggle />
|
| 24 |
</div>
|
| 25 |
</header>
|
| 26 |
+
);
|
| 27 |
}
|
examples/cyberpunk-standalone/src/components/roadmap-section.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
-
"use client"
|
| 2 |
-
import { CheckCircle, Clock, Target } from "lucide-react"
|
| 3 |
-
import { Badge } from "@/components/ui/badge"
|
| 4 |
-
import { cn } from "@/lib/utils"
|
| 5 |
|
| 6 |
interface RoadmapItem {
|
| 7 |
-
title: string
|
| 8 |
-
description: string
|
| 9 |
-
status: "completed" | "planned"
|
| 10 |
}
|
| 11 |
|
| 12 |
const roadmapItems: RoadmapItem[] = [
|
|
@@ -17,7 +17,8 @@ const roadmapItems: RoadmapItem[] = [
|
|
| 17 |
},
|
| 18 |
{
|
| 19 |
title: "calibrate",
|
| 20 |
-
description:
|
|
|
|
| 21 |
status: "completed",
|
| 22 |
},
|
| 23 |
{
|
|
@@ -32,7 +33,8 @@ const roadmapItems: RoadmapItem[] = [
|
|
| 32 |
},
|
| 33 |
{
|
| 34 |
title: "replay",
|
| 35 |
-
description:
|
|
|
|
| 36 |
status: "planned",
|
| 37 |
},
|
| 38 |
{
|
|
@@ -42,10 +44,11 @@ const roadmapItems: RoadmapItem[] = [
|
|
| 42 |
},
|
| 43 |
{
|
| 44 |
title: "eval",
|
| 45 |
-
description:
|
|
|
|
| 46 |
status: "planned",
|
| 47 |
},
|
| 48 |
-
]
|
| 49 |
|
| 50 |
const statusConfig = {
|
| 51 |
completed: {
|
|
@@ -64,20 +67,33 @@ const statusConfig = {
|
|
| 64 |
bgColor: "bg-slate-500/10 dark:bg-muted-foreground/5",
|
| 65 |
borderColor: "border-slate-500/30 dark:border-muted-foreground/20",
|
| 66 |
},
|
| 67 |
-
}
|
| 68 |
|
| 69 |
export function RoadmapSection() {
|
| 70 |
-
const completedCount = roadmapItems.filter(
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
|
| 73 |
return (
|
| 74 |
<div className="font-mono">
|
| 75 |
<div className="mb-6">
|
| 76 |
-
<h2 className="text-
|
| 77 |
<Target className="w-6 h-6" />
|
| 78 |
Roadmap
|
| 79 |
</h2>
|
| 80 |
-
<p className="text-sm text-muted-foreground">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
</div>
|
| 82 |
|
| 83 |
<div className="bg-gradient-to-br from-muted/60 to-muted/40 dark:from-black/40 dark:to-black/20 border border-primary/20 rounded-lg overflow-hidden">
|
|
@@ -85,13 +101,14 @@ export function RoadmapSection() {
|
|
| 85 |
<div className="bg-primary/30 dark:bg-primary/10 border-b border-primary/20 p-4">
|
| 86 |
<div className="flex items-center justify-between">
|
| 87 |
<div className="flex items-center gap-4">
|
| 88 |
-
<div className="text-primary font-bold text-lg tracking-wider">SYSTEM OBJECTIVES</div>
|
| 89 |
<div className="flex items-center gap-2">
|
| 90 |
<div className="w-2 h-2 bg-green-500 dark:bg-green-400 rounded-full animate-pulse"></div>
|
| 91 |
-
<span className="text-green-600 dark:text-green-400 text-
|
|
|
|
|
|
|
| 92 |
<div className="w-2 h-2 bg-slate-500 dark:bg-muted-foreground rounded-full"></div>
|
| 93 |
-
<span className="text-slate-600 dark:text-muted-foreground text-
|
| 94 |
-
{totalCount - completedCount}
|
| 95 |
</span>
|
| 96 |
</div>
|
| 97 |
</div>
|
|
@@ -115,8 +132,8 @@ export function RoadmapSection() {
|
|
| 115 |
<div className="p-6">
|
| 116 |
<div className="space-y-3">
|
| 117 |
{roadmapItems.map((item, index) => {
|
| 118 |
-
const config = statusConfig[item.status]
|
| 119 |
-
const StatusIcon = config.icon
|
| 120 |
|
| 121 |
return (
|
| 122 |
<div
|
|
@@ -124,7 +141,7 @@ export function RoadmapSection() {
|
|
| 124 |
className={cn(
|
| 125 |
"flex items-center gap-4 p-4 rounded border transition-all hover:bg-muted/30 dark:hover:bg-white/5",
|
| 126 |
config.bgColor,
|
| 127 |
-
config.borderColor
|
| 128 |
)}
|
| 129 |
>
|
| 130 |
{/* Number */}
|
|
@@ -134,8 +151,12 @@ export function RoadmapSection() {
|
|
| 134 |
|
| 135 |
{/* Content */}
|
| 136 |
<div className="flex-1 min-w-0">
|
| 137 |
-
<h4 className={cn("font-bold text-lg", config.textColor)}>
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
</div>
|
| 140 |
|
| 141 |
{/* Status Badge */}
|
|
@@ -144,36 +165,41 @@ export function RoadmapSection() {
|
|
| 144 |
className={cn(
|
| 145 |
"text-xs font-bold tracking-wider border-0 px-3 py-1 flex-shrink-0",
|
| 146 |
config.textColor,
|
| 147 |
-
config.bgColor
|
| 148 |
)}
|
| 149 |
>
|
| 150 |
{config.label}
|
| 151 |
</Badge>
|
| 152 |
|
| 153 |
{/* Status Icon */}
|
| 154 |
-
<StatusIcon
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
</div>
|
| 156 |
-
)
|
| 157 |
})}
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
|
| 161 |
{/* Footer */}
|
| 162 |
-
<div className="bg-muted/50 dark:bg-black/30 border-t border-
|
| 163 |
<p className="text-muted-foreground text-sm">
|
| 164 |
-
|
| 165 |
<a
|
| 166 |
-
href="https://
|
| 167 |
target="_blank"
|
| 168 |
rel="noopener noreferrer"
|
| 169 |
className="text-primary hover:text-accent transition-colors underline"
|
| 170 |
>
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
</p>
|
| 175 |
</div>
|
| 176 |
</div>
|
| 177 |
</div>
|
| 178 |
-
)
|
| 179 |
}
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { CheckCircle, Clock, Target, Github } from "lucide-react";
|
| 3 |
+
import { Badge } from "@/components/ui/badge";
|
| 4 |
+
import { cn } from "@/lib/utils";
|
| 5 |
|
| 6 |
interface RoadmapItem {
|
| 7 |
+
title: string;
|
| 8 |
+
description: string;
|
| 9 |
+
status: "completed" | "planned";
|
| 10 |
}
|
| 11 |
|
| 12 |
const roadmapItems: RoadmapItem[] = [
|
|
|
|
| 17 |
},
|
| 18 |
{
|
| 19 |
title: "calibrate",
|
| 20 |
+
description:
|
| 21 |
+
"Real-time joint calibration with visual feedback and data export",
|
| 22 |
status: "completed",
|
| 23 |
},
|
| 24 |
{
|
|
|
|
| 33 |
},
|
| 34 |
{
|
| 35 |
title: "replay",
|
| 36 |
+
description:
|
| 37 |
+
"Replay any recorded episode or episodes from existing datasets",
|
| 38 |
status: "planned",
|
| 39 |
},
|
| 40 |
{
|
|
|
|
| 44 |
},
|
| 45 |
{
|
| 46 |
title: "eval",
|
| 47 |
+
description:
|
| 48 |
+
"Run inference using trained policies for autonomous operation",
|
| 49 |
status: "planned",
|
| 50 |
},
|
| 51 |
+
];
|
| 52 |
|
| 53 |
const statusConfig = {
|
| 54 |
completed: {
|
|
|
|
| 67 |
bgColor: "bg-slate-500/10 dark:bg-muted-foreground/5",
|
| 68 |
borderColor: "border-slate-500/30 dark:border-muted-foreground/20",
|
| 69 |
},
|
| 70 |
+
};
|
| 71 |
|
| 72 |
export function RoadmapSection() {
|
| 73 |
+
const completedCount = roadmapItems.filter(
|
| 74 |
+
(item) => item.status === "completed"
|
| 75 |
+
).length;
|
| 76 |
+
const totalCount = roadmapItems.length;
|
| 77 |
|
| 78 |
return (
|
| 79 |
<div className="font-mono">
|
| 80 |
<div className="mb-6">
|
| 81 |
+
<h2 className="text-3xl font-bold tracking-wider mb-2 uppercase flex items-center gap-3">
|
| 82 |
<Target className="w-6 h-6" />
|
| 83 |
Roadmap
|
| 84 |
</h2>
|
| 85 |
+
<p className="text-sm text-muted-foreground">
|
| 86 |
+
our goal is to provide{" "}
|
| 87 |
+
<a
|
| 88 |
+
href="https://huggingface.co/docs/lerobot"
|
| 89 |
+
target="_blank"
|
| 90 |
+
rel="noopener noreferrer"
|
| 91 |
+
className="text-primary hover:text-accent transition-colors underline"
|
| 92 |
+
>
|
| 93 |
+
LeRobot
|
| 94 |
+
</a>
|
| 95 |
+
's simple, easy-to-use Python functions for the JavaScript community
|
| 96 |
+
</p>
|
| 97 |
</div>
|
| 98 |
|
| 99 |
<div className="bg-gradient-to-br from-muted/60 to-muted/40 dark:from-black/40 dark:to-black/20 border border-primary/20 rounded-lg overflow-hidden">
|
|
|
|
| 101 |
<div className="bg-primary/30 dark:bg-primary/10 border-b border-primary/20 p-4">
|
| 102 |
<div className="flex items-center justify-between">
|
| 103 |
<div className="flex items-center gap-4">
|
|
|
|
| 104 |
<div className="flex items-center gap-2">
|
| 105 |
<div className="w-2 h-2 bg-green-500 dark:bg-green-400 rounded-full animate-pulse"></div>
|
| 106 |
+
<span className="text-green-600 dark:text-green-400 text-xs">
|
| 107 |
+
{completedCount} COMPLETED
|
| 108 |
+
</span>
|
| 109 |
<div className="w-2 h-2 bg-slate-500 dark:bg-muted-foreground rounded-full"></div>
|
| 110 |
+
<span className="text-slate-600 dark:text-muted-foreground text-xs">
|
| 111 |
+
{totalCount - completedCount} PLANNED
|
| 112 |
</span>
|
| 113 |
</div>
|
| 114 |
</div>
|
|
|
|
| 132 |
<div className="p-6">
|
| 133 |
<div className="space-y-3">
|
| 134 |
{roadmapItems.map((item, index) => {
|
| 135 |
+
const config = statusConfig[item.status];
|
| 136 |
+
const StatusIcon = config.icon;
|
| 137 |
|
| 138 |
return (
|
| 139 |
<div
|
|
|
|
| 141 |
className={cn(
|
| 142 |
"flex items-center gap-4 p-4 rounded border transition-all hover:bg-muted/30 dark:hover:bg-white/5",
|
| 143 |
config.bgColor,
|
| 144 |
+
config.borderColor
|
| 145 |
)}
|
| 146 |
>
|
| 147 |
{/* Number */}
|
|
|
|
| 151 |
|
| 152 |
{/* Content */}
|
| 153 |
<div className="flex-1 min-w-0">
|
| 154 |
+
<h4 className={cn("font-bold text-lg", config.textColor)}>
|
| 155 |
+
{item.title}()
|
| 156 |
+
</h4>
|
| 157 |
+
<p className="text-muted-foreground text-sm mt-1">
|
| 158 |
+
{item.description}
|
| 159 |
+
</p>
|
| 160 |
</div>
|
| 161 |
|
| 162 |
{/* Status Badge */}
|
|
|
|
| 165 |
className={cn(
|
| 166 |
"text-xs font-bold tracking-wider border-0 px-3 py-1 flex-shrink-0",
|
| 167 |
config.textColor,
|
| 168 |
+
config.bgColor
|
| 169 |
)}
|
| 170 |
>
|
| 171 |
{config.label}
|
| 172 |
</Badge>
|
| 173 |
|
| 174 |
{/* Status Icon */}
|
| 175 |
+
<StatusIcon
|
| 176 |
+
className={cn(
|
| 177 |
+
"w-4 h-4 flex-shrink-0 ml-3",
|
| 178 |
+
config.textColor
|
| 179 |
+
)}
|
| 180 |
+
/>
|
| 181 |
</div>
|
| 182 |
+
);
|
| 183 |
})}
|
| 184 |
</div>
|
| 185 |
</div>
|
| 186 |
|
| 187 |
{/* Footer */}
|
| 188 |
+
<div className="bg-muted/50 dark:bg-black/30 border-t border-gray-300 dark:border-white/10 p-4">
|
| 189 |
<p className="text-muted-foreground text-sm">
|
| 190 |
+
want to help? LeRobot.js is open source on{" "}
|
| 191 |
<a
|
| 192 |
+
href="https://github.com/lerobot/lerobot.js"
|
| 193 |
target="_blank"
|
| 194 |
rel="noopener noreferrer"
|
| 195 |
className="text-primary hover:text-accent transition-colors underline"
|
| 196 |
>
|
| 197 |
+
<Github className="h-4 w-4 inline align-text-bottom mr-1" />
|
| 198 |
+
GitHub
|
| 199 |
+
</a>
|
| 200 |
</p>
|
| 201 |
</div>
|
| 202 |
</div>
|
| 203 |
</div>
|
| 204 |
+
);
|
| 205 |
}
|
examples/cyberpunk-standalone/src/components/setup-cards.tsx
CHANGED
|
@@ -1,40 +1,48 @@
|
|
| 1 |
-
"use client"
|
| 2 |
-
import { useState } from "react"
|
| 3 |
-
import { Copy, Package, Clock, Check, Terminal } from "lucide-react"
|
| 4 |
-
import { Button } from "@/components/ui/button"
|
| 5 |
-
import { Card } from "@/components/ui/card"
|
| 6 |
-
import { Badge } from "@/components/ui/badge"
|
| 7 |
-
import HudCorners from "@/components/hud-corners"
|
| 8 |
-
import { cn } from "@/lib/utils"
|
| 9 |
|
| 10 |
-
type PackageManager = "npm" | "yarn" | "pnpm" | "bun"
|
| 11 |
|
| 12 |
interface PackageInstallerProps {
|
| 13 |
-
packageName: string
|
| 14 |
-
disabled?: boolean
|
| 15 |
}
|
| 16 |
|
| 17 |
-
function PackageInstaller({
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
const packageManagers: {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
{ value: "pnpm", label: "pnpm", command: `pnpm add ${packageName}` },
|
| 23 |
{ value: "npm", label: "npm", command: `npm i ${packageName}` },
|
| 24 |
{ value: "yarn", label: "yarn", command: `yarn add ${packageName}` },
|
| 25 |
-
]
|
| 26 |
|
| 27 |
const copyToClipboard = async (text: string) => {
|
| 28 |
try {
|
| 29 |
-
await navigator.clipboard.writeText(text)
|
| 30 |
-
setCopied(true)
|
| 31 |
-
setTimeout(() => setCopied(false), 2000)
|
| 32 |
} catch (err) {
|
| 33 |
-
console.error("Failed to copy:", err)
|
| 34 |
}
|
| 35 |
-
}
|
| 36 |
|
| 37 |
-
const currentCommand =
|
|
|
|
| 38 |
|
| 39 |
return (
|
| 40 |
<div className="max-w-md">
|
|
@@ -51,7 +59,7 @@ function PackageInstaller({ packageName, disabled = false }: PackageInstallerPro
|
|
| 51 |
"font-mono text-xs px-3 min-w-[60px] h-8 border transition-colors",
|
| 52 |
selectedPM === pm.value
|
| 53 |
? "bg-primary text-primary-foreground border-primary"
|
| 54 |
-
: "bg-transparent border-input hover:bg-accent hover:text-accent-foreground"
|
| 55 |
)}
|
| 56 |
>
|
| 57 |
{pm.label}
|
|
@@ -65,7 +73,7 @@ function PackageInstaller({ packageName, disabled = false }: PackageInstallerPro
|
|
| 65 |
"border rounded-md p-3 font-mono text-sm transition-colors",
|
| 66 |
disabled
|
| 67 |
? "bg-muted/60 dark:bg-black/20 border-dashed border-muted/40 dark:border-muted/20 text-muted-foreground/70 dark:text-muted-foreground/50"
|
| 68 |
-
: "bg-muted/60 dark:bg-black/40 border-border dark:border-white/10 text-foreground dark:text-primary"
|
| 69 |
)}
|
| 70 |
>
|
| 71 |
{currentCommand}
|
|
@@ -88,7 +96,7 @@ function PackageInstaller({ packageName, disabled = false }: PackageInstallerPro
|
|
| 88 |
</div>
|
| 89 |
</div>
|
| 90 |
</div>
|
| 91 |
-
)
|
| 92 |
}
|
| 93 |
|
| 94 |
export function SetupCards() {
|
|
@@ -105,8 +113,12 @@ export function SetupCards() {
|
|
| 105 |
<Package className="w-6 h-6 text-primary" />
|
| 106 |
</div>
|
| 107 |
<div>
|
| 108 |
-
<h3 className="text-xl font-bold text-primary font-mono tracking-wider uppercase">
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
</div>
|
| 111 |
</div>
|
| 112 |
|
|
@@ -128,8 +140,12 @@ export function SetupCards() {
|
|
| 128 |
<Terminal className="w-6 h-6 text-muted-foreground" />
|
| 129 |
</div>
|
| 130 |
<div>
|
| 131 |
-
<h3 className="text-xl font-bold text-muted-foreground font-mono tracking-wider uppercase">
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
</div>
|
| 134 |
</div>
|
| 135 |
<Badge
|
|
@@ -156,5 +172,5 @@ export function SetupCards() {
|
|
| 156 |
</Card>
|
| 157 |
</HudCorners>
|
| 158 |
</div>
|
| 159 |
-
)
|
| 160 |
}
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { useState } from "react";
|
| 3 |
+
import { Copy, Package, Clock, Check, Terminal } from "lucide-react";
|
| 4 |
+
import { Button } from "@/components/ui/button";
|
| 5 |
+
import { Card } from "@/components/ui/card";
|
| 6 |
+
import { Badge } from "@/components/ui/badge";
|
| 7 |
+
import HudCorners from "@/components/hud-corners";
|
| 8 |
+
import { cn } from "@/lib/utils";
|
| 9 |
|
| 10 |
+
type PackageManager = "npm" | "yarn" | "pnpm" | "bun";
|
| 11 |
|
| 12 |
interface PackageInstallerProps {
|
| 13 |
+
packageName: string;
|
| 14 |
+
disabled?: boolean;
|
| 15 |
}
|
| 16 |
|
| 17 |
+
function PackageInstaller({
|
| 18 |
+
packageName,
|
| 19 |
+
disabled = false,
|
| 20 |
+
}: PackageInstallerProps) {
|
| 21 |
+
const [selectedPM, setSelectedPM] = useState<PackageManager>("pnpm");
|
| 22 |
+
const [copied, setCopied] = useState(false);
|
| 23 |
|
| 24 |
+
const packageManagers: {
|
| 25 |
+
value: PackageManager;
|
| 26 |
+
label: string;
|
| 27 |
+
command: string;
|
| 28 |
+
}[] = [
|
| 29 |
{ value: "pnpm", label: "pnpm", command: `pnpm add ${packageName}` },
|
| 30 |
{ value: "npm", label: "npm", command: `npm i ${packageName}` },
|
| 31 |
{ value: "yarn", label: "yarn", command: `yarn add ${packageName}` },
|
| 32 |
+
];
|
| 33 |
|
| 34 |
const copyToClipboard = async (text: string) => {
|
| 35 |
try {
|
| 36 |
+
await navigator.clipboard.writeText(text);
|
| 37 |
+
setCopied(true);
|
| 38 |
+
setTimeout(() => setCopied(false), 2000);
|
| 39 |
} catch (err) {
|
| 40 |
+
console.error("Failed to copy:", err);
|
| 41 |
}
|
| 42 |
+
};
|
| 43 |
|
| 44 |
+
const currentCommand =
|
| 45 |
+
packageManagers.find((pm) => pm.value === selectedPM)?.command || "";
|
| 46 |
|
| 47 |
return (
|
| 48 |
<div className="max-w-md">
|
|
|
|
| 59 |
"font-mono text-xs px-3 min-w-[60px] h-8 border transition-colors",
|
| 60 |
selectedPM === pm.value
|
| 61 |
? "bg-primary text-primary-foreground border-primary"
|
| 62 |
+
: "bg-transparent border-input hover:bg-accent hover:text-accent-foreground"
|
| 63 |
)}
|
| 64 |
>
|
| 65 |
{pm.label}
|
|
|
|
| 73 |
"border rounded-md p-3 font-mono text-sm transition-colors",
|
| 74 |
disabled
|
| 75 |
? "bg-muted/60 dark:bg-black/20 border-dashed border-muted/40 dark:border-muted/20 text-muted-foreground/70 dark:text-muted-foreground/50"
|
| 76 |
+
: "bg-muted/60 dark:bg-black/40 border-border dark:border-white/10 text-foreground dark:text-primary"
|
| 77 |
)}
|
| 78 |
>
|
| 79 |
{currentCommand}
|
|
|
|
| 96 |
</div>
|
| 97 |
</div>
|
| 98 |
</div>
|
| 99 |
+
);
|
| 100 |
}
|
| 101 |
|
| 102 |
export function SetupCards() {
|
|
|
|
| 113 |
<Package className="w-6 h-6 text-primary" />
|
| 114 |
</div>
|
| 115 |
<div>
|
| 116 |
+
<h3 className="text-xl font-bold text-primary font-mono tracking-wider uppercase">
|
| 117 |
+
web
|
| 118 |
+
</h3>
|
| 119 |
+
<p className="text-sm text-muted-foreground font-mono">
|
| 120 |
+
run LeRobot.js in the browser
|
| 121 |
+
</p>
|
| 122 |
</div>
|
| 123 |
</div>
|
| 124 |
|
|
|
|
| 140 |
<Terminal className="w-6 h-6 text-muted-foreground" />
|
| 141 |
</div>
|
| 142 |
<div>
|
| 143 |
+
<h3 className="text-xl font-bold text-muted-foreground font-mono tracking-wider uppercase">
|
| 144 |
+
node
|
| 145 |
+
</h3>
|
| 146 |
+
<p className="text-sm text-muted-foreground/70 font-mono">
|
| 147 |
+
run LeRobot.js on the server
|
| 148 |
+
</p>
|
| 149 |
</div>
|
| 150 |
</div>
|
| 151 |
<Badge
|
|
|
|
| 172 |
</Card>
|
| 173 |
</HudCorners>
|
| 174 |
</div>
|
| 175 |
+
);
|
| 176 |
}
|