NERDDISCO commited on
Commit
f680c3f
·
1 Parent(s): 7cdda17

feat(example): improved everything to make it release rdy

Browse files
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-12">
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-2xl font-bold text-primary font-mono tracking-wider mb-2 uppercase">
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
- setIsCalibrating(true);
90
- setStatus("🤖 Starting calibration process...");
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
- <Card className="border-0 rounded-none">
228
- <div className="p-4 border-b border-white/10 flex items-center justify-between">
229
- <div className="flex items-center gap-4">
230
- <div className="w-1 h-8 bg-primary"></div>
231
- <div>
232
- <h3 className="text-xl font-bold text-foreground font-mono tracking-wider uppercase">
233
- motor calibration
234
- </h3>
235
- <p className="text-sm text-muted-foreground font-mono">{status}</p>
 
 
 
 
236
  </div>
237
- </div>
238
- <div className="flex gap-4">
239
- {!isCalibrating ? (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  <Button
241
- onClick={handleStart}
 
242
  size="lg"
243
- disabled={isPreparing || !robot.isConnected}
244
  >
245
- {isPreparing ? "Preparing..." : "Start Calibration"}
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="border-t border-white/10">
272
- {motorData.map(([name, data]) => (
273
- <MotorCalibrationVisual
274
- key={name as string}
275
- name={name as string}
276
- data={
277
- data as {
278
- current: number;
279
- min: number;
280
- max: number;
281
- range: number;
 
 
 
 
 
 
 
 
 
 
282
  }
283
- }
284
- />
285
- ))}
286
  </div>
287
- </div>
288
- </Card>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- <Card className="border-0 rounded-none">
41
- <div className="p-4 border-b border-white/10 flex items-center justify-between">
42
- <div className="flex items-center gap-4">
43
- <div className="w-1 h-8 bg-primary"></div>
44
- <div>
45
- <h3 className="text-xl font-bold text-foreground font-mono tracking-wider uppercase">
46
- device registry
47
- </h3>
48
- <div className="flex items-center gap-2 mt-1">
49
- <span className="text-xs text-muted-foreground font-mono">
50
- currently supports SO-100{" "}
51
- </span>
52
- <button
53
- onClick={onScrollToHardware}
54
- className="text-xs text-primary hover:text-accent transition-colors underline font-mono flex items-center gap-1"
55
- >
56
- <ExternalLink className="w-3 h-3" />
57
- add more devices
58
- </button>
 
 
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
- <div className="pt-6 p-6">
76
- {robots.length === 0 ? (
77
- <div className="relative">
78
- <HudCorners className="p-16">
79
- <div className="text-center font-mono">
80
- <div className="mb-6">
81
- {isConnecting ? (
82
- <>
83
- <div className="w-16 h-16 mx-auto mb-4 border-2 border-primary/50 rounded-lg flex items-center justify-center animate-pulse">
84
- <Plus className="w-8 h-8 text-primary animate-spin" />
85
- </div>
86
- <h4 className="text-xl text-primary mb-2 tracking-wider uppercase">
87
- scanning for units
88
- </h4>
89
- <p className="text-sm text-muted-foreground mb-8">
90
- searching for available devices...
91
- </p>
92
- </>
93
- ) : (
94
- <>
95
- <div className="w-16 h-16 mx-auto mb-4 border-2 border-dashed border-primary/50 rounded-lg flex items-center justify-center">
96
- <Plus className="w-8 h-8 text-primary/50" />
97
- </div>
98
- <h4 className="text-xl text-primary mb-2 tracking-wider uppercase">
99
- no units detected
100
- </h4>
101
 
102
- <Button
103
- onClick={onFindNew}
104
- size="lg"
105
- className="font-mono uppercase"
106
- >
107
- <Plus className="w-4 h-4 mr-2" />
108
- add unit
109
- </Button>
110
- </>
111
- )}
 
112
  </div>
113
- </div>
114
- </HudCorners>
115
- </div>
116
- ) : (
117
- <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
118
- {robots.map((robot) => (
119
- <HudCorners key={robot.robotId}>
120
- <Card className="flex flex-col h-full">
121
- <div className="p-4 border-b border-white/10">
122
- <div className="flex items-start justify-between mb-3">
123
- <div className="flex-1">
124
- <h4 className="text-xl font-bold text-primary font-mono tracking-wider">
125
- {robot.name}
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
- <div className="flex-grow p-4">
152
- <div className="grid grid-cols-2 gap-4 text-xs font-mono">
153
- <div>
154
- <span className="text-muted-foreground uppercase">
155
- status:
156
- </span>
157
- <div className="text-primary uppercase">
158
- {robot.isConnected ? "operational" : "disconnected"}
 
159
  </div>
160
- </div>
161
- <div>
162
- <span className="text-muted-foreground uppercase">
163
- type:
164
- </span>
165
- <div className="text-accent uppercase">
166
- {robot.robotType}
167
  </div>
168
  </div>
169
  </div>
170
- </div>
171
 
172
- <CardFooter className="p-4 border-t border-white/10 flex flex-wrap gap-2">
173
- <Button
174
- variant="outline"
175
- size="sm"
176
- onClick={() => onEdit(robot)}
177
- className="font-mono text-xs uppercase"
178
- >
179
- <Pencil className="w-3 h-3 mr-1" /> edit
180
- </Button>
181
- <Button
182
- variant="outline"
183
- size="sm"
184
- onClick={() => onCalibrate(robot)}
185
- className="font-mono text-xs uppercase"
186
- >
187
- <Settings className="w-3 h-3 mr-1" /> calibrate
188
- </Button>
189
- <Button
190
- variant="outline"
191
- size="sm"
192
- onClick={() => onTeleoperate(robot)}
193
- className="font-mono text-xs uppercase"
194
- >
195
- <Gamepad2 className="w-3 h-3 mr-1" /> control
196
- </Button>
197
- <Button
198
- variant="destructive"
199
- size="sm"
200
- onClick={() =>
201
- onRemove(
202
- robot.robotId || robot.serialNumber || "unknown"
203
- )
204
- }
205
- className="font-mono text-xs uppercase"
206
- >
207
- <Trash2 className="w-3 h-3 mr-1" /> remove
208
- </Button>
209
- </CardFooter>
210
- </Card>
211
- </HudCorners>
212
- ))}
213
- </div>
214
- )}
215
- </div>
216
- </Card>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-muted/50 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,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-2xl font-bold text-primary tracking-wider mb-2 uppercase flex items-center gap-3">
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 robotics library.
77
  </p>
78
  </div>
79
 
80
- <div className="bg-muted/40 dark:bg-black/30 border-l-4 border-cyan-500 dark:border-accent-cyan p-6 md:p-8 rounded-r-lg space-y-8">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- {/* Browser Requirements */}
123
- <div>
124
- <h3 className="text-xl font-bold text-cyan-600 dark:text-accent-cyan tracking-wider uppercase">
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-6 mt-4">
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) =&gt; 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&lt;RobotConnection[]&gt;</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&lt;WebCalibrationResults&gt;</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&lt;void&gt;</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, Mail, Plus, ExternalLink } from "lucide-react";
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-2xl font-bold text-primary tracking-wider mb-2 uppercase flex items-center gap-3">
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
- className="font-mono uppercase flex items-center gap-2"
109
- onClick={() =>
110
- window.open(
111
- "mailto:[email protected]?subject=LeRobot.js Hardware Support",
112
- "_blank"
113
- )
114
- }
115
- >
116
- <Mail className="w-4 h-4" />
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: "Real-time joint calibration with visual feedback and data export",
 
