Spaces:
Running
Running
feat: added react / tailwind ui
Browse files- index.html +65 -16
- package.json +17 -2
- pnpm-lock.yaml +1273 -2
- postcss.config.js +6 -0
- postcss.config.mjs +6 -0
- src/demo/App.tsx +20 -0
- src/demo/components/CalibrationPanel.tsx +526 -0
- src/demo/components/CalibrationWizard.tsx +217 -0
- src/demo/components/ErrorBoundary.tsx +65 -0
- src/demo/components/PortManager.tsx +1050 -0
- src/demo/components/ui/alert.tsx +58 -0
- src/demo/components/ui/badge.tsx +35 -0
- src/demo/components/ui/button.tsx +53 -0
- src/demo/components/ui/card.tsx +85 -0
- src/demo/index.css +12 -0
- src/demo/lib/utils.ts +6 -0
- src/demo/main.tsx +10 -0
- src/demo/pages/Calibrate.tsx +121 -0
- src/demo/pages/Home.tsx +98 -0
- src/demo/pages/Setup.tsx +99 -0
- src/demo/types.ts +20 -0
- src/vite-env.d.ts +32 -0
- tailwind.config.js +56 -0
- vanilla.html +32 -0
- vite.config.ts +75 -0
index.html
CHANGED
@@ -3,30 +3,79 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8" />
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
-
<title>🤖 lerobot.js -
|
7 |
<meta
|
8 |
name="description"
|
9 |
-
content="State-of-the-art AI for real-world robotics in JavaScript/TypeScript"
|
10 |
/>
|
11 |
<style>
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
}
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
19 |
}
|
20 |
</style>
|
21 |
</head>
|
22 |
<body>
|
23 |
-
<div id="
|
24 |
-
<script type="module" src="/src/main.
|
25 |
-
<script>
|
26 |
-
// Add loaded class when page is ready
|
27 |
-
window.addEventListener("load", () => {
|
28 |
-
document.body.classList.add("loaded");
|
29 |
-
});
|
30 |
-
</script>
|
31 |
</body>
|
32 |
</html>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8" />
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>🤖 lerobot.js - React Demo</title>
|
7 |
<meta
|
8 |
name="description"
|
9 |
+
content="State-of-the-art AI for real-world robotics in JavaScript/TypeScript - Interactive React Demo"
|
10 |
/>
|
11 |
<style>
|
12 |
+
:root {
|
13 |
+
--background: 0 0% 100%;
|
14 |
+
--foreground: 240 10% 3.9%;
|
15 |
+
--card: 0 0% 100%;
|
16 |
+
--card-foreground: 240 10% 3.9%;
|
17 |
+
--popover: 0 0% 100%;
|
18 |
+
--popover-foreground: 240 10% 3.9%;
|
19 |
+
--primary: 240 9% 17%;
|
20 |
+
--primary-foreground: 0 0% 98%;
|
21 |
+
--secondary: 240 4.8% 95.9%;
|
22 |
+
--secondary-foreground: 240 5.9% 10%;
|
23 |
+
--muted: 240 4.8% 95.9%;
|
24 |
+
--muted-foreground: 240 3.8% 46.1%;
|
25 |
+
--accent: 240 4.8% 95.9%;
|
26 |
+
--accent-foreground: 240 5.9% 10%;
|
27 |
+
--destructive: 0 84.2% 60.2%;
|
28 |
+
--destructive-foreground: 0 0% 98%;
|
29 |
+
--border: 240 5.9% 90%;
|
30 |
+
--input: 240 5.9% 90%;
|
31 |
+
--ring: 240 10% 3.9%;
|
32 |
+
--chart-1: 12 76% 61%;
|
33 |
+
--chart-2: 173 58% 39%;
|
34 |
+
--chart-3: 197 37% 24%;
|
35 |
+
--chart-4: 43 74% 66%;
|
36 |
+
--chart-5: 27 87% 67%;
|
37 |
+
--radius: 0.5rem;
|
38 |
+
}
|
39 |
+
|
40 |
+
.dark {
|
41 |
+
--background: 240 10% 3.9%;
|
42 |
+
--foreground: 0 0% 98%;
|
43 |
+
--card: 240 10% 3.9%;
|
44 |
+
--card-foreground: 0 0% 98%;
|
45 |
+
--popover: 240 10% 3.9%;
|
46 |
+
--popover-foreground: 0 0% 98%;
|
47 |
+
--primary: 0 0% 98%;
|
48 |
+
--primary-foreground: 240 5.9% 10%;
|
49 |
+
--secondary: 240 3.7% 15.9%;
|
50 |
+
--secondary-foreground: 0 0% 98%;
|
51 |
+
--muted: 240 3.7% 15.9%;
|
52 |
+
--muted-foreground: 240 5% 64.9%;
|
53 |
+
--accent: 240 3.7% 15.9%;
|
54 |
+
--accent-foreground: 0 0% 98%;
|
55 |
+
--destructive: 0 62.8% 30.6%;
|
56 |
+
--destructive-foreground: 0 0% 98%;
|
57 |
+
--border: 240 3.7% 15.9%;
|
58 |
+
--input: 240 3.7% 15.9%;
|
59 |
+
--ring: 240 4.9% 83.9%;
|
60 |
+
--chart-1: 220 70% 50%;
|
61 |
+
--chart-2: 160 60% 45%;
|
62 |
+
--chart-3: 30 80% 55%;
|
63 |
+
--chart-4: 280 65% 60%;
|
64 |
+
--chart-5: 340 75% 55%;
|
65 |
}
|
66 |
+
|
67 |
+
* {
|
68 |
+
border-color: hsl(var(--border));
|
69 |
+
}
|
70 |
+
|
71 |
+
body {
|
72 |
+
background-color: hsl(var(--background));
|
73 |
+
color: hsl(var(--foreground));
|
74 |
}
|
75 |
</style>
|
76 |
</head>
|
77 |
<body>
|
78 |
+
<div id="root"></div>
|
79 |
+
<script type="module" src="/src/demo/main.tsx"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
</body>
|
81 |
</html>
|
package.json
CHANGED
@@ -19,10 +19,13 @@
|
|
19 |
"lerobot"
|
20 |
],
|
21 |
"scripts": {
|
22 |
-
"dev": "vite",
|
|
|
|
|
23 |
"build": "pnpm run build:cli",
|
24 |
"build:cli": "tsc --project tsconfig.cli.json",
|
25 |
-
"build:web": "tsc && vite build",
|
|
|
26 |
"preview": "vite preview",
|
27 |
"cli:find-port": "tsx src/cli/index.ts find-port",
|
28 |
"cli:calibrate": "tsx src/cli/index.ts calibrate",
|
@@ -35,6 +38,18 @@
|
|
35 |
},
|
36 |
"devDependencies": {
|
37 |
"@types/node": "^22.10.5",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
"tsx": "^4.19.2",
|
39 |
"typescript": "~5.8.3",
|
40 |
"vite": "^6.3.5"
|
|
|
19 |
"lerobot"
|
20 |
],
|
21 |
"scripts": {
|
22 |
+
"dev": "vite --mode demo",
|
23 |
+
"dev:vanilla": "vite --mode vanilla",
|
24 |
+
"dev:lib": "vite --mode lib",
|
25 |
"build": "pnpm run build:cli",
|
26 |
"build:cli": "tsc --project tsconfig.cli.json",
|
27 |
+
"build:web": "tsc && vite build --mode lib",
|
28 |
+
"build:demo": "tsc && vite build --mode demo",
|
29 |
"preview": "vite preview",
|
30 |
"cli:find-port": "tsx src/cli/index.ts find-port",
|
31 |
"cli:calibrate": "tsx src/cli/index.ts calibrate",
|
|
|
38 |
},
|
39 |
"devDependencies": {
|
40 |
"@types/node": "^22.10.5",
|
41 |
+
"@types/react": "^18.2.79",
|
42 |
+
"@types/react-dom": "^18.2.25",
|
43 |
+
"@vitejs/plugin-react": "^4.2.1",
|
44 |
+
"autoprefixer": "^10.4.19",
|
45 |
+
"class-variance-authority": "^0.7.0",
|
46 |
+
"clsx": "^2.1.1",
|
47 |
+
"lucide-react": "^0.400.0",
|
48 |
+
"postcss": "^8.4.38",
|
49 |
+
"react": "^18.2.0",
|
50 |
+
"react-dom": "^18.2.0",
|
51 |
+
"tailwind-merge": "^2.3.0",
|
52 |
+
"tailwindcss": "^3.4.0",
|
53 |
"tsx": "^4.19.2",
|
54 |
"typescript": "~5.8.3",
|
55 |
"vite": "^6.3.5"
|
pnpm-lock.yaml
CHANGED
@@ -18,6 +18,42 @@ importers:
|
|
18 |
'@types/node':
|
19 |
specifier: ^22.10.5
|
20 |
version: 22.15.31
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
tsx:
|
22 |
specifier: ^4.19.2
|
23 |
version: 4.20.3
|
@@ -26,10 +62,97 @@ importers:
|
|
26 |
version: 5.8.3
|
27 |
vite:
|
28 |
specifier: ^6.3.5
|
29 |
-
version: 6.3.5(@types/[email protected])([email protected])
|
30 |
|
31 |
packages:
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
'@esbuild/[email protected]':
|
34 |
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
35 |
engines: {node: '>=18'}
|
@@ -180,6 +303,47 @@ packages:
|
|
180 |
cpu: [x64]
|
181 |
os: [win32]
|
182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
183 |
'@rollup/[email protected]':
|
184 |
resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==}
|
185 |
cpu: [arm]
|
@@ -344,28 +508,145 @@ packages:
|
|
344 |
resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
|
345 |
engines: {node: '>=12.0.0'}
|
346 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
347 |
'@types/[email protected]':
|
348 |
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
349 |
|
350 |
'@types/[email protected]':
|
351 |
resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==}
|
352 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 | |
354 |
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
|
355 |
engines: {node: '>=18'}
|
356 |
|
|
|
|
|
|
|
|
|
357 | |
358 |
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
|
359 |
engines: {node: '>=12'}
|
360 |
|
|
|
|
|
|
|
|
|
361 | |
362 |
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
363 |
engines: {node: '>=12'}
|
364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
365 | |
366 |
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
367 |
engines: {node: '>=18'}
|
368 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
369 | |
370 |
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
371 |
engines: {node: '>=6.0'}
|
@@ -375,9 +656,27 @@ packages:
|
|
375 |
supports-color:
|
376 |
optional: true
|
377 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
378 | |
379 |
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
|
380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
381 | |
382 |
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
383 |
engines: {node: '>=18'}
|
@@ -387,6 +686,17 @@ packages:
|
|
387 |
engines: {node: '>=18'}
|
388 |
hasBin: true
|
389 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
390 | |
391 |
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
392 |
peerDependencies:
|
@@ -395,11 +705,29 @@ packages:
|
|
395 |
picomatch:
|
396 |
optional: true
|
397 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 | |
399 |
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
400 |
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
401 |
os: [darwin]
|
402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
403 | |
404 |
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
|
405 |
engines: {node: '>=18'}
|
@@ -407,21 +735,129 @@ packages:
|
|
407 | |
408 |
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
409 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
410 | |
411 |
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
|
412 |
engines: {node: '>=18'}
|
413 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
414 | |
415 |
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
|
416 |
engines: {node: '>=18'}
|
417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
418 | |
419 |
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
420 |
engines: {node: '>=18'}
|
421 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
422 | |
423 |
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
424 |
|
|
|
|
|
|
|
425 | |
426 |
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
427 |
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
@@ -434,37 +870,169 @@ packages:
|
|
434 |
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
435 |
hasBin: true
|
436 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
437 | |
438 |
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
439 |
engines: {node: '>=18'}
|
440 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
441 | |
442 |
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
443 |
|
|
|
|
|
|
|
|
|
444 | |
445 |
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
446 |
engines: {node: '>=12'}
|
447 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
448 | |
449 |
resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==}
|
450 |
engines: {node: ^10 || ^12 || >=14}
|
451 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
452 | |
453 |
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
454 |
|
|
|
|
|
|
|
|
|
|
|
455 | |
456 |
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
457 |
engines: {node: '>=18'}
|
458 |
|
|
|
|
|
|
|
|
|
459 | |
460 |
resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==}
|
461 |
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
462 |
hasBin: true
|
463 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
464 | |
465 |
resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
|
466 |
engines: {node: '>=16.0.0'}
|
467 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
468 | |
469 |
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
470 |
engines: {node: '>=14'}
|
@@ -477,18 +1045,61 @@ packages:
|
|
477 |
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
478 |
engines: {node: '>=0.10.0'}
|
479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
480 | |
481 |
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
|
482 |
engines: {node: '>=18'}
|
483 |
|
|
|
|
|
|
|
|
|
484 | |
485 |
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
486 |
engines: {node: '>=12'}
|
487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
488 | |
489 |
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
490 |
engines: {node: '>=12.0.0'}
|
491 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
492 | |
493 |
resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==}
|
494 |
engines: {node: '>=18.0.0'}
|
@@ -502,6 +1113,15 @@ packages:
|
|
502 | |
503 |
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
504 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
505 | |
506 |
resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
|
507 |
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
@@ -542,12 +1162,150 @@ packages:
|
|
542 |
yaml:
|
543 |
optional: true
|
544 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
545 | |
546 |
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
|
547 |
engines: {node: '>=18'}
|
548 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
549 |
snapshots:
|
550 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
551 |
'@esbuild/[email protected]':
|
552 |
optional: true
|
553 |
|
@@ -623,6 +1381,49 @@ snapshots:
|
|
623 |
'@esbuild/[email protected]':
|
624 |
optional: true
|
625 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
626 |
'@rollup/[email protected]':
|
627 |
optional: true
|
628 |
|
@@ -737,30 +1538,172 @@ snapshots:
|
|
737 |
transitivePeerDependencies:
|
738 |
- supports-color
|
739 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
740 |
'@types/[email protected]': {}
|
741 |
|
742 |
'@types/[email protected]':
|
743 |
dependencies:
|
744 |
undici-types: 6.21.0
|
745 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
746 | |
747 |
dependencies:
|
748 |
environment: 1.1.0
|
749 |
|
|
|
|
|
750 | |
751 |
|
|
|
|
|
|
|
|
|
752 | |
753 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
754 | |
755 |
dependencies:
|
756 |
restore-cursor: 5.1.0
|
757 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
758 | |
759 |
dependencies:
|
760 |
ms: 2.1.2
|
761 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
762 | |
763 |
|
|
|
|
|
|
|
|
|
764 | |
765 |
|
766 | |
@@ -791,23 +1734,113 @@ snapshots:
|
|
791 |
'@esbuild/win32-ia32': 0.25.5
|
792 |
'@esbuild/win32-x64': 0.25.5
|
793 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
794 | |
795 |
optionalDependencies:
|
796 |
picomatch: 4.0.2
|
797 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
798 | |
799 |
optional: true
|
800 |
|
|
|
|
|
|
|
|
|
801 | |
802 |
|
803 | |
804 |
dependencies:
|
805 |
resolve-pkg-maps: 1.0.0
|
806 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
807 | |
808 |
dependencies:
|
809 |
get-east-asian-width: 1.3.0
|
810 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
811 | |
812 |
dependencies:
|
813 |
ansi-escapes: 7.0.0
|
@@ -816,37 +1849,158 @@ snapshots:
|
|
816 |
strip-ansi: 7.1.0
|
817 |
wrap-ansi: 9.0.0
|
818 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
819 | |
820 |
|
|
|
|
|
|
|
|
|
|
|
|
|
821 | |
822 |
|
|
|
|
|
|
|
|
|
|
|
|
|
823 | |
824 |
|
825 | |
826 |
|
827 | |
828 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
829 | |
830 |
dependencies:
|
831 |
mimic-function: 5.0.1
|
832 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
833 | |
834 |
|
|
|
|
|
835 | |
836 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
837 | |
838 |
dependencies:
|
839 |
nanoid: 3.3.11
|
840 |
picocolors: 1.1.1
|
841 |
source-map-js: 1.2.1
|
842 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
843 | |
844 |
|
|
|
|
|
|
|
|
|
|
|
|
|
845 | |
846 |
dependencies:
|
847 |
onetime: 7.0.0
|
848 |
signal-exit: 4.1.0
|
849 |
|
|
|
|
|
850 | |
851 |
dependencies:
|
852 |
'@types/estree': 1.0.7
|
@@ -873,6 +2027,16 @@ snapshots:
|
|
873 |
'@rollup/rollup-win32-x64-msvc': 4.43.0
|
874 |
fsevents: 2.3.3
|
875 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
876 | |
877 |
dependencies:
|
878 |
'@serialport/binding-mock': 10.2.2
|
@@ -892,6 +2056,12 @@ snapshots:
|
|
892 |
transitivePeerDependencies:
|
893 |
- supports-color
|
894 |
|
|
|
|
|
|
|
|
|
|
|
|
|
895 | |
896 |
|
897 | |
@@ -901,21 +2071,92 @@ snapshots:
|
|
901 |
|
902 | |
903 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
904 | |
905 |
dependencies:
|
906 |
emoji-regex: 10.4.0
|
907 |
get-east-asian-width: 1.3.0
|
908 |
strip-ansi: 7.1.0
|
909 |
|
|
|
|
|
|
|
|
|
910 | |
911 |
dependencies:
|
912 |
ansi-regex: 6.1.0
|
913 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
914 | |
915 |
dependencies:
|
916 |
fdir: 6.4.6([email protected])
|
917 |
picomatch: 4.0.2
|
918 |
|
|
|
|
|
|
|
|
|
|
|
|
|
919 | |
920 |
dependencies:
|
921 |
esbuild: 0.25.5
|
@@ -927,7 +2168,15 @@ snapshots:
|
|
927 |
|
928 | |
929 |
|
930 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
931 |
dependencies:
|
932 |
esbuild: 0.25.5
|
933 |
fdir: 6.4.6([email protected])
|
@@ -938,10 +2187,32 @@ snapshots:
|
|
938 |
optionalDependencies:
|
939 |
'@types/node': 22.15.31
|
940 |
fsevents: 2.3.3
|
|
|
941 |
tsx: 4.20.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
942 |
|
943 | |
944 |
dependencies:
|
945 |
ansi-styles: 6.2.1
|
946 |
string-width: 7.2.0
|
947 |
strip-ansi: 7.1.0
|
|
|
|
|
|
|
|
|
|
18 |
'@types/node':
|
19 |
specifier: ^22.10.5
|
20 |
version: 22.15.31
|
21 |
+
'@types/react':
|
22 |
+
specifier: ^18.2.79
|
23 |
+
version: 18.3.23
|
24 |
+
'@types/react-dom':
|
25 |
+
specifier: ^18.2.25
|
26 |
+
version: 18.3.7(@types/[email protected])
|
27 |
+
'@vitejs/plugin-react':
|
28 |
+
specifier: ^4.2.1
|
29 |
+
version: 4.5.2([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
|
30 |
+
autoprefixer:
|
31 |
+
specifier: ^10.4.19
|
32 |
+
version: 10.4.21([email protected])
|
33 |
+
class-variance-authority:
|
34 |
+
specifier: ^0.7.0
|
35 |
+
version: 0.7.1
|
36 |
+
clsx:
|
37 |
+
specifier: ^2.1.1
|
38 |
+
version: 2.1.1
|
39 |
+
lucide-react:
|
40 |
+
specifier: ^0.400.0
|
41 |
+
version: 0.400.0([email protected])
|
42 |
+
postcss:
|
43 |
+
specifier: ^8.4.38
|
44 |
+
version: 8.5.5
|
45 |
+
react:
|
46 |
+
specifier: ^18.2.0
|
47 |
+
version: 18.3.1
|
48 |
+
react-dom:
|
49 |
+
specifier: ^18.2.0
|
50 |
+
version: 18.3.1([email protected])
|
51 |
+
tailwind-merge:
|
52 |
+
specifier: ^2.3.0
|
53 |
+
version: 2.6.0
|
54 |
+
tailwindcss:
|
55 |
+
specifier: ^3.4.0
|
56 |
+
version: 3.4.17
|
57 |
tsx:
|
58 |
specifier: ^4.19.2
|
59 |
version: 4.20.3
|
|
|
62 |
version: 5.8.3
|
63 |
vite:
|
64 |
specifier: ^6.3.5
|
65 |
+
version: 6.3.5(@types/[email protected])([email protected])([email protected])([email protected])
|
66 |
|
67 |
packages:
|
68 |
|
69 |
+
'@alloc/[email protected]':
|
70 |
+
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
71 |
+
engines: {node: '>=10'}
|
72 |
+
|
73 |
+
'@ampproject/[email protected]':
|
74 |
+
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
75 |
+
engines: {node: '>=6.0.0'}
|
76 |
+
|
77 |
+
'@babel/[email protected]':
|
78 |
+
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
79 |
+
engines: {node: '>=6.9.0'}
|
80 |
+
|
81 |
+
'@babel/[email protected]':
|
82 |
+
resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==}
|
83 |
+
engines: {node: '>=6.9.0'}
|
84 |
+
|
85 |
+
'@babel/[email protected]':
|
86 |
+
resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==}
|
87 |
+
engines: {node: '>=6.9.0'}
|
88 |
+
|
89 |
+
'@babel/[email protected]':
|
90 |
+
resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
|
91 |
+
engines: {node: '>=6.9.0'}
|
92 |
+
|
93 |
+
'@babel/[email protected]':
|
94 |
+
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
|
95 |
+
engines: {node: '>=6.9.0'}
|
96 |
+
|
97 |
+
'@babel/[email protected]':
|
98 |
+
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
|
99 |
+
engines: {node: '>=6.9.0'}
|
100 |
+
|
101 |
+
'@babel/[email protected]':
|
102 |
+
resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
|
103 |
+
engines: {node: '>=6.9.0'}
|
104 |
+
peerDependencies:
|
105 |
+
'@babel/core': ^7.0.0
|
106 |
+
|
107 |
+
'@babel/[email protected]':
|
108 |
+
resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
|
109 |
+
engines: {node: '>=6.9.0'}
|
110 |
+
|
111 |
+
'@babel/[email protected]':
|
112 |
+
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
113 |
+
engines: {node: '>=6.9.0'}
|
114 |
+
|
115 |
+
'@babel/[email protected]':
|
116 |
+
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
117 |
+
engines: {node: '>=6.9.0'}
|
118 |
+
|
119 |
+
'@babel/[email protected]':
|
120 |
+
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
|
121 |
+
engines: {node: '>=6.9.0'}
|
122 |
+
|
123 |
+
'@babel/[email protected]':
|
124 |
+
resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==}
|
125 |
+
engines: {node: '>=6.9.0'}
|
126 |
+
|
127 |
+
'@babel/[email protected]':
|
128 |
+
resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
|
129 |
+
engines: {node: '>=6.0.0'}
|
130 |
+
hasBin: true
|
131 |
+
|
132 |
+
'@babel/[email protected]':
|
133 |
+
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
|
134 |
+
engines: {node: '>=6.9.0'}
|
135 |
+
peerDependencies:
|
136 |
+
'@babel/core': ^7.0.0-0
|
137 |
+
|
138 |
+
'@babel/[email protected]':
|
139 |
+
resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
|
140 |
+
engines: {node: '>=6.9.0'}
|
141 |
+
peerDependencies:
|
142 |
+
'@babel/core': ^7.0.0-0
|
143 |
+
|
144 |
+
'@babel/[email protected]':
|
145 |
+
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
146 |
+
engines: {node: '>=6.9.0'}
|
147 |
+
|
148 |
+
'@babel/[email protected]':
|
149 |
+
resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==}
|
150 |
+
engines: {node: '>=6.9.0'}
|
151 |
+
|
152 |
+
'@babel/[email protected]':
|
153 |
+
resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
|
154 |
+
engines: {node: '>=6.9.0'}
|
155 |
+
|
156 |
'@esbuild/[email protected]':
|
157 |
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
158 |
engines: {node: '>=18'}
|
|
|
303 |
cpu: [x64]
|
304 |
os: [win32]
|
305 |
|
306 |
+
'@isaacs/[email protected]':
|
307 |
+
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
308 |
+
engines: {node: '>=12'}
|
309 |
+
|
310 |
+
'@jridgewell/[email protected]':
|
311 |
+
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
312 |
+
engines: {node: '>=6.0.0'}
|
313 |
+
|
314 |
+
'@jridgewell/[email protected]':
|
315 |
+
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
316 |
+
engines: {node: '>=6.0.0'}
|
317 |
+
|
318 |
+
'@jridgewell/[email protected]':
|
319 |
+
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
320 |
+
engines: {node: '>=6.0.0'}
|
321 |
+
|
322 |
+
'@jridgewell/[email protected]':
|
323 |
+
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
324 |
+
|
325 |
+
'@jridgewell/[email protected]':
|
326 |
+
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
327 |
+
|
328 |
+
'@nodelib/[email protected]':
|
329 |
+
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
330 |
+
engines: {node: '>= 8'}
|
331 |
+
|
332 |
+
'@nodelib/[email protected]':
|
333 |
+
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
334 |
+
engines: {node: '>= 8'}
|
335 |
+
|
336 |
+
'@nodelib/[email protected]':
|
337 |
+
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
338 |
+
engines: {node: '>= 8'}
|
339 |
+
|
340 |
+
'@pkgjs/[email protected]':
|
341 |
+
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
342 |
+
engines: {node: '>=14'}
|
343 |
+
|
344 |
+
'@rolldown/[email protected]':
|
345 |
+
resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
|
346 |
+
|
347 |
'@rollup/[email protected]':
|
348 |
resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==}
|
349 |
cpu: [arm]
|
|
|
508 |
resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
|
509 |
engines: {node: '>=12.0.0'}
|
510 |
|
511 |
+
'@types/[email protected]':
|
512 |
+
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
513 |
+
|
514 |
+
'@types/[email protected]':
|
515 |
+
resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
|
516 |
+
|
517 |
+
'@types/[email protected]':
|
518 |
+
resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
|
519 |
+
|
520 |
+
'@types/[email protected]':
|
521 |
+
resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
|
522 |
+
|
523 |
'@types/[email protected]':
|
524 |
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
525 |
|
526 |
'@types/[email protected]':
|
527 |
resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==}
|
528 |
|
529 |
+
'@types/[email protected]':
|
530 |
+
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
|
531 |
+
|
532 |
+
'@types/[email protected]':
|
533 |
+
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
|
534 |
+
peerDependencies:
|
535 |
+
'@types/react': ^18.0.0
|
536 |
+
|
537 |
+
'@types/[email protected]':
|
538 |
+
resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==}
|
539 |
+
|
540 |
+
'@vitejs/[email protected]':
|
541 |
+
resolution: {integrity: sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==}
|
542 |
+
engines: {node: ^14.18.0 || >=16.0.0}
|
543 |
+
peerDependencies:
|
544 |
+
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
|
545 |
+
|
546 | |
547 |
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
|
548 |
engines: {node: '>=18'}
|
549 |
|
550 | |
551 |
+
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
552 |
+
engines: {node: '>=8'}
|
553 |
+
|
554 | |
555 |
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
|
556 |
engines: {node: '>=12'}
|
557 |
|
558 | |
559 |
+
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
560 |
+
engines: {node: '>=8'}
|
561 |
+
|
562 | |
563 |
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
564 |
engines: {node: '>=12'}
|
565 |
|
566 | |
567 |
+
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
568 |
+
|
569 | |
570 |
+
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
571 |
+
engines: {node: '>= 8'}
|
572 |
+
|
573 | |
574 |
+
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
575 |
+
|
576 | |
577 |
+
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
|
578 |
+
engines: {node: ^10 || ^12 || >=14}
|
579 |
+
hasBin: true
|
580 |
+
peerDependencies:
|
581 |
+
postcss: ^8.1.0
|
582 |
+
|
583 | |
584 |
+
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
585 |
+
|
586 | |
587 |
+
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
588 |
+
engines: {node: '>=8'}
|
589 |
+
|
590 | |
591 |
+
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
592 |
+
|
593 | |
594 |
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
595 |
+
engines: {node: '>=8'}
|
596 |
+
|
597 | |
598 |
+
resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==}
|
599 |
+
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
600 |
+
hasBin: true
|
601 |
+
|
602 | |
603 |
+
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
604 |
+
engines: {node: '>= 6'}
|
605 |
+
|
606 | |
607 |
+
resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
|
608 |
+
|
609 | |
610 |
+
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
611 |
+
engines: {node: '>= 8.10.0'}
|
612 |
+
|
613 | |
614 |
+
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
615 |
+
|
616 | |
617 |
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
618 |
engines: {node: '>=18'}
|
619 |
|
620 | |
621 |
+
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
622 |
+
engines: {node: '>=6'}
|
623 |
+
|
624 | |
625 |
+
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
626 |
+
engines: {node: '>=7.0.0'}
|
627 |
+
|
628 | |
629 |
+
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
630 |
+
|
631 | |
632 |
+
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
633 |
+
engines: {node: '>= 6'}
|
634 |
+
|
635 | |
636 |
+
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
637 |
+
|
638 | |
639 |
+
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
640 |
+
engines: {node: '>= 8'}
|
641 |
+
|
642 | |
643 |
+
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
644 |
+
engines: {node: '>=4'}
|
645 |
+
hasBin: true
|
646 |
+
|
647 | |
648 |
+
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
649 |
+
|
650 | |
651 |
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
652 |
engines: {node: '>=6.0'}
|
|
|
656 |
supports-color:
|
657 |
optional: true
|
658 |
|
659 | |
660 |
+
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
661 |
+
|
662 | |
663 |
+
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
664 |
+
|
665 | |
666 |
+
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
667 |
+
|
668 | |
669 |
+
resolution: {integrity: sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==}
|
670 |
+
|
671 | |
672 |
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
|
673 |
|
674 | |
675 |
+
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
676 |
+
|
677 | |
678 |
+
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
679 |
+
|
680 | |
681 |
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
682 |
engines: {node: '>=18'}
|
|
|
686 |
engines: {node: '>=18'}
|
687 |
hasBin: true
|
688 |
|
689 | |
690 |
+
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
691 |
+
engines: {node: '>=6'}
|
692 |
+
|
693 | |
694 |
+
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
695 |
+
engines: {node: '>=8.6.0'}
|
696 |
+
|
697 | |
698 |
+
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
699 |
+
|
700 | |
701 |
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
702 |
peerDependencies:
|
|
|
705 |
picomatch:
|
706 |
optional: true
|
707 |
|
708 | |
709 |
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
710 |
+
engines: {node: '>=8'}
|
711 |
+
|
712 | |
713 |
+
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
714 |
+
engines: {node: '>=14'}
|
715 |
+
|
716 | |
717 |
+
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
718 |
+
|
719 | |
720 |
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
721 |
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
722 |
os: [darwin]
|
723 |
|
724 | |
725 |
+
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
726 |
+
|
727 | |
728 |
+
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
729 |
+
engines: {node: '>=6.9.0'}
|
730 |
+
|
731 | |
732 |
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
|
733 |
engines: {node: '>=18'}
|
|
|
735 | |
736 |
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
737 |
|
738 | |
739 |
+
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
740 |
+
engines: {node: '>= 6'}
|
741 |
+
|
742 | |
743 |
+
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
744 |
+
engines: {node: '>=10.13.0'}
|
745 |
+
|
746 | |
747 |
+
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
748 |
+
hasBin: true
|
749 |
+
|
750 | |
751 |
+
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
752 |
+
engines: {node: '>=4'}
|
753 |
+
|
754 | |
755 |
+
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
756 |
+
engines: {node: '>= 0.4'}
|
757 |
+
|
758 | |
759 |
+
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
760 |
+
engines: {node: '>=8'}
|
761 |
+
|
762 | |
763 |
+
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
764 |
+
engines: {node: '>= 0.4'}
|
765 |
+
|
766 | |
767 |
+
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
768 |
+
engines: {node: '>=0.10.0'}
|
769 |
+
|
770 | |
771 |
+
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
772 |
+
engines: {node: '>=8'}
|
773 |
+
|
774 | |
775 |
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
|
776 |
engines: {node: '>=18'}
|
777 |
|
778 | |
779 |
+
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
780 |
+
engines: {node: '>=0.10.0'}
|
781 |
+
|
782 | |
783 |
+
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
784 |
+
engines: {node: '>=0.12.0'}
|
785 |
+
|
786 | |
787 |
+
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
788 |
+
|
789 | |
790 |
+
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
791 |
+
|
792 | |
793 |
+
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
794 |
+
hasBin: true
|
795 |
+
|
796 | |
797 |
+
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
798 |
+
|
799 | |
800 |
+
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
|
801 |
+
engines: {node: '>=6'}
|
802 |
+
hasBin: true
|
803 |
+
|
804 | |
805 |
+
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
806 |
+
engines: {node: '>=6'}
|
807 |
+
hasBin: true
|
808 |
+
|
809 | |
810 |
+
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
811 |
+
engines: {node: '>=14'}
|
812 |
+
|
813 | |
814 |
+
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
815 |
+
|
816 | |
817 |
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
|
818 |
engines: {node: '>=18'}
|
819 |
|
820 | |
821 |
+
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
822 |
+
hasBin: true
|
823 |
+
|
824 | |
825 |
+
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
826 |
+
|
827 | |
828 |
+
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
829 |
+
|
830 | |
831 |
+
resolution: {integrity: sha512-rpp7pFHh3Xd93KHixNgB0SqThMHpYNzsGUu69UaQbSZ75Q/J3m5t6EhKyMT3m4w2WOxmJ2mY0tD3vebnXqQryQ==}
|
832 |
+
peerDependencies:
|
833 |
+
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
834 |
+
|
835 | |
836 |
+
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
837 |
+
engines: {node: '>= 8'}
|
838 |
+
|
839 | |
840 |
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
841 |
+
engines: {node: '>=8.6'}
|
842 |
+
|
843 | |
844 |
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
845 |
engines: {node: '>=18'}
|
846 |
|
847 | |
848 |
+
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
849 |
+
engines: {node: '>=16 || 14 >=14.17'}
|
850 |
+
|
851 | |
852 |
+
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
853 |
+
engines: {node: '>=16 || 14 >=14.17'}
|
854 |
+
|
855 | |
856 |
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
857 |
|
858 | |
859 |
+
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
860 |
+
|
861 | |
862 |
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
863 |
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
|
|
870 |
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
871 |
hasBin: true
|
872 |
|
873 | |
874 |
+
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
875 |
+
|
876 | |
877 |
+
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
878 |
+
engines: {node: '>=0.10.0'}
|
879 |
+
|
880 | |
881 |
+
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
882 |
+
engines: {node: '>=0.10.0'}
|
883 |
+
|
884 | |
885 |
+
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
886 |
+
engines: {node: '>=0.10.0'}
|
887 |
+
|
888 | |
889 |
+
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
890 |
+
engines: {node: '>= 6'}
|
891 |
+
|
892 | |
893 |
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
894 |
engines: {node: '>=18'}
|
895 |
|
896 | |
897 |
+
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
898 |
+
|
899 | |
900 |
+
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
901 |
+
engines: {node: '>=8'}
|
902 |
+
|
903 | |
904 |
+
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
905 |
+
|
906 | |
907 |
+
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
908 |
+
engines: {node: '>=16 || 14 >=14.18'}
|
909 |
+
|
910 | |
911 |
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
912 |
|
913 | |
914 |
+
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
915 |
+
engines: {node: '>=8.6'}
|
916 |
+
|
917 | |
918 |
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
919 |
engines: {node: '>=12'}
|
920 |
|
921 | |
922 |
+
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
923 |
+
engines: {node: '>=0.10.0'}
|
924 |
+
|
925 | |
926 |
+
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
927 |
+
engines: {node: '>= 6'}
|
928 |
+
|
929 | |
930 |
+
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
931 |
+
engines: {node: '>=14.0.0'}
|
932 |
+
peerDependencies:
|
933 |
+
postcss: ^8.0.0
|
934 |
+
|
935 | |
936 |
+
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
|
937 |
+
engines: {node: ^12 || ^14 || >= 16}
|
938 |
+
peerDependencies:
|
939 |
+
postcss: ^8.4.21
|
940 |
+
|
941 | |
942 |
+
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
|
943 |
+
engines: {node: '>= 14'}
|
944 |
+
peerDependencies:
|
945 |
+
postcss: '>=8.0.9'
|
946 |
+
ts-node: '>=9.0.0'
|
947 |
+
peerDependenciesMeta:
|
948 |
+
postcss:
|
949 |
+
optional: true
|
950 |
+
ts-node:
|
951 |
+
optional: true
|
952 |
+
|
953 | |
954 |
+
resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
|
955 |
+
engines: {node: '>=12.0'}
|
956 |
+
peerDependencies:
|
957 |
+
postcss: ^8.2.14
|
958 |
+
|
959 | |
960 |
+
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
961 |
+
engines: {node: '>=4'}
|
962 |
+
|
963 | |
964 |
+
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
965 |
+
|
966 | |
967 |
resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==}
|
968 |
engines: {node: ^10 || ^12 || >=14}
|
969 |
|
970 | |
971 |
+
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
972 |
+
|
973 | |
974 |
+
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
975 |
+
peerDependencies:
|
976 |
+
react: ^18.3.1
|
977 |
+
|
978 | |
979 |
+
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
980 |
+
engines: {node: '>=0.10.0'}
|
981 |
+
|
982 | |
983 |
+
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
984 |
+
engines: {node: '>=0.10.0'}
|
985 |
+
|
986 | |
987 |
+
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
988 |
+
|
989 | |
990 |
+
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
991 |
+
engines: {node: '>=8.10.0'}
|
992 |
+
|
993 | |
994 |
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
995 |
|
996 | |
997 |
+
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
|
998 |
+
engines: {node: '>= 0.4'}
|
999 |
+
hasBin: true
|
1000 |
+
|
1001 | |
1002 |
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
1003 |
engines: {node: '>=18'}
|
1004 |
|
1005 | |
1006 |
+
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
1007 |
+
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
1008 |
+
|
1009 | |
1010 |
resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==}
|
1011 |
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
1012 |
hasBin: true
|
1013 |
|
1014 | |
1015 |
+
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
1016 |
+
|
1017 | |
1018 |
+
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
1019 |
+
|
1020 | |
1021 |
+
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
1022 |
+
hasBin: true
|
1023 |
+
|
1024 | |
1025 |
resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
|
1026 |
engines: {node: '>=16.0.0'}
|
1027 |
|
1028 | |
1029 |
+
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
1030 |
+
engines: {node: '>=8'}
|
1031 |
+
|
1032 | |
1033 |
+
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
1034 |
+
engines: {node: '>=8'}
|
1035 |
+
|
1036 | |
1037 |
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
1038 |
engines: {node: '>=14'}
|
|
|
1045 |
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
1046 |
engines: {node: '>=0.10.0'}
|
1047 |
|
1048 | |
1049 |
+
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
1050 |
+
engines: {node: '>=8'}
|
1051 |
+
|
1052 | |
1053 |
+
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
1054 |
+
engines: {node: '>=12'}
|
1055 |
+
|
1056 | |
1057 |
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
|
1058 |
engines: {node: '>=18'}
|
1059 |
|
1060 | |
1061 |
+
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
1062 |
+
engines: {node: '>=8'}
|
1063 |
+
|
1064 | |
1065 |
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
1066 |
engines: {node: '>=12'}
|
1067 |
|
1068 | |
1069 |
+
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
|
1070 |
+
engines: {node: '>=16 || 14 >=14.17'}
|
1071 |
+
hasBin: true
|
1072 |
+
|
1073 | |
1074 |
+
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
1075 |
+
engines: {node: '>= 0.4'}
|
1076 |
+
|
1077 | |
1078 |
+
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
|
1079 |
+
|
1080 | |
1081 |
+
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
|
1082 |
+
engines: {node: '>=14.0.0'}
|
1083 |
+
hasBin: true
|
1084 |
+
|
1085 | |
1086 |
+
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
1087 |
+
engines: {node: '>=0.8'}
|
1088 |
+
|
1089 | |
1090 |
+
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
1091 |
+
|
1092 | |
1093 |
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
1094 |
engines: {node: '>=12.0.0'}
|
1095 |
|
1096 | |
1097 |
+
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
1098 |
+
engines: {node: '>=8.0'}
|
1099 |
+
|
1100 | |
1101 |
+
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
1102 |
+
|
1103 | |
1104 |
resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==}
|
1105 |
engines: {node: '>=18.0.0'}
|
|
|
1113 | |
1114 |
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
1115 |
|
1116 | |
1117 |
+
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
|
1118 |
+
hasBin: true
|
1119 |
+
peerDependencies:
|
1120 |
+
browserslist: '>= 4.21.0'
|
1121 |
+
|
1122 | |
1123 |
+
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
1124 |
+
|
1125 | |
1126 |
resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
|
1127 |
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
|
|
1162 |
yaml:
|
1163 |
optional: true
|
1164 |
|
1165 | |
1166 |
+
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
1167 |
+
engines: {node: '>= 8'}
|
1168 |
+
hasBin: true
|
1169 |
+
|
1170 | |
1171 |
+
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
1172 |
+
engines: {node: '>=10'}
|
1173 |
+
|
1174 | |
1175 |
+
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
1176 |
+
engines: {node: '>=12'}
|
1177 |
+
|
1178 | |
1179 |
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
|
1180 |
engines: {node: '>=18'}
|
1181 |
|
1182 | |
1183 |
+
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
1184 |
+
|
1185 | |
1186 |
+
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
|
1187 |
+
engines: {node: '>= 14.6'}
|
1188 |
+
hasBin: true
|
1189 |
+
|
1190 |
snapshots:
|
1191 |
|
1192 |
+
'@alloc/[email protected]': {}
|
1193 |
+
|
1194 |
+
'@ampproject/[email protected]':
|
1195 |
+
dependencies:
|
1196 |
+
'@jridgewell/gen-mapping': 0.3.8
|
1197 |
+
'@jridgewell/trace-mapping': 0.3.25
|
1198 |
+
|
1199 |
+
'@babel/[email protected]':
|
1200 |
+
dependencies:
|
1201 |
+
'@babel/helper-validator-identifier': 7.27.1
|
1202 |
+
js-tokens: 4.0.0
|
1203 |
+
picocolors: 1.1.1
|
1204 |
+
|
1205 |
+
'@babel/[email protected]': {}
|
1206 |
+
|
1207 |
+
'@babel/[email protected]':
|
1208 |
+
dependencies:
|
1209 |
+
'@ampproject/remapping': 2.3.0
|
1210 |
+
'@babel/code-frame': 7.27.1
|
1211 |
+
'@babel/generator': 7.27.5
|
1212 |
+
'@babel/helper-compilation-targets': 7.27.2
|
1213 |
+
'@babel/helper-module-transforms': 7.27.3(@babel/[email protected])
|
1214 |
+
'@babel/helpers': 7.27.6
|
1215 |
+
'@babel/parser': 7.27.5
|
1216 |
+
'@babel/template': 7.27.2
|
1217 |
+
'@babel/traverse': 7.27.4
|
1218 |
+
'@babel/types': 7.27.6
|
1219 |
+
convert-source-map: 2.0.0
|
1220 |
+
debug: 4.3.4
|
1221 |
+
gensync: 1.0.0-beta.2
|
1222 |
+
json5: 2.2.3
|
1223 |
+
semver: 6.3.1
|
1224 |
+
transitivePeerDependencies:
|
1225 |
+
- supports-color
|
1226 |
+
|
1227 |
+
'@babel/[email protected]':
|
1228 |
+
dependencies:
|
1229 |
+
'@babel/parser': 7.27.5
|
1230 |
+
'@babel/types': 7.27.6
|
1231 |
+
'@jridgewell/gen-mapping': 0.3.8
|
1232 |
+
'@jridgewell/trace-mapping': 0.3.25
|
1233 |
+
jsesc: 3.1.0
|
1234 |
+
|
1235 |
+
'@babel/[email protected]':
|
1236 |
+
dependencies:
|
1237 |
+
'@babel/compat-data': 7.27.5
|
1238 |
+
'@babel/helper-validator-option': 7.27.1
|
1239 |
+
browserslist: 4.25.0
|
1240 |
+
lru-cache: 5.1.1
|
1241 |
+
semver: 6.3.1
|
1242 |
+
|
1243 |
+
'@babel/[email protected]':
|
1244 |
+
dependencies:
|
1245 |
+
'@babel/traverse': 7.27.4
|
1246 |
+
'@babel/types': 7.27.6
|
1247 |
+
transitivePeerDependencies:
|
1248 |
+
- supports-color
|
1249 |
+
|
1250 |
+
'@babel/[email protected](@babel/[email protected])':
|
1251 |
+
dependencies:
|
1252 |
+
'@babel/core': 7.27.4
|
1253 |
+
'@babel/helper-module-imports': 7.27.1
|
1254 |
+
'@babel/helper-validator-identifier': 7.27.1
|
1255 |
+
'@babel/traverse': 7.27.4
|
1256 |
+
transitivePeerDependencies:
|
1257 |
+
- supports-color
|
1258 |
+
|
1259 |
+
'@babel/[email protected]': {}
|
1260 |
+
|
1261 |
+
'@babel/[email protected]': {}
|
1262 |
+
|
1263 |
+
'@babel/[email protected]': {}
|
1264 |
+
|
1265 |
+
'@babel/[email protected]': {}
|
1266 |
+
|
1267 |
+
'@babel/[email protected]':
|
1268 |
+
dependencies:
|
1269 |
+
'@babel/template': 7.27.2
|
1270 |
+
'@babel/types': 7.27.6
|
1271 |
+
|
1272 |
+
'@babel/[email protected]':
|
1273 |
+
dependencies:
|
1274 |
+
'@babel/types': 7.27.6
|
1275 |
+
|
1276 |
+
'@babel/[email protected](@babel/[email protected])':
|
1277 |
+
dependencies:
|
1278 |
+
'@babel/core': 7.27.4
|
1279 |
+
'@babel/helper-plugin-utils': 7.27.1
|
1280 |
+
|
1281 |
+
'@babel/[email protected](@babel/[email protected])':
|
1282 |
+
dependencies:
|
1283 |
+
'@babel/core': 7.27.4
|
1284 |
+
'@babel/helper-plugin-utils': 7.27.1
|
1285 |
+
|
1286 |
+
'@babel/[email protected]':
|
1287 |
+
dependencies:
|
1288 |
+
'@babel/code-frame': 7.27.1
|
1289 |
+
'@babel/parser': 7.27.5
|
1290 |
+
'@babel/types': 7.27.6
|
1291 |
+
|
1292 |
+
'@babel/[email protected]':
|
1293 |
+
dependencies:
|
1294 |
+
'@babel/code-frame': 7.27.1
|
1295 |
+
'@babel/generator': 7.27.5
|
1296 |
+
'@babel/parser': 7.27.5
|
1297 |
+
'@babel/template': 7.27.2
|
1298 |
+
'@babel/types': 7.27.6
|
1299 |
+
debug: 4.3.4
|
1300 |
+
globals: 11.12.0
|
1301 |
+
transitivePeerDependencies:
|
1302 |
+
- supports-color
|
1303 |
+
|
1304 |
+
'@babel/[email protected]':
|
1305 |
+
dependencies:
|
1306 |
+
'@babel/helper-string-parser': 7.27.1
|
1307 |
+
'@babel/helper-validator-identifier': 7.27.1
|
1308 |
+
|
1309 |
'@esbuild/[email protected]':
|
1310 |
optional: true
|
1311 |
|
|
|
1381 |
'@esbuild/[email protected]':
|
1382 |
optional: true
|
1383 |
|
1384 |
+
'@isaacs/[email protected]':
|
1385 |
+
dependencies:
|
1386 |
+
string-width: 5.1.2
|
1387 |
+
string-width-cjs: [email protected]
|
1388 |
+
strip-ansi: 7.1.0
|
1389 |
+
strip-ansi-cjs: [email protected]
|
1390 |
+
wrap-ansi: 8.1.0
|
1391 |
+
wrap-ansi-cjs: [email protected]
|
1392 |
+
|
1393 |
+
'@jridgewell/[email protected]':
|
1394 |
+
dependencies:
|
1395 |
+
'@jridgewell/set-array': 1.2.1
|
1396 |
+
'@jridgewell/sourcemap-codec': 1.5.0
|
1397 |
+
'@jridgewell/trace-mapping': 0.3.25
|
1398 |
+
|
1399 |
+
'@jridgewell/[email protected]': {}
|
1400 |
+
|
1401 |
+
'@jridgewell/[email protected]': {}
|
1402 |
+
|
1403 |
+
'@jridgewell/[email protected]': {}
|
1404 |
+
|
1405 |
+
'@jridgewell/[email protected]':
|
1406 |
+
dependencies:
|
1407 |
+
'@jridgewell/resolve-uri': 3.1.2
|
1408 |
+
'@jridgewell/sourcemap-codec': 1.5.0
|
1409 |
+
|
1410 |
+
'@nodelib/[email protected]':
|
1411 |
+
dependencies:
|
1412 |
+
'@nodelib/fs.stat': 2.0.5
|
1413 |
+
run-parallel: 1.2.0
|
1414 |
+
|
1415 |
+
'@nodelib/[email protected]': {}
|
1416 |
+
|
1417 |
+
'@nodelib/[email protected]':
|
1418 |
+
dependencies:
|
1419 |
+
'@nodelib/fs.scandir': 2.1.5
|
1420 |
+
fastq: 1.19.1
|
1421 |
+
|
1422 |
+
'@pkgjs/[email protected]':
|
1423 |
+
optional: true
|
1424 |
+
|
1425 |
+
'@rolldown/[email protected]': {}
|
1426 |
+
|
1427 |
'@rollup/[email protected]':
|
1428 |
optional: true
|
1429 |
|
|
|
1538 |
transitivePeerDependencies:
|
1539 |
- supports-color
|
1540 |
|
1541 |
+
'@types/[email protected]':
|
1542 |
+
dependencies:
|
1543 |
+
'@babel/parser': 7.27.5
|
1544 |
+
'@babel/types': 7.27.6
|
1545 |
+
'@types/babel__generator': 7.27.0
|
1546 |
+
'@types/babel__template': 7.4.4
|
1547 |
+
'@types/babel__traverse': 7.20.7
|
1548 |
+
|
1549 |
+
'@types/[email protected]':
|
1550 |
+
dependencies:
|
1551 |
+
'@babel/types': 7.27.6
|
1552 |
+
|
1553 |
+
'@types/[email protected]':
|
1554 |
+
dependencies:
|
1555 |
+
'@babel/parser': 7.27.5
|
1556 |
+
'@babel/types': 7.27.6
|
1557 |
+
|
1558 |
+
'@types/[email protected]':
|
1559 |
+
dependencies:
|
1560 |
+
'@babel/types': 7.27.6
|
1561 |
+
|
1562 |
'@types/[email protected]': {}
|
1563 |
|
1564 |
'@types/[email protected]':
|
1565 |
dependencies:
|
1566 |
undici-types: 6.21.0
|
1567 |
|
1568 |
+
'@types/[email protected]': {}
|
1569 |
+
|
1570 |
+
'@types/[email protected](@types/[email protected])':
|
1571 |
+
dependencies:
|
1572 |
+
'@types/react': 18.3.23
|
1573 |
+
|
1574 |
+
'@types/[email protected]':
|
1575 |
+
dependencies:
|
1576 |
+
'@types/prop-types': 15.7.15
|
1577 |
+
csstype: 3.1.3
|
1578 |
+
|
1579 |
+
'@vitejs/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected]))':
|
1580 |
+
dependencies:
|
1581 |
+
'@babel/core': 7.27.4
|
1582 |
+
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/[email protected])
|
1583 |
+
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/[email protected])
|
1584 |
+
'@rolldown/pluginutils': 1.0.0-beta.11
|
1585 |
+
'@types/babel__core': 7.20.5
|
1586 |
+
react-refresh: 0.17.0
|
1587 |
+
vite: 6.3.5(@types/[email protected])([email protected])([email protected])([email protected])
|
1588 |
+
transitivePeerDependencies:
|
1589 |
+
- supports-color
|
1590 |
+
|
1591 | |
1592 |
dependencies:
|
1593 |
environment: 1.1.0
|
1594 |
|
1595 |
+
[email protected]: {}
|
1596 |
+
|
1597 | |
1598 |
|
1599 | |
1600 |
+
dependencies:
|
1601 |
+
color-convert: 2.0.1
|
1602 |
+
|
1603 | |
1604 |
|
1605 |
+
[email protected]: {}
|
1606 |
+
|
1607 | |
1608 |
+
dependencies:
|
1609 |
+
normalize-path: 3.0.0
|
1610 |
+
picomatch: 2.3.1
|
1611 |
+
|
1612 |
+
[email protected]: {}
|
1613 |
+
|
1614 | |
1615 |
+
dependencies:
|
1616 |
+
browserslist: 4.25.0
|
1617 |
+
caniuse-lite: 1.0.30001723
|
1618 |
+
fraction.js: 4.3.7
|
1619 |
+
normalize-range: 0.1.2
|
1620 |
+
picocolors: 1.1.1
|
1621 |
+
postcss: 8.5.5
|
1622 |
+
postcss-value-parser: 4.2.0
|
1623 |
+
|
1624 |
+
[email protected]: {}
|
1625 |
+
|
1626 |
+
[email protected]: {}
|
1627 |
+
|
1628 | |
1629 |
+
dependencies:
|
1630 |
+
balanced-match: 1.0.2
|
1631 |
+
|
1632 | |
1633 |
+
dependencies:
|
1634 |
+
fill-range: 7.1.1
|
1635 |
+
|
1636 | |
1637 |
+
dependencies:
|
1638 |
+
caniuse-lite: 1.0.30001723
|
1639 |
+
electron-to-chromium: 1.5.168
|
1640 |
+
node-releases: 2.0.19
|
1641 |
+
update-browserslist-db: 1.1.3([email protected])
|
1642 |
+
|
1643 |
+
[email protected]: {}
|
1644 |
+
|
1645 |
+
[email protected]: {}
|
1646 |
+
|
1647 | |
1648 |
+
dependencies:
|
1649 |
+
anymatch: 3.1.3
|
1650 |
+
braces: 3.0.3
|
1651 |
+
glob-parent: 5.1.2
|
1652 |
+
is-binary-path: 2.1.0
|
1653 |
+
is-glob: 4.0.3
|
1654 |
+
normalize-path: 3.0.0
|
1655 |
+
readdirp: 3.6.0
|
1656 |
+
optionalDependencies:
|
1657 |
+
fsevents: 2.3.3
|
1658 |
+
|
1659 | |
1660 |
+
dependencies:
|
1661 |
+
clsx: 2.1.1
|
1662 |
+
|
1663 | |
1664 |
dependencies:
|
1665 |
restore-cursor: 5.1.0
|
1666 |
|
1667 |
+
[email protected]: {}
|
1668 |
+
|
1669 | |
1670 |
+
dependencies:
|
1671 |
+
color-name: 1.1.4
|
1672 |
+
|
1673 |
+
[email protected]: {}
|
1674 |
+
|
1675 |
+
[email protected]: {}
|
1676 |
+
|
1677 |
+
[email protected]: {}
|
1678 |
+
|
1679 | |
1680 |
+
dependencies:
|
1681 |
+
path-key: 3.1.1
|
1682 |
+
shebang-command: 2.0.0
|
1683 |
+
which: 2.0.2
|
1684 |
+
|
1685 |
+
[email protected]: {}
|
1686 |
+
|
1687 |
+
[email protected]: {}
|
1688 |
+
|
1689 | |
1690 |
dependencies:
|
1691 |
ms: 2.1.2
|
1692 |
|
1693 |
+
[email protected]: {}
|
1694 |
+
|
1695 |
+
[email protected]: {}
|
1696 |
+
|
1697 |
+
[email protected]: {}
|
1698 |
+
|
1699 |
+
[email protected]: {}
|
1700 |
+
|
1701 | |
1702 |
|
1703 |
+
[email protected]: {}
|
1704 |
+
|
1705 |
+
[email protected]: {}
|
1706 |
+
|
1707 | |
1708 |
|
1709 | |
|
|
1734 |
'@esbuild/win32-ia32': 0.25.5
|
1735 |
'@esbuild/win32-x64': 0.25.5
|
1736 |
|
1737 |
+
[email protected]: {}
|
1738 |
+
|
1739 | |
1740 |
+
dependencies:
|
1741 |
+
'@nodelib/fs.stat': 2.0.5
|
1742 |
+
'@nodelib/fs.walk': 1.2.8
|
1743 |
+
glob-parent: 5.1.2
|
1744 |
+
merge2: 1.4.1
|
1745 |
+
micromatch: 4.0.8
|
1746 |
+
|
1747 | |
1748 |
+
dependencies:
|
1749 |
+
reusify: 1.1.0
|
1750 |
+
|
1751 | |
1752 |
optionalDependencies:
|
1753 |
picomatch: 4.0.2
|
1754 |
|
1755 | |
1756 |
+
dependencies:
|
1757 |
+
to-regex-range: 5.0.1
|
1758 |
+
|
1759 | |
1760 |
+
dependencies:
|
1761 |
+
cross-spawn: 7.0.6
|
1762 |
+
signal-exit: 4.1.0
|
1763 |
+
|
1764 |
+
[email protected]: {}
|
1765 |
+
|
1766 | |
1767 |
optional: true
|
1768 |
|
1769 |
+
[email protected]: {}
|
1770 |
+
|
1771 |
+
[email protected]: {}
|
1772 |
+
|
1773 | |
1774 |
|
1775 | |
1776 |
dependencies:
|
1777 |
resolve-pkg-maps: 1.0.0
|
1778 |
|
1779 | |
1780 |
+
dependencies:
|
1781 |
+
is-glob: 4.0.3
|
1782 |
+
|
1783 | |
1784 |
+
dependencies:
|
1785 |
+
is-glob: 4.0.3
|
1786 |
+
|
1787 | |
1788 |
+
dependencies:
|
1789 |
+
foreground-child: 3.3.1
|
1790 |
+
jackspeak: 3.4.3
|
1791 |
+
minimatch: 9.0.5
|
1792 |
+
minipass: 7.1.2
|
1793 |
+
package-json-from-dist: 1.0.1
|
1794 |
+
path-scurry: 1.11.1
|
1795 |
+
|
1796 |
+
[email protected]: {}
|
1797 |
+
|
1798 | |
1799 |
+
dependencies:
|
1800 |
+
function-bind: 1.1.2
|
1801 |
+
|
1802 | |
1803 |
+
dependencies:
|
1804 |
+
binary-extensions: 2.3.0
|
1805 |
+
|
1806 | |
1807 |
+
dependencies:
|
1808 |
+
hasown: 2.0.2
|
1809 |
+
|
1810 |
+
[email protected]: {}
|
1811 |
+
|
1812 |
+
[email protected]: {}
|
1813 |
+
|
1814 | |
1815 |
dependencies:
|
1816 |
get-east-asian-width: 1.3.0
|
1817 |
|
1818 | |
1819 |
+
dependencies:
|
1820 |
+
is-extglob: 2.1.1
|
1821 |
+
|
1822 |
+
[email protected]: {}
|
1823 |
+
|
1824 |
+
[email protected]: {}
|
1825 |
+
|
1826 | |
1827 |
+
dependencies:
|
1828 |
+
'@isaacs/cliui': 8.0.2
|
1829 |
+
optionalDependencies:
|
1830 |
+
'@pkgjs/parseargs': 0.11.0
|
1831 |
+
|
1832 |
+
[email protected]: {}
|
1833 |
+
|
1834 |
+
[email protected]: {}
|
1835 |
+
|
1836 |
+
[email protected]: {}
|
1837 |
+
|
1838 |
+
[email protected]: {}
|
1839 |
+
|
1840 |
+
[email protected]: {}
|
1841 |
+
|
1842 |
+
[email protected]: {}
|
1843 |
+
|
1844 | |
1845 |
dependencies:
|
1846 |
ansi-escapes: 7.0.0
|
|
|
1849 |
strip-ansi: 7.1.0
|
1850 |
wrap-ansi: 9.0.0
|
1851 |
|
1852 | |
1853 |
+
dependencies:
|
1854 |
+
js-tokens: 4.0.0
|
1855 |
+
|
1856 |
+
[email protected]: {}
|
1857 |
+
|
1858 | |
1859 |
+
dependencies:
|
1860 |
+
yallist: 3.1.1
|
1861 |
+
|
1862 | |
1863 |
+
dependencies:
|
1864 |
+
react: 18.3.1
|
1865 |
+
|
1866 |
+
[email protected]: {}
|
1867 |
+
|
1868 | |
1869 |
+
dependencies:
|
1870 |
+
braces: 3.0.3
|
1871 |
+
picomatch: 2.3.1
|
1872 |
+
|
1873 | |
1874 |
|
1875 | |
1876 |
+
dependencies:
|
1877 |
+
brace-expansion: 2.0.2
|
1878 |
+
|
1879 |
+
[email protected]: {}
|
1880 |
+
|
1881 | |
1882 |
|
1883 | |
1884 |
+
dependencies:
|
1885 |
+
any-promise: 1.3.0
|
1886 |
+
object-assign: 4.1.1
|
1887 |
+
thenify-all: 1.6.0
|
1888 |
+
|
1889 | |
1890 |
|
1891 | |
1892 |
|
1893 | |
1894 |
|
1895 |
+
[email protected]: {}
|
1896 |
+
|
1897 |
+
[email protected]: {}
|
1898 |
+
|
1899 |
+
[email protected]: {}
|
1900 |
+
|
1901 |
+
[email protected]: {}
|
1902 |
+
|
1903 |
+
[email protected]: {}
|
1904 |
+
|
1905 | |
1906 |
dependencies:
|
1907 |
mimic-function: 5.0.1
|
1908 |
|
1909 |
+
[email protected]: {}
|
1910 |
+
|
1911 |
+
[email protected]: {}
|
1912 |
+
|
1913 |
+
[email protected]: {}
|
1914 |
+
|
1915 | |
1916 |
+
dependencies:
|
1917 |
+
lru-cache: 10.4.3
|
1918 |
+
minipass: 7.1.2
|
1919 |
+
|
1920 | |
1921 |
|
1922 |
+
[email protected]: {}
|
1923 |
+
|
1924 | |
1925 |
|
1926 |
+
[email protected]: {}
|
1927 |
+
|
1928 |
+
[email protected]: {}
|
1929 |
+
|
1930 | |
1931 |
+
dependencies:
|
1932 |
+
postcss: 8.5.5
|
1933 |
+
postcss-value-parser: 4.2.0
|
1934 |
+
read-cache: 1.0.0
|
1935 |
+
resolve: 1.22.10
|
1936 |
+
|
1937 | |
1938 |
+
dependencies:
|
1939 |
+
camelcase-css: 2.0.1
|
1940 |
+
postcss: 8.5.5
|
1941 |
+
|
1942 | |
1943 |
+
dependencies:
|
1944 |
+
lilconfig: 3.1.3
|
1945 |
+
yaml: 2.8.0
|
1946 |
+
optionalDependencies:
|
1947 |
+
postcss: 8.5.5
|
1948 |
+
|
1949 | |
1950 |
+
dependencies:
|
1951 |
+
postcss: 8.5.5
|
1952 |
+
postcss-selector-parser: 6.1.2
|
1953 |
+
|
1954 | |
1955 |
+
dependencies:
|
1956 |
+
cssesc: 3.0.0
|
1957 |
+
util-deprecate: 1.0.2
|
1958 |
+
|
1959 |
+
[email protected]: {}
|
1960 |
+
|
1961 | |
1962 |
dependencies:
|
1963 |
nanoid: 3.3.11
|
1964 |
picocolors: 1.1.1
|
1965 |
source-map-js: 1.2.1
|
1966 |
|
1967 |
+
[email protected]: {}
|
1968 |
+
|
1969 | |
1970 |
+
dependencies:
|
1971 |
+
loose-envify: 1.4.0
|
1972 |
+
react: 18.3.1
|
1973 |
+
scheduler: 0.23.2
|
1974 |
+
|
1975 |
+
[email protected]: {}
|
1976 |
+
|
1977 | |
1978 |
+
dependencies:
|
1979 |
+
loose-envify: 1.4.0
|
1980 |
+
|
1981 | |
1982 |
+
dependencies:
|
1983 |
+
pify: 2.3.0
|
1984 |
+
|
1985 | |
1986 |
+
dependencies:
|
1987 |
+
picomatch: 2.3.1
|
1988 |
+
|
1989 | |
1990 |
|
1991 | |
1992 |
+
dependencies:
|
1993 |
+
is-core-module: 2.16.1
|
1994 |
+
path-parse: 1.0.7
|
1995 |
+
supports-preserve-symlinks-flag: 1.0.0
|
1996 |
+
|
1997 | |
1998 |
dependencies:
|
1999 |
onetime: 7.0.0
|
2000 |
signal-exit: 4.1.0
|
2001 |
|
2002 |
+
[email protected]: {}
|
2003 |
+
|
2004 | |
2005 |
dependencies:
|
2006 |
'@types/estree': 1.0.7
|
|
|
2027 |
'@rollup/rollup-win32-x64-msvc': 4.43.0
|
2028 |
fsevents: 2.3.3
|
2029 |
|
2030 | |
2031 |
+
dependencies:
|
2032 |
+
queue-microtask: 1.2.3
|
2033 |
+
|
2034 | |
2035 |
+
dependencies:
|
2036 |
+
loose-envify: 1.4.0
|
2037 |
+
|
2038 |
+
[email protected]: {}
|
2039 |
+
|
2040 | |
2041 |
dependencies:
|
2042 |
'@serialport/binding-mock': 10.2.2
|
|
|
2056 |
transitivePeerDependencies:
|
2057 |
- supports-color
|
2058 |
|
2059 | |
2060 |
+
dependencies:
|
2061 |
+
shebang-regex: 3.0.0
|
2062 |
+
|
2063 |
+
[email protected]: {}
|
2064 |
+
|
2065 | |
2066 |
|
2067 | |
|
|
2071 |
|
2072 | |
2073 |
|
2074 | |
2075 |
+
dependencies:
|
2076 |
+
emoji-regex: 8.0.0
|
2077 |
+
is-fullwidth-code-point: 3.0.0
|
2078 |
+
strip-ansi: 6.0.1
|
2079 |
+
|
2080 | |
2081 |
+
dependencies:
|
2082 |
+
eastasianwidth: 0.2.0
|
2083 |
+
emoji-regex: 9.2.2
|
2084 |
+
strip-ansi: 7.1.0
|
2085 |
+
|
2086 | |
2087 |
dependencies:
|
2088 |
emoji-regex: 10.4.0
|
2089 |
get-east-asian-width: 1.3.0
|
2090 |
strip-ansi: 7.1.0
|
2091 |
|
2092 | |
2093 |
+
dependencies:
|
2094 |
+
ansi-regex: 5.0.1
|
2095 |
+
|
2096 | |
2097 |
dependencies:
|
2098 |
ansi-regex: 6.1.0
|
2099 |
|
2100 | |
2101 |
+
dependencies:
|
2102 |
+
'@jridgewell/gen-mapping': 0.3.8
|
2103 |
+
commander: 4.1.1
|
2104 |
+
glob: 10.4.5
|
2105 |
+
lines-and-columns: 1.2.4
|
2106 |
+
mz: 2.7.0
|
2107 |
+
pirates: 4.0.7
|
2108 |
+
ts-interface-checker: 0.1.13
|
2109 |
+
|
2110 |
+
[email protected]: {}
|
2111 |
+
|
2112 |
+
[email protected]: {}
|
2113 |
+
|
2114 | |
2115 |
+
dependencies:
|
2116 |
+
'@alloc/quick-lru': 5.2.0
|
2117 |
+
arg: 5.0.2
|
2118 |
+
chokidar: 3.6.0
|
2119 |
+
didyoumean: 1.2.2
|
2120 |
+
dlv: 1.1.3
|
2121 |
+
fast-glob: 3.3.3
|
2122 |
+
glob-parent: 6.0.2
|
2123 |
+
is-glob: 4.0.3
|
2124 |
+
jiti: 1.21.7
|
2125 |
+
lilconfig: 3.1.3
|
2126 |
+
micromatch: 4.0.8
|
2127 |
+
normalize-path: 3.0.0
|
2128 |
+
object-hash: 3.0.0
|
2129 |
+
picocolors: 1.1.1
|
2130 |
+
postcss: 8.5.5
|
2131 |
+
postcss-import: 15.1.0([email protected])
|
2132 |
+
postcss-js: 4.0.1([email protected])
|
2133 |
+
postcss-load-config: 4.0.2([email protected])
|
2134 |
+
postcss-nested: 6.2.0([email protected])
|
2135 |
+
postcss-selector-parser: 6.1.2
|
2136 |
+
resolve: 1.22.10
|
2137 |
+
sucrase: 3.35.0
|
2138 |
+
transitivePeerDependencies:
|
2139 |
+
- ts-node
|
2140 |
+
|
2141 | |
2142 |
+
dependencies:
|
2143 |
+
thenify: 3.3.1
|
2144 |
+
|
2145 | |
2146 |
+
dependencies:
|
2147 |
+
any-promise: 1.3.0
|
2148 |
+
|
2149 | |
2150 |
dependencies:
|
2151 |
fdir: 6.4.6([email protected])
|
2152 |
picomatch: 4.0.2
|
2153 |
|
2154 | |
2155 |
+
dependencies:
|
2156 |
+
is-number: 7.0.0
|
2157 |
+
|
2158 |
+
[email protected]: {}
|
2159 |
+
|
2160 | |
2161 |
dependencies:
|
2162 |
esbuild: 0.25.5
|
|
|
2168 |
|
2169 | |
2170 |
|
2171 |
+
update-browserslist-db@1.1.3(browserslist@4.25.0):
|
2172 |
+
dependencies:
|
2173 |
+
browserslist: 4.25.0
|
2174 |
+
escalade: 3.2.0
|
2175 |
+
picocolors: 1.1.1
|
2176 |
+
|
2177 |
+
[email protected]: {}
|
2178 |
+
|
2179 | |
2180 |
dependencies:
|
2181 |
esbuild: 0.25.5
|
2182 |
fdir: 6.4.6([email protected])
|
|
|
2187 |
optionalDependencies:
|
2188 |
'@types/node': 22.15.31
|
2189 |
fsevents: 2.3.3
|
2190 |
+
jiti: 1.21.7
|
2191 |
tsx: 4.20.3
|
2192 |
+
yaml: 2.8.0
|
2193 |
+
|
2194 | |
2195 |
+
dependencies:
|
2196 |
+
isexe: 2.0.0
|
2197 |
+
|
2198 | |
2199 |
+
dependencies:
|
2200 |
+
ansi-styles: 4.3.0
|
2201 |
+
string-width: 4.2.3
|
2202 |
+
strip-ansi: 6.0.1
|
2203 |
+
|
2204 | |
2205 |
+
dependencies:
|
2206 |
+
ansi-styles: 6.2.1
|
2207 |
+
string-width: 5.1.2
|
2208 |
+
strip-ansi: 7.1.0
|
2209 |
|
2210 | |
2211 |
dependencies:
|
2212 |
ansi-styles: 6.2.1
|
2213 |
string-width: 7.2.0
|
2214 |
strip-ansi: 7.1.0
|
2215 |
+
|
2216 |
+
[email protected]: {}
|
2217 |
+
|
2218 |
+
[email protected]: {}
|
postcss.config.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import tailwindcss from "tailwindcss";
|
2 |
+
import autoprefixer from "autoprefixer";
|
3 |
+
|
4 |
+
export default {
|
5 |
+
plugins: [tailwindcss, autoprefixer],
|
6 |
+
};
|
postcss.config.mjs
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import tailwindcss from "tailwindcss";
|
2 |
+
import autoprefixer from "autoprefixer";
|
3 |
+
|
4 |
+
export default {
|
5 |
+
plugins: [tailwindcss, autoprefixer],
|
6 |
+
};
|
src/demo/App.tsx
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState } from "react";
|
2 |
+
import { Home } from "./pages/Home";
|
3 |
+
import { ErrorBoundary } from "./components/ErrorBoundary";
|
4 |
+
import type { ConnectedRobot } from "./types";
|
5 |
+
|
6 |
+
export function App() {
|
7 |
+
const [connectedRobots, setConnectedRobots] = useState<ConnectedRobot[]>([]);
|
8 |
+
|
9 |
+
return (
|
10 |
+
<ErrorBoundary>
|
11 |
+
<div className="min-h-screen bg-background">
|
12 |
+
<Home
|
13 |
+
onGetStarted={() => {}} // No longer needed
|
14 |
+
connectedRobots={connectedRobots}
|
15 |
+
onConnectedRobotsChange={setConnectedRobots}
|
16 |
+
/>
|
17 |
+
</div>
|
18 |
+
</ErrorBoundary>
|
19 |
+
);
|
20 |
+
}
|
src/demo/components/CalibrationPanel.tsx
ADDED
@@ -0,0 +1,526 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useEffect, useCallback, useRef } from "react";
|
2 |
+
import { Button } from "./ui/button";
|
3 |
+
import {
|
4 |
+
Card,
|
5 |
+
CardContent,
|
6 |
+
CardDescription,
|
7 |
+
CardHeader,
|
8 |
+
CardTitle,
|
9 |
+
} from "./ui/card";
|
10 |
+
import { Badge } from "./ui/badge";
|
11 |
+
import { calibrateWithPort } from "../../lerobot/web/calibrate";
|
12 |
+
import type { ConnectedRobot } from "../types";
|
13 |
+
|
14 |
+
interface CalibrationPanelProps {
|
15 |
+
robot: ConnectedRobot;
|
16 |
+
onFinish: () => void;
|
17 |
+
}
|
18 |
+
|
19 |
+
interface MotorCalibrationData {
|
20 |
+
name: string;
|
21 |
+
current: number;
|
22 |
+
min: number;
|
23 |
+
max: number;
|
24 |
+
range: number;
|
25 |
+
}
|
26 |
+
|
27 |
+
export function CalibrationPanel({ robot, onFinish }: CalibrationPanelProps) {
|
28 |
+
const [isCalibrating, setIsCalibrating] = useState(false);
|
29 |
+
const [motorData, setMotorData] = useState<MotorCalibrationData[]>([]);
|
30 |
+
const [calibrationStatus, setCalibrationStatus] =
|
31 |
+
useState<string>("Ready to calibrate");
|
32 |
+
const [calibrationComplete, setCalibrationComplete] = useState(false);
|
33 |
+
const [readCount, setReadCount] = useState(0);
|
34 |
+
|
35 |
+
const animationFrameRef = useRef<number>();
|
36 |
+
const lastReadTime = useRef<number>(0);
|
37 |
+
const isReading = useRef<boolean>(false);
|
38 |
+
|
39 |
+
// Motor names matching Node CLI exactly
|
40 |
+
const motorNames = [
|
41 |
+
"waist",
|
42 |
+
"shoulder",
|
43 |
+
"elbow",
|
44 |
+
"forearm_roll",
|
45 |
+
"wrist_angle",
|
46 |
+
"wrist_rotate",
|
47 |
+
];
|
48 |
+
|
49 |
+
// Initialize motor data with center positions
|
50 |
+
const initializeMotorData = useCallback(() => {
|
51 |
+
const initialData = motorNames.map((name) => ({
|
52 |
+
name,
|
53 |
+
current: 2047, // Center position for STS3215 (4095/2)
|
54 |
+
min: 2047,
|
55 |
+
max: 2047,
|
56 |
+
range: 0,
|
57 |
+
}));
|
58 |
+
setMotorData(initialData);
|
59 |
+
setReadCount(0);
|
60 |
+
}, []);
|
61 |
+
|
62 |
+
// Keep track of last known good positions to avoid glitches
|
63 |
+
const lastKnownPositions = useRef<number[]>([
|
64 |
+
2047, 2047, 2047, 2047, 2047, 2047,
|
65 |
+
]);
|
66 |
+
|
67 |
+
// Read actual motor positions with robust error handling
|
68 |
+
const readMotorPositions = useCallback(async (): Promise<number[]> => {
|
69 |
+
if (!robot.port || !robot.port.readable || !robot.port.writable) {
|
70 |
+
throw new Error("Robot port not available for communication");
|
71 |
+
}
|
72 |
+
|
73 |
+
const positions: number[] = [];
|
74 |
+
const motorIds = [1, 2, 3, 4, 5, 6];
|
75 |
+
|
76 |
+
// Get persistent reader/writer for this session
|
77 |
+
const reader = robot.port.readable.getReader();
|
78 |
+
const writer = robot.port.writable.getWriter();
|
79 |
+
|
80 |
+
try {
|
81 |
+
for (let index = 0; index < motorIds.length; index++) {
|
82 |
+
const motorId = motorIds[index];
|
83 |
+
let success = false;
|
84 |
+
let retries = 2; // Allow 2 retries per motor
|
85 |
+
|
86 |
+
while (!success && retries > 0) {
|
87 |
+
try {
|
88 |
+
// Create STS3215 Read Position packet
|
89 |
+
const packet = new Uint8Array([
|
90 |
+
0xff,
|
91 |
+
0xff,
|
92 |
+
motorId,
|
93 |
+
0x04,
|
94 |
+
0x02,
|
95 |
+
0x38,
|
96 |
+
0x02,
|
97 |
+
0x00,
|
98 |
+
]);
|
99 |
+
const checksum = ~(motorId + 0x04 + 0x02 + 0x38 + 0x02) & 0xff;
|
100 |
+
packet[7] = checksum;
|
101 |
+
|
102 |
+
// Write packet
|
103 |
+
await writer.write(packet);
|
104 |
+
|
105 |
+
// Wait for response
|
106 |
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
107 |
+
|
108 |
+
// Read with timeout
|
109 |
+
const timeoutPromise = new Promise((_, reject) =>
|
110 |
+
setTimeout(() => reject(new Error("Timeout")), 100)
|
111 |
+
);
|
112 |
+
|
113 |
+
const result = (await Promise.race([
|
114 |
+
reader.read(),
|
115 |
+
timeoutPromise,
|
116 |
+
])) as ReadableStreamReadResult<Uint8Array>;
|
117 |
+
|
118 |
+
if (
|
119 |
+
result &&
|
120 |
+
!result.done &&
|
121 |
+
result.value &&
|
122 |
+
result.value.length >= 7
|
123 |
+
) {
|
124 |
+
const response = result.value;
|
125 |
+
const responseId = response[2];
|
126 |
+
const error = response[4];
|
127 |
+
|
128 |
+
// Check if this is the response we're looking for
|
129 |
+
if (responseId === motorId && error === 0) {
|
130 |
+
const position = response[5] | (response[6] << 8);
|
131 |
+
positions.push(position);
|
132 |
+
lastKnownPositions.current[index] = position; // Update last known good position
|
133 |
+
success = true;
|
134 |
+
} else {
|
135 |
+
// Wrong motor ID or error - might be out of sync, try again
|
136 |
+
retries--;
|
137 |
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
138 |
+
}
|
139 |
+
} else {
|
140 |
+
retries--;
|
141 |
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
142 |
+
}
|
143 |
+
} catch (error) {
|
144 |
+
retries--;
|
145 |
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
146 |
+
}
|
147 |
+
}
|
148 |
+
|
149 |
+
if (!success) {
|
150 |
+
// Use last known good position instead of fallback center position
|
151 |
+
positions.push(lastKnownPositions.current[index]);
|
152 |
+
}
|
153 |
+
|
154 |
+
// Small delay between motors
|
155 |
+
await new Promise((resolve) => setTimeout(resolve, 2));
|
156 |
+
}
|
157 |
+
} finally {
|
158 |
+
reader.releaseLock();
|
159 |
+
writer.releaseLock();
|
160 |
+
}
|
161 |
+
|
162 |
+
return positions;
|
163 |
+
}, [robot.port]);
|
164 |
+
|
165 |
+
// Update motor data with new readings - NO SIMULATION, REAL VALUES ONLY
|
166 |
+
const updateMotorData = useCallback(async () => {
|
167 |
+
if (!isCalibrating || isReading.current) return;
|
168 |
+
|
169 |
+
const now = performance.now();
|
170 |
+
// Read at ~15Hz to reduce serial communication load (66ms intervals)
|
171 |
+
if (now - lastReadTime.current < 66) return;
|
172 |
+
|
173 |
+
lastReadTime.current = now;
|
174 |
+
isReading.current = true;
|
175 |
+
|
176 |
+
try {
|
177 |
+
const positions = await readMotorPositions();
|
178 |
+
|
179 |
+
// Always update since we're now keeping last known good positions
|
180 |
+
// Only show warning if all motors are still at center position (no successful reads yet)
|
181 |
+
const allAtCenter = positions.every((pos) => pos === 2047);
|
182 |
+
if (allAtCenter && readCount === 0) {
|
183 |
+
console.log("No motor data received yet - still trying to connect");
|
184 |
+
setCalibrationStatus("Connecting to motors - please wait...");
|
185 |
+
}
|
186 |
+
|
187 |
+
setMotorData((prev) =>
|
188 |
+
prev.map((motor, index) => {
|
189 |
+
const current = positions[index];
|
190 |
+
const min = Math.min(motor.min, current);
|
191 |
+
const max = Math.max(motor.max, current);
|
192 |
+
const range = max - min;
|
193 |
+
|
194 |
+
return {
|
195 |
+
...motor,
|
196 |
+
current,
|
197 |
+
min,
|
198 |
+
max,
|
199 |
+
range,
|
200 |
+
};
|
201 |
+
})
|
202 |
+
);
|
203 |
+
|
204 |
+
setReadCount((prev) => prev + 1);
|
205 |
+
console.log(`Real motor positions:`, positions);
|
206 |
+
} catch (error) {
|
207 |
+
console.warn("Failed to read motor positions:", error);
|
208 |
+
setCalibrationStatus(
|
209 |
+
`Error reading motors: ${
|
210 |
+
error instanceof Error ? error.message : error
|
211 |
+
}`
|
212 |
+
);
|
213 |
+
} finally {
|
214 |
+
isReading.current = false;
|
215 |
+
}
|
216 |
+
}, [isCalibrating, readMotorPositions]);
|
217 |
+
|
218 |
+
// Animation loop using RAF (requestAnimationFrame)
|
219 |
+
const animationLoop = useCallback(() => {
|
220 |
+
updateMotorData();
|
221 |
+
|
222 |
+
if (isCalibrating) {
|
223 |
+
animationFrameRef.current = requestAnimationFrame(animationLoop);
|
224 |
+
}
|
225 |
+
}, [isCalibrating, updateMotorData]);
|
226 |
+
|
227 |
+
useEffect(() => {
|
228 |
+
initializeMotorData();
|
229 |
+
}, [initializeMotorData]);
|
230 |
+
|
231 |
+
useEffect(() => {
|
232 |
+
if (isCalibrating) {
|
233 |
+
animationFrameRef.current = requestAnimationFrame(animationLoop);
|
234 |
+
} else {
|
235 |
+
if (animationFrameRef.current) {
|
236 |
+
cancelAnimationFrame(animationFrameRef.current);
|
237 |
+
}
|
238 |
+
}
|
239 |
+
|
240 |
+
return () => {
|
241 |
+
if (animationFrameRef.current) {
|
242 |
+
cancelAnimationFrame(animationFrameRef.current);
|
243 |
+
}
|
244 |
+
};
|
245 |
+
}, [isCalibrating, animationLoop]);
|
246 |
+
|
247 |
+
const startCalibration = async () => {
|
248 |
+
if (!robot.port || !robot.robotType) {
|
249 |
+
setCalibrationStatus("Error: Invalid robot configuration");
|
250 |
+
return;
|
251 |
+
}
|
252 |
+
|
253 |
+
setCalibrationStatus(
|
254 |
+
"Initializing calibration - reading current positions..."
|
255 |
+
);
|
256 |
+
|
257 |
+
try {
|
258 |
+
// Get current positions to use as starting point for min/max
|
259 |
+
const currentPositions = await readMotorPositions();
|
260 |
+
|
261 |
+
// Reset calibration data with current positions as both min and max
|
262 |
+
const freshData = motorNames.map((name, index) => ({
|
263 |
+
name,
|
264 |
+
current: currentPositions[index],
|
265 |
+
min: currentPositions[index], // Start with current position
|
266 |
+
max: currentPositions[index], // Start with current position
|
267 |
+
range: 0, // No range yet
|
268 |
+
}));
|
269 |
+
|
270 |
+
setMotorData(freshData);
|
271 |
+
setReadCount(0);
|
272 |
+
setIsCalibrating(true);
|
273 |
+
setCalibrationComplete(false);
|
274 |
+
setCalibrationStatus(
|
275 |
+
"Recording ranges of motion - move all joints through their full range..."
|
276 |
+
);
|
277 |
+
} catch (error) {
|
278 |
+
setCalibrationStatus(
|
279 |
+
`Error starting calibration: ${
|
280 |
+
error instanceof Error ? error.message : error
|
281 |
+
}`
|
282 |
+
);
|
283 |
+
}
|
284 |
+
};
|
285 |
+
|
286 |
+
// Generate calibration config JSON matching Node CLI format
|
287 |
+
const generateConfigJSON = () => {
|
288 |
+
const calibrationData = {
|
289 |
+
homing_offset: motorData.map((motor) => motor.current - 2047), // Center offset
|
290 |
+
drive_mode: [3, 3, 3, 3, 3, 3], // SO-100 standard drive mode
|
291 |
+
start_pos: motorData.map((motor) => motor.min),
|
292 |
+
end_pos: motorData.map((motor) => motor.max),
|
293 |
+
calib_mode: ["middle", "middle", "middle", "middle", "middle", "middle"], // SO-100 standard
|
294 |
+
motor_names: motorNames,
|
295 |
+
};
|
296 |
+
|
297 |
+
return calibrationData;
|
298 |
+
};
|
299 |
+
|
300 |
+
// Download calibration config as JSON file
|
301 |
+
const downloadConfigJSON = () => {
|
302 |
+
const configData = generateConfigJSON();
|
303 |
+
const jsonString = JSON.stringify(configData, null, 2);
|
304 |
+
const blob = new Blob([jsonString], { type: "application/json" });
|
305 |
+
const url = URL.createObjectURL(blob);
|
306 |
+
|
307 |
+
const link = document.createElement("a");
|
308 |
+
link.href = url;
|
309 |
+
link.download = `${robot.robotId || robot.robotType}_calibration.json`;
|
310 |
+
document.body.appendChild(link);
|
311 |
+
link.click();
|
312 |
+
document.body.removeChild(link);
|
313 |
+
URL.revokeObjectURL(url);
|
314 |
+
};
|
315 |
+
|
316 |
+
const finishCalibration = () => {
|
317 |
+
setIsCalibrating(false);
|
318 |
+
setCalibrationComplete(true);
|
319 |
+
setCalibrationStatus(
|
320 |
+
`✅ Calibration completed! Recorded ${readCount} position readings.`
|
321 |
+
);
|
322 |
+
|
323 |
+
// Save calibration config to localStorage using serial number
|
324 |
+
const configData = generateConfigJSON();
|
325 |
+
const serialNumber = (robot as any).serialNumber;
|
326 |
+
|
327 |
+
if (!serialNumber) {
|
328 |
+
console.warn("⚠️ No serial number available for calibration storage");
|
329 |
+
setCalibrationStatus(
|
330 |
+
`⚠️ Calibration completed but cannot save - no robot serial number`
|
331 |
+
);
|
332 |
+
return;
|
333 |
+
}
|
334 |
+
|
335 |
+
const calibrationKey = `lerobot-calibration-${serialNumber}`;
|
336 |
+
try {
|
337 |
+
localStorage.setItem(
|
338 |
+
calibrationKey,
|
339 |
+
JSON.stringify({
|
340 |
+
config: configData,
|
341 |
+
timestamp: new Date().toISOString(),
|
342 |
+
serialNumber: serialNumber,
|
343 |
+
robotId: robot.robotId,
|
344 |
+
robotType: robot.robotType,
|
345 |
+
readCount: readCount,
|
346 |
+
})
|
347 |
+
);
|
348 |
+
console.log(`💾 Calibration saved for robot serial: ${serialNumber}`);
|
349 |
+
} catch (error) {
|
350 |
+
console.warn("Failed to save calibration to localStorage:", error);
|
351 |
+
setCalibrationStatus(
|
352 |
+
`⚠️ Calibration completed but save failed: ${error}`
|
353 |
+
);
|
354 |
+
}
|
355 |
+
};
|
356 |
+
|
357 |
+
return (
|
358 |
+
<div className="space-y-4">
|
359 |
+
{/* Calibration Status Card */}
|
360 |
+
<Card>
|
361 |
+
<CardHeader>
|
362 |
+
<div className="flex items-center justify-between">
|
363 |
+
<div>
|
364 |
+
<CardTitle className="text-lg">
|
365 |
+
🛠️ Calibrating: {robot.robotId}
|
366 |
+
</CardTitle>
|
367 |
+
<CardDescription>
|
368 |
+
{robot.robotType?.replace("_", " ")} • {robot.name}
|
369 |
+
</CardDescription>
|
370 |
+
</div>
|
371 |
+
<Badge
|
372 |
+
variant={
|
373 |
+
isCalibrating
|
374 |
+
? "default"
|
375 |
+
: calibrationComplete
|
376 |
+
? "default"
|
377 |
+
: "outline"
|
378 |
+
}
|
379 |
+
>
|
380 |
+
{isCalibrating
|
381 |
+
? "Recording"
|
382 |
+
: calibrationComplete
|
383 |
+
? "Complete"
|
384 |
+
: "Ready"}
|
385 |
+
</Badge>
|
386 |
+
</div>
|
387 |
+
</CardHeader>
|
388 |
+
<CardContent>
|
389 |
+
<div className="space-y-4">
|
390 |
+
<div className="p-3 bg-blue-50 rounded-lg">
|
391 |
+
<p className="text-sm font-medium text-blue-900">Status:</p>
|
392 |
+
<p className="text-sm text-blue-800">{calibrationStatus}</p>
|
393 |
+
{isCalibrating && (
|
394 |
+
<p className="text-xs text-blue-600 mt-1">
|
395 |
+
Readings: {readCount} | Press "Finish Calibration" when done
|
396 |
+
</p>
|
397 |
+
)}
|
398 |
+
</div>
|
399 |
+
|
400 |
+
<div className="flex gap-2">
|
401 |
+
{!isCalibrating && !calibrationComplete && (
|
402 |
+
<Button onClick={startCalibration}>Start Calibration</Button>
|
403 |
+
)}
|
404 |
+
|
405 |
+
{isCalibrating && (
|
406 |
+
<Button onClick={finishCalibration} variant="outline">
|
407 |
+
Finish Calibration
|
408 |
+
</Button>
|
409 |
+
)}
|
410 |
+
|
411 |
+
{calibrationComplete && (
|
412 |
+
<>
|
413 |
+
<Button onClick={downloadConfigJSON} variant="outline">
|
414 |
+
Download Config JSON
|
415 |
+
</Button>
|
416 |
+
<Button onClick={onFinish}>Done</Button>
|
417 |
+
</>
|
418 |
+
)}
|
419 |
+
</div>
|
420 |
+
</div>
|
421 |
+
</CardContent>
|
422 |
+
</Card>
|
423 |
+
|
424 |
+
{/* Configuration JSON Display */}
|
425 |
+
{calibrationComplete && (
|
426 |
+
<Card>
|
427 |
+
<CardHeader>
|
428 |
+
<CardTitle className="text-lg">
|
429 |
+
🎯 Calibration Configuration
|
430 |
+
</CardTitle>
|
431 |
+
<CardDescription>
|
432 |
+
Copy this JSON or download it for your robot setup
|
433 |
+
</CardDescription>
|
434 |
+
</CardHeader>
|
435 |
+
<CardContent>
|
436 |
+
<div className="space-y-3">
|
437 |
+
<pre className="bg-gray-100 p-4 rounded-lg text-sm overflow-x-auto border">
|
438 |
+
<code>{JSON.stringify(generateConfigJSON(), null, 2)}</code>
|
439 |
+
</pre>
|
440 |
+
<div className="flex gap-2">
|
441 |
+
<Button onClick={downloadConfigJSON} variant="outline">
|
442 |
+
📄 Download JSON File
|
443 |
+
</Button>
|
444 |
+
<Button
|
445 |
+
onClick={() => {
|
446 |
+
navigator.clipboard.writeText(
|
447 |
+
JSON.stringify(generateConfigJSON(), null, 2)
|
448 |
+
);
|
449 |
+
}}
|
450 |
+
variant="outline"
|
451 |
+
>
|
452 |
+
📋 Copy to Clipboard
|
453 |
+
</Button>
|
454 |
+
</div>
|
455 |
+
</div>
|
456 |
+
</CardContent>
|
457 |
+
</Card>
|
458 |
+
)}
|
459 |
+
|
460 |
+
{/* Live Position Recording Table (matching Node CLI exactly) */}
|
461 |
+
<Card>
|
462 |
+
<CardHeader>
|
463 |
+
<CardTitle className="text-lg">Live Position Recording</CardTitle>
|
464 |
+
<CardDescription>
|
465 |
+
Real-time motor position feedback - exactly like Node CLI
|
466 |
+
</CardDescription>
|
467 |
+
</CardHeader>
|
468 |
+
<CardContent>
|
469 |
+
<div className="overflow-hidden rounded-lg border">
|
470 |
+
<table className="w-full font-mono text-sm">
|
471 |
+
<thead className="bg-gray-50">
|
472 |
+
<tr>
|
473 |
+
<th className="px-4 py-2 text-left font-medium text-gray-900">
|
474 |
+
Motor Name
|
475 |
+
</th>
|
476 |
+
<th className="px-4 py-2 text-right font-medium text-gray-900">
|
477 |
+
Current
|
478 |
+
</th>
|
479 |
+
<th className="px-4 py-2 text-right font-medium text-gray-900">
|
480 |
+
Min
|
481 |
+
</th>
|
482 |
+
<th className="px-4 py-2 text-right font-medium text-gray-900">
|
483 |
+
Max
|
484 |
+
</th>
|
485 |
+
<th className="px-4 py-2 text-right font-medium text-gray-900">
|
486 |
+
Range
|
487 |
+
</th>
|
488 |
+
</tr>
|
489 |
+
</thead>
|
490 |
+
<tbody className="divide-y divide-gray-200">
|
491 |
+
{motorData.map((motor, index) => (
|
492 |
+
<tr key={index} className="hover:bg-gray-50">
|
493 |
+
<td className="px-4 py-2 font-medium flex items-center gap-2">
|
494 |
+
{motor.name}
|
495 |
+
{motor.range > 100 && (
|
496 |
+
<span className="text-green-600 text-xs">✓</span>
|
497 |
+
)}
|
498 |
+
</td>
|
499 |
+
<td className="px-4 py-2 text-right">{motor.current}</td>
|
500 |
+
<td className="px-4 py-2 text-right">{motor.min}</td>
|
501 |
+
<td className="px-4 py-2 text-right">{motor.max}</td>
|
502 |
+
<td className="px-4 py-2 text-right font-medium">
|
503 |
+
<span
|
504 |
+
className={
|
505 |
+
motor.range > 100 ? "text-green-600" : "text-gray-500"
|
506 |
+
}
|
507 |
+
>
|
508 |
+
{motor.range}
|
509 |
+
</span>
|
510 |
+
</td>
|
511 |
+
</tr>
|
512 |
+
))}
|
513 |
+
</tbody>
|
514 |
+
</table>
|
515 |
+
</div>
|
516 |
+
|
517 |
+
{isCalibrating && (
|
518 |
+
<div className="mt-3 text-center text-sm text-gray-600">
|
519 |
+
Move joints through their full range of motion...
|
520 |
+
</div>
|
521 |
+
)}
|
522 |
+
</CardContent>
|
523 |
+
</Card>
|
524 |
+
</div>
|
525 |
+
);
|
526 |
+
}
|
src/demo/components/CalibrationWizard.tsx
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useEffect } from "react";
|
2 |
+
import { Button } from "./ui/button";
|
3 |
+
import {
|
4 |
+
Card,
|
5 |
+
CardContent,
|
6 |
+
CardDescription,
|
7 |
+
CardHeader,
|
8 |
+
CardTitle,
|
9 |
+
} from "./ui/card";
|
10 |
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
11 |
+
import { Badge } from "./ui/badge";
|
12 |
+
import { calibrateWithPort } from "../../lerobot/web/calibrate";
|
13 |
+
import type { ConnectedRobot } from "../types";
|
14 |
+
|
15 |
+
interface CalibrationWizardProps {
|
16 |
+
robot: ConnectedRobot;
|
17 |
+
onComplete: () => void;
|
18 |
+
onCancel: () => void;
|
19 |
+
}
|
20 |
+
|
21 |
+
interface CalibrationStep {
|
22 |
+
id: string;
|
23 |
+
title: string;
|
24 |
+
description: string;
|
25 |
+
status: "pending" | "running" | "complete" | "error";
|
26 |
+
message?: string;
|
27 |
+
}
|
28 |
+
|
29 |
+
export function CalibrationWizard({
|
30 |
+
robot,
|
31 |
+
onComplete,
|
32 |
+
onCancel,
|
33 |
+
}: CalibrationWizardProps) {
|
34 |
+
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
35 |
+
const [steps, setSteps] = useState<CalibrationStep[]>([
|
36 |
+
{
|
37 |
+
id: "init",
|
38 |
+
title: "Initialize Robot",
|
39 |
+
description: "Connecting to robot and checking status",
|
40 |
+
status: "pending",
|
41 |
+
},
|
42 |
+
{
|
43 |
+
id: "calibrate",
|
44 |
+
title: "Calibrate Motors",
|
45 |
+
description: "Running calibration sequence",
|
46 |
+
status: "pending",
|
47 |
+
},
|
48 |
+
{
|
49 |
+
id: "verify",
|
50 |
+
title: "Verify Calibration",
|
51 |
+
description: "Testing calibrated positions",
|
52 |
+
status: "pending",
|
53 |
+
},
|
54 |
+
{
|
55 |
+
id: "complete",
|
56 |
+
title: "Complete",
|
57 |
+
description: "Calibration finished successfully",
|
58 |
+
status: "pending",
|
59 |
+
},
|
60 |
+
]);
|
61 |
+
|
62 |
+
const [isRunning, setIsRunning] = useState(false);
|
63 |
+
const [error, setError] = useState<string | null>(null);
|
64 |
+
|
65 |
+
useEffect(() => {
|
66 |
+
startCalibration();
|
67 |
+
}, []);
|
68 |
+
|
69 |
+
const updateStep = (
|
70 |
+
stepId: string,
|
71 |
+
status: CalibrationStep["status"],
|
72 |
+
message?: string
|
73 |
+
) => {
|
74 |
+
setSteps((prev) =>
|
75 |
+
prev.map((step) =>
|
76 |
+
step.id === stepId ? { ...step, status, message } : step
|
77 |
+
)
|
78 |
+
);
|
79 |
+
};
|
80 |
+
|
81 |
+
const startCalibration = async () => {
|
82 |
+
if (!robot.port || !robot.robotType) {
|
83 |
+
setError("Invalid robot configuration");
|
84 |
+
return;
|
85 |
+
}
|
86 |
+
|
87 |
+
setIsRunning(true);
|
88 |
+
setError(null);
|
89 |
+
|
90 |
+
try {
|
91 |
+
// Step 1: Initialize
|
92 |
+
setCurrentStepIndex(0);
|
93 |
+
updateStep("init", "running");
|
94 |
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
95 |
+
updateStep("init", "complete", "Robot initialized successfully");
|
96 |
+
|
97 |
+
// Step 2: Calibrate
|
98 |
+
setCurrentStepIndex(1);
|
99 |
+
updateStep("calibrate", "running");
|
100 |
+
|
101 |
+
try {
|
102 |
+
await calibrateWithPort(robot.port, robot.robotType);
|
103 |
+
updateStep("calibrate", "complete", "Motor calibration completed");
|
104 |
+
} catch (error) {
|
105 |
+
updateStep(
|
106 |
+
"calibrate",
|
107 |
+
"error",
|
108 |
+
error instanceof Error ? error.message : "Calibration failed"
|
109 |
+
);
|
110 |
+
throw error;
|
111 |
+
}
|
112 |
+
|
113 |
+
// Step 3: Verify
|
114 |
+
setCurrentStepIndex(2);
|
115 |
+
updateStep("verify", "running");
|
116 |
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
117 |
+
updateStep("verify", "complete", "Calibration verified");
|
118 |
+
|
119 |
+
// Step 4: Complete
|
120 |
+
setCurrentStepIndex(3);
|
121 |
+
updateStep("complete", "complete", "Robot is ready for use");
|
122 |
+
|
123 |
+
setTimeout(() => {
|
124 |
+
onComplete();
|
125 |
+
}, 2000);
|
126 |
+
} catch (error) {
|
127 |
+
setError(error instanceof Error ? error.message : "Calibration failed");
|
128 |
+
} finally {
|
129 |
+
setIsRunning(false);
|
130 |
+
}
|
131 |
+
};
|
132 |
+
|
133 |
+
const getStepIcon = (status: CalibrationStep["status"]) => {
|
134 |
+
switch (status) {
|
135 |
+
case "pending":
|
136 |
+
return "⏳";
|
137 |
+
case "running":
|
138 |
+
return "🔄";
|
139 |
+
case "complete":
|
140 |
+
return "✅";
|
141 |
+
case "error":
|
142 |
+
return "❌";
|
143 |
+
default:
|
144 |
+
return "⏳";
|
145 |
+
}
|
146 |
+
};
|
147 |
+
|
148 |
+
const getStepBadgeVariant = (status: CalibrationStep["status"]) => {
|
149 |
+
switch (status) {
|
150 |
+
case "pending":
|
151 |
+
return "secondary" as const;
|
152 |
+
case "running":
|
153 |
+
return "default" as const;
|
154 |
+
case "complete":
|
155 |
+
return "default" as const;
|
156 |
+
case "error":
|
157 |
+
return "destructive" as const;
|
158 |
+
default:
|
159 |
+
return "secondary" as const;
|
160 |
+
}
|
161 |
+
};
|
162 |
+
|
163 |
+
return (
|
164 |
+
<div className="space-y-6">
|
165 |
+
<div className="text-center">
|
166 |
+
<h3 className="text-lg font-semibold mb-2">Calibration in Progress</h3>
|
167 |
+
<p className="text-muted-foreground">
|
168 |
+
Calibrating {robot.robotId} ({robot.robotType?.replace("_", " ")})
|
169 |
+
</p>
|
170 |
+
</div>
|
171 |
+
|
172 |
+
{error && (
|
173 |
+
<Alert variant="destructive">
|
174 |
+
<AlertDescription>{error}</AlertDescription>
|
175 |
+
</Alert>
|
176 |
+
)}
|
177 |
+
|
178 |
+
<div className="space-y-4">
|
179 |
+
{steps.map((step, index) => (
|
180 |
+
<Card
|
181 |
+
key={step.id}
|
182 |
+
className={index === currentStepIndex ? "ring-2 ring-blue-500" : ""}
|
183 |
+
>
|
184 |
+
<CardHeader className="pb-3">
|
185 |
+
<div className="flex items-center justify-between">
|
186 |
+
<div className="flex items-center space-x-3">
|
187 |
+
<span className="text-2xl">{getStepIcon(step.status)}</span>
|
188 |
+
<div>
|
189 |
+
<CardTitle className="text-base">{step.title}</CardTitle>
|
190 |
+
<CardDescription className="text-sm">
|
191 |
+
{step.description}
|
192 |
+
</CardDescription>
|
193 |
+
</div>
|
194 |
+
</div>
|
195 |
+
<Badge variant={getStepBadgeVariant(step.status)}>
|
196 |
+
{step.status.charAt(0).toUpperCase() + step.status.slice(1)}
|
197 |
+
</Badge>
|
198 |
+
</div>
|
199 |
+
</CardHeader>
|
200 |
+
{step.message && (
|
201 |
+
<CardContent className="pt-0">
|
202 |
+
<p className="text-sm text-muted-foreground">{step.message}</p>
|
203 |
+
</CardContent>
|
204 |
+
)}
|
205 |
+
</Card>
|
206 |
+
))}
|
207 |
+
</div>
|
208 |
+
|
209 |
+
<div className="flex justify-center space-x-4">
|
210 |
+
<Button variant="outline" onClick={onCancel} disabled={isRunning}>
|
211 |
+
Cancel
|
212 |
+
</Button>
|
213 |
+
{error && <Button onClick={startCalibration}>Retry Calibration</Button>}
|
214 |
+
</div>
|
215 |
+
</div>
|
216 |
+
);
|
217 |
+
}
|
src/demo/components/ErrorBoundary.tsx
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { Component, ErrorInfo, ReactNode } from "react";
|
2 |
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
3 |
+
import { Button } from "./ui/button";
|
4 |
+
|
5 |
+
interface Props {
|
6 |
+
children: ReactNode;
|
7 |
+
}
|
8 |
+
|
9 |
+
interface State {
|
10 |
+
hasError: boolean;
|
11 |
+
error?: Error;
|
12 |
+
}
|
13 |
+
|
14 |
+
export class ErrorBoundary extends Component<Props, State> {
|
15 |
+
constructor(props: Props) {
|
16 |
+
super(props);
|
17 |
+
this.state = { hasError: false };
|
18 |
+
}
|
19 |
+
|
20 |
+
static getDerivedStateFromError(error: Error): State {
|
21 |
+
return { hasError: true, error };
|
22 |
+
}
|
23 |
+
|
24 |
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
25 |
+
console.error("ErrorBoundary caught an error:", error, errorInfo);
|
26 |
+
}
|
27 |
+
|
28 |
+
render() {
|
29 |
+
if (this.state.hasError) {
|
30 |
+
return (
|
31 |
+
<div className="min-h-screen flex items-center justify-center p-8">
|
32 |
+
<div className="max-w-md w-full">
|
33 |
+
<Alert variant="destructive">
|
34 |
+
<AlertTitle>Something went wrong</AlertTitle>
|
35 |
+
<AlertDescription>
|
36 |
+
The application encountered an error. Please try refreshing the
|
37 |
+
page or contact support if the problem persists.
|
38 |
+
</AlertDescription>
|
39 |
+
</Alert>
|
40 |
+
<div className="mt-4 flex gap-2">
|
41 |
+
<Button onClick={() => window.location.reload()}>
|
42 |
+
Refresh Page
|
43 |
+
</Button>
|
44 |
+
<Button
|
45 |
+
variant="outline"
|
46 |
+
onClick={() =>
|
47 |
+
this.setState({ hasError: false, error: undefined })
|
48 |
+
}
|
49 |
+
>
|
50 |
+
Try Again
|
51 |
+
</Button>
|
52 |
+
</div>
|
53 |
+
{process.env.NODE_ENV === "development" && this.state.error && (
|
54 |
+
<div className="mt-4 p-4 bg-gray-100 rounded-md text-xs">
|
55 |
+
<pre>{this.state.error.stack}</pre>
|
56 |
+
</div>
|
57 |
+
)}
|
58 |
+
</div>
|
59 |
+
</div>
|
60 |
+
);
|
61 |
+
}
|
62 |
+
|
63 |
+
return this.props.children;
|
64 |
+
}
|
65 |
+
}
|
src/demo/components/PortManager.tsx
ADDED
@@ -0,0 +1,1050 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useEffect } from "react";
|
2 |
+
import { Button } from "./ui/button";
|
3 |
+
import {
|
4 |
+
Card,
|
5 |
+
CardContent,
|
6 |
+
CardDescription,
|
7 |
+
CardHeader,
|
8 |
+
CardTitle,
|
9 |
+
} from "./ui/card";
|
10 |
+
import { Alert, AlertDescription } from "./ui/alert";
|
11 |
+
import { Badge } from "./ui/badge";
|
12 |
+
import { isWebSerialSupported } from "../../lerobot/web/calibrate";
|
13 |
+
import type { ConnectedRobot } from "../types";
|
14 |
+
|
15 |
+
interface PortManagerProps {
|
16 |
+
connectedRobots: ConnectedRobot[];
|
17 |
+
onConnectedRobotsChange: (robots: ConnectedRobot[]) => void;
|
18 |
+
onCalibrate?: (
|
19 |
+
port: SerialPort,
|
20 |
+
robotType: "so100_follower" | "so100_leader",
|
21 |
+
robotId: string
|
22 |
+
) => void;
|
23 |
+
}
|
24 |
+
|
25 |
+
export function PortManager({
|
26 |
+
connectedRobots,
|
27 |
+
onConnectedRobotsChange,
|
28 |
+
onCalibrate,
|
29 |
+
}: PortManagerProps) {
|
30 |
+
const [isConnecting, setIsConnecting] = useState(false);
|
31 |
+
const [isFindingPorts, setIsFindingPorts] = useState(false);
|
32 |
+
const [findPortsLog, setFindPortsLog] = useState<string[]>([]);
|
33 |
+
const [error, setError] = useState<string | null>(null);
|
34 |
+
|
35 |
+
// Load saved port data from localStorage on mount
|
36 |
+
useEffect(() => {
|
37 |
+
loadSavedPorts();
|
38 |
+
}, []);
|
39 |
+
|
40 |
+
// Save port data to localStorage whenever connectedPorts changes
|
41 |
+
useEffect(() => {
|
42 |
+
savePortsToStorage();
|
43 |
+
}, [connectedRobots]);
|
44 |
+
|
45 |
+
const loadSavedPorts = async () => {
|
46 |
+
try {
|
47 |
+
const saved = localStorage.getItem("lerobot-ports");
|
48 |
+
if (!saved) return;
|
49 |
+
|
50 |
+
const savedData = JSON.parse(saved);
|
51 |
+
const existingPorts = await navigator.serial.getPorts();
|
52 |
+
|
53 |
+
const restoredPorts: ConnectedRobot[] = [];
|
54 |
+
|
55 |
+
for (const port of existingPorts) {
|
56 |
+
// Find saved data by matching port info instead of display name
|
57 |
+
const portInfo = port.getInfo();
|
58 |
+
const savedPort = savedData.find((p: any) => {
|
59 |
+
// Try to match by USB vendor/product ID if available
|
60 |
+
if (portInfo.usbVendorId && portInfo.usbProductId) {
|
61 |
+
return (
|
62 |
+
p.usbVendorId === portInfo.usbVendorId &&
|
63 |
+
p.usbProductId === portInfo.usbProductId
|
64 |
+
);
|
65 |
+
}
|
66 |
+
// Fallback to name matching
|
67 |
+
return p.name === getPortDisplayName(port);
|
68 |
+
});
|
69 |
+
|
70 |
+
// Auto-connect to paired robots
|
71 |
+
let isConnected = false;
|
72 |
+
try {
|
73 |
+
// Check if already open
|
74 |
+
if (port.readable !== null && port.writable !== null) {
|
75 |
+
isConnected = true;
|
76 |
+
} else {
|
77 |
+
// Auto-open paired robots
|
78 |
+
await port.open({ baudRate: 1000000 });
|
79 |
+
isConnected = true;
|
80 |
+
}
|
81 |
+
} catch (error) {
|
82 |
+
console.log("Could not auto-connect to paired robot:", error);
|
83 |
+
isConnected = false;
|
84 |
+
}
|
85 |
+
|
86 |
+
restoredPorts.push({
|
87 |
+
port,
|
88 |
+
name: getPortDisplayName(port),
|
89 |
+
isConnected,
|
90 |
+
robotType: savedPort?.robotType,
|
91 |
+
robotId: savedPort?.robotId,
|
92 |
+
});
|
93 |
+
}
|
94 |
+
|
95 |
+
onConnectedRobotsChange(restoredPorts);
|
96 |
+
} catch (error) {
|
97 |
+
console.error("Failed to load saved ports:", error);
|
98 |
+
}
|
99 |
+
};
|
100 |
+
|
101 |
+
const savePortsToStorage = () => {
|
102 |
+
try {
|
103 |
+
const dataToSave = connectedRobots.map((p) => {
|
104 |
+
const portInfo = p.port.getInfo();
|
105 |
+
return {
|
106 |
+
name: p.name,
|
107 |
+
robotType: p.robotType,
|
108 |
+
robotId: p.robotId,
|
109 |
+
usbVendorId: portInfo.usbVendorId,
|
110 |
+
usbProductId: portInfo.usbProductId,
|
111 |
+
};
|
112 |
+
});
|
113 |
+
localStorage.setItem("lerobot-ports", JSON.stringify(dataToSave));
|
114 |
+
} catch (error) {
|
115 |
+
console.error("Failed to save ports to storage:", error);
|
116 |
+
}
|
117 |
+
};
|
118 |
+
|
119 |
+
const getPortDisplayName = (port: SerialPort): string => {
|
120 |
+
try {
|
121 |
+
const info = port.getInfo();
|
122 |
+
if (info.usbVendorId && info.usbProductId) {
|
123 |
+
return `USB Port (${info.usbVendorId}:${info.usbProductId})`;
|
124 |
+
}
|
125 |
+
if (info.usbVendorId) {
|
126 |
+
return `Serial Port (VID:${info.usbVendorId
|
127 |
+
.toString(16)
|
128 |
+
.toUpperCase()})`;
|
129 |
+
}
|
130 |
+
} catch (error) {
|
131 |
+
// getInfo() might not be available
|
132 |
+
}
|
133 |
+
return `Serial Port ${Date.now()}`;
|
134 |
+
};
|
135 |
+
|
136 |
+
const handleConnect = async () => {
|
137 |
+
if (!isWebSerialSupported()) {
|
138 |
+
setError("Web Serial API is not supported in this browser");
|
139 |
+
return;
|
140 |
+
}
|
141 |
+
|
142 |
+
try {
|
143 |
+
setIsConnecting(true);
|
144 |
+
setError(null);
|
145 |
+
|
146 |
+
// Step 1: Request Web Serial port
|
147 |
+
console.log("Step 1: Requesting Web Serial port...");
|
148 |
+
const port = await navigator.serial.requestPort();
|
149 |
+
await port.open({ baudRate: 1000000 });
|
150 |
+
|
151 |
+
// Step 2: Request WebUSB device for metadata
|
152 |
+
console.log(
|
153 |
+
"Step 2: Requesting WebUSB device for unique identification..."
|
154 |
+
);
|
155 |
+
let serialNumber = null;
|
156 |
+
let usbMetadata = null;
|
157 |
+
|
158 |
+
try {
|
159 |
+
// Request USB device access for metadata
|
160 |
+
const usbDevice = await navigator.usb.requestDevice({
|
161 |
+
filters: [
|
162 |
+
{ vendorId: 0x0403 }, // FTDI
|
163 |
+
{ vendorId: 0x067b }, // Prolific
|
164 |
+
{ vendorId: 0x10c4 }, // Silicon Labs
|
165 |
+
{ vendorId: 0x1a86 }, // QinHeng Electronics (CH340)
|
166 |
+
{ vendorId: 0x239a }, // Adafruit
|
167 |
+
{ vendorId: 0x2341 }, // Arduino
|
168 |
+
{ vendorId: 0x2e8a }, // Raspberry Pi Foundation
|
169 |
+
{ vendorId: 0x1b4f }, // SparkFun
|
170 |
+
],
|
171 |
+
});
|
172 |
+
|
173 |
+
if (usbDevice) {
|
174 |
+
serialNumber =
|
175 |
+
usbDevice.serialNumber ||
|
176 |
+
`${usbDevice.vendorId}-${usbDevice.productId}-${Date.now()}`;
|
177 |
+
usbMetadata = {
|
178 |
+
vendorId: `0x${usbDevice.vendorId.toString(16).padStart(4, "0")}`,
|
179 |
+
productId: `0x${usbDevice.productId.toString(16).padStart(4, "0")}`,
|
180 |
+
serialNumber: usbDevice.serialNumber || "Generated ID",
|
181 |
+
manufacturerName: usbDevice.manufacturerName || "Unknown",
|
182 |
+
productName: usbDevice.productName || "Unknown",
|
183 |
+
usbVersionMajor: usbDevice.usbVersionMajor,
|
184 |
+
usbVersionMinor: usbDevice.usbVersionMinor,
|
185 |
+
deviceClass: usbDevice.deviceClass,
|
186 |
+
deviceSubclass: usbDevice.deviceSubclass,
|
187 |
+
deviceProtocol: usbDevice.deviceProtocol,
|
188 |
+
};
|
189 |
+
console.log("✅ USB device metadata acquired:", usbMetadata);
|
190 |
+
}
|
191 |
+
} catch (usbError) {
|
192 |
+
console.log(
|
193 |
+
"⚠️ WebUSB request failed, generating fallback ID:",
|
194 |
+
usbError
|
195 |
+
);
|
196 |
+
// Generate a fallback unique ID if WebUSB fails
|
197 |
+
serialNumber = `fallback-${Date.now()}-${Math.random()
|
198 |
+
.toString(36)
|
199 |
+
.substr(2, 9)}`;
|
200 |
+
usbMetadata = {
|
201 |
+
vendorId: "Unknown",
|
202 |
+
productId: "Unknown",
|
203 |
+
serialNumber: serialNumber,
|
204 |
+
manufacturerName: "USB Metadata Not Available",
|
205 |
+
productName: "Check browser WebUSB support",
|
206 |
+
};
|
207 |
+
}
|
208 |
+
|
209 |
+
const portName = getPortDisplayName(port);
|
210 |
+
|
211 |
+
// Step 3: Check if this robot (by serial number) is already connected
|
212 |
+
const existingIndex = connectedRobots.findIndex(
|
213 |
+
(robot) => robot.serialNumber === serialNumber
|
214 |
+
);
|
215 |
+
|
216 |
+
if (existingIndex === -1) {
|
217 |
+
// New robot - add to list
|
218 |
+
const newRobot: ConnectedRobot = {
|
219 |
+
port,
|
220 |
+
name: portName,
|
221 |
+
isConnected: true,
|
222 |
+
serialNumber: serialNumber!,
|
223 |
+
usbMetadata: usbMetadata || undefined,
|
224 |
+
};
|
225 |
+
|
226 |
+
// Try to load saved robot info by serial number
|
227 |
+
try {
|
228 |
+
const savedRobotKey = `lerobot-robot-${serialNumber}`;
|
229 |
+
const savedData = localStorage.getItem(savedRobotKey);
|
230 |
+
if (savedData) {
|
231 |
+
const parsed = JSON.parse(savedData);
|
232 |
+
newRobot.robotType = parsed.robotType;
|
233 |
+
newRobot.robotId = parsed.robotId;
|
234 |
+
console.log("📋 Loaded saved robot configuration:", parsed);
|
235 |
+
}
|
236 |
+
} catch (error) {
|
237 |
+
console.warn("Failed to load saved robot data:", error);
|
238 |
+
}
|
239 |
+
|
240 |
+
onConnectedRobotsChange([...connectedRobots, newRobot]);
|
241 |
+
console.log("🤖 New robot connected with ID:", serialNumber);
|
242 |
+
} else {
|
243 |
+
// Existing robot - update port and connection status
|
244 |
+
const updatedRobots = connectedRobots.map((robot, index) =>
|
245 |
+
index === existingIndex
|
246 |
+
? { ...robot, port, isConnected: true, name: portName }
|
247 |
+
: robot
|
248 |
+
);
|
249 |
+
onConnectedRobotsChange(updatedRobots);
|
250 |
+
console.log("🔄 Existing robot reconnected:", serialNumber);
|
251 |
+
}
|
252 |
+
} catch (error) {
|
253 |
+
if (
|
254 |
+
error instanceof Error &&
|
255 |
+
(error.message.includes("cancelled") ||
|
256 |
+
error.message.includes("No port selected by the user") ||
|
257 |
+
error.name === "NotAllowedError")
|
258 |
+
) {
|
259 |
+
// User cancelled - no error message needed, just log to console
|
260 |
+
console.log("Connection cancelled by user");
|
261 |
+
return;
|
262 |
+
}
|
263 |
+
setError(
|
264 |
+
error instanceof Error ? error.message : "Failed to connect to robot"
|
265 |
+
);
|
266 |
+
} finally {
|
267 |
+
setIsConnecting(false);
|
268 |
+
}
|
269 |
+
};
|
270 |
+
|
271 |
+
const handleDisconnect = async (index: number) => {
|
272 |
+
try {
|
273 |
+
const portInfo = connectedRobots[index];
|
274 |
+
if (portInfo.isConnected) {
|
275 |
+
await portInfo.port.close();
|
276 |
+
}
|
277 |
+
|
278 |
+
const updatedRobots = connectedRobots.filter((_, i) => i !== index);
|
279 |
+
onConnectedRobotsChange(updatedRobots);
|
280 |
+
} catch (error) {
|
281 |
+
setError(
|
282 |
+
error instanceof Error ? error.message : "Failed to disconnect port"
|
283 |
+
);
|
284 |
+
}
|
285 |
+
};
|
286 |
+
|
287 |
+
const handleUpdatePortInfo = (
|
288 |
+
index: number,
|
289 |
+
robotType: "so100_follower" | "so100_leader",
|
290 |
+
robotId: string
|
291 |
+
) => {
|
292 |
+
const updatedRobots = connectedRobots.map((robot, i) => {
|
293 |
+
if (i === index) {
|
294 |
+
const updatedRobot = { ...robot, robotType, robotId };
|
295 |
+
|
296 |
+
// Save robot configuration to localStorage using serial number
|
297 |
+
if (updatedRobot.serialNumber) {
|
298 |
+
try {
|
299 |
+
const robotKey = `lerobot-robot-${updatedRobot.serialNumber}`;
|
300 |
+
const robotData = {
|
301 |
+
robotType,
|
302 |
+
robotId,
|
303 |
+
serialNumber: updatedRobot.serialNumber,
|
304 |
+
lastUpdated: new Date().toISOString(),
|
305 |
+
};
|
306 |
+
localStorage.setItem(robotKey, JSON.stringify(robotData));
|
307 |
+
console.log(
|
308 |
+
"💾 Saved robot configuration for:",
|
309 |
+
updatedRobot.serialNumber
|
310 |
+
);
|
311 |
+
} catch (error) {
|
312 |
+
console.warn("Failed to save robot configuration:", error);
|
313 |
+
}
|
314 |
+
}
|
315 |
+
|
316 |
+
return updatedRobot;
|
317 |
+
}
|
318 |
+
return robot;
|
319 |
+
});
|
320 |
+
onConnectedRobotsChange(updatedRobots);
|
321 |
+
};
|
322 |
+
|
323 |
+
const handleFindPorts = async () => {
|
324 |
+
if (!isWebSerialSupported()) {
|
325 |
+
setError("Web Serial API is not supported in this browser");
|
326 |
+
return;
|
327 |
+
}
|
328 |
+
|
329 |
+
try {
|
330 |
+
setIsFindingPorts(true);
|
331 |
+
setFindPortsLog([]);
|
332 |
+
setError(null);
|
333 |
+
|
334 |
+
// Get initial ports
|
335 |
+
const initialPorts = await navigator.serial.getPorts();
|
336 |
+
setFindPortsLog((prev) => [
|
337 |
+
...prev,
|
338 |
+
`Found ${initialPorts.length} existing paired port(s)`,
|
339 |
+
]);
|
340 |
+
|
341 |
+
// Ask user to disconnect
|
342 |
+
setFindPortsLog((prev) => [
|
343 |
+
...prev,
|
344 |
+
"Please disconnect the USB cable from your robot and click OK",
|
345 |
+
]);
|
346 |
+
|
347 |
+
// Simple implementation - just show the instruction
|
348 |
+
// In a real implementation, we'd monitor port changes
|
349 |
+
const confirmed = confirm(
|
350 |
+
"Disconnect the USB cable from your robot and click OK when done"
|
351 |
+
);
|
352 |
+
|
353 |
+
if (confirmed) {
|
354 |
+
setFindPortsLog((prev) => [...prev, "Reconnect the USB cable now"]);
|
355 |
+
|
356 |
+
// Request port selection
|
357 |
+
const port = await navigator.serial.requestPort();
|
358 |
+
await port.open({ baudRate: 1000000 });
|
359 |
+
|
360 |
+
const portName = getPortDisplayName(port);
|
361 |
+
setFindPortsLog((prev) => [...prev, `Identified port: ${portName}`]);
|
362 |
+
|
363 |
+
// Add to connected ports if not already there
|
364 |
+
const existingIndex = connectedRobots.findIndex(
|
365 |
+
(p) => p.name === portName
|
366 |
+
);
|
367 |
+
if (existingIndex === -1) {
|
368 |
+
const newPort: ConnectedRobot = {
|
369 |
+
port,
|
370 |
+
name: portName,
|
371 |
+
isConnected: true,
|
372 |
+
};
|
373 |
+
onConnectedRobotsChange([...connectedRobots, newPort]);
|
374 |
+
}
|
375 |
+
}
|
376 |
+
} catch (error) {
|
377 |
+
if (
|
378 |
+
error instanceof Error &&
|
379 |
+
(error.message.includes("cancelled") ||
|
380 |
+
error.name === "NotAllowedError")
|
381 |
+
) {
|
382 |
+
// User cancelled - no message needed, just log to console
|
383 |
+
console.log("Port identification cancelled by user");
|
384 |
+
return;
|
385 |
+
}
|
386 |
+
setError(error instanceof Error ? error.message : "Failed to find ports");
|
387 |
+
} finally {
|
388 |
+
setIsFindingPorts(false);
|
389 |
+
}
|
390 |
+
};
|
391 |
+
|
392 |
+
const ensurePortIsOpen = async (robotIndex: number) => {
|
393 |
+
const robot = connectedRobots[robotIndex];
|
394 |
+
if (!robot) return false;
|
395 |
+
|
396 |
+
try {
|
397 |
+
// If port is already open, we're good
|
398 |
+
if (robot.port.readable !== null && robot.port.writable !== null) {
|
399 |
+
return true;
|
400 |
+
}
|
401 |
+
|
402 |
+
// Try to open the port
|
403 |
+
await robot.port.open({ baudRate: 1000000 });
|
404 |
+
|
405 |
+
// Update the robot's connection status
|
406 |
+
const updatedRobots = connectedRobots.map((r, i) =>
|
407 |
+
i === robotIndex ? { ...r, isConnected: true } : r
|
408 |
+
);
|
409 |
+
onConnectedRobotsChange(updatedRobots);
|
410 |
+
|
411 |
+
return true;
|
412 |
+
} catch (error) {
|
413 |
+
console.error("Failed to open port for calibration:", error);
|
414 |
+
setError(error instanceof Error ? error.message : "Failed to open port");
|
415 |
+
return false;
|
416 |
+
}
|
417 |
+
};
|
418 |
+
|
419 |
+
const handleCalibrate = async (port: ConnectedRobot) => {
|
420 |
+
if (!port.robotType || !port.robotId) {
|
421 |
+
setError("Please set robot type and ID before calibrating");
|
422 |
+
return;
|
423 |
+
}
|
424 |
+
|
425 |
+
// Find the robot index
|
426 |
+
const robotIndex = connectedRobots.findIndex((r) => r.port === port.port);
|
427 |
+
if (robotIndex === -1) {
|
428 |
+
setError("Robot not found in connected robots list");
|
429 |
+
return;
|
430 |
+
}
|
431 |
+
|
432 |
+
// Ensure port is open before calibrating
|
433 |
+
const isOpen = await ensurePortIsOpen(robotIndex);
|
434 |
+
if (!isOpen) {
|
435 |
+
return; // Error already set in ensurePortIsOpen
|
436 |
+
}
|
437 |
+
|
438 |
+
if (onCalibrate) {
|
439 |
+
onCalibrate(port.port, port.robotType, port.robotId);
|
440 |
+
}
|
441 |
+
};
|
442 |
+
|
443 |
+
return (
|
444 |
+
<Card>
|
445 |
+
<CardHeader>
|
446 |
+
<CardTitle>🔌 Robot Connection Manager</CardTitle>
|
447 |
+
<CardDescription>
|
448 |
+
Connect, identify, and manage your robot arms
|
449 |
+
</CardDescription>
|
450 |
+
</CardHeader>
|
451 |
+
<CardContent>
|
452 |
+
<div className="space-y-6">
|
453 |
+
{/* Error Display */}
|
454 |
+
{error && (
|
455 |
+
<Alert variant="destructive">
|
456 |
+
<AlertDescription>{error}</AlertDescription>
|
457 |
+
</Alert>
|
458 |
+
)}
|
459 |
+
|
460 |
+
{/* Connection Controls */}
|
461 |
+
<div className="flex gap-2">
|
462 |
+
<Button
|
463 |
+
onClick={handleConnect}
|
464 |
+
disabled={isConnecting || !isWebSerialSupported()}
|
465 |
+
className="flex-1"
|
466 |
+
>
|
467 |
+
{isConnecting ? "Connecting..." : "Connect Robot"}
|
468 |
+
</Button>
|
469 |
+
<Button
|
470 |
+
variant="outline"
|
471 |
+
onClick={handleFindPorts}
|
472 |
+
disabled={isFindingPorts || !isWebSerialSupported()}
|
473 |
+
className="flex-1"
|
474 |
+
>
|
475 |
+
{isFindingPorts ? "Finding..." : "Find Port"}
|
476 |
+
</Button>
|
477 |
+
</div>
|
478 |
+
|
479 |
+
{/* Find Ports Log */}
|
480 |
+
{findPortsLog.length > 0 && (
|
481 |
+
<div className="bg-gray-50 p-3 rounded-md text-sm space-y-1">
|
482 |
+
{findPortsLog.map((log, index) => (
|
483 |
+
<div key={index} className="text-gray-700">
|
484 |
+
{log}
|
485 |
+
</div>
|
486 |
+
))}
|
487 |
+
</div>
|
488 |
+
)}
|
489 |
+
|
490 |
+
{/* Connected Ports */}
|
491 |
+
<div>
|
492 |
+
<h4 className="font-semibold mb-3">
|
493 |
+
Connected Robots ({connectedRobots.length})
|
494 |
+
</h4>
|
495 |
+
|
496 |
+
{connectedRobots.length === 0 ? (
|
497 |
+
<div className="text-center py-8 text-gray-500">
|
498 |
+
<div className="text-2xl mb-2">🤖</div>
|
499 |
+
<p>No robots connected</p>
|
500 |
+
<p className="text-xs">
|
501 |
+
Use "Connect Robot" or "Find Port" to add robots
|
502 |
+
</p>
|
503 |
+
</div>
|
504 |
+
) : (
|
505 |
+
<div className="space-y-4">
|
506 |
+
{connectedRobots.map((portInfo, index) => (
|
507 |
+
<PortCard
|
508 |
+
key={index}
|
509 |
+
portInfo={portInfo}
|
510 |
+
onDisconnect={() => handleDisconnect(index)}
|
511 |
+
onUpdateInfo={(robotType, robotId) =>
|
512 |
+
handleUpdatePortInfo(index, robotType, robotId)
|
513 |
+
}
|
514 |
+
onCalibrate={() => handleCalibrate(portInfo)}
|
515 |
+
/>
|
516 |
+
))}
|
517 |
+
</div>
|
518 |
+
)}
|
519 |
+
</div>
|
520 |
+
</div>
|
521 |
+
</CardContent>
|
522 |
+
</Card>
|
523 |
+
);
|
524 |
+
}
|
525 |
+
|
526 |
+
interface PortCardProps {
|
527 |
+
portInfo: ConnectedRobot;
|
528 |
+
onDisconnect: () => void;
|
529 |
+
onUpdateInfo: (
|
530 |
+
robotType: "so100_follower" | "so100_leader",
|
531 |
+
robotId: string
|
532 |
+
) => void;
|
533 |
+
onCalibrate: () => void;
|
534 |
+
}
|
535 |
+
|
536 |
+
function PortCard({
|
537 |
+
portInfo,
|
538 |
+
onDisconnect,
|
539 |
+
onUpdateInfo,
|
540 |
+
onCalibrate,
|
541 |
+
}: PortCardProps) {
|
542 |
+
const [robotType, setRobotType] = useState<"so100_follower" | "so100_leader">(
|
543 |
+
portInfo.robotType || "so100_follower"
|
544 |
+
);
|
545 |
+
const [robotId, setRobotId] = useState(portInfo.robotId || "");
|
546 |
+
const [isEditing, setIsEditing] = useState(false);
|
547 |
+
const [isScanning, setIsScanning] = useState(false);
|
548 |
+
const [motorIDs, setMotorIDs] = useState<number[]>([]);
|
549 |
+
const [portMetadata, setPortMetadata] = useState<any>(null);
|
550 |
+
const [showDeviceInfo, setShowDeviceInfo] = useState(false);
|
551 |
+
|
552 |
+
// Check for calibration in localStorage using serial number
|
553 |
+
const getCalibrationStatus = () => {
|
554 |
+
if (!portInfo.serialNumber) return null;
|
555 |
+
|
556 |
+
const calibrationKey = `lerobot-calibration-${portInfo.serialNumber}`;
|
557 |
+
try {
|
558 |
+
const saved = localStorage.getItem(calibrationKey);
|
559 |
+
if (saved) {
|
560 |
+
const calibrationData = JSON.parse(saved);
|
561 |
+
return {
|
562 |
+
timestamp: calibrationData.timestamp,
|
563 |
+
readCount: calibrationData.readCount,
|
564 |
+
};
|
565 |
+
}
|
566 |
+
} catch (error) {
|
567 |
+
console.warn("Failed to read calibration from localStorage:", error);
|
568 |
+
}
|
569 |
+
return null;
|
570 |
+
};
|
571 |
+
|
572 |
+
const calibrationStatus = getCalibrationStatus();
|
573 |
+
|
574 |
+
const handleSave = () => {
|
575 |
+
if (robotId.trim()) {
|
576 |
+
onUpdateInfo(robotType, robotId.trim());
|
577 |
+
setIsEditing(false);
|
578 |
+
}
|
579 |
+
};
|
580 |
+
|
581 |
+
// Use current values (either from props or local state)
|
582 |
+
const currentRobotType = portInfo.robotType || robotType;
|
583 |
+
const currentRobotId = portInfo.robotId || robotId;
|
584 |
+
|
585 |
+
const handleCancel = () => {
|
586 |
+
setRobotType(portInfo.robotType || "so100_follower");
|
587 |
+
setRobotId(portInfo.robotId || "");
|
588 |
+
setIsEditing(false);
|
589 |
+
};
|
590 |
+
|
591 |
+
// Scan for motor IDs and gather USB device metadata
|
592 |
+
const scanDeviceInfo = async () => {
|
593 |
+
if (!portInfo.port || !portInfo.isConnected) {
|
594 |
+
console.warn("Port not connected");
|
595 |
+
return;
|
596 |
+
}
|
597 |
+
|
598 |
+
setIsScanning(true);
|
599 |
+
setMotorIDs([]);
|
600 |
+
setPortMetadata(null);
|
601 |
+
const foundIDs: number[] = [];
|
602 |
+
|
603 |
+
try {
|
604 |
+
// Try to get USB device info using WebUSB for better metadata
|
605 |
+
let usbDeviceInfo = null;
|
606 |
+
|
607 |
+
try {
|
608 |
+
// First, check if we already have USB device permissions
|
609 |
+
let usbDevices = await navigator.usb.getDevices();
|
610 |
+
console.log("Already permitted USB devices:", usbDevices);
|
611 |
+
|
612 |
+
// If no devices found, request permission for USB-to-serial devices
|
613 |
+
if (usbDevices.length === 0) {
|
614 |
+
console.log(
|
615 |
+
"No USB permissions yet, requesting access to USB-to-serial devices..."
|
616 |
+
);
|
617 |
+
|
618 |
+
// Request access to common USB-to-serial chips
|
619 |
+
try {
|
620 |
+
const device = await navigator.usb.requestDevice({
|
621 |
+
filters: [
|
622 |
+
{ vendorId: 0x0403 }, // FTDI
|
623 |
+
{ vendorId: 0x067b }, // Prolific
|
624 |
+
{ vendorId: 0x10c4 }, // Silicon Labs
|
625 |
+
{ vendorId: 0x1a86 }, // QinHeng Electronics (CH340)
|
626 |
+
{ vendorId: 0x239a }, // Adafruit
|
627 |
+
{ vendorId: 0x2341 }, // Arduino
|
628 |
+
{ vendorId: 0x2e8a }, // Raspberry Pi Foundation
|
629 |
+
{ vendorId: 0x1b4f }, // SparkFun
|
630 |
+
],
|
631 |
+
});
|
632 |
+
|
633 |
+
if (device) {
|
634 |
+
usbDevices = [device];
|
635 |
+
console.log("USB device access granted:", device);
|
636 |
+
}
|
637 |
+
} catch (requestError) {
|
638 |
+
console.log(
|
639 |
+
"User cancelled USB device selection or no devices found"
|
640 |
+
);
|
641 |
+
// Try requesting any device as fallback
|
642 |
+
try {
|
643 |
+
const anyDevice = await navigator.usb.requestDevice({
|
644 |
+
filters: [], // Allow any USB device
|
645 |
+
});
|
646 |
+
if (anyDevice) {
|
647 |
+
usbDevices = [anyDevice];
|
648 |
+
console.log("Fallback USB device selected:", anyDevice);
|
649 |
+
}
|
650 |
+
} catch (fallbackError) {
|
651 |
+
console.log("No USB device selected");
|
652 |
+
}
|
653 |
+
}
|
654 |
+
}
|
655 |
+
|
656 |
+
// Try to match with Web Serial port (this is tricky, so we'll take the first available)
|
657 |
+
if (usbDevices.length > 0) {
|
658 |
+
// Look for common USB-to-serial chip vendor IDs
|
659 |
+
const serialChipVendors = [
|
660 |
+
0x0403, // FTDI
|
661 |
+
0x067b, // Prolific
|
662 |
+
0x10c4, // Silicon Labs
|
663 |
+
0x1a86, // QinHeng Electronics (CH340)
|
664 |
+
0x239a, // Adafruit
|
665 |
+
0x2341, // Arduino
|
666 |
+
0x2e8a, // Raspberry Pi Foundation
|
667 |
+
0x1b4f, // SparkFun
|
668 |
+
];
|
669 |
+
|
670 |
+
const serialDevice =
|
671 |
+
usbDevices.find((device) =>
|
672 |
+
serialChipVendors.includes(device.vendorId)
|
673 |
+
) || usbDevices[0]; // Fallback to first device
|
674 |
+
|
675 |
+
if (serialDevice) {
|
676 |
+
usbDeviceInfo = {
|
677 |
+
vendorId: `0x${serialDevice.vendorId
|
678 |
+
.toString(16)
|
679 |
+
.padStart(4, "0")}`,
|
680 |
+
productId: `0x${serialDevice.productId
|
681 |
+
.toString(16)
|
682 |
+
.padStart(4, "0")}`,
|
683 |
+
serialNumber: serialDevice.serialNumber || "Not available",
|
684 |
+
manufacturerName: serialDevice.manufacturerName || "Unknown",
|
685 |
+
productName: serialDevice.productName || "Unknown",
|
686 |
+
usbVersionMajor: serialDevice.usbVersionMajor,
|
687 |
+
usbVersionMinor: serialDevice.usbVersionMinor,
|
688 |
+
deviceClass: serialDevice.deviceClass,
|
689 |
+
deviceSubclass: serialDevice.deviceSubclass,
|
690 |
+
deviceProtocol: serialDevice.deviceProtocol,
|
691 |
+
};
|
692 |
+
console.log("USB device info:", usbDeviceInfo);
|
693 |
+
}
|
694 |
+
}
|
695 |
+
} catch (usbError) {
|
696 |
+
console.log("WebUSB not available or no permissions:", usbError);
|
697 |
+
// Fallback to Web Serial API info
|
698 |
+
const portInfo_metadata = portInfo.port.getInfo();
|
699 |
+
console.log("Serial port metadata fallback:", portInfo_metadata);
|
700 |
+
if (Object.keys(portInfo_metadata).length > 0) {
|
701 |
+
usbDeviceInfo = {
|
702 |
+
vendorId: portInfo_metadata.usbVendorId
|
703 |
+
? `0x${portInfo_metadata.usbVendorId
|
704 |
+
.toString(16)
|
705 |
+
.padStart(4, "0")}`
|
706 |
+
: "Not available",
|
707 |
+
productId: portInfo_metadata.usbProductId
|
708 |
+
? `0x${portInfo_metadata.usbProductId
|
709 |
+
.toString(16)
|
710 |
+
.padStart(4, "0")}`
|
711 |
+
: "Not available",
|
712 |
+
serialNumber: "Not available via Web Serial",
|
713 |
+
manufacturerName: "Not available via Web Serial",
|
714 |
+
productName: "Not available via Web Serial",
|
715 |
+
};
|
716 |
+
}
|
717 |
+
}
|
718 |
+
|
719 |
+
setPortMetadata(usbDeviceInfo);
|
720 |
+
|
721 |
+
// Get reader/writer for the port
|
722 |
+
const reader = portInfo.port.readable?.getReader();
|
723 |
+
const writer = portInfo.port.writable?.getWriter();
|
724 |
+
|
725 |
+
if (!reader || !writer) {
|
726 |
+
console.warn("Cannot access port reader/writer");
|
727 |
+
setShowDeviceInfo(true);
|
728 |
+
return;
|
729 |
+
}
|
730 |
+
|
731 |
+
// Test motor IDs 1-10 (common range for servos)
|
732 |
+
for (let motorId = 1; motorId <= 10; motorId++) {
|
733 |
+
try {
|
734 |
+
// Create STS3215 ping packet
|
735 |
+
const packet = new Uint8Array([
|
736 |
+
0xff,
|
737 |
+
0xff,
|
738 |
+
motorId,
|
739 |
+
0x02,
|
740 |
+
0x01,
|
741 |
+
0x00,
|
742 |
+
]);
|
743 |
+
const checksum = ~(motorId + 0x02 + 0x01) & 0xff;
|
744 |
+
packet[5] = checksum;
|
745 |
+
|
746 |
+
// Send ping
|
747 |
+
await writer.write(packet);
|
748 |
+
|
749 |
+
// Wait a bit for response
|
750 |
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
751 |
+
|
752 |
+
// Try to read response with timeout
|
753 |
+
const timeoutPromise = new Promise((_, reject) =>
|
754 |
+
setTimeout(() => reject(new Error("Timeout")), 50)
|
755 |
+
);
|
756 |
+
|
757 |
+
try {
|
758 |
+
const result = (await Promise.race([
|
759 |
+
reader.read(),
|
760 |
+
timeoutPromise,
|
761 |
+
])) as ReadableStreamReadResult<Uint8Array>;
|
762 |
+
|
763 |
+
if (
|
764 |
+
result &&
|
765 |
+
!result.done &&
|
766 |
+
result.value &&
|
767 |
+
result.value.length >= 6
|
768 |
+
) {
|
769 |
+
const response = result.value;
|
770 |
+
const responseId = response[2];
|
771 |
+
|
772 |
+
// If we got a response with matching ID, motor exists
|
773 |
+
if (responseId === motorId) {
|
774 |
+
foundIDs.push(motorId);
|
775 |
+
}
|
776 |
+
}
|
777 |
+
} catch (readError) {
|
778 |
+
// No response from this motor ID - that's normal
|
779 |
+
}
|
780 |
+
} catch (error) {
|
781 |
+
console.warn(`Error testing motor ID ${motorId}:`, error);
|
782 |
+
}
|
783 |
+
|
784 |
+
// Small delay between tests
|
785 |
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
786 |
+
}
|
787 |
+
|
788 |
+
reader.releaseLock();
|
789 |
+
writer.releaseLock();
|
790 |
+
|
791 |
+
setMotorIDs(foundIDs);
|
792 |
+
setShowDeviceInfo(true);
|
793 |
+
} catch (error) {
|
794 |
+
console.error("Device info scan failed:", error);
|
795 |
+
} finally {
|
796 |
+
setIsScanning(false);
|
797 |
+
}
|
798 |
+
};
|
799 |
+
|
800 |
+
return (
|
801 |
+
<div className="border rounded-lg p-4 space-y-3">
|
802 |
+
{/* Header with port name and status */}
|
803 |
+
<div className="flex items-center justify-between">
|
804 |
+
<div className="flex items-center space-x-2">
|
805 |
+
<div className="flex flex-col">
|
806 |
+
<span className="font-medium">{portInfo.name}</span>
|
807 |
+
{portInfo.serialNumber && (
|
808 |
+
<span className="text-xs text-gray-500 font-mono">
|
809 |
+
ID:{" "}
|
810 |
+
{portInfo.serialNumber.length > 20
|
811 |
+
? portInfo.serialNumber.substring(0, 20) + "..."
|
812 |
+
: portInfo.serialNumber}
|
813 |
+
</span>
|
814 |
+
)}
|
815 |
+
</div>
|
816 |
+
<Badge variant={portInfo.isConnected ? "default" : "outline"}>
|
817 |
+
{portInfo.isConnected ? "Connected" : "Available"}
|
818 |
+
</Badge>
|
819 |
+
{portInfo.usbMetadata && (
|
820 |
+
<Badge variant="outline" className="text-xs">
|
821 |
+
{portInfo.usbMetadata.manufacturerName}
|
822 |
+
</Badge>
|
823 |
+
)}
|
824 |
+
</div>
|
825 |
+
<Button variant="destructive" size="sm" onClick={onDisconnect}>
|
826 |
+
Remove
|
827 |
+
</Button>
|
828 |
+
</div>
|
829 |
+
|
830 |
+
{/* Robot Info Display (when not editing) */}
|
831 |
+
{!isEditing && currentRobotType && currentRobotId && (
|
832 |
+
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
833 |
+
<div className="flex items-center space-x-3">
|
834 |
+
<div>
|
835 |
+
<div className="font-medium text-sm">{currentRobotId}</div>
|
836 |
+
<div className="text-xs text-gray-600">
|
837 |
+
{currentRobotType.replace("_", " ")}
|
838 |
+
</div>
|
839 |
+
</div>
|
840 |
+
{calibrationStatus && (
|
841 |
+
<Badge variant="default" className="bg-green-100 text-green-800">
|
842 |
+
✅ Calibrated
|
843 |
+
</Badge>
|
844 |
+
)}
|
845 |
+
</div>
|
846 |
+
<Button
|
847 |
+
variant="outline"
|
848 |
+
size="sm"
|
849 |
+
onClick={() => setIsEditing(true)}
|
850 |
+
>
|
851 |
+
Edit
|
852 |
+
</Button>
|
853 |
+
</div>
|
854 |
+
)}
|
855 |
+
|
856 |
+
{/* Setup prompt for unconfigured robots */}
|
857 |
+
{!isEditing && (!currentRobotType || !currentRobotId) && (
|
858 |
+
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
859 |
+
<div className="text-sm text-blue-800">
|
860 |
+
Robot needs configuration before use
|
861 |
+
</div>
|
862 |
+
<Button
|
863 |
+
variant="outline"
|
864 |
+
size="sm"
|
865 |
+
onClick={() => setIsEditing(true)}
|
866 |
+
>
|
867 |
+
Configure
|
868 |
+
</Button>
|
869 |
+
</div>
|
870 |
+
)}
|
871 |
+
|
872 |
+
{/* Robot Configuration Form (when editing) */}
|
873 |
+
{isEditing && (
|
874 |
+
<div className="space-y-3 p-3 bg-gray-50 rounded-lg">
|
875 |
+
<div className="grid grid-cols-2 gap-3">
|
876 |
+
<div>
|
877 |
+
<label className="text-sm font-medium block mb-1">
|
878 |
+
Robot Type
|
879 |
+
</label>
|
880 |
+
<select
|
881 |
+
value={robotType}
|
882 |
+
onChange={(e) =>
|
883 |
+
setRobotType(
|
884 |
+
e.target.value as "so100_follower" | "so100_leader"
|
885 |
+
)
|
886 |
+
}
|
887 |
+
className="w-full px-2 py-1 border rounded text-sm"
|
888 |
+
>
|
889 |
+
<option value="so100_follower">SO-100 Follower</option>
|
890 |
+
<option value="so100_leader">SO-100 Leader</option>
|
891 |
+
</select>
|
892 |
+
</div>
|
893 |
+
<div>
|
894 |
+
<label className="text-sm font-medium block mb-1">Robot ID</label>
|
895 |
+
<input
|
896 |
+
type="text"
|
897 |
+
value={robotId}
|
898 |
+
onChange={(e) => setRobotId(e.target.value)}
|
899 |
+
placeholder="e.g., my_robot"
|
900 |
+
className="w-full px-2 py-1 border rounded text-sm"
|
901 |
+
/>
|
902 |
+
</div>
|
903 |
+
</div>
|
904 |
+
|
905 |
+
<div className="flex gap-2">
|
906 |
+
<Button size="sm" onClick={handleSave} disabled={!robotId.trim()}>
|
907 |
+
Save
|
908 |
+
</Button>
|
909 |
+
<Button size="sm" variant="outline" onClick={handleCancel}>
|
910 |
+
Cancel
|
911 |
+
</Button>
|
912 |
+
</div>
|
913 |
+
</div>
|
914 |
+
)}
|
915 |
+
|
916 |
+
{/* Calibration Status and Action */}
|
917 |
+
{currentRobotType && currentRobotId && (
|
918 |
+
<div className="space-y-3">
|
919 |
+
<div className="flex items-center justify-between">
|
920 |
+
<div className="text-sm text-gray-600">
|
921 |
+
{calibrationStatus ? (
|
922 |
+
<span>
|
923 |
+
Last calibrated:{" "}
|
924 |
+
{new Date(calibrationStatus.timestamp).toLocaleDateString()}
|
925 |
+
<span className="text-xs ml-1">
|
926 |
+
({calibrationStatus.readCount} readings)
|
927 |
+
</span>
|
928 |
+
</span>
|
929 |
+
) : (
|
930 |
+
<span>Not calibrated yet</span>
|
931 |
+
)}
|
932 |
+
</div>
|
933 |
+
<Button
|
934 |
+
size="sm"
|
935 |
+
variant={calibrationStatus ? "outline" : "default"}
|
936 |
+
onClick={onCalibrate}
|
937 |
+
disabled={!currentRobotType || !currentRobotId}
|
938 |
+
>
|
939 |
+
{calibrationStatus ? "Re-calibrate" : "Calibrate"}
|
940 |
+
</Button>
|
941 |
+
</div>
|
942 |
+
|
943 |
+
{/* Device Info Scanner */}
|
944 |
+
<div className="flex items-center justify-between">
|
945 |
+
<div className="text-sm text-gray-600">
|
946 |
+
Scan device info and motor IDs
|
947 |
+
</div>
|
948 |
+
<Button
|
949 |
+
size="sm"
|
950 |
+
variant="outline"
|
951 |
+
onClick={scanDeviceInfo}
|
952 |
+
disabled={!portInfo.isConnected || isScanning}
|
953 |
+
>
|
954 |
+
{isScanning ? "Scanning..." : "Show Device Info"}
|
955 |
+
</Button>
|
956 |
+
</div>
|
957 |
+
|
958 |
+
{/* Device Info Results */}
|
959 |
+
{showDeviceInfo && (
|
960 |
+
<div className="p-3 bg-gray-50 rounded-lg space-y-3">
|
961 |
+
{/* USB Device Information */}
|
962 |
+
{portMetadata && (
|
963 |
+
<div>
|
964 |
+
<div className="text-sm font-medium mb-2">
|
965 |
+
📱 USB Device Info:
|
966 |
+
</div>
|
967 |
+
<div className="space-y-1 text-xs">
|
968 |
+
<div className="flex justify-between">
|
969 |
+
<span className="text-gray-600">Vendor ID:</span>
|
970 |
+
<span className="font-mono">{portMetadata.vendorId}</span>
|
971 |
+
</div>
|
972 |
+
<div className="flex justify-between">
|
973 |
+
<span className="text-gray-600">Product ID:</span>
|
974 |
+
<span className="font-mono">
|
975 |
+
{portMetadata.productId}
|
976 |
+
</span>
|
977 |
+
</div>
|
978 |
+
<div className="flex justify-between">
|
979 |
+
<span className="text-gray-600">Serial Number:</span>
|
980 |
+
<span className="font-mono text-green-600 font-semibold">
|
981 |
+
{portMetadata.serialNumber}
|
982 |
+
</span>
|
983 |
+
</div>
|
984 |
+
<div className="flex justify-between">
|
985 |
+
<span className="text-gray-600">Manufacturer:</span>
|
986 |
+
<span>{portMetadata.manufacturerName}</span>
|
987 |
+
</div>
|
988 |
+
<div className="flex justify-between">
|
989 |
+
<span className="text-gray-600">Product:</span>
|
990 |
+
<span>{portMetadata.productName}</span>
|
991 |
+
</div>
|
992 |
+
{portMetadata.usbVersionMajor && (
|
993 |
+
<div className="flex justify-between">
|
994 |
+
<span className="text-gray-600">USB Version:</span>
|
995 |
+
<span>
|
996 |
+
{portMetadata.usbVersionMajor}.
|
997 |
+
{portMetadata.usbVersionMinor}
|
998 |
+
</span>
|
999 |
+
</div>
|
1000 |
+
)}
|
1001 |
+
{portMetadata.deviceClass !== undefined && (
|
1002 |
+
<div className="flex justify-between">
|
1003 |
+
<span className="text-gray-600">Device Class:</span>
|
1004 |
+
<span>
|
1005 |
+
0x
|
1006 |
+
{portMetadata.deviceClass
|
1007 |
+
.toString(16)
|
1008 |
+
.padStart(2, "0")}
|
1009 |
+
</span>
|
1010 |
+
</div>
|
1011 |
+
)}
|
1012 |
+
</div>
|
1013 |
+
</div>
|
1014 |
+
)}
|
1015 |
+
|
1016 |
+
{/* Motor IDs */}
|
1017 |
+
<div>
|
1018 |
+
<div className="text-sm font-medium mb-2">
|
1019 |
+
🤖 Found Motor IDs:
|
1020 |
+
</div>
|
1021 |
+
{motorIDs.length > 0 ? (
|
1022 |
+
<div className="flex flex-wrap gap-2">
|
1023 |
+
{motorIDs.map((id) => (
|
1024 |
+
<Badge key={id} variant="outline" className="text-xs">
|
1025 |
+
Motor {id}
|
1026 |
+
</Badge>
|
1027 |
+
))}
|
1028 |
+
</div>
|
1029 |
+
) : (
|
1030 |
+
<div className="text-sm text-gray-500">
|
1031 |
+
No motor IDs found. Check connection and power.
|
1032 |
+
</div>
|
1033 |
+
)}
|
1034 |
+
</div>
|
1035 |
+
|
1036 |
+
<Button
|
1037 |
+
size="sm"
|
1038 |
+
variant="outline"
|
1039 |
+
onClick={() => setShowDeviceInfo(false)}
|
1040 |
+
className="mt-2 text-xs"
|
1041 |
+
>
|
1042 |
+
Hide
|
1043 |
+
</Button>
|
1044 |
+
</div>
|
1045 |
+
)}
|
1046 |
+
</div>
|
1047 |
+
)}
|
1048 |
+
</div>
|
1049 |
+
);
|
1050 |
+
}
|
src/demo/components/ui/alert.tsx
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority";
|
3 |
+
import { cn } from "../../lib/utils";
|
4 |
+
|
5 |
+
const alertVariants = cva(
|
6 |
+
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
7 |
+
{
|
8 |
+
variants: {
|
9 |
+
variant: {
|
10 |
+
default: "bg-background text-foreground",
|
11 |
+
destructive:
|
12 |
+
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
13 |
+
},
|
14 |
+
},
|
15 |
+
defaultVariants: {
|
16 |
+
variant: "default",
|
17 |
+
},
|
18 |
+
}
|
19 |
+
);
|
20 |
+
|
21 |
+
const Alert = React.forwardRef<
|
22 |
+
HTMLDivElement,
|
23 |
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
24 |
+
>(({ className, variant, ...props }, ref) => (
|
25 |
+
<div
|
26 |
+
ref={ref}
|
27 |
+
role="alert"
|
28 |
+
className={cn(alertVariants({ variant }), className)}
|
29 |
+
{...props}
|
30 |
+
/>
|
31 |
+
));
|
32 |
+
Alert.displayName = "Alert";
|
33 |
+
|
34 |
+
const AlertTitle = React.forwardRef<
|
35 |
+
HTMLParagraphElement,
|
36 |
+
React.HTMLAttributes<HTMLHeadingElement>
|
37 |
+
>(({ className, ...props }, ref) => (
|
38 |
+
<h5
|
39 |
+
ref={ref}
|
40 |
+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
41 |
+
{...props}
|
42 |
+
/>
|
43 |
+
));
|
44 |
+
AlertTitle.displayName = "AlertTitle";
|
45 |
+
|
46 |
+
const AlertDescription = React.forwardRef<
|
47 |
+
HTMLParagraphElement,
|
48 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
49 |
+
>(({ className, ...props }, ref) => (
|
50 |
+
<div
|
51 |
+
ref={ref}
|
52 |
+
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
53 |
+
{...props}
|
54 |
+
/>
|
55 |
+
));
|
56 |
+
AlertDescription.displayName = "AlertDescription";
|
57 |
+
|
58 |
+
export { Alert, AlertTitle, AlertDescription };
|
src/demo/components/ui/badge.tsx
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority";
|
3 |
+
import { cn } from "../../lib/utils";
|
4 |
+
|
5 |
+
const badgeVariants = cva(
|
6 |
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
7 |
+
{
|
8 |
+
variants: {
|
9 |
+
variant: {
|
10 |
+
default:
|
11 |
+
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
12 |
+
secondary:
|
13 |
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
14 |
+
destructive:
|
15 |
+
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
16 |
+
outline: "text-foreground",
|
17 |
+
},
|
18 |
+
},
|
19 |
+
defaultVariants: {
|
20 |
+
variant: "default",
|
21 |
+
},
|
22 |
+
}
|
23 |
+
);
|
24 |
+
|
25 |
+
export interface BadgeProps
|
26 |
+
extends React.HTMLAttributes<HTMLDivElement>,
|
27 |
+
VariantProps<typeof badgeVariants> {}
|
28 |
+
|
29 |
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
30 |
+
return (
|
31 |
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
32 |
+
);
|
33 |
+
}
|
34 |
+
|
35 |
+
export { Badge, badgeVariants };
|
src/demo/components/ui/button.tsx
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority";
|
3 |
+
import { cn } from "../../lib/utils";
|
4 |
+
|
5 |
+
const buttonVariants = cva(
|
6 |
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
7 |
+
{
|
8 |
+
variants: {
|
9 |
+
variant: {
|
10 |
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
11 |
+
destructive:
|
12 |
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
13 |
+
outline:
|
14 |
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
15 |
+
secondary:
|
16 |
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
17 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
18 |
+
link: "text-primary underline-offset-4 hover:underline",
|
19 |
+
},
|
20 |
+
size: {
|
21 |
+
default: "h-10 px-4 py-2",
|
22 |
+
sm: "h-9 rounded-md px-3",
|
23 |
+
lg: "h-11 rounded-md px-8",
|
24 |
+
icon: "h-10 w-10",
|
25 |
+
},
|
26 |
+
},
|
27 |
+
defaultVariants: {
|
28 |
+
variant: "default",
|
29 |
+
size: "default",
|
30 |
+
},
|
31 |
+
}
|
32 |
+
);
|
33 |
+
|
34 |
+
export interface ButtonProps
|
35 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
36 |
+
VariantProps<typeof buttonVariants> {
|
37 |
+
asChild?: boolean;
|
38 |
+
}
|
39 |
+
|
40 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
41 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
42 |
+
return (
|
43 |
+
<button
|
44 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
45 |
+
ref={ref}
|
46 |
+
{...props}
|
47 |
+
/>
|
48 |
+
);
|
49 |
+
}
|
50 |
+
);
|
51 |
+
Button.displayName = "Button";
|
52 |
+
|
53 |
+
export { Button, buttonVariants };
|
src/demo/components/ui/card.tsx
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import { cn } from "../../lib/utils";
|
3 |
+
|
4 |
+
const Card = React.forwardRef<
|
5 |
+
HTMLDivElement,
|
6 |
+
React.HTMLAttributes<HTMLDivElement>
|
7 |
+
>(({ className, ...props }, ref) => (
|
8 |
+
<div
|
9 |
+
ref={ref}
|
10 |
+
className={cn(
|
11 |
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
12 |
+
className
|
13 |
+
)}
|
14 |
+
{...props}
|
15 |
+
/>
|
16 |
+
));
|
17 |
+
Card.displayName = "Card";
|
18 |
+
|
19 |
+
const CardHeader = React.forwardRef<
|
20 |
+
HTMLDivElement,
|
21 |
+
React.HTMLAttributes<HTMLDivElement>
|
22 |
+
>(({ className, ...props }, ref) => (
|
23 |
+
<div
|
24 |
+
ref={ref}
|
25 |
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
26 |
+
{...props}
|
27 |
+
/>
|
28 |
+
));
|
29 |
+
CardHeader.displayName = "CardHeader";
|
30 |
+
|
31 |
+
const CardTitle = React.forwardRef<
|
32 |
+
HTMLParagraphElement,
|
33 |
+
React.HTMLAttributes<HTMLHeadingElement>
|
34 |
+
>(({ className, ...props }, ref) => (
|
35 |
+
<h3
|
36 |
+
ref={ref}
|
37 |
+
className={cn(
|
38 |
+
"text-2xl font-semibold leading-none tracking-tight",
|
39 |
+
className
|
40 |
+
)}
|
41 |
+
{...props}
|
42 |
+
/>
|
43 |
+
));
|
44 |
+
CardTitle.displayName = "CardTitle";
|
45 |
+
|
46 |
+
const CardDescription = React.forwardRef<
|
47 |
+
HTMLParagraphElement,
|
48 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
49 |
+
>(({ className, ...props }, ref) => (
|
50 |
+
<p
|
51 |
+
ref={ref}
|
52 |
+
className={cn("text-sm text-muted-foreground", className)}
|
53 |
+
{...props}
|
54 |
+
/>
|
55 |
+
));
|
56 |
+
CardDescription.displayName = "CardDescription";
|
57 |
+
|
58 |
+
const CardContent = React.forwardRef<
|
59 |
+
HTMLDivElement,
|
60 |
+
React.HTMLAttributes<HTMLDivElement>
|
61 |
+
>(({ className, ...props }, ref) => (
|
62 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
63 |
+
));
|
64 |
+
CardContent.displayName = "CardContent";
|
65 |
+
|
66 |
+
const CardFooter = React.forwardRef<
|
67 |
+
HTMLDivElement,
|
68 |
+
React.HTMLAttributes<HTMLDivElement>
|
69 |
+
>(({ className, ...props }, ref) => (
|
70 |
+
<div
|
71 |
+
ref={ref}
|
72 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
73 |
+
{...props}
|
74 |
+
/>
|
75 |
+
));
|
76 |
+
CardFooter.displayName = "CardFooter";
|
77 |
+
|
78 |
+
export {
|
79 |
+
Card,
|
80 |
+
CardHeader,
|
81 |
+
CardFooter,
|
82 |
+
CardTitle,
|
83 |
+
CardDescription,
|
84 |
+
CardContent,
|
85 |
+
};
|
src/demo/index.css
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
@layer base {
|
6 |
+
* {
|
7 |
+
@apply border-border;
|
8 |
+
}
|
9 |
+
body {
|
10 |
+
@apply bg-background text-foreground;
|
11 |
+
}
|
12 |
+
}
|
src/demo/lib/utils.ts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { type ClassValue, clsx } from "clsx";
|
2 |
+
import { twMerge } from "tailwind-merge";
|
3 |
+
|
4 |
+
export function cn(...inputs: ClassValue[]) {
|
5 |
+
return twMerge(clsx(inputs));
|
6 |
+
}
|
src/demo/main.tsx
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import ReactDOM from "react-dom/client";
|
3 |
+
import { App } from "./App";
|
4 |
+
import "./index.css";
|
5 |
+
|
6 |
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
7 |
+
<React.StrictMode>
|
8 |
+
<App />
|
9 |
+
</React.StrictMode>
|
10 |
+
);
|
src/demo/pages/Calibrate.tsx
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useEffect } from "react";
|
2 |
+
import { Button } from "../components/ui/button";
|
3 |
+
import {
|
4 |
+
Card,
|
5 |
+
CardContent,
|
6 |
+
CardDescription,
|
7 |
+
CardHeader,
|
8 |
+
CardTitle,
|
9 |
+
} from "../components/ui/card";
|
10 |
+
import { Alert, AlertDescription } from "../components/ui/alert";
|
11 |
+
import { Badge } from "../components/ui/badge";
|
12 |
+
import { CalibrationWizard } from "../components/CalibrationWizard";
|
13 |
+
import type { ConnectedRobot } from "../types";
|
14 |
+
|
15 |
+
interface CalibrateProps {
|
16 |
+
selectedRobot: ConnectedRobot;
|
17 |
+
onBack: () => void;
|
18 |
+
onHome: () => void;
|
19 |
+
}
|
20 |
+
|
21 |
+
export function Calibrate({ selectedRobot, onBack, onHome }: CalibrateProps) {
|
22 |
+
const [calibrationStarted, setCalibrationStarted] = useState(false);
|
23 |
+
|
24 |
+
// Auto-start calibration when component mounts
|
25 |
+
useEffect(() => {
|
26 |
+
if (selectedRobot && selectedRobot.isConnected) {
|
27 |
+
setCalibrationStarted(true);
|
28 |
+
}
|
29 |
+
}, [selectedRobot]);
|
30 |
+
|
31 |
+
if (!selectedRobot) {
|
32 |
+
return (
|
33 |
+
<div className="container mx-auto px-4 py-8">
|
34 |
+
<Alert variant="destructive">
|
35 |
+
<AlertDescription>
|
36 |
+
No robot selected. Please go back to setup.
|
37 |
+
</AlertDescription>
|
38 |
+
</Alert>
|
39 |
+
<div className="mt-4">
|
40 |
+
<Button onClick={onBack}>Back to Setup</Button>
|
41 |
+
</div>
|
42 |
+
</div>
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
return (
|
47 |
+
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
48 |
+
<div className="space-y-6">
|
49 |
+
<div className="text-center space-y-2">
|
50 |
+
<h1 className="text-3xl font-bold">Robot Calibration</h1>
|
51 |
+
<p className="text-muted-foreground">
|
52 |
+
Calibrating: {selectedRobot.robotId}
|
53 |
+
</p>
|
54 |
+
</div>
|
55 |
+
|
56 |
+
<Card>
|
57 |
+
<CardHeader>
|
58 |
+
<div className="flex items-center justify-between">
|
59 |
+
<div>
|
60 |
+
<CardTitle className="text-xl">
|
61 |
+
{selectedRobot.robotId}
|
62 |
+
</CardTitle>
|
63 |
+
<CardDescription>{selectedRobot.name}</CardDescription>
|
64 |
+
</div>
|
65 |
+
<div className="flex items-center space-x-2">
|
66 |
+
<Badge
|
67 |
+
variant={selectedRobot.isConnected ? "default" : "secondary"}
|
68 |
+
>
|
69 |
+
{selectedRobot.isConnected ? "Connected" : "Disconnected"}
|
70 |
+
</Badge>
|
71 |
+
<Badge variant="outline">
|
72 |
+
{selectedRobot.robotType?.replace("_", " ")}
|
73 |
+
</Badge>
|
74 |
+
</div>
|
75 |
+
</div>
|
76 |
+
</CardHeader>
|
77 |
+
<CardContent>
|
78 |
+
{!selectedRobot.isConnected ? (
|
79 |
+
<Alert variant="destructive">
|
80 |
+
<AlertDescription>
|
81 |
+
Robot is not connected. Please check connection and try again.
|
82 |
+
</AlertDescription>
|
83 |
+
</Alert>
|
84 |
+
) : calibrationStarted ? (
|
85 |
+
<CalibrationWizard
|
86 |
+
robot={selectedRobot}
|
87 |
+
onComplete={onHome}
|
88 |
+
onCancel={onBack}
|
89 |
+
/>
|
90 |
+
) : (
|
91 |
+
<div className="space-y-4">
|
92 |
+
<div className="text-center py-8">
|
93 |
+
<div className="text-2xl mb-4">🛠️</div>
|
94 |
+
<h3 className="text-lg font-semibold mb-2">
|
95 |
+
Ready to Calibrate
|
96 |
+
</h3>
|
97 |
+
<p className="text-muted-foreground mb-4">
|
98 |
+
Make sure your robot arm is in a safe position and you have
|
99 |
+
a clear workspace.
|
100 |
+
</p>
|
101 |
+
<Button onClick={() => setCalibrationStarted(true)} size="lg">
|
102 |
+
Start Calibration
|
103 |
+
</Button>
|
104 |
+
</div>
|
105 |
+
</div>
|
106 |
+
)}
|
107 |
+
</CardContent>
|
108 |
+
</Card>
|
109 |
+
|
110 |
+
<div className="flex justify-center space-x-4">
|
111 |
+
<Button variant="outline" onClick={onBack}>
|
112 |
+
Back to Setup
|
113 |
+
</Button>
|
114 |
+
<Button variant="outline" onClick={onHome}>
|
115 |
+
Back to Home
|
116 |
+
</Button>
|
117 |
+
</div>
|
118 |
+
</div>
|
119 |
+
</div>
|
120 |
+
);
|
121 |
+
}
|
src/demo/pages/Home.tsx
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState } from "react";
|
2 |
+
import { Button } from "../components/ui/button";
|
3 |
+
import {
|
4 |
+
Card,
|
5 |
+
CardContent,
|
6 |
+
CardDescription,
|
7 |
+
CardHeader,
|
8 |
+
CardTitle,
|
9 |
+
} from "../components/ui/card";
|
10 |
+
import { Alert, AlertDescription } from "../components/ui/alert";
|
11 |
+
import { PortManager } from "../components/PortManager";
|
12 |
+
import { CalibrationPanel } from "../components/CalibrationPanel";
|
13 |
+
import { isWebSerialSupported } from "../../lerobot/web/calibrate";
|
14 |
+
import type { ConnectedRobot } from "../types";
|
15 |
+
|
16 |
+
interface HomeProps {
|
17 |
+
onGetStarted: () => void;
|
18 |
+
connectedRobots: ConnectedRobot[];
|
19 |
+
onConnectedRobotsChange: (robots: ConnectedRobot[]) => void;
|
20 |
+
}
|
21 |
+
|
22 |
+
export function Home({
|
23 |
+
onGetStarted,
|
24 |
+
connectedRobots,
|
25 |
+
onConnectedRobotsChange,
|
26 |
+
}: HomeProps) {
|
27 |
+
const [calibratingRobot, setCalibratingRobot] =
|
28 |
+
useState<ConnectedRobot | null>(null);
|
29 |
+
const isSupported = isWebSerialSupported();
|
30 |
+
|
31 |
+
const handleCalibrate = (
|
32 |
+
port: SerialPort,
|
33 |
+
robotType: "so100_follower" | "so100_leader",
|
34 |
+
robotId: string
|
35 |
+
) => {
|
36 |
+
// Find the robot from connectedRobots
|
37 |
+
const robot = connectedRobots.find((r) => r.port === port);
|
38 |
+
if (robot) {
|
39 |
+
setCalibratingRobot(robot);
|
40 |
+
}
|
41 |
+
};
|
42 |
+
|
43 |
+
const handleFinishCalibration = () => {
|
44 |
+
setCalibratingRobot(null);
|
45 |
+
};
|
46 |
+
|
47 |
+
return (
|
48 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
49 |
+
<div className="container mx-auto px-6 py-12">
|
50 |
+
{/* Header */}
|
51 |
+
<div className="text-center mb-12">
|
52 |
+
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
53 |
+
🤖 LeRobot.js
|
54 |
+
</h1>
|
55 |
+
<p className="text-xl text-gray-600 mb-8">
|
56 |
+
State-of-the-art AI for real-world robotics in JavaScript
|
57 |
+
</p>
|
58 |
+
|
59 |
+
{!isSupported && (
|
60 |
+
<Alert variant="destructive" className="max-w-2xl mx-auto mb-8">
|
61 |
+
<AlertDescription>
|
62 |
+
Web Serial API is not supported in this browser. Please use
|
63 |
+
Chrome, Edge, or another Chromium-based browser to use this
|
64 |
+
demo.
|
65 |
+
</AlertDescription>
|
66 |
+
</Alert>
|
67 |
+
)}
|
68 |
+
</div>
|
69 |
+
|
70 |
+
{/* Main Content */}
|
71 |
+
{calibratingRobot ? (
|
72 |
+
<div className="max-w-6xl mx-auto">
|
73 |
+
<div className="mb-4">
|
74 |
+
<Button
|
75 |
+
variant="outline"
|
76 |
+
onClick={() => setCalibratingRobot(null)}
|
77 |
+
>
|
78 |
+
← Back to Dashboard
|
79 |
+
</Button>
|
80 |
+
</div>
|
81 |
+
<CalibrationPanel
|
82 |
+
robot={calibratingRobot}
|
83 |
+
onFinish={handleFinishCalibration}
|
84 |
+
/>
|
85 |
+
</div>
|
86 |
+
) : (
|
87 |
+
<div className="max-w-6xl mx-auto">
|
88 |
+
<PortManager
|
89 |
+
onCalibrate={handleCalibrate}
|
90 |
+
connectedRobots={connectedRobots}
|
91 |
+
onConnectedRobotsChange={onConnectedRobotsChange}
|
92 |
+
/>
|
93 |
+
</div>
|
94 |
+
)}
|
95 |
+
</div>
|
96 |
+
</div>
|
97 |
+
);
|
98 |
+
}
|
src/demo/pages/Setup.tsx
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import { Button } from "../components/ui/button";
|
3 |
+
import {
|
4 |
+
Card,
|
5 |
+
CardContent,
|
6 |
+
CardDescription,
|
7 |
+
CardHeader,
|
8 |
+
CardTitle,
|
9 |
+
} from "../components/ui/card";
|
10 |
+
import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
|
11 |
+
import { Badge } from "../components/ui/badge";
|
12 |
+
import type { ConnectedRobot } from "../types";
|
13 |
+
|
14 |
+
interface SetupProps {
|
15 |
+
connectedRobots: ConnectedRobot[];
|
16 |
+
onBack: () => void;
|
17 |
+
onNext: (robot: ConnectedRobot) => void;
|
18 |
+
}
|
19 |
+
|
20 |
+
export function Setup({ connectedRobots, onBack, onNext }: SetupProps) {
|
21 |
+
const configuredRobots = connectedRobots.filter(
|
22 |
+
(r) => r.robotType && r.robotId
|
23 |
+
);
|
24 |
+
|
25 |
+
return (
|
26 |
+
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
27 |
+
<div className="space-y-6">
|
28 |
+
<div className="text-center space-y-2">
|
29 |
+
<h1 className="text-3xl font-bold">Robot Setup</h1>
|
30 |
+
<p className="text-muted-foreground">
|
31 |
+
Select a connected robot to calibrate
|
32 |
+
</p>
|
33 |
+
</div>
|
34 |
+
|
35 |
+
<div className="space-y-4">
|
36 |
+
<div className="flex items-center justify-between">
|
37 |
+
<h2 className="text-xl font-semibold">Connected Robots</h2>
|
38 |
+
<Badge variant="outline">{configuredRobots.length} ready</Badge>
|
39 |
+
</div>
|
40 |
+
|
41 |
+
{configuredRobots.length === 0 ? (
|
42 |
+
<Card>
|
43 |
+
<CardContent className="text-center py-8">
|
44 |
+
<div className="text-muted-foreground space-y-2">
|
45 |
+
<p>No configured robots found.</p>
|
46 |
+
<p className="text-sm">
|
47 |
+
Go back to the home page to connect and configure your
|
48 |
+
robots.
|
49 |
+
</p>
|
50 |
+
</div>
|
51 |
+
</CardContent>
|
52 |
+
</Card>
|
53 |
+
) : (
|
54 |
+
<div className="grid gap-4">
|
55 |
+
{configuredRobots.map((robot, index) => (
|
56 |
+
<Card
|
57 |
+
key={index}
|
58 |
+
className="cursor-pointer hover:shadow-md transition-shadow"
|
59 |
+
>
|
60 |
+
<CardHeader>
|
61 |
+
<div className="flex items-center justify-between">
|
62 |
+
<div>
|
63 |
+
<CardTitle className="text-lg">
|
64 |
+
{robot.robotId}
|
65 |
+
</CardTitle>
|
66 |
+
<CardDescription>{robot.name}</CardDescription>
|
67 |
+
</div>
|
68 |
+
<div className="flex items-center space-x-2">
|
69 |
+
<Badge
|
70 |
+
variant={robot.isConnected ? "default" : "outline"}
|
71 |
+
>
|
72 |
+
{robot.isConnected ? "Connected" : "Available"}
|
73 |
+
</Badge>
|
74 |
+
<Badge variant="outline">
|
75 |
+
{robot.robotType?.replace("_", " ")}
|
76 |
+
</Badge>
|
77 |
+
</div>
|
78 |
+
</div>
|
79 |
+
</CardHeader>
|
80 |
+
<CardContent>
|
81 |
+
<Button onClick={() => onNext(robot)} className="w-full">
|
82 |
+
Calibrate This Robot
|
83 |
+
</Button>
|
84 |
+
</CardContent>
|
85 |
+
</Card>
|
86 |
+
))}
|
87 |
+
</div>
|
88 |
+
)}
|
89 |
+
</div>
|
90 |
+
|
91 |
+
<div className="flex justify-center">
|
92 |
+
<Button variant="outline" onClick={onBack}>
|
93 |
+
Back to Home
|
94 |
+
</Button>
|
95 |
+
</div>
|
96 |
+
</div>
|
97 |
+
</div>
|
98 |
+
);
|
99 |
+
}
|
src/demo/types.ts
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface ConnectedRobot {
|
2 |
+
port: SerialPort;
|
3 |
+
name: string;
|
4 |
+
isConnected: boolean;
|
5 |
+
robotType?: "so100_follower" | "so100_leader";
|
6 |
+
robotId?: string;
|
7 |
+
serialNumber?: string; // Unique identifier from USB device
|
8 |
+
usbMetadata?: {
|
9 |
+
vendorId: string;
|
10 |
+
productId: string;
|
11 |
+
serialNumber: string;
|
12 |
+
manufacturerName: string;
|
13 |
+
productName: string;
|
14 |
+
usbVersionMajor?: number;
|
15 |
+
usbVersionMinor?: number;
|
16 |
+
deviceClass?: number;
|
17 |
+
deviceSubclass?: number;
|
18 |
+
deviceProtocol?: number;
|
19 |
+
};
|
20 |
+
}
|
src/vite-env.d.ts
CHANGED
@@ -1 +1,33 @@
|
|
1 |
/// <reference types="vite/client" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
/// <reference types="vite/client" />
|
2 |
+
|
3 |
+
// WebUSB API type declarations
|
4 |
+
interface USBDevice {
|
5 |
+
vendorId: number;
|
6 |
+
productId: number;
|
7 |
+
serialNumber?: string;
|
8 |
+
manufacturerName?: string;
|
9 |
+
productName?: string;
|
10 |
+
usbVersionMajor: number;
|
11 |
+
usbVersionMinor: number;
|
12 |
+
deviceClass: number;
|
13 |
+
deviceSubclass: number;
|
14 |
+
deviceProtocol: number;
|
15 |
+
}
|
16 |
+
|
17 |
+
interface USBDeviceFilter {
|
18 |
+
vendorId?: number;
|
19 |
+
productId?: number;
|
20 |
+
}
|
21 |
+
|
22 |
+
interface USBDeviceRequestOptions {
|
23 |
+
filters: USBDeviceFilter[];
|
24 |
+
}
|
25 |
+
|
26 |
+
interface USB {
|
27 |
+
getDevices(): Promise<USBDevice[]>;
|
28 |
+
requestDevice(options: USBDeviceRequestOptions): Promise<USBDevice>;
|
29 |
+
}
|
30 |
+
|
31 |
+
interface Navigator {
|
32 |
+
usb: USB;
|
33 |
+
}
|
tailwind.config.js
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** @type {import('tailwindcss').Config} */
|
2 |
+
export default {
|
3 |
+
content: ["./index.html", "./src/demo/**/*.{js,ts,jsx,tsx}"],
|
4 |
+
theme: {
|
5 |
+
extend: {
|
6 |
+
borderRadius: {
|
7 |
+
lg: "var(--radius)",
|
8 |
+
md: "calc(var(--radius) - 2px)",
|
9 |
+
sm: "calc(var(--radius) - 4px)",
|
10 |
+
},
|
11 |
+
colors: {
|
12 |
+
background: "hsl(var(--background))",
|
13 |
+
foreground: "hsl(var(--foreground))",
|
14 |
+
card: {
|
15 |
+
DEFAULT: "hsl(var(--card))",
|
16 |
+
foreground: "hsl(var(--foreground))",
|
17 |
+
},
|
18 |
+
popover: {
|
19 |
+
DEFAULT: "hsl(var(--popover))",
|
20 |
+
foreground: "hsl(var(--popover-foreground))",
|
21 |
+
},
|
22 |
+
primary: {
|
23 |
+
DEFAULT: "hsl(var(--primary))",
|
24 |
+
foreground: "hsl(var(--primary-foreground))",
|
25 |
+
},
|
26 |
+
secondary: {
|
27 |
+
DEFAULT: "hsl(var(--secondary))",
|
28 |
+
foreground: "hsl(var(--secondary-foreground))",
|
29 |
+
},
|
30 |
+
muted: {
|
31 |
+
DEFAULT: "hsl(var(--muted))",
|
32 |
+
foreground: "hsl(var(--muted-foreground))",
|
33 |
+
},
|
34 |
+
accent: {
|
35 |
+
DEFAULT: "hsl(var(--accent))",
|
36 |
+
foreground: "hsl(var(--accent-foreground))",
|
37 |
+
},
|
38 |
+
destructive: {
|
39 |
+
DEFAULT: "hsl(var(--destructive))",
|
40 |
+
foreground: "hsl(var(--destructive-foreground))",
|
41 |
+
},
|
42 |
+
border: "hsl(var(--border))",
|
43 |
+
input: "hsl(var(--input))",
|
44 |
+
ring: "hsl(var(--ring))",
|
45 |
+
chart: {
|
46 |
+
1: "hsl(var(--chart-1))",
|
47 |
+
2: "hsl(var(--chart-2))",
|
48 |
+
3: "hsl(var(--chart-3))",
|
49 |
+
4: "hsl(var(--chart-4))",
|
50 |
+
5: "hsl(var(--chart-5))",
|
51 |
+
},
|
52 |
+
},
|
53 |
+
},
|
54 |
+
},
|
55 |
+
plugins: [],
|
56 |
+
};
|
vanilla.html
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>🤖 lerobot.js - Vanilla Demo</title>
|
7 |
+
<meta
|
8 |
+
name="description"
|
9 |
+
content="State-of-the-art AI for real-world robotics in JavaScript/TypeScript"
|
10 |
+
/>
|
11 |
+
<style>
|
12 |
+
/* Prevent flash of unstyled content */
|
13 |
+
body {
|
14 |
+
opacity: 0;
|
15 |
+
}
|
16 |
+
body.loaded {
|
17 |
+
opacity: 1;
|
18 |
+
transition: opacity 0.3s ease;
|
19 |
+
}
|
20 |
+
</style>
|
21 |
+
</head>
|
22 |
+
<body>
|
23 |
+
<div id="app"></div>
|
24 |
+
<script type="module" src="/src/main.ts"></script>
|
25 |
+
<script>
|
26 |
+
// Add loaded class when page is ready
|
27 |
+
window.addEventListener("load", () => {
|
28 |
+
document.body.classList.add("loaded");
|
29 |
+
});
|
30 |
+
</script>
|
31 |
+
</body>
|
32 |
+
</html>
|
vite.config.ts
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { defineConfig } from "vite";
|
2 |
+
import react from "@vitejs/plugin-react";
|
3 |
+
import { resolve } from "path";
|
4 |
+
|
5 |
+
export default defineConfig(({ mode }) => {
|
6 |
+
const baseConfig = {
|
7 |
+
plugins: [],
|
8 |
+
resolve: {
|
9 |
+
alias: {
|
10 |
+
"@": resolve(__dirname, "./src"),
|
11 |
+
},
|
12 |
+
},
|
13 |
+
};
|
14 |
+
|
15 |
+
if (mode === "demo") {
|
16 |
+
// React demo mode - includes React, Tailwind, shadcn/ui
|
17 |
+
return {
|
18 |
+
...baseConfig,
|
19 |
+
plugins: [react()],
|
20 |
+
css: {
|
21 |
+
postcss: "./postcss.config.mjs",
|
22 |
+
},
|
23 |
+
build: {
|
24 |
+
outDir: "dist/demo",
|
25 |
+
rollupOptions: {
|
26 |
+
input: {
|
27 |
+
main: resolve(__dirname, "index.html"),
|
28 |
+
},
|
29 |
+
},
|
30 |
+
},
|
31 |
+
};
|
32 |
+
}
|
33 |
+
|
34 |
+
if (mode === "vanilla") {
|
35 |
+
// Vanilla mode - current implementation without React
|
36 |
+
return {
|
37 |
+
...baseConfig,
|
38 |
+
build: {
|
39 |
+
outDir: "dist/vanilla",
|
40 |
+
rollupOptions: {
|
41 |
+
input: {
|
42 |
+
main: resolve(__dirname, "vanilla.html"),
|
43 |
+
},
|
44 |
+
},
|
45 |
+
},
|
46 |
+
};
|
47 |
+
}
|
48 |
+
|
49 |
+
if (mode === "lib") {
|
50 |
+
// Library mode - core library without any demo UI
|
51 |
+
return {
|
52 |
+
...baseConfig,
|
53 |
+
build: {
|
54 |
+
lib: {
|
55 |
+
entry: resolve(__dirname, "src/main.ts"),
|
56 |
+
name: "LeRobot",
|
57 |
+
fileName: "lerobot",
|
58 |
+
},
|
59 |
+
rollupOptions: {
|
60 |
+
external: ["serialport", "react", "react-dom"],
|
61 |
+
output: {
|
62 |
+
globals: {
|
63 |
+
serialport: "SerialPort",
|
64 |
+
react: "React",
|
65 |
+
"react-dom": "ReactDOM",
|
66 |
+
},
|
67 |
+
},
|
68 |
+
},
|
69 |
+
},
|
70 |
+
};
|
71 |
+
}
|
72 |
+
|
73 |
+
// Default mode (fallback to demo)
|
74 |
+
return defineConfig({ mode: "demo" });
|
75 |
+
});
|