jurmy24 commited on
Commit
0f1e910
·
1 Parent(s): 3a909c0

updates to frontend

Browse files
src/App.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import { Toaster } from "@/components/ui/toaster";
2
  import { Toaster as Sonner } from "@/components/ui/sonner";
3
  import { TooltipProvider } from "@/components/ui/tooltip";
@@ -11,6 +12,7 @@ import Recording from "./pages/Recording";
11
  import Calibration from "./pages/Calibration";
12
  import Training from "./pages/Training";
13
  import { UrdfProvider } from "./contexts/UrdfContext";
 
14
 
15
  const queryClient = new QueryClient();
16
 
@@ -28,6 +30,7 @@ const App = () => (
28
  <Route path="/recording" element={<Recording />} />
29
  <Route path="/calibration" element={<Calibration />} />
30
  <Route path="/training" element={<Training />} />
 
31
  {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
32
  <Route path="*" element={<NotFound />} />
33
  </Routes>
 
1
+
2
  import { Toaster } from "@/components/ui/toaster";
3
  import { Toaster as Sonner } from "@/components/ui/sonner";
4
  import { TooltipProvider } from "@/components/ui/tooltip";
 
12
  import Calibration from "./pages/Calibration";
13
  import Training from "./pages/Training";
14
  import { UrdfProvider } from "./contexts/UrdfContext";
15
+ import EditDataset from "./pages/EditDataset";
16
 
17
  const queryClient = new QueryClient();
18
 
 
30
  <Route path="/recording" element={<Recording />} />
31
  <Route path="/calibration" element={<Calibration />} />
32
  <Route path="/training" element={<Training />} />
33
+ <Route path="/edit-dataset" element={<EditDataset />} />
34
  {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
35
  <Route path="*" element={<NotFound />} />
36
  </Routes>
src/components/Logo.tsx CHANGED
@@ -6,19 +6,14 @@ interface LogoProps extends React.HTMLAttributes<HTMLDivElement> {
6
  iconOnly?: boolean;
7
  }
8
 
9
- const Logo: React.FC<LogoProps> = ({ className, iconOnly = false }) => {
10
- return (
11
- <div className={cn("flex items-center gap-2", className)}>
12
- <img
13
- src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png"
14
- alt="LiveLab Logo"
15
- className="h-8 w-8"
16
- />
17
- {!iconOnly && (
18
- <span className="text-xl font-bold text-white">LiveLab</span>
19
- )}
20
- </div>
21
- );
22
  };
23
 
24
  export default Logo;
 
6
  iconOnly?: boolean;
7
  }
8
 
9
+ const Logo: React.FC<LogoProps> = ({
10
+ className,
11
+ iconOnly = false
12
+ }) => {
13
+ return <div className={cn("flex items-center gap-2", className)}>
14
+ <img src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png" alt="LeLab Logo" className="h-8 w-8" />
15
+ {!iconOnly && <span className="font-bold text-white text-2xl">LeLab</span>}
16
+ </div>;
 
 
 
 
 
17
  };
18
 
19
  export default Logo;
src/components/control/RobotArm.tsx CHANGED
@@ -1,39 +1,46 @@
1
 
2
  import React from 'react';
 
 
3
 
4
  const RobotArm = () => {
5
  return (
6
- <group>
7
- {/* Base */}
8
- <mesh position={[0, -0.25, 0]}>
9
- <cylinderGeometry args={[1, 1, 0.5]} />
10
- <meshPhongMaterial color="#333333" />
11
- </mesh>
12
-
13
- {/* First joint */}
14
- <mesh position={[0, 0.5, 0]}>
15
- <boxGeometry args={[0.3, 1.5, 0.3]} />
16
- <meshPhongMaterial color="#ff6b35" />
17
- </mesh>
18
-
19
- {/* Second segment */}
20
- <mesh position={[0.9, 1.2, 0]} rotation={[0, 0, 0.3]}>
21
- <boxGeometry args={[1.8, 0.25, 0.25]} />
22
- <meshPhongMaterial color="#ffdd44" />
23
- </mesh>
24
-
25
- {/* Third segment */}
26
- <mesh position={[1.8, 1.7, 0]} rotation={[0, 0, -0.5]}>
27
- <boxGeometry args={[1.2, 0.2, 0.2]} />
28
- <meshPhongMaterial color="#ff6b35" />
29
- </mesh>
30
-
31
- {/* End effector */}
32
- <mesh position={[2.3, 1.3, 0]}>
33
- <boxGeometry args={[0.3, 0.3, 0.15]} />
34
- <meshPhongMaterial color="#ffdd44" />
35
- </mesh>
36
- </group>
 
 
 
 
 
37
  );
38
  };
39
 
 
1
 
2
  import React from 'react';
3
+ import { Canvas } from '@react-three/fiber';
4
+ import { OrbitControls } from '@react-three/drei';
5
 
6
  const RobotArm = () => {
7
  return (
8
+ <Canvas>
9
+ <ambientLight intensity={0.5} />
10
+ <directionalLight position={[10, 10, 5]} intensity={1} />
11
+ <group>
12
+ {/* Base */}
13
+ <mesh position={[0, -0.25, 0]}>
14
+ <cylinderGeometry args={[1, 1, 0.5]} />
15
+ <meshPhongMaterial color="#333333" />
16
+ </mesh>
17
+
18
+ {/* First joint */}
19
+ <mesh position={[0, 0.5, 0]}>
20
+ <boxGeometry args={[0.3, 1.5, 0.3]} />
21
+ <meshPhongMaterial color="#ff6b35" />
22
+ </mesh>
23
+
24
+ {/* Second segment */}
25
+ <mesh position={[0.9, 1.2, 0]} rotation={[0, 0, 0.3]}>
26
+ <boxGeometry args={[1.8, 0.25, 0.25]} />
27
+ <meshPhongMaterial color="#ffdd44" />
28
+ </mesh>
29
+
30
+ {/* Third segment */}
31
+ <mesh position={[1.8, 1.7, 0]} rotation={[0, 0, -0.5]}>
32
+ <boxGeometry args={[1.2, 0.2, 0.2]} />
33
+ <meshPhongMaterial color="#ff6b35" />
34
+ </mesh>
35
+
36
+ {/* End effector */}
37
+ <mesh position={[2.3, 1.3, 0]}>
38
+ <boxGeometry args={[0.3, 0.3, 0.15]} />
39
+ <meshPhongMaterial color="#ffdd44" />
40
+ </mesh>
41
+ </group>
42
+ <OrbitControls />
43
+ </Canvas>
44
  );
45
  };
46
 
src/components/control/VisualizerPanel.tsx CHANGED
@@ -1,9 +1,11 @@
 
1
  import React from "react";
2
  import { Button } from "@/components/ui/button";
3
- import { ArrowLeft } from "lucide-react";
4
  import { cn } from "@/lib/utils";
5
  import UrdfViewer from "../UrdfViewer";
6
  import UrdfProcessorInitializer from "../UrdfProcessorInitializer";
 
7
 
8
  interface VisualizerPanelProps {
9
  onGoBack: () => void;
@@ -17,48 +19,40 @@ const VisualizerPanel: React.FC<VisualizerPanelProps> = ({
17
  return (
18
  <div
19
  className={cn(
20
- "w-full lg:w-1/2 p-2 sm:p-4 space-y-4 flex flex-col",
21
  className
22
  )}
23
  >
24
  <div className="bg-gray-900 rounded-lg p-4 flex-1 flex flex-col">
25
- <div className="flex items-center justify-between mb-4">
26
- <div className="flex items-center gap-3">
27
- <img
28
- src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png"
29
- alt="LiveLab Logo"
30
- className="h-8 w-8"
31
- />
32
- <h2 className="text-xl font-bold text-white">LiveLab</h2>
33
- </div>
34
  <Button
35
  variant="ghost"
36
  size="icon"
37
  onClick={onGoBack}
38
- className="text-gray-400 hover:text-white hover:bg-gray-800"
39
  >
40
  <ArrowLeft className="h-5 w-5" />
41
  </Button>
 
 
 
42
  </div>
43
  <div className="flex-1 bg-black rounded border border-gray-800 min-h-[50vh] lg:min-h-0">
44
- {/* <Canvas camera={{ position: [5, 3, 5], fov: 50 }}>
45
- <ambientLight intensity={0.4} />
46
- <directionalLight position={[10, 10, 5]} intensity={1} />
47
- <RobotArm />
48
- <OrbitControls enablePan={true} enableZoom={true} enableRotate={true} />
49
- </Canvas> */}
50
  <UrdfProcessorInitializer />
51
  <UrdfViewer />
52
  </div>
53
  </div>
54
 
55
- <div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
56
  {[1, 2, 3, 4].map((cam) => (
57
  <div
58
  key={cam}
59
- className="aspect-video bg-gray-900 rounded border border-gray-700 flex items-center justify-center"
60
  >
61
- <span className="text-gray-400 text-sm">Camera {cam}</span>
 
 
 
62
  </div>
63
  ))}
64
  </div>
 
1
+
2
  import React from "react";
3
  import { Button } from "@/components/ui/button";
4
+ import { ArrowLeft, VideoOff } from "lucide-react";
5
  import { cn } from "@/lib/utils";
6
  import UrdfViewer from "../UrdfViewer";
7
  import UrdfProcessorInitializer from "../UrdfProcessorInitializer";
8
+ import Logo from "@/components/Logo";
9
 
10
  interface VisualizerPanelProps {
11
  onGoBack: () => void;
 
19
  return (
20
  <div
21
  className={cn(
22
+ "w-full p-2 sm:p-4 space-y-4 lg:space-y-0 lg:space-x-4 flex flex-col lg:flex-row",
23
  className
24
  )}
25
  >
26
  <div className="bg-gray-900 rounded-lg p-4 flex-1 flex flex-col">
27
+ <div className="flex items-center gap-4 mb-4">
 
 
 
 
 
 
 
 
28
  <Button
29
  variant="ghost"
30
  size="icon"
31
  onClick={onGoBack}
32
+ className="text-gray-400 hover:text-white hover:bg-gray-800 flex-shrink-0"
33
  >
34
  <ArrowLeft className="h-5 w-5" />
35
  </Button>
36
+ <Logo iconOnly={true} />
37
+ <div className="w-px h-6 bg-gray-700" />
38
+ <h2 className="text-xl font-medium text-gray-200">Teleoperation</h2>
39
  </div>
40
  <div className="flex-1 bg-black rounded border border-gray-800 min-h-[50vh] lg:min-h-0">
 
 
 
 
 
 
41
  <UrdfProcessorInitializer />
42
  <UrdfViewer />
43
  </div>
44
  </div>
45
 
46
+ <div className="grid grid-cols-2 lg:grid-cols-2 gap-2 lg:w-96 flex-shrink-0">
47
  {[1, 2, 3, 4].map((cam) => (
48
  <div
49
  key={cam}
50
+ className="aspect-video bg-gray-900 rounded-lg border border-gray-800 flex flex-col items-center justify-center p-2"
51
  >
52
+ <VideoOff className="h-8 w-8 text-gray-600 mb-2" />
53
+ <span className="text-gray-500 text-xs text-center">
54
+ No Camera Available
55
+ </span>
56
  </div>
57
  ))}
58
  </div>
src/components/landing/LandingHeader.tsx CHANGED
@@ -2,7 +2,7 @@ import React from 'react';
2
  const LandingHeader = () => {
3
  return <div className="text-center space-y-4 max-w-lg w-full">
4
  <img src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png" alt="LiveLab Logo" className="mx-auto h-20 w-20" />
5
- <h1 className="text-5xl font-bold tracking-tight">LiveLab</h1>
6
  <p className="text-xl text-gray-400">LeRobot but on HFSpace.</p>
7
  </div>;
8
  };
 
2
  const LandingHeader = () => {
3
  return <div className="text-center space-y-4 max-w-lg w-full">
4
  <img src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png" alt="LiveLab Logo" className="mx-auto h-20 w-20" />
5
+ <h1 className="text-5xl font-bold tracking-tight">LeLab</h1>
6
  <p className="text-xl text-gray-400">LeRobot but on HFSpace.</p>
7
  </div>;
8
  };
src/components/training/TrainingHeader.tsx CHANGED
@@ -1,59 +1,41 @@
1
-
2
  import React from 'react';
3
  import { useNavigate } from 'react-router-dom';
4
  import { Button } from '@/components/ui/button';
5
  import { ArrowLeft } from 'lucide-react';
6
  import Logo from '@/components/Logo';
7
  import { TrainingStatus } from './types';
8
-
9
  interface TrainingHeaderProps {
10
  trainingStatus: TrainingStatus;
11
  }
12
-
13
- const TrainingHeader: React.FC<TrainingHeaderProps> = ({ trainingStatus }) => {
 
14
  const navigate = useNavigate();
15
-
16
  const getStatusColor = () => {
17
  if (trainingStatus.training_active) return "text-green-400";
18
  return "text-gray-400";
19
  };
20
-
21
  const getStatusText = () => {
22
  if (trainingStatus.training_active) return "Training Active";
23
  return "Ready to Train";
24
  };
25
-
26
- return (
27
- <div className="flex items-center justify-between mb-8">
28
- <div className="flex items-center gap-4">
29
- <Button
30
- variant="ghost"
31
- size="icon"
32
- onClick={() => navigate("/")}
33
- className="text-slate-400 hover:bg-slate-800 hover:text-white rounded-lg"
34
- >
35
  <ArrowLeft className="w-5 h-5" />
36
  </Button>
37
  <Logo />
38
- <h1 className="text-3xl font-bold text-white">
39
  Training Dashboard
40
  </h1>
41
  </div>
42
 
43
  <div className="flex items-center gap-3">
44
- <div
45
- className={`w-3 h-3 rounded-full ${
46
- trainingStatus.training_active
47
- ? "bg-green-400 animate-pulse"
48
- : "bg-slate-500"
49
- }`}
50
- ></div>
51
  <span className={`font-semibold ${getStatusColor()}`}>
52
  {getStatusText()}
53
  </span>
54
  </div>
55
- </div>
56
- );
57
  };
58
-
59
- export default TrainingHeader;
 
 
1
  import React from 'react';
2
  import { useNavigate } from 'react-router-dom';
3
  import { Button } from '@/components/ui/button';
4
  import { ArrowLeft } from 'lucide-react';
5
  import Logo from '@/components/Logo';
6
  import { TrainingStatus } from './types';
 
7
  interface TrainingHeaderProps {
8
  trainingStatus: TrainingStatus;
9
  }
10
+ const TrainingHeader: React.FC<TrainingHeaderProps> = ({
11
+ trainingStatus
12
+ }) => {
13
  const navigate = useNavigate();
 
14
  const getStatusColor = () => {
15
  if (trainingStatus.training_active) return "text-green-400";
16
  return "text-gray-400";
17
  };
 
18
  const getStatusText = () => {
19
  if (trainingStatus.training_active) return "Training Active";
20
  return "Ready to Train";
21
  };
22
+ return <div className="flex items-center justify-between mb-8">
23
+ <div className="flex items-center gap-4 text-3xl">
24
+ <Button variant="ghost" size="icon" onClick={() => navigate("/")} className="text-slate-400 hover:bg-slate-800 hover:text-white rounded-lg">
 
 
 
 
 
 
 
25
  <ArrowLeft className="w-5 h-5" />
26
  </Button>
27
  <Logo />
28
+ <h1 className="font-bold text-white text-2xl">
29
  Training Dashboard
30
  </h1>
31
  </div>
32
 
33
  <div className="flex items-center gap-3">
34
+ <div className={`w-3 h-3 rounded-full ${trainingStatus.training_active ? "bg-green-400 animate-pulse" : "bg-slate-500"}`}></div>
 
 
 
 
 
 
35
  <span className={`font-semibold ${getStatusColor()}`}>
36
  {getStatusText()}
37
  </span>
38
  </div>
39
+ </div>;
 
40
  };
41
+ export default TrainingHeader;
 
src/pages/Calibration.tsx CHANGED
@@ -17,7 +17,6 @@ import { Separator } from "@/components/ui/separator";
17
  import {
18
  ArrowLeft,
19
  Settings,
20
- Wrench,
21
  Activity,
22
  CheckCircle,
23
  XCircle,
@@ -30,6 +29,7 @@ import {
30
  List,
31
  } from "lucide-react";
32
  import { useToast } from "@/hooks/use-toast";
 
33
 
34
  interface CalibrationStatus {
35
  calibration_active: boolean;
@@ -361,7 +361,7 @@ const Calibration = () => {
361
  switch (calibrationStatus.status) {
362
  case "idle":
363
  return {
364
- color: "bg-gray-500",
365
  icon: <Settings className="w-4 h-4" />,
366
  text: "Idle",
367
  };
@@ -397,7 +397,7 @@ const Calibration = () => {
397
  };
398
  default:
399
  return {
400
- color: "bg-gray-500",
401
  icon: <Settings className="w-4 h-4" />,
402
  text: "Unknown",
403
  };
@@ -414,32 +414,31 @@ const Calibration = () => {
414
  }, [calibrationStatus.console_output]);
415
 
416
  return (
417
- <div className="min-h-screen bg-gray-900 text-white p-4">
418
  <div className="max-w-4xl mx-auto">
419
  {/* Header */}
420
  <div className="flex items-center gap-4 mb-6">
421
  <Button
422
- variant="outline"
423
- size="sm"
424
- onClick={() => navigate("/")}
425
- className="border-gray-700 hover:bg-gray-800"
426
  >
427
- <ArrowLeft className="w-4 h-4 mr-2" />
428
- Back to Home
429
  </Button>
430
  <div className="flex items-center gap-3">
431
- <Wrench className="w-8 h-8 text-orange-500" />
432
  <h1 className="text-3xl font-bold">Device Calibration</h1>
433
  </div>
434
  </div>
435
 
436
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
437
  {/* Configuration Panel */}
438
- <Card className="bg-gray-800 border-gray-700">
439
  <CardHeader>
440
- <CardTitle className="flex items-center gap-2">
441
- <Settings className="w-5 h-5" />
442
- Calibration Configuration
443
  </CardTitle>
444
  </CardHeader>
445
  <CardContent className="space-y-6">
@@ -447,24 +446,24 @@ const Calibration = () => {
447
  <div className="space-y-2">
448
  <Label
449
  htmlFor="deviceType"
450
- className="text-sm font-medium text-gray-300"
451
  >
452
  Device Type *
453
  </Label>
454
  <Select value={deviceType} onValueChange={setDeviceType}>
455
- <SelectTrigger className="bg-gray-700 border-gray-600 text-white">
456
  <SelectValue placeholder="Select device type" />
457
  </SelectTrigger>
458
- <SelectContent className="bg-gray-800 border-gray-700">
459
  <SelectItem
460
  value="robot"
461
- className="text-white hover:bg-gray-700"
462
  >
463
  Robot (Follower)
464
  </SelectItem>
465
  <SelectItem
466
  value="teleop"
467
- className="text-white hover:bg-gray-700"
468
  >
469
  Teleoperator (Leader)
470
  </SelectItem>
@@ -476,7 +475,7 @@ const Calibration = () => {
476
  <div className="space-y-2">
477
  <Label
478
  htmlFor="port"
479
- className="text-sm font-medium text-gray-300"
480
  >
481
  Port *
482
  </Label>
@@ -484,8 +483,8 @@ const Calibration = () => {
484
  id="port"
485
  value={port}
486
  onChange={(e) => setPort(e.target.value)}
487
- placeholder="/dev/tty.usbmodem5A460816421"
488
- className="bg-gray-700 border-gray-600 text-white"
489
  />
490
  </div>
491
 
@@ -493,7 +492,7 @@ const Calibration = () => {
493
  <div className="space-y-2">
494
  <Label
495
  htmlFor="configFile"
496
- className="text-sm font-medium text-gray-300"
497
  >
498
  Calibration Config *
499
  </Label>
@@ -501,8 +500,8 @@ const Calibration = () => {
501
  id="configFile"
502
  value={configFile}
503
  onChange={(e) => setConfigFile(e.target.value)}
504
- placeholder="config_name (without .json extension)"
505
- className="bg-gray-700 border-gray-600 text-white"
506
  />
507
  </div>
508
 
@@ -510,18 +509,18 @@ const Calibration = () => {
510
  {deviceType && (
511
  <div className="space-y-3">
512
  <div className="flex items-center gap-2">
513
- <List className="w-4 h-4 text-gray-400" />
514
- <Label className="text-sm font-medium text-gray-300">
515
  Available Configurations
516
  </Label>
517
  {isLoadingConfigs && (
518
- <Loader2 className="w-4 h-4 animate-spin text-gray-400" />
519
  )}
520
  </div>
521
 
522
- <div className="max-h-40 overflow-y-auto bg-gray-700 rounded-lg border border-gray-600">
523
  {availableConfigs.length === 0 ? (
524
- <div className="p-3 text-center text-gray-400 text-sm">
525
  {isLoadingConfigs
526
  ? "Loading..."
527
  : "No configurations found"}
@@ -531,7 +530,7 @@ const Calibration = () => {
531
  {availableConfigs.map((config) => (
532
  <div
533
  key={config.name}
534
- className="flex items-center justify-between bg-gray-600 rounded px-3 py-2 hover:bg-gray-500 transition-colors"
535
  >
536
  <div className="flex-1 min-w-0">
537
  <button
@@ -541,7 +540,7 @@ const Calibration = () => {
541
  >
542
  {config.name}
543
  </button>
544
- <div className="text-xs text-gray-400">
545
  {new Date(
546
  config.modified * 1000
547
  ).toLocaleDateString()}
@@ -554,7 +553,7 @@ const Calibration = () => {
554
  e.stopPropagation();
555
  handleDeleteConfig(config.name);
556
  }}
557
- className="ml-3 p-1 text-red-400 hover:text-red-300 hover:bg-red-900/20 rounded transition-colors"
558
  title={`Delete ${config.name}`}
559
  >
560
  <Trash2 className="w-4 h-4" />
@@ -567,14 +566,14 @@ const Calibration = () => {
567
  </div>
568
  )}
569
 
570
- <Separator className="bg-gray-700" />
571
 
572
  {/* Action Buttons */}
573
  <div className="flex flex-col gap-3">
574
  {!calibrationStatus.calibration_active ? (
575
  <Button
576
  onClick={handleStartCalibration}
577
- className="w-full bg-orange-500 hover:bg-orange-600 text-white py-6 text-lg"
578
  disabled={
579
  isLoadingConfigs || !deviceType || !port || !configFile
580
  }
@@ -586,7 +585,7 @@ const Calibration = () => {
586
  <Button
587
  onClick={handleStopCalibration}
588
  variant="destructive"
589
- className="w-full py-6 text-lg"
590
  >
591
  <Square className="w-5 h-5 mr-2" />
592
  Stop Calibration
@@ -596,7 +595,7 @@ const Calibration = () => {
596
  <Button
597
  onClick={handleReset}
598
  variant="outline"
599
- className="w-full border-gray-600 hover:bg-gray-700 py-6 text-lg"
600
  disabled={calibrationStatus.calibration_active}
601
  >
602
  <RefreshCw className="w-5 h-5 mr-2" />
@@ -607,44 +606,46 @@ const Calibration = () => {
607
  </Card>
608
 
609
  {/* Status Panel */}
610
- <Card className="bg-gray-800 border-gray-700">
611
  <CardHeader>
612
- <CardTitle className="flex items-center gap-2">
613
- <Activity className="w-5 h-5" />
614
- Calibration Status
615
  </CardTitle>
616
  </CardHeader>
617
  <CardContent className="space-y-4">
618
  {/* Current Status */}
619
- <div className="flex items-center justify-between">
620
- <span className="text-gray-300">Status:</span>
621
- <Badge className={`${statusDisplay.color} text-white`}>
 
 
622
  {statusDisplay.icon}
623
  <span className="ml-2">{statusDisplay.text}</span>
624
  </Badge>
625
  </div>
626
 
627
  {calibrationStatus.device_type && (
628
- <div className="flex items-center justify-between">
629
- <span className="text-gray-300">Device:</span>
630
  <span className="text-white capitalize">
631
  {calibrationStatus.device_type}
632
  </span>
633
  </div>
634
  )}
635
 
636
- {/* Calibration Console - Show during calibration */}
637
  {calibrationStatus.calibration_active && (
638
  <div className="space-y-3">
639
  <div className="flex items-center gap-2">
640
- <Settings className="w-4 h-4 text-gray-400" />
641
- <span className="text-sm font-medium text-gray-300">
642
  Calibration Console
643
  </span>
644
  </div>
645
 
646
  {/* Console Output */}
647
- <div className="bg-black rounded-lg p-4 font-mono text-sm">
648
  <div
649
  ref={consoleRef}
650
  className="text-green-400 h-80 overflow-y-auto whitespace-pre-wrap"
@@ -658,13 +659,13 @@ const Calibration = () => {
658
  <Button
659
  onClick={handleSendEnter}
660
  disabled={!calibrationStatus.calibration_active}
661
- className="bg-blue-500 hover:bg-blue-600 px-8 py-2"
662
  >
663
  Press Enter
664
  </Button>
665
  </div>
666
 
667
- <div className="text-xs text-gray-400 text-center">
668
  Click the button above to send Enter to the calibration
669
  process
670
  </div>
@@ -673,38 +674,35 @@ const Calibration = () => {
673
 
674
  {/* Status Messages */}
675
  {calibrationStatus.status === "connecting" && (
676
- <Alert className="bg-yellow-900/50 border-yellow-700">
677
  <AlertCircle className="h-4 w-4" />
678
  <AlertDescription>
679
- Connecting to the device. Please ensure the device is
680
- properly connected.
681
  </AlertDescription>
682
  </Alert>
683
  )}
684
 
685
  {calibrationStatus.status === "calibrating" && (
686
- <Alert className="bg-blue-900/50 border-blue-700">
687
  <Activity className="h-4 w-4" />
688
  <AlertDescription>
689
- Calibration in progress. Please follow the instructions on
690
- the device and do not disconnect.
691
  </AlertDescription>
692
  </Alert>
693
  )}
694
 
695
  {calibrationStatus.status === "completed" && (
696
- <Alert className="bg-green-900/50 border-green-700">
697
  <CheckCircle className="h-4 w-4" />
698
  <AlertDescription>
699
- Calibration completed successfully! The device is now ready
700
- for use.
701
  </AlertDescription>
702
  </Alert>
703
  )}
704
 
705
  {calibrationStatus.status === "error" &&
706
  calibrationStatus.error && (
707
- <Alert className="bg-red-900/50 border-red-700">
708
  <XCircle className="h-4 w-4" />
709
  <AlertDescription>
710
  <strong>Error:</strong> {calibrationStatus.error}
@@ -713,19 +711,17 @@ const Calibration = () => {
713
  )}
714
 
715
  {/* Instructions */}
716
- <div className="bg-gray-700 p-4 rounded-lg">
717
- <h4 className="font-semibold mb-2">
718
- Calibration Instructions:
719
  </h4>
720
- <ol className="text-sm text-gray-300 space-y-1">
721
- <li>1. Select the device type you want to calibrate</li>
722
- <li>2. Enter the correct port for your device</li>
723
- <li>3. Choose the appropriate calibration configuration</li>
724
- <li>4. Move the robot in a middle position</li>
725
- <li>
726
- 5. Click "Start Calibration" and follow device prompts
727
- </li>
728
- <li>6. Move each motor all the way on both sides</li>
729
  </ol>
730
  </div>
731
  </CardContent>
 
17
  import {
18
  ArrowLeft,
19
  Settings,
 
20
  Activity,
21
  CheckCircle,
22
  XCircle,
 
29
  List,
30
  } from "lucide-react";
31
  import { useToast } from "@/hooks/use-toast";
32
+ import Logo from "@/components/Logo";
33
 
34
  interface CalibrationStatus {
35
  calibration_active: boolean;
 
361
  switch (calibrationStatus.status) {
362
  case "idle":
363
  return {
364
+ color: "bg-slate-500",
365
  icon: <Settings className="w-4 h-4" />,
366
  text: "Idle",
367
  };
 
397
  };
398
  default:
399
  return {
400
+ color: "bg-slate-500",
401
  icon: <Settings className="w-4 h-4" />,
402
  text: "Unknown",
403
  };
 
414
  }, [calibrationStatus.console_output]);
415
 
416
  return (
417
+ <div className="min-h-screen bg-slate-900 text-white p-4">
418
  <div className="max-w-4xl mx-auto">
419
  {/* Header */}
420
  <div className="flex items-center gap-4 mb-6">
421
  <Button
422
+ variant="ghost"
423
+ size="icon"
424
+ onClick={() => navigate(-1)}
425
+ className="text-slate-400 hover:text-white hover:bg-slate-800"
426
  >
427
+ <ArrowLeft className="w-5 h-5" />
 
428
  </Button>
429
  <div className="flex items-center gap-3">
430
+ <Logo iconOnly />
431
  <h1 className="text-3xl font-bold">Device Calibration</h1>
432
  </div>
433
  </div>
434
 
435
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
436
  {/* Configuration Panel */}
437
+ <Card className="bg-slate-800/60 border-slate-700 backdrop-blur-sm">
438
  <CardHeader>
439
+ <CardTitle className="flex items-center gap-2 text-slate-200">
440
+ <Settings className="w-5 h-5 text-blue-400" />
441
+ Configuration
442
  </CardTitle>
443
  </CardHeader>
444
  <CardContent className="space-y-6">
 
446
  <div className="space-y-2">
447
  <Label
448
  htmlFor="deviceType"
449
+ className="text-sm font-medium text-slate-300"
450
  >
451
  Device Type *
452
  </Label>
453
  <Select value={deviceType} onValueChange={setDeviceType}>
454
+ <SelectTrigger className="bg-slate-700 border-slate-600 text-white rounded-md">
455
  <SelectValue placeholder="Select device type" />
456
  </SelectTrigger>
457
+ <SelectContent className="bg-slate-800 border-slate-700 text-white">
458
  <SelectItem
459
  value="robot"
460
+ className="hover:bg-slate-700"
461
  >
462
  Robot (Follower)
463
  </SelectItem>
464
  <SelectItem
465
  value="teleop"
466
+ className="hover:bg-slate-700"
467
  >
468
  Teleoperator (Leader)
469
  </SelectItem>
 
475
  <div className="space-y-2">
476
  <Label
477
  htmlFor="port"
478
+ className="text-sm font-medium text-slate-300"
479
  >
480
  Port *
481
  </Label>
 
483
  id="port"
484
  value={port}
485
  onChange={(e) => setPort(e.target.value)}
486
+ placeholder="/dev/tty.usbmodem..."
487
+ className="bg-slate-700 border-slate-600 text-white rounded-md"
488
  />
489
  </div>
490
 
 
492
  <div className="space-y-2">
493
  <Label
494
  htmlFor="configFile"
495
+ className="text-sm font-medium text-slate-300"
496
  >
497
  Calibration Config *
498
  </Label>
 
500
  id="configFile"
501
  value={configFile}
502
  onChange={(e) => setConfigFile(e.target.value)}
503
+ placeholder="config_name (e.g., my_robot_v1)"
504
+ className="bg-slate-700 border-slate-600 text-white rounded-md"
505
  />
506
  </div>
507
 
 
509
  {deviceType && (
510
  <div className="space-y-3">
511
  <div className="flex items-center gap-2">
512
+ <List className="w-4 h-4 text-slate-400" />
513
+ <Label className="text-sm font-medium text-slate-300">
514
  Available Configurations
515
  </Label>
516
  {isLoadingConfigs && (
517
+ <Loader2 className="w-4 h-4 animate-spin text-slate-400" />
518
  )}
519
  </div>
520
 
521
+ <div className="max-h-40 overflow-y-auto bg-slate-900/50 rounded-lg border border-slate-700">
522
  {availableConfigs.length === 0 ? (
523
+ <div className="p-3 text-center text-slate-400 text-sm">
524
  {isLoadingConfigs
525
  ? "Loading..."
526
  : "No configurations found"}
 
530
  {availableConfigs.map((config) => (
531
  <div
532
  key={config.name}
533
+ className="flex items-center justify-between bg-slate-700/50 rounded-md px-3 py-2 hover:bg-slate-700 transition-colors"
534
  >
535
  <div className="flex-1 min-w-0">
536
  <button
 
540
  >
541
  {config.name}
542
  </button>
543
+ <div className="text-xs text-slate-400">
544
  {new Date(
545
  config.modified * 1000
546
  ).toLocaleDateString()}
 
553
  e.stopPropagation();
554
  handleDeleteConfig(config.name);
555
  }}
556
+ className="ml-3 p-1 text-red-500/80 hover:text-red-500 hover:bg-red-500/10 rounded-full transition-colors"
557
  title={`Delete ${config.name}`}
558
  >
559
  <Trash2 className="w-4 h-4" />
 
566
  </div>
567
  )}
568
 
569
+ <Separator className="bg-slate-700" />
570
 
571
  {/* Action Buttons */}
572
  <div className="flex flex-col gap-3">
573
  {!calibrationStatus.calibration_active ? (
574
  <Button
575
  onClick={handleStartCalibration}
576
+ className="w-full bg-blue-600 hover:bg-blue-700 text-white rounded-full py-6 text-lg"
577
  disabled={
578
  isLoadingConfigs || !deviceType || !port || !configFile
579
  }
 
585
  <Button
586
  onClick={handleStopCalibration}
587
  variant="destructive"
588
+ className="w-full rounded-full py-6 text-lg"
589
  >
590
  <Square className="w-5 h-5 mr-2" />
591
  Stop Calibration
 
595
  <Button
596
  onClick={handleReset}
597
  variant="outline"
598
+ className="w-full border-slate-600 hover:bg-slate-700 rounded-full py-6 text-lg"
599
  disabled={calibrationStatus.calibration_active}
600
  >
601
  <RefreshCw className="w-5 h-5 mr-2" />
 
606
  </Card>
607
 
608
  {/* Status Panel */}
609
+ <Card className="bg-slate-800/60 border-slate-700 backdrop-blur-sm">
610
  <CardHeader>
611
+ <CardTitle className="flex items-center gap-2 text-slate-200">
612
+ <Activity className="w-5 h-5 text-teal-400" />
613
+ Status
614
  </CardTitle>
615
  </CardHeader>
616
  <CardContent className="space-y-4">
617
  {/* Current Status */}
618
+ <div className="flex items-center justify-between p-3 bg-slate-900/50 rounded-md">
619
+ <span className="text-slate-300">Status:</span>
620
+ <Badge
621
+ className={`${statusDisplay.color} text-white rounded-md`}
622
+ >
623
  {statusDisplay.icon}
624
  <span className="ml-2">{statusDisplay.text}</span>
625
  </Badge>
626
  </div>
627
 
628
  {calibrationStatus.device_type && (
629
+ <div className="flex items-center justify-between p-3 bg-slate-900/50 rounded-md">
630
+ <span className="text-slate-300">Device:</span>
631
  <span className="text-white capitalize">
632
  {calibrationStatus.device_type}
633
  </span>
634
  </div>
635
  )}
636
 
637
+ {/* Calibration Console */}
638
  {calibrationStatus.calibration_active && (
639
  <div className="space-y-3">
640
  <div className="flex items-center gap-2">
641
+ <Settings className="w-4 h-4 text-slate-400" />
642
+ <span className="text-sm font-medium text-slate-300">
643
  Calibration Console
644
  </span>
645
  </div>
646
 
647
  {/* Console Output */}
648
+ <div className="bg-black rounded-lg p-4 font-mono text-sm border border-slate-700">
649
  <div
650
  ref={consoleRef}
651
  className="text-green-400 h-80 overflow-y-auto whitespace-pre-wrap"
 
659
  <Button
660
  onClick={handleSendEnter}
661
  disabled={!calibrationStatus.calibration_active}
662
+ className="bg-blue-600 hover:bg-blue-700 px-8 py-2 rounded-full"
663
  >
664
  Press Enter
665
  </Button>
666
  </div>
667
 
668
+ <div className="text-xs text-slate-400 text-center">
669
  Click the button above to send Enter to the calibration
670
  process
671
  </div>
 
674
 
675
  {/* Status Messages */}
676
  {calibrationStatus.status === "connecting" && (
677
+ <Alert className="bg-yellow-900/50 border-yellow-700 text-yellow-200">
678
  <AlertCircle className="h-4 w-4" />
679
  <AlertDescription>
680
+ Connecting to the device. Please ensure it's connected.
 
681
  </AlertDescription>
682
  </Alert>
683
  )}
684
 
685
  {calibrationStatus.status === "calibrating" && (
686
+ <Alert className="bg-blue-900/50 border-blue-700 text-blue-200">
687
  <Activity className="h-4 w-4" />
688
  <AlertDescription>
689
+ Calibration in progress. Follow device instructions.
 
690
  </AlertDescription>
691
  </Alert>
692
  )}
693
 
694
  {calibrationStatus.status === "completed" && (
695
+ <Alert className="bg-green-900/50 border-green-700 text-green-200">
696
  <CheckCircle className="h-4 w-4" />
697
  <AlertDescription>
698
+ Calibration completed successfully!
 
699
  </AlertDescription>
700
  </Alert>
701
  )}
702
 
703
  {calibrationStatus.status === "error" &&
704
  calibrationStatus.error && (
705
+ <Alert className="bg-red-900/50 border-red-700 text-red-200">
706
  <XCircle className="h-4 w-4" />
707
  <AlertDescription>
708
  <strong>Error:</strong> {calibrationStatus.error}
 
711
  )}
712
 
713
  {/* Instructions */}
714
+ <div className="bg-slate-900/50 p-4 rounded-lg border border-slate-700">
715
+ <h4 className="font-semibold mb-2 text-slate-200">
716
+ Instructions:
717
  </h4>
718
+ <ol className="text-sm text-slate-300 space-y-1 list-decimal list-inside">
719
+ <li>Select device type.</li>
720
+ <li>Enter the correct port.</li>
721
+ <li>Provide a name for the new calibration config.</li>
722
+ <li>Move the robot to a middle position.</li>
723
+ <li>Click "Start Calibration" & follow device prompts.</li>
724
+ <li>Move each motor to its limits on both sides.</li>
 
 
725
  </ol>
726
  </div>
727
  </CardContent>
src/pages/Landing.tsx CHANGED
@@ -1,25 +1,15 @@
1
- import React, { useState, useEffect } from "react";
 
2
  import { useNavigate } from "react-router-dom";
3
- import { Button } from "@/components/ui/button";
4
- import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
5
- import { Label } from "@/components/ui/label";
6
- import { Input } from "@/components/ui/input";
7
- import {
8
- Select,
9
- SelectContent,
10
- SelectItem,
11
- SelectTrigger,
12
- SelectValue,
13
- } from "@/components/ui/select";
14
- import {
15
- Dialog,
16
- DialogContent,
17
- DialogHeader,
18
- DialogTitle,
19
- DialogDescription,
20
- } from "@/components/ui/dialog";
21
  import { useToast } from "@/hooks/use-toast";
22
- import { Camera, Mic, Settings, Wrench, GraduationCap } from "lucide-react";
 
 
 
 
 
 
 
23
 
24
  const Landing = () => {
25
  const [robotModel, setRobotModel] = useState("");
@@ -90,9 +80,9 @@ const Landing = () => {
90
  }
91
  };
92
 
93
- const handleCalibrationClick = () => {
94
  if (robotModel) {
95
- navigate("/calibration");
96
  }
97
  };
98
 
@@ -102,6 +92,12 @@ const Landing = () => {
102
  }
103
  };
104
 
 
 
 
 
 
 
105
  const handleStartTeleoperation = async () => {
106
  if (!leaderConfig || !followerConfig) {
107
  toast({
@@ -169,7 +165,6 @@ const Landing = () => {
169
  return;
170
  }
171
 
172
- // Navigate to recording page with configuration
173
  const recordingConfig = {
174
  leader_port: recordLeaderPort,
175
  follower_port: recordFollowerPort,
@@ -178,8 +173,8 @@ const Landing = () => {
178
  dataset_repo_id: datasetRepoId,
179
  single_task: singleTask,
180
  num_episodes: numEpisodes,
181
- episode_time_s: 60, // Default 60 seconds - use manual controls to end episodes early
182
- reset_time_s: 15, // Default 15 seconds - use manual controls for faster resets
183
  fps: 30,
184
  video: true,
185
  push_to_hub: false,
@@ -223,477 +218,102 @@ const Landing = () => {
223
  }
224
  };
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  return (
227
- <div className="min-h-screen bg-black text-white flex flex-col items-center justify-center p-4">
228
- <div className="text-center space-y-4 max-w-lg w-full">
229
- <img
230
- src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png"
231
- alt="LiveLab Logo"
232
- className="mx-auto h-20 w-20"
233
- />
234
- <h1 className="text-5xl font-bold tracking-tight">LiveLab</h1>
235
- <p className="text-xl text-gray-400">
236
- Control a robotic arm through telepresence. Your browser becomes both
237
- sensor and controller.
238
- </p>
239
- </div>
240
 
241
  <div className="mt-12 p-8 bg-gray-900 rounded-lg shadow-xl w-full max-w-lg space-y-6 border border-gray-700">
242
- <h2 className="text-2xl font-semibold text-center text-white">
243
- Select Robot Model
244
- </h2>
245
- <RadioGroup
246
- value={robotModel}
247
  onValueChange={setRobotModel}
248
- className="space-y-4"
249
- >
250
- <div>
251
- <RadioGroupItem value="SO100" id="so100" className="sr-only" />
252
- <Label
253
- htmlFor="so100"
254
- className="flex items-center space-x-4 p-4 rounded-md bg-gray-800 border border-gray-700 cursor-pointer transition-all has-[:checked]:border-orange-500 has-[:checked]:bg-gray-800/50"
255
- >
256
- <span className="w-6 h-6 rounded-full border-2 border-gray-500 flex items-center justify-center group-has-[:checked]:border-orange-500">
257
- {robotModel === "SO100" && (
258
- <span className="w-3 h-3 rounded-full bg-orange-500" />
259
- )}
260
- </span>
261
- <span className="text-lg flex-1">SO100</span>
262
- </Label>
263
- </div>
264
- <div>
265
- <RadioGroupItem value="SO101" id="so101" className="sr-only" />
266
- <Label
267
- htmlFor="so101"
268
- className="flex items-center space-x-4 p-4 rounded-md bg-gray-800 border border-gray-700 cursor-pointer transition-all has-[:checked]:border-orange-500 has-[:checked]:bg-gray-800/50"
269
- >
270
- <span className="w-6 h-6 rounded-full border-2 border-gray-500 flex items-center justify-center group-has-[:checked]:border-orange-500">
271
- {robotModel === "SO101" && (
272
- <span className="w-3 h-3 rounded-full bg-orange-500" />
273
- )}
274
- </span>
275
- <span className="text-lg flex-1">SO101</span>
276
- </Label>
277
- </div>
278
- </RadioGroup>
279
- <div className="flex flex-col sm:flex-row gap-4">
280
- <Button
281
- onClick={handleBeginSession}
282
- disabled={!robotModel}
283
- className="flex-1 bg-orange-500 hover:bg-orange-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
284
- >
285
- Begin Session
286
- </Button>
287
- <Button
288
- onClick={handleCalibrationClick}
289
- disabled={!robotModel}
290
- className="flex-1 bg-blue-500 hover:bg-blue-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
291
- >
292
- <Wrench className="w-5 h-5 mr-2" />
293
- Calibration
294
- </Button>
295
- <Button
296
- onClick={handleTeleoperationClick}
297
- disabled={!robotModel}
298
- className="flex-1 bg-yellow-500 hover:bg-yellow-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
299
- >
300
- Teleoperation
301
- </Button>
302
- <Button
303
- onClick={handleRecordingClick}
304
- disabled={!robotModel}
305
- className="flex-1 bg-red-500 hover:bg-red-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
306
- >
307
- Recording
308
- </Button>
309
- <Button
310
- onClick={handleTrainingClick}
311
- disabled={!robotModel}
312
- className="flex-1 bg-purple-500 hover:bg-purple-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
313
- >
314
- <GraduationCap className="w-5 h-5 mr-2" />
315
- Training
316
- </Button>
317
- </div>
318
  </div>
319
 
320
- {/* Permission Modal */}
321
- <Dialog open={showPermissionModal} onOpenChange={setShowPermissionModal}>
322
- <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[480px] p-8">
323
- <DialogHeader>
324
- <div className="flex justify-center items-center gap-4 mb-4">
325
- <Camera className="w-8 h-8 text-orange-500" />
326
- <Mic className="w-8 h-8 text-orange-500" />
327
- </div>
328
- <DialogTitle className="text-white text-center text-2xl font-bold">
329
- Enable Camera & Microphone
330
- </DialogTitle>
331
- </DialogHeader>
332
- <div className="text-center space-y-6 py-4">
333
- <DialogDescription className="text-gray-400 text-base leading-relaxed">
334
- LiveLab requires access to your camera and microphone for a fully
335
- immersive telepresence experience. This enables real-time video
336
- feedback and voice command capabilities.
337
- </DialogDescription>
338
- <div className="flex flex-col sm:flex-row gap-4 justify-center pt-2">
339
- <Button
340
- onClick={() => handlePermissions(true)}
341
- className="w-full sm:w-auto bg-orange-500 hover:bg-orange-600 text-white px-10 py-6 text-lg transition-all shadow-md shadow-orange-500/30 hover:shadow-lg hover:shadow-orange-500/40"
342
- >
343
- Allow Access
344
- </Button>
345
- <Button
346
- onClick={() => handlePermissions(false)}
347
- variant="outline"
348
- className="w-full sm:w-auto border-gray-500 hover:border-gray-200 px-10 py-6 text-lg text-zinc-500 bg-zinc-900 hover:bg-zinc-800"
349
- >
350
- Continue without
351
- </Button>
352
- </div>
353
- </div>
354
- </DialogContent>
355
- </Dialog>
356
 
357
- {/* Teleoperation Configuration Modal */}
358
- <Dialog
359
  open={showTeleoperationModal}
360
  onOpenChange={setShowTeleoperationModal}
361
- >
362
- <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8">
363
- <DialogHeader>
364
- <div className="flex justify-center items-center gap-4 mb-4">
365
- <Settings className="w-8 h-8 text-yellow-500" />
366
- </div>
367
- <DialogTitle className="text-white text-center text-2xl font-bold">
368
- Configure Teleoperation
369
- </DialogTitle>
370
- </DialogHeader>
371
- <div className="space-y-6 py-4">
372
- <DialogDescription className="text-gray-400 text-base leading-relaxed text-center">
373
- Configure the robot arm ports and calibration settings for
374
- teleoperation.
375
- </DialogDescription>
376
-
377
- <div className="grid grid-cols-1 gap-6">
378
- <div className="space-y-2">
379
- <Label
380
- htmlFor="leaderPort"
381
- className="text-sm font-medium text-gray-300"
382
- >
383
- Leader Port
384
- </Label>
385
- <Input
386
- id="leaderPort"
387
- value={leaderPort}
388
- onChange={(e) => setLeaderPort(e.target.value)}
389
- placeholder="/dev/tty.usbmodem5A460816421"
390
- className="bg-gray-800 border-gray-700 text-white"
391
- />
392
- </div>
393
-
394
- <div className="space-y-2">
395
- <Label
396
- htmlFor="leaderConfig"
397
- className="text-sm font-medium text-gray-300"
398
- >
399
- Leader Calibration Config
400
- </Label>
401
- <Select value={leaderConfig} onValueChange={setLeaderConfig}>
402
- <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
403
- <SelectValue
404
- placeholder={
405
- isLoadingConfigs
406
- ? "Loading configs..."
407
- : "Select leader config"
408
- }
409
- />
410
- </SelectTrigger>
411
- <SelectContent className="bg-gray-800 border-gray-700">
412
- {leaderConfigs.map((config) => (
413
- <SelectItem
414
- key={config}
415
- value={config}
416
- className="text-white hover:bg-gray-700"
417
- >
418
- {config}
419
- </SelectItem>
420
- ))}
421
- </SelectContent>
422
- </Select>
423
- </div>
424
-
425
- <div className="space-y-2">
426
- <Label
427
- htmlFor="followerPort"
428
- className="text-sm font-medium text-gray-300"
429
- >
430
- Follower Port
431
- </Label>
432
- <Input
433
- id="followerPort"
434
- value={followerPort}
435
- onChange={(e) => setFollowerPort(e.target.value)}
436
- placeholder="/dev/tty.usbmodem5A460816621"
437
- className="bg-gray-800 border-gray-700 text-white"
438
- />
439
- </div>
440
-
441
- <div className="space-y-2">
442
- <Label
443
- htmlFor="followerConfig"
444
- className="text-sm font-medium text-gray-300"
445
- >
446
- Follower Calibration Config
447
- </Label>
448
- <Select
449
- value={followerConfig}
450
- onValueChange={setFollowerConfig}
451
- >
452
- <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
453
- <SelectValue
454
- placeholder={
455
- isLoadingConfigs
456
- ? "Loading configs..."
457
- : "Select follower config"
458
- }
459
- />
460
- </SelectTrigger>
461
- <SelectContent className="bg-gray-800 border-gray-700">
462
- {followerConfigs.map((config) => (
463
- <SelectItem
464
- key={config}
465
- value={config}
466
- className="text-white hover:bg-gray-700"
467
- >
468
- {config}
469
- </SelectItem>
470
- ))}
471
- </SelectContent>
472
- </Select>
473
- </div>
474
- </div>
475
-
476
- <div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
477
- <Button
478
- onClick={handleStartTeleoperation}
479
- className="w-full sm:w-auto bg-yellow-500 hover:bg-yellow-600 text-white px-10 py-6 text-lg transition-all shadow-md shadow-yellow-500/30 hover:shadow-lg hover:shadow-yellow-500/40"
480
- disabled={isLoadingConfigs}
481
- >
482
- Start Teleoperation
483
- </Button>
484
- <Button
485
- onClick={() => setShowTeleoperationModal(false)}
486
- variant="outline"
487
- className="w-full sm:w-auto border-gray-500 hover:border-gray-200 px-10 py-6 text-lg text-zinc-500 bg-zinc-900 hover:bg-zinc-800"
488
- >
489
- Cancel
490
- </Button>
491
- </div>
492
- </div>
493
- </DialogContent>
494
- </Dialog>
495
-
496
- {/* Recording Configuration Modal */}
497
- <Dialog open={showRecordingModal} onOpenChange={setShowRecordingModal}>
498
- <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8 max-h-[90vh] overflow-y-auto">
499
- <DialogHeader>
500
- <div className="flex justify-center items-center gap-4 mb-4">
501
- <div className="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
502
- <span className="text-white font-bold text-sm">REC</span>
503
- </div>
504
- </div>
505
- <DialogTitle className="text-white text-center text-2xl font-bold">
506
- Configure Recording
507
- </DialogTitle>
508
- </DialogHeader>
509
- <div className="space-y-6 py-4">
510
- <DialogDescription className="text-gray-400 text-base leading-relaxed text-center">
511
- Configure the robot arm settings and dataset parameters for
512
- recording.
513
- </DialogDescription>
514
-
515
- <div className="grid grid-cols-1 gap-6">
516
- {/* Robot Configuration */}
517
- <div className="space-y-4">
518
- <h3 className="text-lg font-semibold text-white border-b border-gray-700 pb-2">
519
- Robot Configuration
520
- </h3>
521
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
522
- <div className="space-y-2">
523
- <Label
524
- htmlFor="recordLeaderPort"
525
- className="text-sm font-medium text-gray-300"
526
- >
527
- Leader Port
528
- </Label>
529
- <Input
530
- id="recordLeaderPort"
531
- value={recordLeaderPort}
532
- onChange={(e) => setRecordLeaderPort(e.target.value)}
533
- placeholder="/dev/tty.usbmodem5A460816421"
534
- className="bg-gray-800 border-gray-700 text-white"
535
- />
536
- </div>
537
- <div className="space-y-2">
538
- <Label
539
- htmlFor="recordLeaderConfig"
540
- className="text-sm font-medium text-gray-300"
541
- >
542
- Leader Calibration Config
543
- </Label>
544
- <Select
545
- value={recordLeaderConfig}
546
- onValueChange={setRecordLeaderConfig}
547
- >
548
- <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
549
- <SelectValue
550
- placeholder={
551
- isLoadingConfigs
552
- ? "Loading configs..."
553
- : "Select leader config"
554
- }
555
- />
556
- </SelectTrigger>
557
- <SelectContent className="bg-gray-800 border-gray-700">
558
- {leaderConfigs.map((config) => (
559
- <SelectItem
560
- key={config}
561
- value={config}
562
- className="text-white hover:bg-gray-700"
563
- >
564
- {config}
565
- </SelectItem>
566
- ))}
567
- </SelectContent>
568
- </Select>
569
- </div>
570
- <div className="space-y-2">
571
- <Label
572
- htmlFor="recordFollowerPort"
573
- className="text-sm font-medium text-gray-300"
574
- >
575
- Follower Port
576
- </Label>
577
- <Input
578
- id="recordFollowerPort"
579
- value={recordFollowerPort}
580
- onChange={(e) => setRecordFollowerPort(e.target.value)}
581
- placeholder="/dev/tty.usbmodem5A460816621"
582
- className="bg-gray-800 border-gray-700 text-white"
583
- />
584
- </div>
585
- <div className="space-y-2">
586
- <Label
587
- htmlFor="recordFollowerConfig"
588
- className="text-sm font-medium text-gray-300"
589
- >
590
- Follower Calibration Config
591
- </Label>
592
- <Select
593
- value={recordFollowerConfig}
594
- onValueChange={setRecordFollowerConfig}
595
- >
596
- <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
597
- <SelectValue
598
- placeholder={
599
- isLoadingConfigs
600
- ? "Loading configs..."
601
- : "Select follower config"
602
- }
603
- />
604
- </SelectTrigger>
605
- <SelectContent className="bg-gray-800 border-gray-700">
606
- {followerConfigs.map((config) => (
607
- <SelectItem
608
- key={config}
609
- value={config}
610
- className="text-white hover:bg-gray-700"
611
- >
612
- {config}
613
- </SelectItem>
614
- ))}
615
- </SelectContent>
616
- </Select>
617
- </div>
618
- </div>
619
- </div>
620
-
621
- {/* Dataset Configuration */}
622
- <div className="space-y-4">
623
- <h3 className="text-lg font-semibold text-white border-b border-gray-700 pb-2">
624
- Dataset Configuration
625
- </h3>
626
- <div className="grid grid-cols-1 gap-4">
627
- <div className="space-y-2">
628
- <Label
629
- htmlFor="datasetRepoId"
630
- className="text-sm font-medium text-gray-300"
631
- >
632
- Dataset Repository ID *
633
- </Label>
634
- <Input
635
- id="datasetRepoId"
636
- value={datasetRepoId}
637
- onChange={(e) => setDatasetRepoId(e.target.value)}
638
- placeholder="username/dataset_name"
639
- className="bg-gray-800 border-gray-700 text-white"
640
- />
641
- </div>
642
- <div className="space-y-2">
643
- <Label
644
- htmlFor="singleTask"
645
- className="text-sm font-medium text-gray-300"
646
- >
647
- Task Name *
648
- </Label>
649
- <Input
650
- id="singleTask"
651
- value={singleTask}
652
- onChange={(e) => setSingleTask(e.target.value)}
653
- placeholder="e.g., pick_and_place"
654
- className="bg-gray-800 border-gray-700 text-white"
655
- />
656
- </div>
657
- <div className="space-y-2">
658
- <Label
659
- htmlFor="numEpisodes"
660
- className="text-sm font-medium text-gray-300"
661
- >
662
- Number of Episodes
663
- </Label>
664
- <Input
665
- id="numEpisodes"
666
- type="number"
667
- min="1"
668
- max="100"
669
- value={numEpisodes}
670
- onChange={(e) => setNumEpisodes(parseInt(e.target.value))}
671
- className="bg-gray-800 border-gray-700 text-white"
672
- />
673
- </div>
674
- </div>
675
- </div>
676
- </div>
677
 
678
- <div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
679
- <Button
680
- onClick={handleStartRecording}
681
- className="w-full sm:w-auto bg-red-500 hover:bg-red-600 text-white px-10 py-6 text-lg transition-all shadow-md shadow-red-500/30 hover:shadow-lg hover:shadow-red-500/40"
682
- disabled={isLoadingConfigs}
683
- >
684
- Start Recording
685
- </Button>
686
- <Button
687
- onClick={() => setShowRecordingModal(false)}
688
- variant="outline"
689
- className="w-full sm:w-auto border-gray-500 hover:border-gray-200 px-10 py-6 text-lg text-zinc-500 bg-zinc-900 hover:bg-zinc-800"
690
- >
691
- Cancel
692
- </Button>
693
- </div>
694
- </div>
695
- </DialogContent>
696
- </Dialog>
 
 
 
697
  </div>
698
  );
699
  };
 
1
+
2
+ import React, { useState } from "react";
3
  import { useNavigate } from "react-router-dom";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import { useToast } from "@/hooks/use-toast";
5
+ import { ArrowRight } from "lucide-react";
6
+ import LandingHeader from "@/components/landing/LandingHeader";
7
+ import RobotModelSelector from "@/components/landing/RobotModelSelector";
8
+ import ActionList from "@/components/landing/ActionList";
9
+ import PermissionModal from "@/components/landing/PermissionModal";
10
+ import TeleoperationModal from "@/components/landing/TeleoperationModal";
11
+ import RecordingModal from "@/components/landing/RecordingModal";
12
+ import { Action } from "@/components/landing/types";
13
 
14
  const Landing = () => {
15
  const [robotModel, setRobotModel] = useState("");
 
80
  }
81
  };
82
 
83
+ const handleEditDatasetClick = () => {
84
  if (robotModel) {
85
+ navigate("/edit-dataset");
86
  }
87
  };
88
 
 
92
  }
93
  };
94
 
95
+ const handleReplayDatasetClick = () => {
96
+ if (robotModel) {
97
+ navigate("/edit-dataset");
98
+ }
99
+ };
100
+
101
  const handleStartTeleoperation = async () => {
102
  if (!leaderConfig || !followerConfig) {
103
  toast({
 
165
  return;
166
  }
167
 
 
168
  const recordingConfig = {
169
  leader_port: recordLeaderPort,
170
  follower_port: recordFollowerPort,
 
173
  dataset_repo_id: datasetRepoId,
174
  single_task: singleTask,
175
  num_episodes: numEpisodes,
176
+ episode_time_s: 60,
177
+ reset_time_s: 15,
178
  fps: 30,
179
  video: true,
180
  push_to_hub: false,
 
218
  }
219
  };
220
 
221
+ const actions: Action[] = [
222
+ {
223
+ title: "Begin Session",
224
+ description: "Start a new control session.",
225
+ handler: handleBeginSession,
226
+ color: "bg-orange-500 hover:bg-orange-600",
227
+ },
228
+ {
229
+ title: "Teleoperation",
230
+ description: "Control the robot arm in real-time.",
231
+ handler: handleTeleoperationClick,
232
+ color: "bg-yellow-500 hover:bg-yellow-600",
233
+ },
234
+ {
235
+ title: "Record Dataset",
236
+ description: "Record episodes for training data.",
237
+ handler: handleRecordingClick,
238
+ color: "bg-red-500 hover:bg-red-600",
239
+ },
240
+ {
241
+ title: "Edit Dataset",
242
+ description: "Review and modify recorded datasets.",
243
+ handler: handleEditDatasetClick,
244
+ color: "bg-blue-500 hover:bg-blue-600",
245
+ },
246
+ {
247
+ title: "Training",
248
+ description: "Train a model on your datasets.",
249
+ handler: handleTrainingClick,
250
+ color: "bg-green-500 hover:bg-green-600",
251
+ },
252
+ {
253
+ title: "Replay Dataset",
254
+ description: "Replay and analyze recorded datasets.",
255
+ handler: handleReplayDatasetClick,
256
+ color: "bg-purple-500 hover:bg-purple-600",
257
+ },
258
+ ];
259
+
260
  return (
261
+ <div className="min-h-screen bg-black text-white flex flex-col items-center p-4 pt-12 sm:pt-20">
262
+ <LandingHeader />
 
 
 
 
 
 
 
 
 
 
 
263
 
264
  <div className="mt-12 p-8 bg-gray-900 rounded-lg shadow-xl w-full max-w-lg space-y-6 border border-gray-700">
265
+ <RobotModelSelector
266
+ robotModel={robotModel}
 
 
 
267
  onValueChange={setRobotModel}
268
+ />
269
+ <ActionList actions={actions} robotModel={robotModel} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  </div>
271
 
272
+ <PermissionModal
273
+ open={showPermissionModal}
274
+ onOpenChange={setShowPermissionModal}
275
+ onPermissionsResult={handlePermissions}
276
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ <TeleoperationModal
 
279
  open={showTeleoperationModal}
280
  onOpenChange={setShowTeleoperationModal}
281
+ leaderPort={leaderPort}
282
+ setLeaderPort={setLeaderPort}
283
+ followerPort={followerPort}
284
+ setFollowerPort={setFollowerPort}
285
+ leaderConfig={leaderConfig}
286
+ setLeaderConfig={setLeaderConfig}
287
+ followerConfig={followerConfig}
288
+ setFollowerConfig={setFollowerConfig}
289
+ leaderConfigs={leaderConfigs}
290
+ followerConfigs={followerConfigs}
291
+ isLoadingConfigs={isLoadingConfigs}
292
+ onStart={handleStartTeleoperation}
293
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
+ <RecordingModal
296
+ open={showRecordingModal}
297
+ onOpenChange={setShowRecordingModal}
298
+ leaderPort={recordLeaderPort}
299
+ setLeaderPort={setRecordLeaderPort}
300
+ followerPort={recordFollowerPort}
301
+ setFollowerPort={setRecordFollowerPort}
302
+ leaderConfig={recordLeaderConfig}
303
+ setLeaderConfig={setRecordLeaderConfig}
304
+ followerConfig={recordFollowerConfig}
305
+ setFollowerConfig={setRecordFollowerConfig}
306
+ leaderConfigs={leaderConfigs}
307
+ followerConfigs={followerConfigs}
308
+ datasetRepoId={datasetRepoId}
309
+ setDatasetRepoId={setDatasetRepoId}
310
+ singleTask={singleTask}
311
+ setSingleTask={setSingleTask}
312
+ numEpisodes={numEpisodes}
313
+ setNumEpisodes={setNumEpisodes}
314
+ isLoadingConfigs={isLoadingConfigs}
315
+ onStart={handleStartRecording}
316
+ />
317
  </div>
318
  );
319
  };
src/pages/Training.tsx CHANGED
@@ -1,117 +1,14 @@
 
1
  import React, { useState, useEffect, useRef } from "react";
2
- import { useNavigate } from "react-router-dom";
3
- import { Button } from "@/components/ui/button";
4
- import { Input } from "@/components/ui/input";
5
- import { Label } from "@/components/ui/label";
6
- import { Textarea } from "@/components/ui/textarea";
7
- import { Switch } from "@/components/ui/switch";
8
- import {
9
- Select,
10
- SelectContent,
11
- SelectItem,
12
- SelectTrigger,
13
- SelectValue,
14
- } from "@/components/ui/select";
15
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
16
- import { Progress } from "@/components/ui/progress";
17
  import { useToast } from "@/components/ui/use-toast";
18
- import {
19
- Play,
20
- Square,
21
- ArrowLeft,
22
- Settings,
23
- Activity,
24
- FileText,
25
- Cpu,
26
- Database,
27
- TrendingUp,
28
- Clock,
29
- AlertCircle,
30
- CheckCircle,
31
- Loader2,
32
- } from "lucide-react";
33
-
34
- interface TrainingConfig {
35
- // Dataset configuration - exact matches from CLI
36
- dataset_repo_id: string; // --dataset.repo_id
37
- dataset_revision?: string; // --dataset.revision
38
- dataset_root?: string; // --dataset.root
39
- dataset_episodes?: number[]; // --dataset.episodes
40
-
41
- // Policy configuration - only type is configurable at top level
42
- policy_type: string; // --policy.type (act, diffusion, pi0, smolvla, tdmpc, vqbet, pi0fast, sac, reward_classifier)
43
-
44
- // Core training parameters - exact matches from CLI
45
- steps: number; // --steps
46
- batch_size: number; // --batch_size
47
- seed?: number; // --seed
48
- num_workers: number; // --num_workers
49
-
50
- // Logging and checkpointing - exact matches from CLI
51
- log_freq: number; // --log_freq
52
- save_freq: number; // --save_freq
53
- eval_freq: number; // --eval_freq
54
- save_checkpoint: boolean; // --save_checkpoint
55
-
56
- // Output configuration - exact matches from CLI
57
- output_dir: string; // --output_dir
58
- resume: boolean; // --resume
59
- job_name?: string; // --job_name
60
-
61
- // Weights & Biases - exact matches from CLI
62
- wandb_enable: boolean; // --wandb.enable
63
- wandb_project?: string; // --wandb.project
64
- wandb_entity?: string; // --wandb.entity
65
- wandb_notes?: string; // --wandb.notes
66
- wandb_run_id?: string; // --wandb.run_id
67
- wandb_mode?: string; // --wandb.mode (online, offline, disabled)
68
- wandb_disable_artifact: boolean; // --wandb.disable_artifact
69
-
70
- // Environment and evaluation - exact matches from CLI
71
- env_type?: string; // --env.type (aloha, pusht, xarm, gym_manipulator, hil)
72
- env_task?: string; // --env.task
73
- eval_n_episodes: number; // --eval.n_episodes
74
- eval_batch_size: number; // --eval.batch_size
75
- eval_use_async_envs: boolean; // --eval.use_async_envs
76
-
77
- // Policy-specific parameters that are commonly used
78
- policy_device?: string; // --policy.device
79
- policy_use_amp: boolean; // --policy.use_amp
80
-
81
- // Optimizer parameters - exact matches from CLI
82
- optimizer_type?: string; // --optimizer.type (adam, adamw, sgd, multi_adam)
83
- optimizer_lr?: number; // --optimizer.lr (will use policy default if not set)
84
- optimizer_weight_decay?: number; // --optimizer.weight_decay
85
- optimizer_grad_clip_norm?: number; // --optimizer.grad_clip_norm
86
-
87
- // Advanced configuration
88
- use_policy_training_preset: boolean; // --use_policy_training_preset
89
- config_path?: string; // --config_path
90
- }
91
-
92
- interface TrainingStatus {
93
- training_active: boolean;
94
- current_step: number;
95
- total_steps: number;
96
- current_loss?: number;
97
- current_lr?: number;
98
- grad_norm?: number;
99
- epoch_time?: number;
100
- eta_seconds?: number;
101
- available_controls: {
102
- stop_training: boolean;
103
- pause_training: boolean;
104
- resume_training: boolean;
105
- };
106
- }
107
-
108
- interface LogEntry {
109
- timestamp: number;
110
- message: string;
111
- }
112
 
113
  const Training = () => {
114
- const navigate = useNavigate();
115
  const { toast } = useToast();
116
  const logContainerRef = useRef<HTMLDivElement>(null);
117
 
@@ -298,945 +195,33 @@ const Training = () => {
298
  return (trainingStatus.current_step / trainingStatus.total_steps) * 100;
299
  };
300
 
301
- const getStatusColor = () => {
302
- if (trainingStatus.training_active) return "text-green-400";
303
- return "text-gray-400";
304
- };
305
-
306
- const getStatusText = () => {
307
- if (trainingStatus.training_active) return "Training Active";
308
- return "Ready to Train";
309
- };
310
-
311
  return (
312
- <div className="min-h-screen bg-gray-950 text-white p-4">
313
  <div className="max-w-7xl mx-auto">
314
- {/* Header */}
315
- <div className="flex items-center justify-between mb-8">
316
- <div className="flex items-center gap-4">
317
- <Button
318
- variant="ghost"
319
- size="sm"
320
- onClick={() => navigate("/")}
321
- className="text-gray-400 hover:text-white"
322
- >
323
- <ArrowLeft className="w-4 h-4 mr-2" />
324
- Back to Home
325
- </Button>
326
- <h1 className="text-4xl font-bold text-white">
327
- Training Dashboard
328
- </h1>
329
- </div>
330
-
331
- <div className="flex items-center gap-3">
332
- <div
333
- className={`w-2 h-2 rounded-full ${
334
- trainingStatus.training_active ? "bg-green-400" : "bg-gray-400"
335
- }`}
336
- ></div>
337
- <span className={`font-semibold ${getStatusColor()}`}>
338
- {getStatusText()}
339
- </span>
340
- </div>
341
- </div>
342
-
343
- {/* Tab Navigation */}
344
- <div className="flex gap-2 mb-6">
345
- <Button
346
- variant={activeTab === "config" ? "default" : "ghost"}
347
- onClick={() => setActiveTab("config")}
348
- className="flex items-center gap-2"
349
- >
350
- <Settings className="w-4 h-4" />
351
- Configuration
352
- </Button>
353
- <Button
354
- variant={activeTab === "monitoring" ? "default" : "ghost"}
355
- onClick={() => setActiveTab("monitoring")}
356
- className="flex items-center gap-2"
357
- >
358
- <Activity className="w-4 h-4" />
359
- Monitoring
360
- </Button>
361
- </div>
362
-
363
- {/* Configuration Tab */}
364
  {activeTab === "config" && (
365
- <div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
366
- {/* Dataset Configuration */}
367
- <Card className="bg-gray-900 border-gray-700">
368
- <CardHeader>
369
- <CardTitle className="flex items-center gap-2 text-white">
370
- <Database className="w-5 h-5" />
371
- Dataset Configuration
372
- </CardTitle>
373
- </CardHeader>
374
- <CardContent className="space-y-4">
375
- <div>
376
- <Label htmlFor="dataset_repo_id" className="text-gray-300">
377
- Dataset Repository ID *
378
- </Label>
379
- <Input
380
- id="dataset_repo_id"
381
- value={trainingConfig.dataset_repo_id}
382
- onChange={(e) =>
383
- updateConfig("dataset_repo_id", e.target.value)
384
- }
385
- placeholder="e.g., your-username/your-dataset"
386
- className="bg-gray-800 border-gray-600 text-white"
387
- />
388
- <p className="text-xs text-gray-500 mt-1">
389
- HuggingFace Hub dataset repository ID
390
- </p>
391
- </div>
392
-
393
- <div>
394
- <Label htmlFor="dataset_revision" className="text-gray-300">
395
- Dataset Revision (optional)
396
- </Label>
397
- <Input
398
- id="dataset_revision"
399
- value={trainingConfig.dataset_revision || ""}
400
- onChange={(e) =>
401
- updateConfig(
402
- "dataset_revision",
403
- e.target.value || undefined
404
- )
405
- }
406
- placeholder="main"
407
- className="bg-gray-800 border-gray-600 text-white"
408
- />
409
- <p className="text-xs text-gray-500 mt-1">
410
- Git revision (branch, tag, or commit hash)
411
- </p>
412
- </div>
413
-
414
- <div>
415
- <Label htmlFor="dataset_root" className="text-gray-300">
416
- Dataset Root Directory (optional)
417
- </Label>
418
- <Input
419
- id="dataset_root"
420
- value={trainingConfig.dataset_root || ""}
421
- onChange={(e) =>
422
- updateConfig("dataset_root", e.target.value || undefined)
423
- }
424
- placeholder="./data"
425
- className="bg-gray-800 border-gray-600 text-white"
426
- />
427
- </div>
428
- </CardContent>
429
- </Card>
430
-
431
- {/* Policy Configuration */}
432
- <Card className="bg-gray-900 border-gray-700">
433
- <CardHeader>
434
- <CardTitle className="flex items-center gap-2 text-white">
435
- <Cpu className="w-5 h-5" />
436
- Policy Configuration
437
- </CardTitle>
438
- </CardHeader>
439
- <CardContent className="space-y-4">
440
- <div>
441
- <Label htmlFor="policy_type" className="text-gray-300">
442
- Policy Type
443
- </Label>
444
- <Select
445
- value={trainingConfig.policy_type}
446
- onValueChange={(value) =>
447
- updateConfig("policy_type", value)
448
- }
449
- >
450
- <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
451
- <SelectValue />
452
- </SelectTrigger>
453
- <SelectContent className="bg-gray-800 border-gray-600">
454
- <SelectItem value="act">
455
- ACT (Action Chunking Transformer)
456
- </SelectItem>
457
- <SelectItem value="diffusion">
458
- Diffusion Policy
459
- </SelectItem>
460
- <SelectItem value="pi0">PI0</SelectItem>
461
- <SelectItem value="smolvla">SmolVLA</SelectItem>
462
- <SelectItem value="tdmpc">TD-MPC</SelectItem>
463
- <SelectItem value="vqbet">VQ-BeT</SelectItem>
464
- <SelectItem value="pi0fast">PI0 Fast</SelectItem>
465
- <SelectItem value="sac">SAC</SelectItem>
466
- <SelectItem value="reward_classifier">
467
- Reward Classifier
468
- </SelectItem>
469
- </SelectContent>
470
- </Select>
471
- </div>
472
-
473
- <div>
474
- <Label htmlFor="policy_device" className="text-gray-300">
475
- Device
476
- </Label>
477
- <Select
478
- value={trainingConfig.policy_device || "cuda"}
479
- onValueChange={(value) =>
480
- updateConfig("policy_device", value)
481
- }
482
- >
483
- <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
484
- <SelectValue />
485
- </SelectTrigger>
486
- <SelectContent className="bg-gray-800 border-gray-600">
487
- <SelectItem value="cuda">CUDA (GPU)</SelectItem>
488
- <SelectItem value="cpu">CPU</SelectItem>
489
- <SelectItem value="mps">MPS (Apple Silicon)</SelectItem>
490
- </SelectContent>
491
- </Select>
492
- </div>
493
-
494
- <div className="flex items-center space-x-3">
495
- <Switch
496
- id="policy_use_amp"
497
- checked={trainingConfig.policy_use_amp}
498
- onCheckedChange={(checked) =>
499
- updateConfig("policy_use_amp", checked)
500
- }
501
- />
502
- <Label htmlFor="policy_use_amp" className="text-gray-300">
503
- Use Automatic Mixed Precision (AMP)
504
- </Label>
505
- </div>
506
- </CardContent>
507
- </Card>
508
-
509
- {/* Training Parameters */}
510
- <Card className="bg-gray-900 border-gray-700">
511
- <CardHeader>
512
- <CardTitle className="flex items-center gap-2 text-white">
513
- <TrendingUp className="w-5 h-5" />
514
- Training Parameters
515
- </CardTitle>
516
- </CardHeader>
517
- <CardContent className="space-y-4">
518
- <div className="grid grid-cols-2 gap-4">
519
- <div>
520
- <Label htmlFor="steps" className="text-gray-300">
521
- Training Steps
522
- </Label>
523
- <Input
524
- id="steps"
525
- type="number"
526
- value={trainingConfig.steps}
527
- onChange={(e) =>
528
- updateConfig("steps", parseInt(e.target.value))
529
- }
530
- className="bg-gray-800 border-gray-600 text-white"
531
- />
532
- </div>
533
-
534
- <div>
535
- <Label htmlFor="batch_size" className="text-gray-300">
536
- Batch Size
537
- </Label>
538
- <Input
539
- id="batch_size"
540
- type="number"
541
- value={trainingConfig.batch_size}
542
- onChange={(e) =>
543
- updateConfig("batch_size", parseInt(e.target.value))
544
- }
545
- className="bg-gray-800 border-gray-600 text-white"
546
- />
547
- </div>
548
- </div>
549
-
550
- <div className="grid grid-cols-2 gap-4">
551
- <div>
552
- <Label htmlFor="seed" className="text-gray-300">
553
- Random Seed
554
- </Label>
555
- <Input
556
- id="seed"
557
- type="number"
558
- value={trainingConfig.seed || ""}
559
- onChange={(e) =>
560
- updateConfig(
561
- "seed",
562
- e.target.value ? parseInt(e.target.value) : undefined
563
- )
564
- }
565
- className="bg-gray-800 border-gray-600 text-white"
566
- />
567
- </div>
568
-
569
- <div>
570
- <Label htmlFor="num_workers" className="text-gray-300">
571
- Number of Workers
572
- </Label>
573
- <Input
574
- id="num_workers"
575
- type="number"
576
- value={trainingConfig.num_workers}
577
- onChange={(e) =>
578
- updateConfig("num_workers", parseInt(e.target.value))
579
- }
580
- className="bg-gray-800 border-gray-600 text-white"
581
- />
582
- </div>
583
- </div>
584
- </CardContent>
585
- </Card>
586
-
587
- {/* Optimizer Configuration */}
588
- <Card className="bg-gray-900 border-gray-700">
589
- <CardHeader>
590
- <CardTitle className="flex items-center gap-2 text-white">
591
- <Settings className="w-5 h-5" />
592
- Optimizer Configuration
593
- </CardTitle>
594
- </CardHeader>
595
- <CardContent className="space-y-4">
596
- <div>
597
- <Label htmlFor="optimizer_type" className="text-gray-300">
598
- Optimizer Type
599
- </Label>
600
- <Select
601
- value={trainingConfig.optimizer_type || "adam"}
602
- onValueChange={(value) =>
603
- updateConfig("optimizer_type", value)
604
- }
605
- >
606
- <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
607
- <SelectValue />
608
- </SelectTrigger>
609
- <SelectContent className="bg-gray-800 border-gray-600">
610
- <SelectItem value="adam">Adam</SelectItem>
611
- <SelectItem value="adamw">AdamW</SelectItem>
612
- <SelectItem value="sgd">SGD</SelectItem>
613
- <SelectItem value="multi_adam">Multi Adam</SelectItem>
614
- </SelectContent>
615
- </Select>
616
- </div>
617
-
618
- <div className="grid grid-cols-3 gap-4">
619
- <div>
620
- <Label htmlFor="optimizer_lr" className="text-gray-300">
621
- Learning Rate
622
- </Label>
623
- <Input
624
- id="optimizer_lr"
625
- type="number"
626
- step="0.0001"
627
- value={trainingConfig.optimizer_lr || ""}
628
- onChange={(e) =>
629
- updateConfig(
630
- "optimizer_lr",
631
- e.target.value
632
- ? parseFloat(e.target.value)
633
- : undefined
634
- )
635
- }
636
- placeholder="Use policy default"
637
- className="bg-gray-800 border-gray-600 text-white"
638
- />
639
- </div>
640
-
641
- <div>
642
- <Label
643
- htmlFor="optimizer_weight_decay"
644
- className="text-gray-300"
645
- >
646
- Weight Decay
647
- </Label>
648
- <Input
649
- id="optimizer_weight_decay"
650
- type="number"
651
- step="0.0001"
652
- value={trainingConfig.optimizer_weight_decay || ""}
653
- onChange={(e) =>
654
- updateConfig(
655
- "optimizer_weight_decay",
656
- e.target.value
657
- ? parseFloat(e.target.value)
658
- : undefined
659
- )
660
- }
661
- placeholder="Use policy default"
662
- className="bg-gray-800 border-gray-600 text-white"
663
- />
664
- </div>
665
-
666
- <div>
667
- <Label
668
- htmlFor="optimizer_grad_clip_norm"
669
- className="text-gray-300"
670
- >
671
- Gradient Clipping
672
- </Label>
673
- <Input
674
- id="optimizer_grad_clip_norm"
675
- type="number"
676
- value={trainingConfig.optimizer_grad_clip_norm || ""}
677
- onChange={(e) =>
678
- updateConfig(
679
- "optimizer_grad_clip_norm",
680
- e.target.value
681
- ? parseFloat(e.target.value)
682
- : undefined
683
- )
684
- }
685
- placeholder="Use policy default"
686
- className="bg-gray-800 border-gray-600 text-white"
687
- />
688
- </div>
689
- </div>
690
- </CardContent>
691
- </Card>
692
-
693
- {/* Logging Configuration */}
694
- <Card className="bg-gray-900 border-gray-700">
695
- <CardHeader>
696
- <CardTitle className="flex items-center gap-2 text-white">
697
- <FileText className="w-5 h-5" />
698
- Logging & Checkpointing
699
- </CardTitle>
700
- </CardHeader>
701
- <CardContent className="space-y-4">
702
- <div className="grid grid-cols-3 gap-4">
703
- <div>
704
- <Label htmlFor="log_freq" className="text-gray-300">
705
- Log Frequency
706
- </Label>
707
- <Input
708
- id="log_freq"
709
- type="number"
710
- value={trainingConfig.log_freq}
711
- onChange={(e) =>
712
- updateConfig("log_freq", parseInt(e.target.value))
713
- }
714
- className="bg-gray-800 border-gray-600 text-white"
715
- />
716
- </div>
717
-
718
- <div>
719
- <Label htmlFor="save_freq" className="text-gray-300">
720
- Save Frequency
721
- </Label>
722
- <Input
723
- id="save_freq"
724
- type="number"
725
- value={trainingConfig.save_freq}
726
- onChange={(e) =>
727
- updateConfig("save_freq", parseInt(e.target.value))
728
- }
729
- className="bg-gray-800 border-gray-600 text-white"
730
- />
731
- </div>
732
-
733
- <div>
734
- <Label htmlFor="eval_freq" className="text-gray-300">
735
- Eval Frequency
736
- </Label>
737
- <Input
738
- id="eval_freq"
739
- type="number"
740
- value={trainingConfig.eval_freq}
741
- onChange={(e) =>
742
- updateConfig("eval_freq", parseInt(e.target.value))
743
- }
744
- className="bg-gray-800 border-gray-600 text-white"
745
- />
746
- </div>
747
- </div>
748
-
749
- <div>
750
- <Label htmlFor="output_dir" className="text-gray-300">
751
- Output Directory
752
- </Label>
753
- <Input
754
- id="output_dir"
755
- value={trainingConfig.output_dir}
756
- onChange={(e) => updateConfig("output_dir", e.target.value)}
757
- className="bg-gray-800 border-gray-600 text-white"
758
- />
759
- </div>
760
-
761
- <div>
762
- <Label htmlFor="job_name" className="text-gray-300">
763
- Job Name (optional)
764
- </Label>
765
- <Input
766
- id="job_name"
767
- value={trainingConfig.job_name || ""}
768
- onChange={(e) =>
769
- updateConfig("job_name", e.target.value || undefined)
770
- }
771
- className="bg-gray-800 border-gray-600 text-white"
772
- />
773
- </div>
774
-
775
- <div className="flex items-center space-x-3">
776
- <Switch
777
- id="save_checkpoint"
778
- checked={trainingConfig.save_checkpoint}
779
- onCheckedChange={(checked) =>
780
- updateConfig("save_checkpoint", checked)
781
- }
782
- />
783
- <Label htmlFor="save_checkpoint" className="text-gray-300">
784
- Save Checkpoints
785
- </Label>
786
- </div>
787
-
788
- <div className="flex items-center space-x-3">
789
- <Switch
790
- id="resume"
791
- checked={trainingConfig.resume}
792
- onCheckedChange={(checked) =>
793
- updateConfig("resume", checked)
794
- }
795
- />
796
- <Label htmlFor="resume" className="text-gray-300">
797
- Resume from Checkpoint
798
- </Label>
799
- </div>
800
- </CardContent>
801
- </Card>
802
-
803
- {/* Weights & Biases Configuration */}
804
- <Card className="bg-gray-900 border-gray-700">
805
- <CardHeader>
806
- <CardTitle className="flex items-center gap-2 text-white">
807
- <TrendingUp className="w-5 h-5" />
808
- Weights & Biases
809
- </CardTitle>
810
- </CardHeader>
811
- <CardContent className="space-y-4">
812
- <div className="flex items-center space-x-3">
813
- <Switch
814
- id="wandb_enable"
815
- checked={trainingConfig.wandb_enable}
816
- onCheckedChange={(checked) =>
817
- updateConfig("wandb_enable", checked)
818
- }
819
- />
820
- <Label htmlFor="wandb_enable" className="text-gray-300">
821
- Enable Weights & Biases Logging
822
- </Label>
823
- </div>
824
-
825
- {trainingConfig.wandb_enable && (
826
- <>
827
- <div>
828
- <Label htmlFor="wandb_project" className="text-gray-300">
829
- W&B Project Name
830
- </Label>
831
- <Input
832
- id="wandb_project"
833
- value={trainingConfig.wandb_project || ""}
834
- onChange={(e) =>
835
- updateConfig(
836
- "wandb_project",
837
- e.target.value || undefined
838
- )
839
- }
840
- placeholder="my-robotics-project"
841
- className="bg-gray-800 border-gray-600 text-white"
842
- />
843
- </div>
844
-
845
- <div>
846
- <Label htmlFor="wandb_entity" className="text-gray-300">
847
- W&B Entity (optional)
848
- </Label>
849
- <Input
850
- id="wandb_entity"
851
- value={trainingConfig.wandb_entity || ""}
852
- onChange={(e) =>
853
- updateConfig(
854
- "wandb_entity",
855
- e.target.value || undefined
856
- )
857
- }
858
- placeholder="your-username"
859
- className="bg-gray-800 border-gray-600 text-white"
860
- />
861
- </div>
862
-
863
- <div>
864
- <Label htmlFor="wandb_notes" className="text-gray-300">
865
- W&B Notes (optional)
866
- </Label>
867
- <Input
868
- id="wandb_notes"
869
- value={trainingConfig.wandb_notes || ""}
870
- onChange={(e) =>
871
- updateConfig(
872
- "wandb_notes",
873
- e.target.value || undefined
874
- )
875
- }
876
- placeholder="Training run notes..."
877
- className="bg-gray-800 border-gray-600 text-white"
878
- />
879
- </div>
880
-
881
- <div>
882
- <Label htmlFor="wandb_mode" className="text-gray-300">
883
- W&B Mode
884
- </Label>
885
- <Select
886
- value={trainingConfig.wandb_mode || "online"}
887
- onValueChange={(value) =>
888
- updateConfig("wandb_mode", value)
889
- }
890
- >
891
- <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
892
- <SelectValue />
893
- </SelectTrigger>
894
- <SelectContent className="bg-gray-800 border-gray-600">
895
- <SelectItem value="online">Online</SelectItem>
896
- <SelectItem value="offline">Offline</SelectItem>
897
- <SelectItem value="disabled">Disabled</SelectItem>
898
- </SelectContent>
899
- </Select>
900
- </div>
901
-
902
- <div className="flex items-center space-x-3">
903
- <Switch
904
- id="wandb_disable_artifact"
905
- checked={trainingConfig.wandb_disable_artifact}
906
- onCheckedChange={(checked) =>
907
- updateConfig("wandb_disable_artifact", checked)
908
- }
909
- />
910
- <Label
911
- htmlFor="wandb_disable_artifact"
912
- className="text-gray-300"
913
- >
914
- Disable Artifacts
915
- </Label>
916
- </div>
917
- </>
918
- )}
919
- </CardContent>
920
- </Card>
921
-
922
- {/* Environment & Evaluation Configuration */}
923
- <Card className="bg-gray-900 border-gray-700">
924
- <CardHeader>
925
- <CardTitle className="flex items-center gap-2 text-white">
926
- <Activity className="w-5 h-5" />
927
- Environment & Evaluation
928
- </CardTitle>
929
- </CardHeader>
930
- <CardContent className="space-y-4">
931
- <div>
932
- <Label htmlFor="env_type" className="text-gray-300">
933
- Environment Type (optional)
934
- </Label>
935
- <Select
936
- value={trainingConfig.env_type || "none"}
937
- onValueChange={(value) =>
938
- updateConfig(
939
- "env_type",
940
- value === "none" ? undefined : value
941
- )
942
- }
943
- >
944
- <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
945
- <SelectValue placeholder="Select environment type" />
946
- </SelectTrigger>
947
- <SelectContent className="bg-gray-800 border-gray-600">
948
- <SelectItem value="none">None</SelectItem>
949
- <SelectItem value="aloha">Aloha</SelectItem>
950
- <SelectItem value="pusht">PushT</SelectItem>
951
- <SelectItem value="xarm">XArm</SelectItem>
952
- <SelectItem value="gym_manipulator">
953
- Gym Manipulator
954
- </SelectItem>
955
- <SelectItem value="hil">HIL</SelectItem>
956
- </SelectContent>
957
- </Select>
958
- </div>
959
-
960
- <div>
961
- <Label htmlFor="env_task" className="text-gray-300">
962
- Environment Task (optional)
963
- </Label>
964
- <Input
965
- id="env_task"
966
- value={trainingConfig.env_task || ""}
967
- onChange={(e) =>
968
- updateConfig("env_task", e.target.value || undefined)
969
- }
970
- placeholder="e.g., insertion_human"
971
- className="bg-gray-800 border-gray-600 text-white"
972
- />
973
- </div>
974
-
975
- <div className="grid grid-cols-2 gap-4">
976
- <div>
977
- <Label htmlFor="eval_n_episodes" className="text-gray-300">
978
- Eval Episodes
979
- </Label>
980
- <Input
981
- id="eval_n_episodes"
982
- type="number"
983
- value={trainingConfig.eval_n_episodes}
984
- onChange={(e) =>
985
- updateConfig(
986
- "eval_n_episodes",
987
- parseInt(e.target.value)
988
- )
989
- }
990
- className="bg-gray-800 border-gray-600 text-white"
991
- />
992
- </div>
993
-
994
- <div>
995
- <Label htmlFor="eval_batch_size" className="text-gray-300">
996
- Eval Batch Size
997
- </Label>
998
- <Input
999
- id="eval_batch_size"
1000
- type="number"
1001
- value={trainingConfig.eval_batch_size}
1002
- onChange={(e) =>
1003
- updateConfig(
1004
- "eval_batch_size",
1005
- parseInt(e.target.value)
1006
- )
1007
- }
1008
- className="bg-gray-800 border-gray-600 text-white"
1009
- />
1010
- </div>
1011
- </div>
1012
-
1013
- <div className="flex items-center space-x-3">
1014
- <Switch
1015
- id="eval_use_async_envs"
1016
- checked={trainingConfig.eval_use_async_envs}
1017
- onCheckedChange={(checked) =>
1018
- updateConfig("eval_use_async_envs", checked)
1019
- }
1020
- />
1021
- <Label
1022
- htmlFor="eval_use_async_envs"
1023
- className="text-gray-300"
1024
- >
1025
- Use Asynchronous Environments
1026
- </Label>
1027
- </div>
1028
- </CardContent>
1029
- </Card>
1030
-
1031
- {/* Advanced Options */}
1032
- <Card className="bg-gray-900 border-gray-700">
1033
- <CardHeader>
1034
- <CardTitle className="flex items-center gap-2 text-white">
1035
- <Settings className="w-5 h-5" />
1036
- Advanced Options
1037
- </CardTitle>
1038
- </CardHeader>
1039
- <CardContent className="space-y-4">
1040
- <div>
1041
- <Label htmlFor="config_path" className="text-gray-300">
1042
- Config Path (optional)
1043
- </Label>
1044
- <Input
1045
- id="config_path"
1046
- value={trainingConfig.config_path || ""}
1047
- onChange={(e) =>
1048
- updateConfig("config_path", e.target.value || undefined)
1049
- }
1050
- placeholder="path/to/config.yaml"
1051
- className="bg-gray-800 border-gray-600 text-white"
1052
- />
1053
- </div>
1054
-
1055
- <div className="flex items-center space-x-3">
1056
- <Switch
1057
- id="use_policy_training_preset"
1058
- checked={trainingConfig.use_policy_training_preset}
1059
- onCheckedChange={(checked) =>
1060
- updateConfig("use_policy_training_preset", checked)
1061
- }
1062
- />
1063
- <Label
1064
- htmlFor="use_policy_training_preset"
1065
- className="text-gray-300"
1066
- >
1067
- Use Policy Training Preset
1068
- </Label>
1069
- </div>
1070
- </CardContent>
1071
- </Card>
1072
- </div>
1073
  )}
1074
 
1075
- {/* Monitoring Tab */}
1076
  {activeTab === "monitoring" && (
1077
- <div className="space-y-6">
1078
- {/* Training Progress */}
1079
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
1080
- <Card className="bg-gray-900 border-gray-700">
1081
- <CardContent className="p-6 text-center">
1082
- <h3 className="text-sm text-gray-400 mb-2">
1083
- Training Progress
1084
- </h3>
1085
- <div className="text-3xl font-bold text-blue-400 mb-2">
1086
- {trainingStatus.current_step} / {trainingStatus.total_steps}
1087
- </div>
1088
- <Progress value={getProgressPercentage()} className="mb-2" />
1089
- <div className="text-sm text-gray-400">
1090
- {getProgressPercentage().toFixed(1)}% Complete
1091
- </div>
1092
- </CardContent>
1093
- </Card>
1094
-
1095
- <Card className="bg-gray-900 border-gray-700">
1096
- <CardContent className="p-6 text-center">
1097
- <h3 className="text-sm text-gray-400 mb-2">Current Loss</h3>
1098
- <div className="text-3xl font-bold text-green-400 mb-2">
1099
- {trainingStatus.current_loss?.toFixed(4) || "N/A"}
1100
- </div>
1101
- <div className="text-sm text-gray-400">Training Loss</div>
1102
- </CardContent>
1103
- </Card>
1104
-
1105
- <Card className="bg-gray-900 border-gray-700">
1106
- <CardContent className="p-6 text-center">
1107
- <h3 className="text-sm text-gray-400 mb-2">Learning Rate</h3>
1108
- <div className="text-3xl font-bold text-orange-400 mb-2">
1109
- {trainingStatus.current_lr?.toExponential(2) || "N/A"}
1110
- </div>
1111
- <div className="text-sm text-gray-400">Current LR</div>
1112
- </CardContent>
1113
- </Card>
1114
-
1115
- <Card className="bg-gray-900 border-gray-700">
1116
- <CardContent className="p-6 text-center">
1117
- <h3 className="text-sm text-gray-400 mb-2">ETA</h3>
1118
- <div className="text-3xl font-bold text-purple-400 mb-2">
1119
- {trainingStatus.eta_seconds
1120
- ? formatTime(trainingStatus.eta_seconds)
1121
- : "N/A"}
1122
- </div>
1123
- <div className="text-sm text-gray-400">Estimated Time</div>
1124
- </CardContent>
1125
- </Card>
1126
-
1127
- <Card className="bg-gray-900 border-gray-700">
1128
- <CardContent className="p-6 text-center">
1129
- <h3 className="text-sm text-gray-400 mb-2">Gradient Norm</h3>
1130
- <div className="text-3xl font-bold text-cyan-400 mb-2">
1131
- {trainingStatus.grad_norm?.toFixed(3) || "N/A"}
1132
- </div>
1133
- <div className="text-sm text-gray-400">Gradient Clipping</div>
1134
- </CardContent>
1135
- </Card>
1136
-
1137
- <Card className="bg-gray-900 border-gray-700">
1138
- <CardContent className="p-6 text-center">
1139
- <h3 className="text-sm text-gray-400 mb-2">
1140
- Training Status
1141
- </h3>
1142
- <div className="text-2xl font-bold text-yellow-400 mb-2">
1143
- {trainingStatus.training_active ? "Active" : "Stopped"}
1144
- </div>
1145
- <div className="text-sm text-gray-400">Current State</div>
1146
- </CardContent>
1147
- </Card>
1148
-
1149
- <Card className="bg-gray-900 border-gray-700">
1150
- <CardContent className="p-6 text-center">
1151
- <h3 className="text-sm text-gray-400 mb-2">Dataset</h3>
1152
- <div className="text-lg font-bold text-pink-400 mb-2 truncate">
1153
- {trainingConfig.dataset_repo_id || "Not Set"}
1154
- </div>
1155
- <div className="text-sm text-gray-400">Repository ID</div>
1156
- </CardContent>
1157
- </Card>
1158
-
1159
- <Card className="bg-gray-900 border-gray-700">
1160
- <CardContent className="p-6 text-center">
1161
- <h3 className="text-sm text-gray-400 mb-2">Policy</h3>
1162
- <div className="text-lg font-bold text-indigo-400 mb-2 uppercase">
1163
- {trainingConfig.policy_type}
1164
- </div>
1165
- <div className="text-sm text-gray-400">Model Type</div>
1166
- </CardContent>
1167
- </Card>
1168
- </div>
1169
-
1170
- {/* Training Logs */}
1171
- <Card className="bg-gray-900 border-gray-700">
1172
- <CardHeader>
1173
- <CardTitle className="flex items-center gap-2 text-white">
1174
- <FileText className="w-5 h-5" />
1175
- Training Logs
1176
- </CardTitle>
1177
- </CardHeader>
1178
- <CardContent>
1179
- <div
1180
- ref={logContainerRef}
1181
- className="bg-gray-950 rounded-lg p-4 h-96 overflow-y-auto font-mono text-sm"
1182
- >
1183
- {logs.length === 0 ? (
1184
- <div className="text-gray-500 text-center py-8">
1185
- No training logs yet. Start training to see output.
1186
- </div>
1187
- ) : (
1188
- logs.map((log, index) => (
1189
- <div key={index} className="mb-1">
1190
- <span className="text-gray-500">
1191
- {new Date(log.timestamp * 1000).toLocaleTimeString()}
1192
- </span>
1193
- <span className="ml-2 text-gray-300">
1194
- {log.message}
1195
- </span>
1196
- </div>
1197
- ))
1198
- )}
1199
- </div>
1200
- </CardContent>
1201
- </Card>
1202
- </div>
1203
  )}
1204
-
1205
- {/* Control Buttons */}
1206
- <div className="fixed bottom-6 right-6 flex gap-3">
1207
- {!trainingStatus.training_active ? (
1208
- <Button
1209
- onClick={handleStartTraining}
1210
- disabled={
1211
- isStartingTraining || !trainingConfig.dataset_repo_id.trim()
1212
- }
1213
- size="lg"
1214
- className="bg-green-500 hover:bg-green-600 text-white font-semibold px-8 py-4 text-lg shadow-lg"
1215
- >
1216
- {isStartingTraining ? (
1217
- <>
1218
- <Loader2 className="w-5 h-5 mr-2 animate-spin" />
1219
- Starting...
1220
- </>
1221
- ) : (
1222
- <>
1223
- <Play className="w-5 h-5 mr-2" />
1224
- Start Training
1225
- </>
1226
- )}
1227
- </Button>
1228
- ) : (
1229
- <Button
1230
- onClick={handleStopTraining}
1231
- disabled={!trainingStatus.available_controls.stop_training}
1232
- size="lg"
1233
- className="bg-red-500 hover:bg-red-600 text-white font-semibold px-8 py-4 text-lg shadow-lg"
1234
- >
1235
- <Square className="w-5 h-5 mr-2" />
1236
- Stop Training
1237
- </Button>
1238
- )}
1239
- </div>
1240
  </div>
1241
  </div>
1242
  );
 
1
+
2
  import React, { useState, useEffect, useRef } from "react";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import { useToast } from "@/components/ui/use-toast";
4
+ import { TrainingConfig, TrainingStatus, LogEntry } from "@/components/training/types";
5
+ import TrainingHeader from "@/components/training/TrainingHeader";
6
+ import TrainingTabs from "@/components/training/TrainingTabs";
7
+ import ConfigurationTab from "@/components/training/ConfigurationTab";
8
+ import MonitoringTab from "@/components/training/MonitoringTab";
9
+ import TrainingControls from "@/components/training/TrainingControls";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  const Training = () => {
 
12
  const { toast } = useToast();
13
  const logContainerRef = useRef<HTMLDivElement>(null);
14
 
 
195
  return (trainingStatus.current_step / trainingStatus.total_steps) * 100;
196
  };
197
 
 
 
 
 
 
 
 
 
 
 
198
  return (
199
+ <div className="min-h-screen bg-slate-900 text-white p-4">
200
  <div className="max-w-7xl mx-auto">
201
+ <TrainingHeader trainingStatus={trainingStatus} />
202
+ <TrainingTabs activeTab={activeTab} setActiveTab={setActiveTab} />
203
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  {activeTab === "config" && (
205
+ <ConfigurationTab config={trainingConfig} updateConfig={updateConfig} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  )}
207
 
 
208
  {activeTab === "monitoring" && (
209
+ <MonitoringTab
210
+ trainingStatus={trainingStatus}
211
+ logs={logs}
212
+ logContainerRef={logContainerRef}
213
+ getProgressPercentage={getProgressPercentage}
214
+ formatTime={formatTime}
215
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  )}
217
+
218
+ <TrainingControls
219
+ trainingStatus={trainingStatus}
220
+ isStartingTraining={isStartingTraining}
221
+ trainingConfig={trainingConfig}
222
+ handleStartTraining={handleStartTraining}
223
+ handleStopTraining={handleStopTraining}
224
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  </div>
226
  </div>
227
  );