21
  status: "completed",
22
  },
23
  {
@@ -32,7 +33,8 @@ const roadmapItems: RoadmapItem[] = [
32
  },
33
  {
34
  title: "replay",
35
- description: "Replay any recorded episode or episodes from existing datasets",
 
36
  status: "planned",
37
  },
38
  {
@@ -42,10 +44,11 @@ const roadmapItems: RoadmapItem[] = [
42
  },
43
  {
44
  title: "eval",
45
- description: "Run inference using trained policies for autonomous operation",
 
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((item) => item.status === "completed").length
71
- const totalCount = roadmapItems.length
 
 
72
 
73
  return (
74
  <div className="font-mono">
75
  <div className="mb-6">
76
- <h2 className="text-2xl font-bold text-primary tracking-wider mb-2 uppercase flex items-center gap-3">
77
  <Target className="w-6 h-6" />
78
  Roadmap
79
  </h2>
80
- <p className="text-sm text-muted-foreground">lfg o7</p>
 
 
 
 
 
 
 
 
 
 
 
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-sm">{completedCount} ACTIVE</span>
 
 
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-sm">
94
- {totalCount - completedCount} QUEUED
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)}>{item.title}()</h4>
138
- <p className="text-muted-foreground text-sm mt-1">{item.description}</p>
 
 
 
 
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 className={cn("w-4 h-4 flex-shrink-0 ml-3", config.textColor)} />
 
 
 
 
 
155
  </div>
156
- )
157
  })}
158
  </div>
159
  </div>
160
 
161
  {/* Footer */}
162
- <div className="bg-muted/50 dark:bg-black/30 border-t border-border dark:border-white/10 p-4">
163
  <p className="text-muted-foreground text-sm">
164
- <span className="text-cyan-600 dark:text-accent-cyan">REFERENCE:</span> functions based on the original{" "}
165
  <a
166
- href="https://huggingface.co/docs/lerobot"
167
  target="_blank"
168
  rel="noopener noreferrer"
169
  className="text-primary hover:text-accent transition-colors underline"
170
  >
171
- LeRobot
172
- </a>{" "}
173
- (python) and adapted for web robotic ai
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({ packageName, disabled = false }: PackageInstallerProps) {
18
- const [selectedPM, setSelectedPM] = useState<PackageManager>("pnpm")
19
- const [copied, setCopied] = useState(false)
 
 
 
20
 
21
- const packageManagers: { value: PackageManager; label: string; command: string }[] = [
 
 
 
 
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 = packageManagers.find((pm) => pm.value === selectedPM)?.command || ""
 
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">web</h3>
109
- <p className="text-sm text-muted-foreground font-mono">run lerobot in the browser</p>
 
 
 
 
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">node</h3>
132
- <p className="text-sm text-muted-foreground/70 font-mono">run lerobot on the server</p>
 
 
 
 
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
  }