jurmy24 commited on
Commit
9d3c32a
·
1 Parent(s): b057866

initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +35 -0
  2. README.md +77 -5
  3. bun.lockb +3 -0
  4. components.json +20 -0
  5. eslint.config.js +29 -0
  6. index.html +24 -0
  7. package.json +88 -0
  8. postcss.config.js +6 -0
  9. public/favicon.ico +0 -0
  10. public/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png +0 -0
  11. public/placeholder.svg +1 -0
  12. public/robots.txt +14 -0
  13. public/so-101-urdf/CMakeLists.txt +38 -0
  14. public/so-101-urdf/README.md +41 -0
  15. public/so-101-urdf/config/joint_names_so_arm_urdf.yaml +1 -0
  16. public/so-101-urdf/joints_properties.xml +12 -0
  17. public/so-101-urdf/meshes/base_motor_holder_so101_v1.stl +3 -0
  18. public/so-101-urdf/meshes/base_so101_v2.stl +3 -0
  19. public/so-101-urdf/meshes/motor_holder_so101_base_v1.stl +3 -0
  20. public/so-101-urdf/meshes/motor_holder_so101_wrist_v1.stl +3 -0
  21. public/so-101-urdf/meshes/moving_jaw_so101_v1.stl +3 -0
  22. public/so-101-urdf/meshes/rotation_pitch_so101_v1.stl +3 -0
  23. public/so-101-urdf/meshes/sts3215_03a_no_horn_v1.stl +3 -0
  24. public/so-101-urdf/meshes/sts3215_03a_v1.stl +3 -0
  25. public/so-101-urdf/meshes/under_arm_so101_v1.stl +3 -0
  26. public/so-101-urdf/meshes/upper_arm_so101_v1.stl +3 -0
  27. public/so-101-urdf/meshes/waveshare_mounting_plate_so101_v2.stl +3 -0
  28. public/so-101-urdf/meshes/wrist_roll_follower_so101_v1.stl +3 -0
  29. public/so-101-urdf/meshes/wrist_roll_pitch_so101_v2.stl +3 -0
  30. public/so-101-urdf/package.xml +26 -0
  31. public/so-101-urdf/urdf/so101_new_calib.urdf +435 -0
  32. src/.gitignore +1 -0
  33. src/App.css +42 -0
  34. src/App.tsx +40 -0
  35. src/components/UrdfProcessorInitializer.tsx +40 -0
  36. src/components/UrdfViewer.tsx +236 -0
  37. src/components/control/CommandBar.tsx +81 -0
  38. src/components/control/MetricsPanel.tsx +190 -0
  39. src/components/control/RobotArm.tsx +40 -0
  40. src/components/control/VisualizerPanel.tsx +69 -0
  41. src/components/test/WebSocketTest.tsx +131 -0
  42. src/components/ui/accordion.tsx +56 -0
  43. src/components/ui/alert-dialog.tsx +139 -0
  44. src/components/ui/alert.tsx +59 -0
  45. src/components/ui/aspect-ratio.tsx +5 -0
  46. src/components/ui/avatar.tsx +48 -0
  47. src/components/ui/badge.tsx +36 -0
  48. src/components/ui/breadcrumb.tsx +115 -0
  49. src/components/ui/button.tsx +56 -0
  50. src/components/ui/calendar.tsx +64 -0
Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-alpine
2
+
3
+ # Use the existing node user (usually UID 1000)
4
+ # Set up environment variables for the node user
5
+ ENV HOME=/home/node \
6
+ PATH=/home/node/.local/bin:$PATH
7
+
8
+ # Create and set up app directory owned by node user
9
+ # Go to user's home directory first to ensure it exists
10
+ WORKDIR $HOME
11
+ RUN mkdir -p $HOME/app && \
12
+ chown -R node:node $HOME/app && \
13
+ chmod -R 755 $HOME/app # Set initial permissions
14
+ WORKDIR $HOME/app
15
+
16
+ # Switch to the node user
17
+ USER node
18
+
19
+ # Copy package files (owned by node)
20
+ COPY --chown=node:node package*.json ./
21
+
22
+ # Install dependencies
23
+ RUN npm install
24
+
25
+ # Copy the entire viewer directory (owned by node)
26
+ COPY --chown=node:node . .
27
+
28
+ # Build the application
29
+ RUN npm run build
30
+
31
+ # Expose port
32
+ EXPOSE 7860
33
+
34
+ # Start the application
35
+ CMD ["npm", "run", "preview", "--", "--port", "7860", "--host"]
README.md CHANGED
@@ -1,12 +1,84 @@
1
  ---
2
  title: LeLab
3
- emoji: 🌍
4
- colorFrom: pink
5
- colorTo: purple
6
  sdk: docker
 
7
  pinned: false
8
- license: mit
9
  short_description: Simple Interface to use LeRobot
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: LeLab
3
+ emoji:
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
 
9
  short_description: Simple Interface to use LeRobot
10
  ---
11
 
12
+ # Welcome to your Lovable project
13
+
14
+ ## Project info
15
+
16
+ **URL**: https://lovable.dev/projects/58aee4a4-2f51-49a3-a4d9-56d3d66140b4
17
+
18
+ ## How can I edit this code?
19
+
20
+ There are several ways of editing your application.
21
+
22
+ **Use Lovable**
23
+
24
+ Simply visit the [Lovable Project](https://lovable.dev/projects/58aee4a4-2f51-49a3-a4d9-56d3d66140b4) and start prompting.
25
+
26
+ Changes made via Lovable will be committed automatically to this repo.
27
+
28
+ **Use your preferred IDE**
29
+
30
+ If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
31
+
32
+ The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
33
+
34
+ Follow these steps:
35
+
36
+ ```sh
37
+ # Step 1: Clone the repository using the project's Git URL.
38
+ git clone <YOUR_GIT_URL>
39
+
40
+ # Step 2: Navigate to the project directory.
41
+ cd <YOUR_PROJECT_NAME>
42
+
43
+ # Step 3: Install the necessary dependencies.
44
+ npm i
45
+
46
+ # Step 4: Start the development server with auto-reloading and an instant preview.
47
+ npm run dev
48
+ ```
49
+
50
+ **Edit a file directly in GitHub**
51
+
52
+ - Navigate to the desired file(s).
53
+ - Click the "Edit" button (pencil icon) at the top right of the file view.
54
+ - Make your changes and commit the changes.
55
+
56
+ **Use GitHub Codespaces**
57
+
58
+ - Navigate to the main page of your repository.
59
+ - Click on the "Code" button (green button) near the top right.
60
+ - Select the "Codespaces" tab.
61
+ - Click on "New codespace" to launch a new Codespace environment.
62
+ - Edit files directly within the Codespace and commit and push your changes once you're done.
63
+
64
+ ## What technologies are used for this project?
65
+
66
+ This project is built with:
67
+
68
+ - Vite
69
+ - TypeScript
70
+ - React
71
+ - shadcn-ui
72
+ - Tailwind CSS
73
+
74
+ ## How can I deploy this project?
75
+
76
+ Simply open [Lovable](https://lovable.dev/projects/58aee4a4-2f51-49a3-a4d9-56d3d66140b4) and click on Share -> Publish.
77
+
78
+ ## Can I connect a custom domain to my Lovable project?
79
+
80
+ Yes, you can!
81
+
82
+ To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
83
+
84
+ Read more here: [Setting up a custom domain](https://docs.lovable.dev/tips-tricks/custom-domain#step-by-step-guide)
bun.lockb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e9ef82613bb5c109eaa4b79a4432e742b40c028b8841047e5af1bd2941e15d91
3
+ size 228980
components.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "src/index.css",
9
+ "baseColor": "slate",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ }
20
+ }
eslint.config.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import reactHooks from "eslint-plugin-react-hooks";
4
+ import reactRefresh from "eslint-plugin-react-refresh";
5
+ import tseslint from "typescript-eslint";
6
+
7
+ export default tseslint.config(
8
+ { ignores: ["dist"] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ["**/*.{ts,tsx}"],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ "react-hooks": reactHooks,
18
+ "react-refresh": reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ "react-refresh/only-export-components": [
23
+ "warn",
24
+ { allowConstantExport: true },
25
+ ],
26
+ "@typescript-eslint/no-unused-vars": "off",
27
+ },
28
+ }
29
+ );
index.html ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>robot-insight-control-panel</title>
7
+ <meta name="description" content="Lovable Generated Project" />
8
+ <meta name="author" content="Lovable" />
9
+
10
+ <meta property="og:title" content="robot-insight-control-panel" />
11
+ <meta property="og:description" content="Lovable Generated Project" />
12
+ <meta property="og:type" content="website" />
13
+ <meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
14
+
15
+ <meta name="twitter:card" content="summary_large_image" />
16
+ <meta name="twitter:site" content="@lovable_dev" />
17
+ <meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
18
+ </head>
19
+
20
+ <body>
21
+ <div id="root"></div>
22
+ <script type="module" src="/src/main.tsx"></script>
23
+ </body>
24
+ </html>
package.json ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "vite_react_shadcn_ts",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "build:dev": "vite build --mode development",
10
+ "lint": "eslint .",
11
+ "preview": "vite preview"
12
+ },
13
+ "dependencies": {
14
+ "@hookform/resolvers": "^3.9.0",
15
+ "@radix-ui/react-accordion": "^1.2.0",
16
+ "@radix-ui/react-alert-dialog": "^1.1.1",
17
+ "@radix-ui/react-aspect-ratio": "^1.1.0",
18
+ "@radix-ui/react-avatar": "^1.1.0",
19
+ "@radix-ui/react-checkbox": "^1.1.1",
20
+ "@radix-ui/react-collapsible": "^1.1.0",
21
+ "@radix-ui/react-context-menu": "^2.2.1",
22
+ "@radix-ui/react-dialog": "^1.1.2",
23
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
24
+ "@radix-ui/react-hover-card": "^1.1.1",
25
+ "@radix-ui/react-label": "^2.1.0",
26
+ "@radix-ui/react-menubar": "^1.1.1",
27
+ "@radix-ui/react-navigation-menu": "^1.2.0",
28
+ "@radix-ui/react-popover": "^1.1.1",
29
+ "@radix-ui/react-progress": "^1.1.0",
30
+ "@radix-ui/react-radio-group": "^1.2.0",
31
+ "@radix-ui/react-scroll-area": "^1.1.0",
32
+ "@radix-ui/react-select": "^2.1.1",
33
+ "@radix-ui/react-separator": "^1.1.0",
34
+ "@radix-ui/react-slider": "^1.2.0",
35
+ "@radix-ui/react-slot": "^1.1.0",
36
+ "@radix-ui/react-switch": "^1.1.0",
37
+ "@radix-ui/react-tabs": "^1.1.0",
38
+ "@radix-ui/react-toast": "^1.2.1",
39
+ "@radix-ui/react-toggle": "^1.1.0",
40
+ "@radix-ui/react-toggle-group": "^1.1.0",
41
+ "@radix-ui/react-tooltip": "^1.1.4",
42
+ "@react-three/drei": "^9.122.0",
43
+ "@react-three/fiber": "^8.18.0",
44
+ "@tanstack/react-query": "^5.56.2",
45
+ "class-variance-authority": "^0.7.1",
46
+ "clsx": "^2.1.1",
47
+ "cmdk": "^1.0.0",
48
+ "date-fns": "^3.6.0",
49
+ "embla-carousel-react": "^8.3.0",
50
+ "input-otp": "^1.2.4",
51
+ "jszip": "^3.10.1",
52
+ "lucide-react": "^0.462.0",
53
+ "next-themes": "^0.3.0",
54
+ "react": "^18.3.1",
55
+ "react-day-picker": "^8.10.1",
56
+ "react-dom": "^18.3.1",
57
+ "react-hook-form": "^7.53.0",
58
+ "react-resizable-panels": "^2.1.3",
59
+ "react-router-dom": "^6.26.2",
60
+ "recharts": "^2.12.7",
61
+ "sonner": "^1.5.0",
62
+ "tailwind-merge": "^2.5.2",
63
+ "tailwindcss-animate": "^1.0.7",
64
+ "three": "^0.177.0",
65
+ "urdf-loader": "^0.12.6",
66
+ "vaul": "^0.9.3",
67
+ "zod": "^3.23.8"
68
+ },
69
+ "devDependencies": {
70
+ "@eslint/js": "^9.9.0",
71
+ "@tailwindcss/typography": "^0.5.15",
72
+ "@types/node": "^22.5.5",
73
+ "@types/react": "^18.3.3",
74
+ "@types/react-dom": "^18.3.0",
75
+ "@vitejs/plugin-react-swc": "^3.5.0",
76
+ "autoprefixer": "^10.4.20",
77
+ "eslint": "^9.9.0",
78
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
79
+ "eslint-plugin-react-refresh": "^0.4.9",
80
+ "globals": "^15.9.0",
81
+ "lovable-tagger": "^1.1.7",
82
+ "postcss": "^8.4.47",
83
+ "tailwindcss": "^3.4.11",
84
+ "typescript": "^5.5.3",
85
+ "typescript-eslint": "^8.0.1",
86
+ "vite": "^5.4.1"
87
+ }
88
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/favicon.ico ADDED
public/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png ADDED
public/placeholder.svg ADDED
public/robots.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ User-agent: Googlebot
2
+ Allow: /
3
+
4
+ User-agent: Bingbot
5
+ Allow: /
6
+
7
+ User-agent: Twitterbot
8
+ Allow: /
9
+
10
+ User-agent: facebookexternalhit
11
+ Allow: /
12
+
13
+ User-agent: *
14
+ Allow: /
public/so-101-urdf/CMakeLists.txt ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 3.10.2)
2
+
3
+ project(so_arm_description)
4
+
5
+ find_package(ament_cmake REQUIRED)
6
+ find_package(urdf REQUIRED)
7
+
8
+ # Install the mesh files from SO101/assets
9
+ install(
10
+ DIRECTORY
11
+ SO101/assets/
12
+ DESTINATION
13
+ share/${PROJECT_NAME}/meshes
14
+ FILES_MATCHING PATTERN "*.stl"
15
+ )
16
+
17
+ # Install URDF files
18
+ install(
19
+ DIRECTORY
20
+ urdf/
21
+ DESTINATION
22
+ share/${PROJECT_NAME}/urdf
23
+ FILES_MATCHING PATTERN "*.urdf"
24
+ )
25
+
26
+ # Install other directories
27
+ install(
28
+ DIRECTORY
29
+ meshes
30
+ config
31
+ launch
32
+ DESTINATION
33
+ share/${PROJECT_NAME}
34
+ OPTIONAL
35
+ )
36
+
37
+ ament_package()
38
+
public/so-101-urdf/README.md ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SO-ARM ROS2 URDF Package
2
+
3
+ A complete ROS2 package for the SO-ARM101 robotic arm with URDF description.
4
+
5
+ ## 📋 Overview
6
+
7
+ This package provides a complete ROS2 implementation for the SO-ARM101 robotic arm, including:
8
+ - URDF robot description with visual and collision meshes
9
+ - RViz visualization with pre-configured displays
10
+ - Launch files for easy robot visualization
11
+ - Integration with MoveIt for motion planning
12
+ - Joint state publishers for interactive control
13
+
14
+ ## 🎯 Original Source
15
+ https://github.com/TheRobotStudio/SO-ARM100/tree/main/Simulation/SO101
16
+
17
+
18
+ ## 🚀 Key Improvements Made
19
+
20
+ ### 1. **Complete ROS2 Package Structure**
21
+ - ✅ Proper `package.xml` with all necessary dependencies
22
+ - ✅ CMakeLists.txt for ROS2 build system
23
+ - ✅ Organized directory structure following ROS2 conventions
24
+
25
+ ### 2. **Enhanced Visualization**
26
+ - ✅ Fixed mesh file paths for proper package integration
27
+
28
+
29
+ ### Build Instructions
30
+ 1. Clone this repository into your ROS2 workspace:
31
+ ```bash
32
+ cd ~/your_ros2_ws/src
33
+ git clone <your-repo-url> so_arm_description
34
+ ```
35
+
36
+ 2. Build the package:
37
+ ```bash
38
+ cd ~/your_ros2_ws
39
+ colcon build --packages-select so_arm_description
40
+ source install/setup.bash
41
+ ```
public/so-101-urdf/config/joint_names_so_arm_urdf.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ controller_joint_names: ['', 'Rotation', 'Pitch', 'Elbow', 'Wrist_Pitch', 'Wrist_Roll', 'Jaw', ]
public/so-101-urdf/joints_properties.xml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <default>
2
+ <default class="sts3215">
3
+ <geom contype="0" conaffinity="0"/>
4
+ <joint damping="0.60" frictionloss="0.052" armature="0.028"/>
5
+ <position kp="17.8" kv="0.0" forcerange="-3.35 3.35"/>
6
+ </default>
7
+ <default class="backlash">
8
+ <!-- +/- 0.5° of backlash -->
9
+ <joint damping="0.01" frictionloss="0" armature="0.01" limited="true"
10
+ range="-0.008726646259971648 0.008726646259971648"/>
11
+ </default>
12
+ </default>
public/so-101-urdf/meshes/base_motor_holder_so101_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8cd2f241037ea377af1191fffe0dd9d9006beea6dcc48543660ed41647072424
3
+ size 1877084
public/so-101-urdf/meshes/base_so101_v2.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bb12b7026575e1f70ccc7240051f9d943553bf34e5128537de6cd86fae33924d
3
+ size 471584
public/so-101-urdf/meshes/motor_holder_so101_base_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:31242ae6fb59d8b15c66617b88ad8e9bded62d57c35d11c0c43a70d2f4caa95b
3
+ size 1129384
public/so-101-urdf/meshes/motor_holder_so101_wrist_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:887f92e6013cb64ea3a1ab8675e92da1e0beacfd5e001f972523540545e08011
3
+ size 1052184
public/so-101-urdf/meshes/moving_jaw_so101_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:785a9dded2f474bc1d869e0d3dae398a3dcd9c0c345640040472210d2861fa9d
3
+ size 1413584
public/so-101-urdf/meshes/rotation_pitch_so101_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9be900cc2a2bf718102841ef82ef8d2873842427648092c8ed2ca1e2ef4ffa34
3
+ size 883684
public/so-101-urdf/meshes/sts3215_03a_no_horn_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:75ef3781b752e4065891aea855e34dc161a38a549549cd0970cedd07eae6f887
3
+ size 865884
public/so-101-urdf/meshes/sts3215_03a_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a37c871fb502483ab96c256baf457d36f2e97afc9205313d9c5ab275ef941cd0
3
+ size 954084
public/so-101-urdf/meshes/under_arm_so101_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d01d1f2de365651dcad9d6669e94ff87ff7652b5bb2d10752a66a456a86dbc71
3
+ size 1975884
public/so-101-urdf/meshes/upper_arm_so101_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:475056e03a17e71919b82fd88ab9a0b898ab50164f2a7943652a6b2941bb2d4f
3
+ size 1303484
public/so-101-urdf/meshes/waveshare_mounting_plate_so101_v2.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e197e24005a07d01bbc06a8c42311664eaeda415bf859f68fa247884d0f1a6e9
3
+ size 62784
public/so-101-urdf/meshes/wrist_roll_follower_so101_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4b17b410a12d64ec39554abc3e8054d8a97384b2dc4a8d95a5ecb2a93670f5f4
3
+ size 1439884
public/so-101-urdf/meshes/wrist_roll_pitch_so101_v2.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6c7ec5525b4d8b9e397a30ab4bb0037156a5d5f38a4adf2c7d943d6c56eda5ae
3
+ size 2699784
public/so-101-urdf/package.xml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3
+ <package format="3">
4
+ <name>so_arm_description</name>
5
+ <version>1.0.0</version>
6
+ <description>SO-ARM101 URDF Resources</description>
7
+
8
+ <author email="[email protected]">LycheeAI</author>
9
+
10
+ <maintainer email="[email protected]">LycheeAI</maintainer>
11
+
12
+ <license>BSD</license>
13
+
14
+ <buildtool_depend>ament_cmake</buildtool_depend>
15
+
16
+ <depend>urdf</depend>
17
+ <exec_depend>robot_state_publisher</exec_depend>
18
+ <exec_depend>joint_state_publisher</exec_depend>
19
+ <exec_depend>joint_state_publisher_gui</exec_depend>
20
+ <exec_depend>rviz2</exec_depend>
21
+ <exec_depend>xacro</exec_depend>
22
+
23
+ <export>
24
+ <build_type>ament_cmake</build_type>
25
+ </export>
26
+ </package>
public/so-101-urdf/urdf/so101_new_calib.urdf ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version='1.0' encoding='utf-8'?>
2
+ <!-- Generated using onshape-to-robot -->
3
+ <!-- Onshape https://cad.onshape.com/documents/7715cc284bb430fe6dab4ffd/w/4fd0791b683777b02f8d975a/e/826c553ede3b7592eb9ca800 -->
4
+ <robot name="so101_new_calib">
5
+
6
+ <!-- Materials -->
7
+ <material name="3d_printed">
8
+ <color rgba="1.0 0.82 0.12 1.0"/>
9
+ </material>
10
+ <material name="sts3215">
11
+ <color rgba="0.1 0.1 0.1 1.0"/>
12
+ </material>
13
+
14
+ <!-- Link base -->
15
+ <link name="base">
16
+ <inertial>
17
+ <origin xyz="0.020739 0.00204287 0.065966" rpy="0 0 0"/>
18
+ <mass value="0.147"/>
19
+ <inertia ixx="0.000136117" ixy="4.59787e-07" ixz="9.75275e-08" iyy="0.000114686" iyz="-4.97151e-06" izz="0.000130364"/>
20
+ </inertial>
21
+ <!-- Part base_motor_holder_so101_v1 -->
22
+ <visual>
23
+ <origin xyz="0.0206915 0.0221255 0.0300817" rpy="1.5708 -1.23909e-16 2.33147e-15"/>
24
+ <geometry>
25
+ <mesh filename="package://so_arm_description/meshes/base_motor_holder_so101_v1.stl"/>
26
+ </geometry>
27
+ <material name="3d_printed"/>
28
+ </visual>
29
+ <collision>
30
+ <origin xyz="0.0206915 0.0221255 0.0300817" rpy="1.5708 -1.23909e-16 2.33147e-15"/>
31
+ <geometry>
32
+ <mesh filename="package://so_arm_description/meshes/base_motor_holder_so101_v1.stl"/>
33
+ </geometry>
34
+ </collision>
35
+ <!-- Part base_so101_v2 -->
36
+ <visual>
37
+ <origin xyz="0.0207909 0.0221255 0.0300817" rpy="1.5708 -0 0"/>
38
+ <geometry>
39
+ <mesh filename="package://so_arm_description/meshes/base_so101_v2.stl"/>
40
+ </geometry>
41
+ <material name="3d_printed"/>
42
+ </visual>
43
+ <collision>
44
+ <origin xyz="0.0207909 0.0221255 0.0300817" rpy="1.5708 -0 0"/>
45
+ <geometry>
46
+ <mesh filename="package://so_arm_description/meshes/base_so101_v2.stl"/>
47
+ </geometry>
48
+ </collision>
49
+ <!-- Part sts3215_03a_v1 -->
50
+ <visual>
51
+ <origin xyz="0.0207909 -0.0105745 0.0761817" rpy="-2.20282e-15 2.77556e-17 -1.5708"/>
52
+ <geometry>
53
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
54
+ </geometry>
55
+ <material name="sts3215"/>
56
+ </visual>
57
+ <collision>
58
+ <origin xyz="0.0207909 -0.0105745 0.0761817" rpy="-2.20282e-15 2.77556e-17 -1.5708"/>
59
+ <geometry>
60
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
61
+ </geometry>
62
+ </collision>
63
+ <!-- Part waveshare_mounting_plate_so101_v2 -->
64
+ <visual>
65
+ <origin xyz="0.0205915 0.0467435 0.0798817" rpy="1.5708 -1.21716e-14 2.33147e-15"/>
66
+ <geometry>
67
+ <mesh filename="package://so_arm_description/meshes/waveshare_mounting_plate_so101_v2.stl"/>
68
+ </geometry>
69
+ <material name="3d_printed"/>
70
+ </visual>
71
+ <collision>
72
+ <origin xyz="0.0205915 0.0467435 0.0798817" rpy="1.5708 -1.21716e-14 2.33147e-15"/>
73
+ <geometry>
74
+ <mesh filename="package://so_arm_description/meshes/waveshare_mounting_plate_so101_v2.stl"/>
75
+ </geometry>
76
+ </collision>
77
+ </link>
78
+
79
+ <!-- Link shoulder -->
80
+ <link name="shoulder">
81
+ <inertial>
82
+ <origin xyz="-0.0307604 -1.66727e-05 -0.0252713" rpy="0 0 0"/>
83
+ <mass value="0.100006"/>
84
+ <inertia ixx="8.3759e-05" ixy="7.55525e-08" ixz="-1.16342e-06" iyy="8.10403e-05" iyz="1.54663e-07" izz="2.39783e-05"/>
85
+ </inertial>
86
+ <!-- Part sts3215_03a_v1_2 -->
87
+ <visual>
88
+ <origin xyz="-0.0303992 0.000422241 -0.0417" rpy="1.5708 1.5708 0"/>
89
+ <geometry>
90
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
91
+ </geometry>
92
+ <material name="sts3215"/>
93
+ </visual>
94
+ <collision>
95
+ <origin xyz="-0.0303992 0.000422241 -0.0417" rpy="1.5708 1.5708 0"/>
96
+ <geometry>
97
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
98
+ </geometry>
99
+ </collision>
100
+ <!-- Part motor_holder_so101_base_v1 -->
101
+ <visual>
102
+ <origin xyz="-0.0675992 -0.000177759 0.0158499" rpy="1.5708 -1.5708 0"/>
103
+ <geometry>
104
+ <mesh filename="package://so_arm_description/meshes/motor_holder_so101_base_v1.stl"/>
105
+ </geometry>
106
+ <material name="3d_printed"/>
107
+ </visual>
108
+ <collision>
109
+ <origin xyz="-0.0675992 -0.000177759 0.0158499" rpy="1.5708 -1.5708 0"/>
110
+ <geometry>
111
+ <mesh filename="package://so_arm_description/meshes/motor_holder_so101_base_v1.stl"/>
112
+ </geometry>
113
+ </collision>
114
+ <!-- Part rotation_pitch_so101_v1 -->
115
+ <visual>
116
+ <origin xyz="0.0122008 2.22413e-05 0.0464" rpy="-1.5708 -0 0"/>
117
+ <geometry>
118
+ <mesh filename="package://so_arm_description/meshes/rotation_pitch_so101_v1.stl"/>
119
+ </geometry>
120
+ <material name="3d_printed"/>
121
+ </visual>
122
+ <collision>
123
+ <origin xyz="0.0122008 2.22413e-05 0.0464" rpy="-1.5708 -0 0"/>
124
+ <geometry>
125
+ <mesh filename="package://so_arm_description/meshes/rotation_pitch_so101_v1.stl"/>
126
+ </geometry>
127
+ </collision>
128
+ </link>
129
+
130
+ <!-- Link upper_arm -->
131
+ <link name="upper_arm">
132
+ <inertial>
133
+ <origin xyz="-0.0898471 -0.00838224 0.0184089" rpy="0 0 0"/>
134
+ <mass value="0.103"/>
135
+ <inertia ixx="4.08002e-05" ixy="-1.97819e-05" ixz="-4.03016e-08" iyy="0.000147318" iyz="8.97326e-09" izz="0.000142487"/>
136
+ </inertial>
137
+ <!-- Part sts3215_03a_v1_3 -->
138
+ <visual>
139
+ <origin xyz="-0.11257 -0.0155 0.0187" rpy="-3.14159 -6.8695e-16 -1.5708"/>
140
+ <geometry>
141
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
142
+ </geometry>
143
+ <material name="sts3215"/>
144
+ </visual>
145
+ <collision>
146
+ <origin xyz="-0.11257 -0.0155 0.0187" rpy="-3.14159 -6.8695e-16 -1.5708"/>
147
+ <geometry>
148
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
149
+ </geometry>
150
+ </collision>
151
+ <!-- Part upper_arm_so101_v1 -->
152
+ <visual>
153
+ <origin xyz="-0.065085 0.012 0.0182" rpy="3.14159 -9.35612e-32 0"/>
154
+ <geometry>
155
+ <mesh filename="package://so_arm_description/meshes/upper_arm_so101_v1.stl"/>
156
+ </geometry>
157
+ <material name="3d_printed"/>
158
+ </visual>
159
+ <collision>
160
+ <origin xyz="-0.065085 0.012 0.0182" rpy="3.14159 -9.35612e-32 0"/>
161
+ <geometry>
162
+ <mesh filename="package://so_arm_description/meshes/upper_arm_so101_v1.stl"/>
163
+ </geometry>
164
+ </collision>
165
+ </link>
166
+
167
+ <!-- Link lower_arm -->
168
+ <link name="lower_arm">
169
+ <inertial>
170
+ <origin xyz="-0.0980701 0.00324376 0.0182831" rpy="0 0 0"/>
171
+ <mass value="0.104"/>
172
+ <inertia ixx="2.87438e-05" ixy="7.41152e-06" ixz="1.26409e-06" iyy="0.000159844" iyz="-4.90188e-08" izz="0.00014529"/>
173
+ </inertial>
174
+ <!-- Part under_arm_so101_v1 -->
175
+ <visual>
176
+ <origin xyz="-0.0648499 -0.032 0.0182" rpy="-3.14159 -0 3.9443e-31"/>
177
+ <geometry>
178
+ <mesh filename="package://so_arm_description/meshes/under_arm_so101_v1.stl"/>
179
+ </geometry>
180
+ <material name="3d_printed"/>
181
+ </visual>
182
+ <collision>
183
+ <origin xyz="-0.0648499 -0.032 0.0182" rpy="-3.14159 -0 3.9443e-31"/>
184
+ <geometry>
185
+ <mesh filename="package://so_arm_description/meshes/under_arm_so101_v1.stl"/>
186
+ </geometry>
187
+ </collision>
188
+ <!-- Part motor_holder_so101_wrist_v1 -->
189
+ <visual>
190
+ <origin xyz="-0.0648499 -0.032 0.018" rpy="-3.14159 4.73317e-30 7.88861e-31"/>
191
+ <geometry>
192
+ <mesh filename="package://so_arm_description/meshes/motor_holder_so101_wrist_v1.stl"/>
193
+ </geometry>
194
+ <material name="3d_printed"/>
195
+ </visual>
196
+ <collision>
197
+ <origin xyz="-0.0648499 -0.032 0.018" rpy="-3.14159 4.73317e-30 7.88861e-31"/>
198
+ <geometry>
199
+ <mesh filename="package://so_arm_description/meshes/motor_holder_so101_wrist_v1.stl"/>
200
+ </geometry>
201
+ </collision>
202
+ <!-- Part sts3215_03a_v1_4 -->
203
+ <visual>
204
+ <origin xyz="-0.1224 0.0052 0.0187" rpy="-3.14159 -3.58047e-15 -3.14159"/>
205
+ <geometry>
206
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
207
+ </geometry>
208
+ <material name="sts3215"/>
209
+ </visual>
210
+ <collision>
211
+ <origin xyz="-0.1224 0.0052 0.0187" rpy="-3.14159 -3.58047e-15 -3.14159"/>
212
+ <geometry>
213
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
214
+ </geometry>
215
+ </collision>
216
+ </link>
217
+
218
+ <!-- Link wrist -->
219
+ <link name="wrist">
220
+ <inertial>
221
+ <origin xyz="-0.000103312 -0.0386143 0.0281156" rpy="0 0 0"/>
222
+ <mass value="0.079"/>
223
+ <inertia ixx="3.68263e-05" ixy="1.7893e-08" ixz="-5.28128e-08" iyy="2.5391e-05" iyz="3.6412e-06" izz="2.1e-05"/>
224
+ </inertial>
225
+ <!-- Part sts3215_03a_no_horn_v1 -->
226
+ <visual>
227
+ <origin xyz="5.55112e-17 -0.0424 0.0306" rpy="1.5708 1.5708 0"/>
228
+ <geometry>
229
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_no_horn_v1.stl"/>
230
+ </geometry>
231
+ <material name="sts3215"/>
232
+ </visual>
233
+ <collision>
234
+ <origin xyz="5.55112e-17 -0.0424 0.0306" rpy="1.5708 1.5708 0"/>
235
+ <geometry>
236
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_no_horn_v1.stl"/>
237
+ </geometry>
238
+ </collision>
239
+ <!-- Part wrist_roll_pitch_so101_v2 -->
240
+ <visual>
241
+ <origin xyz="0 -0.028 0.0181" rpy="-1.5708 -1.5708 0"/>
242
+ <geometry>
243
+ <mesh filename="package://so_arm_description/meshes/wrist_roll_pitch_so101_v2.stl"/>
244
+ </geometry>
245
+ <material name="3d_printed"/>
246
+ </visual>
247
+ <collision>
248
+ <origin xyz="0 -0.028 0.0181" rpy="-1.5708 -1.5708 0"/>
249
+ <geometry>
250
+ <mesh filename="package://so_arm_description/meshes/wrist_roll_pitch_so101_v2.stl"/>
251
+ </geometry>
252
+ </collision>
253
+ </link>
254
+
255
+ <!-- Link gripper -->
256
+ <link name="gripper">
257
+ <inertial>
258
+ <origin xyz="0.000213627 0.000245138 -0.025187" rpy="0 0 0"/>
259
+ <mass value="0.087"/>
260
+ <inertia ixx="2.75087e-05" ixy="-3.35241e-07" ixz="-5.7352e-06" iyy="4.33657e-05" iyz="-5.17847e-08" izz="3.45059e-05"/>
261
+ </inertial>
262
+ <!-- Part sts3215_03a_v1_5 -->
263
+ <visual>
264
+ <origin xyz="0.0077 0.0001 -0.0234" rpy="-1.5708 -5.55112e-17 -1.38213e-14"/>
265
+ <geometry>
266
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
267
+ </geometry>
268
+ <material name="sts3215"/>
269
+ </visual>
270
+ <collision>
271
+ <origin xyz="0.0077 0.0001 -0.0234" rpy="-1.5708 -5.55112e-17 -1.38213e-14"/>
272
+ <geometry>
273
+ <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
274
+ </geometry>
275
+ </collision>
276
+ <!-- Part wrist_roll_follower_so101_v1 -->
277
+ <visual>
278
+ <origin xyz="5.55112e-17 -0.000218214 0.000949706" rpy="-3.14159 -5.55112e-17 -9.17912e-24"/>
279
+ <geometry>
280
+ <mesh filename="package://so_arm_description/meshes/wrist_roll_follower_so101_v1.stl"/>
281
+ </geometry>
282
+ <material name="3d_printed"/>
283
+ </visual>
284
+ <collision>
285
+ <origin xyz="5.55112e-17 -0.000218214 0.000949706" rpy="-3.14159 -5.55112e-17 -9.17912e-24"/>
286
+ <geometry>
287
+ <mesh filename="package://so_arm_description/meshes/wrist_roll_follower_so101_v1.stl"/>
288
+ </geometry>
289
+ </collision>
290
+ </link>
291
+
292
+ <!-- Link jaw -->
293
+ <link name="jaw">
294
+ <inertial>
295
+ <origin xyz="-0.00157495 -0.0300244 0.0192755" rpy="0 0 0"/>
296
+ <mass value="0.012"/>
297
+ <inertia ixx="6.61427e-06" ixy="-3.19807e-07" ixz="-5.90717e-09" iyy="1.89032e-06" iyz="-1.09945e-07" izz="5.28738e-06"/>
298
+ </inertial>
299
+ <!-- Part moving_jaw_so101_v1 -->
300
+ <visual>
301
+ <origin xyz="-5.55112e-17 -1.94746e-17 0.0189" rpy="9.53145e-17 -4.66093e-24 0"/>
302
+ <geometry>
303
+ <mesh filename="package://so_arm_description/meshes/moving_jaw_so101_v1.stl"/>
304
+ </geometry>
305
+ <material name="3d_printed"/>
306
+ </visual>
307
+ <collision>
308
+ <origin xyz="-5.55112e-17 -1.94746e-17 0.0189" rpy="9.53145e-17 -4.66093e-24 0"/>
309
+ <geometry>
310
+ <mesh filename="package://so_arm_description/meshes/moving_jaw_so101_v1.stl"/>
311
+ </geometry>
312
+ </collision>
313
+ </link>
314
+
315
+ <!-- Joint from gripper to jaw -->
316
+ <joint name="Jaw" type="revolute">
317
+ <origin xyz="0.0202 0.0188 -0.0234" rpy="1.5708 -5.14108e-17 -1.38655e-14"/>
318
+ <parent link="gripper"/>
319
+ <child link="jaw"/>
320
+ <axis xyz="0 0 1"/>
321
+ <limit effort="10" velocity="10" lower="-0.174533" upper="1.74533"/>
322
+ </joint>
323
+
324
+ <transmission name="6_trans">
325
+ <type>transmission_interface/SimpleTransmission</type>
326
+ <joint name="Jaw">
327
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
328
+ </joint>
329
+ <actuator name="motor6">
330
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
331
+ <mechanicalReduction>1</mechanicalReduction>
332
+ </actuator>
333
+ </transmission>
334
+
335
+ <!-- Joint from wrist to gripper -->
336
+ <joint name="Wrist_Roll" type="revolute">
337
+ <origin xyz="0 -0.0611 0.0181" rpy="1.5708 -9.38083e-08 3.14159"/>
338
+ <parent link="wrist"/>
339
+ <child link="gripper"/>
340
+ <axis xyz="0 0 1"/>
341
+ <limit effort="10" velocity="10" lower="-2.79253" upper="2.79253"/>
342
+ </joint>
343
+
344
+ <transmission name="5_trans">
345
+ <type>transmission_interface/SimpleTransmission</type>
346
+ <joint name="Wrist_Roll">
347
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
348
+ </joint>
349
+ <actuator name="motor5">
350
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
351
+ <mechanicalReduction>1</mechanicalReduction>
352
+ </actuator>
353
+ </transmission>
354
+
355
+ <!-- Joint from lower_arm to wrist -->
356
+ <joint name="Wrist_Pitch" type="revolute">
357
+ <origin xyz="-0.1349 0.0052 1.65232e-16" rpy="3.2474e-15 2.86219e-15 -1.5708"/>
358
+ <parent link="lower_arm"/>
359
+ <child link="wrist"/>
360
+ <axis xyz="0 0 1"/>
361
+ <limit effort="10" velocity="10" lower="-1.65806" upper="1.65806"/>
362
+ </joint>
363
+
364
+ <transmission name="4_trans">
365
+ <type>transmission_interface/SimpleTransmission</type>
366
+ <joint name="Wrist_Pitch">
367
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
368
+ </joint>
369
+ <actuator name="motor4">
370
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
371
+ <mechanicalReduction>1</mechanicalReduction>
372
+ </actuator>
373
+ </transmission>
374
+
375
+ <!-- Joint from upper_arm to lower_arm -->
376
+ <joint name="Elbow" type="revolute">
377
+ <origin xyz="-0.11257 -0.028 2.46331e-16" rpy="-1.22818e-15 5.75928e-16 1.5708"/>
378
+ <parent link="upper_arm"/>
379
+ <child link="lower_arm"/>
380
+ <axis xyz="0 0 1"/>
381
+ <limit effort="10" velocity="10" lower="-1.74533" upper="1.5708"/>
382
+ </joint>
383
+
384
+ <transmission name="3_trans">
385
+ <type>transmission_interface/SimpleTransmission</type>
386
+ <joint name="Elbow">
387
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
388
+ </joint>
389
+ <actuator name="motor3">
390
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
391
+ <mechanicalReduction>1</mechanicalReduction>
392
+ </actuator>
393
+ </transmission>
394
+
395
+ <!-- Joint from shoulder to upper_arm -->
396
+ <joint name="Pitch" type="revolute">
397
+ <origin xyz="-0.0303992 -0.0182778 -0.0542" rpy="-1.5708 -1.5708 0"/>
398
+ <parent link="shoulder"/>
399
+ <child link="upper_arm"/>
400
+ <axis xyz="0 0 1"/>
401
+ <limit effort="10" velocity="10" lower="-1.74533" upper="1.74533"/>
402
+ </joint>
403
+
404
+ <transmission name="2_trans">
405
+ <type>transmission_interface/SimpleTransmission</type>
406
+ <joint name="Pitch">
407
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
408
+ </joint>
409
+ <actuator name="motor2">
410
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
411
+ <mechanicalReduction>1</mechanicalReduction>
412
+ </actuator>
413
+ </transmission>
414
+
415
+ <!-- Joint from base to shoulder -->
416
+ <joint name="Rotation" type="revolute">
417
+ <origin xyz="0.0207909 -0.0230745 0.0948817" rpy="-3.14159 6.03684e-16 1.5708"/>
418
+ <parent link="base"/>
419
+ <child link="shoulder"/>
420
+ <axis xyz="0 0 1"/>
421
+ <limit effort="10" velocity="10" lower="-1.91986" upper="1.91986"/>
422
+ </joint>
423
+
424
+ <transmission name="1_trans">
425
+ <type>transmission_interface/SimpleTransmission</type>
426
+ <joint name="Rotation">
427
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
428
+ </joint>
429
+ <actuator name="motor1">
430
+ <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
431
+ <mechanicalReduction>1</mechanicalReduction>
432
+ </actuator>
433
+ </transmission>
434
+
435
+ </robot>
src/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ node_modules/
src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
src/App.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Toaster } from "@/components/ui/toaster";
2
+ import { Toaster as Sonner } from "@/components/ui/sonner";
3
+ import { TooltipProvider } from "@/components/ui/tooltip";
4
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5
+ import { BrowserRouter, Routes, Route } from "react-router-dom";
6
+ import Index from "./pages/Index";
7
+ import NotFound from "./pages/NotFound";
8
+ import Landing from "./pages/Landing";
9
+ import TeleoperationPage from "./pages/Teleoperation";
10
+ 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
+
17
+ const App = () => (
18
+ <QueryClientProvider client={queryClient}>
19
+ <TooltipProvider>
20
+ <Toaster />
21
+ <Sonner />
22
+ <UrdfProvider>
23
+ <BrowserRouter>
24
+ <Routes>
25
+ <Route path="/" element={<Landing />} />
26
+ <Route path="/control" element={<Index />} />
27
+ <Route path="/teleoperation" element={<TeleoperationPage />} />
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>
34
+ </BrowserRouter>
35
+ </UrdfProvider>
36
+ </TooltipProvider>
37
+ </QueryClientProvider>
38
+ );
39
+
40
+ export default App;
src/components/UrdfProcessorInitializer.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useMemo } from "react";
2
+ import { useUrdf } from "@/hooks/useUrdf";
3
+
4
+ /**
5
+ * Component that only handles initializing the URDF processor
6
+ * This component doesn't render anything visible, just initializes the processor
7
+ */
8
+ const UrdfProcessorInitializer: React.FC = () => {
9
+ const { registerUrdfProcessor } = useUrdf();
10
+
11
+ // Create the URDF processor
12
+ const urdfProcessor = useMemo(
13
+ () => ({
14
+ loadUrdf: (urdfPath: string) => {
15
+ console.log("📂 URDF path set:", urdfPath);
16
+ // This will be handled by the actual viewer component
17
+ return urdfPath;
18
+ },
19
+ setUrlModifierFunc: (func: (url: string) => string) => {
20
+ console.log("🔗 URL modifier function set");
21
+ return func;
22
+ },
23
+ getPackage: () => {
24
+ return "";
25
+ },
26
+ }),
27
+ []
28
+ );
29
+
30
+ // Register the URDF processor with the context
31
+ useEffect(() => {
32
+ console.log("🔧 Registering URDF processor");
33
+ registerUrdfProcessor(urdfProcessor);
34
+ }, [registerUrdfProcessor, urdfProcessor]);
35
+
36
+ // This component doesn't render anything
37
+ return null;
38
+ };
39
+
40
+ export default UrdfProcessorInitializer;
src/components/UrdfViewer.tsx ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, {
2
+ useEffect,
3
+ useRef,
4
+ useState,
5
+ useMemo,
6
+ useCallback,
7
+ } from "react";
8
+ import { cn } from "@/lib/utils";
9
+
10
+ import URDFManipulator from "urdf-loader/src/urdf-manipulator-element.js";
11
+ import { useUrdf } from "@/hooks/useUrdf";
12
+ import { useRealTimeJoints } from "@/hooks/useRealTimeJoints";
13
+ import {
14
+ createUrdfViewer,
15
+ setupMeshLoader,
16
+ setupJointHighlighting,
17
+ setupModelLoading,
18
+ URDFViewerElement,
19
+ } from "@/lib/urdfViewerHelpers";
20
+
21
+ // Register the URDFManipulator as a custom element if it hasn't been already
22
+ if (typeof window !== "undefined" && !customElements.get("urdf-viewer")) {
23
+ customElements.define("urdf-viewer", URDFManipulator);
24
+ }
25
+
26
+ // Extend the interface for the URDF viewer element to include background property
27
+ interface UrdfViewerElement extends HTMLElement {
28
+ background?: string;
29
+ setJointValue?: (jointName: string, value: number) => void;
30
+ }
31
+
32
+ const UrdfViewer: React.FC = () => {
33
+ const containerRef = useRef<HTMLDivElement>(null);
34
+ const [highlightedJoint, setHighlightedJoint] = useState<string | null>(null);
35
+ const { registerUrdfProcessor, alternativeUrdfModels, isDefaultModel } =
36
+ useUrdf();
37
+
38
+ // Add state for animation control
39
+ useState<boolean>(isDefaultModel);
40
+ const cleanupAnimationRef = useRef<(() => void) | null>(null);
41
+ const viewerRef = useRef<URDFViewerElement | null>(null);
42
+ const hasInitializedRef = useRef<boolean>(false);
43
+
44
+ // Real-time joint updates via WebSocket
45
+ const { isConnected: isWebSocketConnected } = useRealTimeJoints({
46
+ viewerRef,
47
+ enabled: isDefaultModel, // Only enable WebSocket for default model
48
+ });
49
+
50
+ // Add state for custom URDF path
51
+ const [customUrdfPath, setCustomUrdfPath] = useState<string | null>(null);
52
+ const [urlModifierFunc, setUrlModifierFunc] = useState<
53
+ ((url: string) => string) | null
54
+ >(null);
55
+
56
+ const packageRef = useRef<string>("");
57
+
58
+ // Implement UrdfProcessor interface for drag and drop
59
+ const urdfProcessor = useMemo(
60
+ () => ({
61
+ loadUrdf: (urdfPath: string) => {
62
+ setCustomUrdfPath(urdfPath);
63
+ },
64
+ setUrlModifierFunc: (func: (url: string) => string) => {
65
+ setUrlModifierFunc(() => func);
66
+ },
67
+ getPackage: () => {
68
+ return packageRef.current;
69
+ },
70
+ }),
71
+ []
72
+ );
73
+
74
+ // Register the URDF processor with the global drag and drop context
75
+ useEffect(() => {
76
+ registerUrdfProcessor(urdfProcessor);
77
+ }, [registerUrdfProcessor, urdfProcessor]);
78
+
79
+ // Create URL modifier function for default model
80
+ const defaultUrlModifier = useCallback((url: string) => {
81
+ console.log(`🔗 defaultUrlModifier called with: ${url}`);
82
+
83
+ // Handle various package:// URL formats for the default SO-101 model
84
+ if (url.startsWith("package://so_arm_description/meshes/")) {
85
+ const modifiedUrl = url.replace(
86
+ "package://so_arm_description/meshes/",
87
+ "/so-101-urdf/meshes/"
88
+ );
89
+ console.log(`🔗 Modified URL (package): ${modifiedUrl}`);
90
+ return modifiedUrl;
91
+ }
92
+
93
+ // Handle case where package path might be partially resolved
94
+ if (url.includes("so_arm_description/meshes/")) {
95
+ const modifiedUrl = url.replace(
96
+ /.*so_arm_description\/meshes\//,
97
+ "/so-101-urdf/meshes/"
98
+ );
99
+ console.log(`🔗 Modified URL (partial): ${modifiedUrl}`);
100
+ return modifiedUrl;
101
+ }
102
+
103
+ // Handle the specific problematic path pattern we're seeing in logs
104
+ if (url.includes("/so-101-urdf/so_arm_description/meshes/")) {
105
+ const modifiedUrl = url.replace(
106
+ "/so-101-urdf/so_arm_description/meshes/",
107
+ "/so-101-urdf/meshes/"
108
+ );
109
+ console.log(`🔗 Modified URL (problematic path): ${modifiedUrl}`);
110
+ return modifiedUrl;
111
+ }
112
+
113
+ // Handle relative paths that might need mesh folder prefix
114
+ if (
115
+ url.endsWith(".stl") &&
116
+ !url.startsWith("/") &&
117
+ !url.startsWith("http")
118
+ ) {
119
+ const modifiedUrl = `/so-101-urdf/meshes/${url}`;
120
+ console.log(`🔗 Modified URL (relative): ${modifiedUrl}`);
121
+ return modifiedUrl;
122
+ }
123
+
124
+ console.log(`🔗 Unmodified URL: ${url}`);
125
+ return url;
126
+ }, []);
127
+
128
+ // Main effect to create and setup the viewer only once
129
+ useEffect(() => {
130
+ if (!containerRef.current) return;
131
+
132
+ // Create and configure the URDF viewer element
133
+ const viewer = createUrdfViewer(containerRef.current, true);
134
+ viewerRef.current = viewer; // Store reference to the viewer
135
+
136
+ // Setup mesh loading function with appropriate URL modifier
137
+ const activeUrlModifier = isDefaultModel
138
+ ? defaultUrlModifier
139
+ : urlModifierFunc;
140
+ setupMeshLoader(viewer, activeUrlModifier);
141
+
142
+ // Determine which URDF to load - fixed path to match the actual available file
143
+ const urdfPath = isDefaultModel
144
+ ? "/so-101-urdf/urdf/so101_new_calib.urdf"
145
+ : customUrdfPath || "";
146
+
147
+ // Set the package path for the default model
148
+ if (isDefaultModel) {
149
+ packageRef.current = "/"; // Set to root so we can handle full path resolution in URL modifier
150
+ }
151
+
152
+ // Setup model loading if a path is available
153
+ let cleanupModelLoading = () => {};
154
+ if (urdfPath) {
155
+ cleanupModelLoading = setupModelLoading(
156
+ viewer,
157
+ urdfPath,
158
+ packageRef.current,
159
+ setCustomUrdfPath,
160
+ alternativeUrdfModels
161
+ );
162
+ }
163
+
164
+ // Setup joint highlighting
165
+ const cleanupJointHighlighting = setupJointHighlighting(
166
+ viewer,
167
+ setHighlightedJoint
168
+ );
169
+
170
+ // Setup animation event handler for the default model or when hasAnimation is true
171
+ const onModelProcessed = () => {
172
+ hasInitializedRef.current = true;
173
+ if ("setJointValue" in viewer) {
174
+ // Clear any existing animation
175
+ if (cleanupAnimationRef.current) {
176
+ cleanupAnimationRef.current();
177
+ cleanupAnimationRef.current = null;
178
+ }
179
+ }
180
+ };
181
+
182
+ viewer.addEventListener("urdf-processed", onModelProcessed);
183
+
184
+ // Return cleanup function
185
+ return () => {
186
+ if (cleanupAnimationRef.current) {
187
+ cleanupAnimationRef.current();
188
+ cleanupAnimationRef.current = null;
189
+ }
190
+ hasInitializedRef.current = false;
191
+ cleanupJointHighlighting();
192
+ cleanupModelLoading();
193
+ viewer.removeEventListener("urdf-processed", onModelProcessed);
194
+ };
195
+ }, [isDefaultModel, customUrdfPath, urlModifierFunc, defaultUrlModifier]);
196
+
197
+ return (
198
+ <div
199
+ className={cn(
200
+ "w-full h-full transition-all duration-300 ease-in-out relative",
201
+ "bg-gradient-to-br from-gray-900 to-gray-800"
202
+ )}
203
+ >
204
+ <div ref={containerRef} className="w-full h-full" />
205
+
206
+ {/* Joint highlight indicator */}
207
+ {highlightedJoint && (
208
+ <div className="absolute bottom-4 right-4 bg-black/70 text-white px-3 py-2 rounded-md text-sm font-mono z-10">
209
+ Joint: {highlightedJoint}
210
+ </div>
211
+ )}
212
+
213
+ {/* WebSocket connection status */}
214
+ {isDefaultModel && (
215
+ <div className="absolute top-4 right-4 z-10">
216
+ <div
217
+ className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-mono ${
218
+ isWebSocketConnected
219
+ ? "bg-green-900/70 text-green-300"
220
+ : "bg-red-900/70 text-red-300"
221
+ }`}
222
+ >
223
+ <div
224
+ className={`w-2 h-2 rounded-full ${
225
+ isWebSocketConnected ? "bg-green-400" : "bg-red-400"
226
+ }`}
227
+ />
228
+ {isWebSocketConnected ? "Live Robot Data" : "Disconnected"}
229
+ </div>
230
+ </div>
231
+ )}
232
+ </div>
233
+ );
234
+ };
235
+
236
+ export default UrdfViewer;
src/components/control/CommandBar.tsx ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import { Mic, MicOff, Send, Camera } from 'lucide-react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Input } from '@/components/ui/input';
6
+
7
+ interface CommandBarProps {
8
+ command: string;
9
+ setCommand: (command: string) => void;
10
+ handleSendCommand: () => void;
11
+ isVoiceActive: boolean;
12
+ setIsVoiceActive: (isActive: boolean) => void;
13
+ showCamera: boolean;
14
+ setShowCamera: (show: boolean) => void;
15
+ handleEndSession: () => void;
16
+ }
17
+
18
+ const CommandBar: React.FC<CommandBarProps> = ({
19
+ command,
20
+ setCommand,
21
+ handleSendCommand,
22
+ isVoiceActive,
23
+ setIsVoiceActive,
24
+ showCamera,
25
+ setShowCamera,
26
+ handleEndSession
27
+ }) => {
28
+ return (
29
+ <div className="bg-gray-900 p-4 space-y-4">
30
+ <div className="flex flex-col sm:flex-row gap-4 items-center max-w-4xl mx-auto w-full">
31
+ <Input
32
+ value={command}
33
+ onChange={(e) => setCommand(e.target.value)}
34
+ placeholder="Tell the robot what to do..."
35
+ className="flex-1 bg-gray-800 border-gray-600 text-white placeholder-gray-400 text-lg py-3"
36
+ onKeyPress={(e) => e.key === 'Enter' && handleSendCommand()}
37
+ />
38
+ <Button
39
+ onClick={handleSendCommand}
40
+ className="bg-orange-500 hover:bg-orange-600 px-6 py-3 self-stretch sm:self-auto"
41
+ >
42
+ <Send strokeWidth={1.5} />
43
+ Send
44
+ </Button>
45
+ </div>
46
+
47
+ <div className="flex justify-center items-center gap-6">
48
+ <div className="flex flex-wrap justify-center gap-2 sm:gap-4">
49
+ <Button
50
+ onClick={() => setIsVoiceActive(!isVoiceActive)}
51
+ className={`px-6 py-2 ${
52
+ isVoiceActive ? 'bg-gray-600 text-white hover:bg-gray-500' : 'bg-gray-800 text-gray-300 hover:bg-gray-700'
53
+ }`}
54
+ >
55
+ {isVoiceActive ? <Mic strokeWidth={1.5} /> : <MicOff strokeWidth={1.5} />}
56
+ Voice Command
57
+ </Button>
58
+
59
+ <Button
60
+ onClick={() => setShowCamera(!showCamera)}
61
+ className={`px-6 py-2 ${
62
+ showCamera ? 'bg-gray-600 text-white hover:bg-gray-500' : 'bg-gray-800 text-gray-300 hover:bg-gray-700'
63
+ }`}
64
+ >
65
+ <Camera strokeWidth={1.5} />
66
+ Show Camera
67
+ </Button>
68
+
69
+ <Button
70
+ onClick={handleEndSession}
71
+ className="bg-red-600 hover:bg-red-700 px-6 py-2"
72
+ >
73
+ End Session
74
+ </Button>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export default CommandBar;
src/components/control/MetricsPanel.tsx ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useEffect, useRef } from 'react';
3
+ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
4
+ import { Camera, MicOff } from 'lucide-react';
5
+
6
+ interface MetricsPanelProps {
7
+ activeTab: 'SENSORS' | 'MOTORS';
8
+ setActiveTab: (tab: 'SENSORS' | 'MOTORS') => void;
9
+ sensorData: any[];
10
+ motorData: any[];
11
+ hasPermissions: boolean;
12
+ streamRef: React.RefObject<MediaStream | null>;
13
+ isVoiceActive: boolean;
14
+ micLevel: number;
15
+ }
16
+
17
+ const MetricsPanel: React.FC<MetricsPanelProps> = ({
18
+ activeTab,
19
+ setActiveTab,
20
+ sensorData,
21
+ motorData,
22
+ hasPermissions,
23
+ streamRef,
24
+ isVoiceActive,
25
+ micLevel,
26
+ }) => {
27
+ const sensorVideoRef = useRef<HTMLVideoElement>(null);
28
+
29
+ useEffect(() => {
30
+ if (activeTab === 'SENSORS' && hasPermissions && sensorVideoRef.current && streamRef.current) {
31
+ if (sensorVideoRef.current.srcObject !== streamRef.current) {
32
+ sensorVideoRef.current.srcObject = streamRef.current;
33
+ }
34
+ }
35
+ }, [activeTab, hasPermissions, streamRef]);
36
+
37
+ return (
38
+ <div className="w-full lg:w-1/2 p-2 sm:p-4">
39
+ <div className="bg-gray-900 rounded-lg p-4 h-full flex flex-col">
40
+ {/* Tab Headers */}
41
+ <div className="flex mb-4">
42
+ <button
43
+ onClick={() => setActiveTab('MOTORS')}
44
+ className={`px-6 py-2 rounded-t-lg text-sm sm:text-base ${
45
+ activeTab === 'MOTORS'
46
+ ? 'bg-orange-500 text-white'
47
+ : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
48
+ }`}
49
+ >
50
+ MOTORS
51
+ </button>
52
+ <button
53
+ onClick={() => setActiveTab('SENSORS')}
54
+ className={`px-6 py-2 rounded-t-lg ml-2 text-sm sm:text-base ${
55
+ activeTab === 'SENSORS'
56
+ ? 'bg-orange-500 text-white'
57
+ : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
58
+ }`}
59
+ >
60
+ SENSORS
61
+ </button>
62
+ </div>
63
+
64
+ {/* Chart Content */}
65
+ <div className="flex-1 overflow-y-auto">
66
+ {activeTab === 'SENSORS' && (
67
+ <div className="space-y-4">
68
+ {/* Webcam Feed */}
69
+ <div className="border border-gray-800 rounded p-2 flex flex-col h-64">
70
+ <h3 className="text-sm text-white font-medium mb-2">Live Camera Feed</h3>
71
+ {hasPermissions ? (
72
+ <div className="flex-1 bg-black rounded overflow-hidden">
73
+ <video
74
+ ref={sensorVideoRef}
75
+ autoPlay
76
+ muted
77
+ playsInline
78
+ className="w-full h-full object-contain"
79
+ />
80
+ </div>
81
+ ) : (
82
+ <div className="flex-1 flex items-center justify-center bg-black rounded">
83
+ <div className="text-center">
84
+ <Camera className="w-12 h-12 mx-auto text-gray-500 mb-2" />
85
+ <p className="text-gray-400">Camera permission not granted.</p>
86
+ </div>
87
+ </div>
88
+ )}
89
+ </div>
90
+
91
+ {/* Mic Detection & Other Sensors */}
92
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
93
+ <div className="border border-gray-800 rounded p-2 flex flex-col justify-center min-h-[120px]">
94
+ <h3 className="text-sm text-center text-white font-medium mb-2">Voice Activity</h3>
95
+ {hasPermissions ? (
96
+ <div className="flex-1 flex flex-col items-center justify-center gap-2 text-center">
97
+ <div className="flex items-end h-10 gap-px w-full justify-center">
98
+ {[...Array(15)].map((_, i) => {
99
+ const barIsActive = isVoiceActive && i < (micLevel / 120 * 15);
100
+ return (
101
+ <div
102
+ key={i}
103
+ className={`w-1.5 rounded-full transition-colors duration-75 ${barIsActive ? 'bg-orange-500' : 'bg-gray-700'}`}
104
+ style={{ height: `${(i / 15 * 60) + 20}%` }}
105
+ />
106
+ );
107
+ })}
108
+ </div>
109
+ <p className="text-xs text-gray-300">
110
+ {isVoiceActive ? "Voice commands active" : "Voice commands muted"}
111
+ </p>
112
+ </div>
113
+ ) : (
114
+ <div className="flex-1 flex items-center justify-center bg-black rounded">
115
+ <div className="text-center">
116
+ <MicOff className="w-8 h-8 mx-auto text-gray-500 mb-2" />
117
+ <p className="text-gray-400">Microphone permission not granted.</p>
118
+ </div>
119
+ </div>
120
+ )}
121
+ </div>
122
+
123
+ {/* Sensor Charts */}
124
+ {['sensor3', 'sensor4'].map((sensor, index) => (
125
+ <div key={sensor} className="border border-gray-800 rounded p-2 flex flex-col h-auto min-h-[120px]">
126
+ <h3 className="text-sm text-white font-medium mb-2">Sensor {index + 3}</h3>
127
+ <ResponsiveContainer width="100%" height="90%">
128
+ <LineChart data={sensorData}>
129
+ <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
130
+ <XAxis hide />
131
+ <YAxis fontSize={12} stroke="#9CA3AF" />
132
+ <Tooltip
133
+ contentStyle={{
134
+ backgroundColor: '#1F2937',
135
+ border: '1px solid #374151',
136
+ color: '#fff'
137
+ }}
138
+ />
139
+ <Line
140
+ type="monotone"
141
+ dataKey={sensor}
142
+ stroke={index % 2 === 1 ? '#ff6b35' : '#ffdd44'}
143
+ strokeWidth={2}
144
+ dot={false}
145
+ />
146
+ </LineChart>
147
+ </ResponsiveContainer>
148
+ </div>
149
+ ))}
150
+ </div>
151
+ </div>
152
+ )}
153
+
154
+ {activeTab === 'MOTORS' && (
155
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
156
+ {['motor1', 'motor2', 'motor3', 'motor4', 'motor5', 'motor6'].map((motor, index) => (
157
+ <div key={motor} className="border border-gray-800 rounded p-2 h-40">
158
+ <h3 className="text-sm text-white font-medium mb-2">Motor {index + 1}</h3>
159
+ <ResponsiveContainer width="100%" height="80%">
160
+ <LineChart data={motorData}>
161
+ <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
162
+ <XAxis hide />
163
+ <YAxis fontSize={12} stroke="#9CA3AF" />
164
+ <Tooltip
165
+ contentStyle={{
166
+ backgroundColor: '#1F2937',
167
+ border: '1px solid #374151',
168
+ color: '#fff'
169
+ }}
170
+ />
171
+ <Line
172
+ type="monotone"
173
+ dataKey={motor}
174
+ stroke={index % 2 === 0 ? '#ff6b35' : '#ffdd44'}
175
+ strokeWidth={2}
176
+ dot={false}
177
+ />
178
+ </LineChart>
179
+ </ResponsiveContainer>
180
+ </div>
181
+ ))}
182
+ </div>
183
+ )}
184
+ </div>
185
+ </div>
186
+ </div>
187
+ );
188
+ };
189
+
190
+ export default MetricsPanel;
src/components/control/RobotArm.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
40
+ export default RobotArm;
src/components/control/VisualizerPanel.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
10
+ className?: string;
11
+ }
12
+
13
+ const VisualizerPanel: React.FC<VisualizerPanelProps> = ({
14
+ onGoBack,
15
+ className,
16
+ }) => {
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>
65
+ </div>
66
+ );
67
+ };
68
+
69
+ export default VisualizerPanel;
src/components/test/WebSocketTest.tsx ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from "react";
2
+ import { Button } from "@/components/ui/button";
3
+
4
+ interface JointData {
5
+ type: "joint_update";
6
+ joints: Record<string, number>;
7
+ timestamp: number;
8
+ }
9
+
10
+ const WebSocketTest: React.FC = () => {
11
+ const [isConnected, setIsConnected] = useState(false);
12
+ const [lastMessage, setLastMessage] = useState<JointData | null>(null);
13
+ const [connectionStatus, setConnectionStatus] =
14
+ useState<string>("Disconnected");
15
+ const [ws, setWs] = useState<WebSocket | null>(null);
16
+
17
+ const connect = () => {
18
+ // First test server health
19
+ fetch("http://localhost:8000/health")
20
+ .then((response) => response.json())
21
+ .then((data) => {
22
+ console.log("Server health:", data);
23
+
24
+ // Now try WebSocket connection
25
+ const websocket = new WebSocket("ws://localhost:8000/ws/joint-data");
26
+
27
+ websocket.onopen = () => {
28
+ console.log("WebSocket connected");
29
+ setIsConnected(true);
30
+ setConnectionStatus("Connected");
31
+ setWs(websocket);
32
+ };
33
+
34
+ websocket.onmessage = (event) => {
35
+ try {
36
+ const data: JointData = JSON.parse(event.data);
37
+ setLastMessage(data);
38
+ console.log("Received joint data:", data);
39
+ } catch (error) {
40
+ console.error("Error parsing message:", error);
41
+ }
42
+ };
43
+
44
+ websocket.onclose = (event) => {
45
+ console.log("WebSocket closed:", event.code, event.reason);
46
+ setIsConnected(false);
47
+ setConnectionStatus(`Closed (${event.code})`);
48
+ setWs(null);
49
+ };
50
+
51
+ websocket.onerror = (error) => {
52
+ console.error("WebSocket error:", error);
53
+ setConnectionStatus("Error");
54
+ };
55
+ })
56
+ .catch((error) => {
57
+ console.error("Server health check failed:", error);
58
+ setConnectionStatus("Server unreachable");
59
+ });
60
+ };
61
+
62
+ const disconnect = () => {
63
+ if (ws) {
64
+ ws.close();
65
+ }
66
+ };
67
+
68
+ useEffect(() => {
69
+ return () => {
70
+ if (ws) {
71
+ ws.close();
72
+ }
73
+ };
74
+ }, [ws]);
75
+
76
+ return (
77
+ <div className="p-4 bg-gray-900 text-white rounded-lg">
78
+ <h3 className="text-lg font-bold mb-4">WebSocket Connection Test</h3>
79
+
80
+ <div className="space-y-4">
81
+ <div className="flex items-center gap-4">
82
+ <div
83
+ className={`w-3 h-3 rounded-full ${
84
+ isConnected ? "bg-green-500" : "bg-red-500"
85
+ }`}
86
+ />
87
+ <span>Status: {connectionStatus}</span>
88
+ </div>
89
+
90
+ <div className="flex gap-2">
91
+ <Button onClick={connect} disabled={isConnected}>
92
+ Connect
93
+ </Button>
94
+ <Button
95
+ onClick={disconnect}
96
+ disabled={!isConnected}
97
+ variant="outline"
98
+ >
99
+ Disconnect
100
+ </Button>
101
+ </div>
102
+
103
+ {lastMessage && (
104
+ <div className="bg-gray-800 p-3 rounded">
105
+ <h4 className="font-semibold mb-2">Last Joint Data:</h4>
106
+ <div className="text-sm font-mono">
107
+ <div>
108
+ Timestamp:{" "}
109
+ {new Date(lastMessage.timestamp * 1000).toLocaleTimeString()}
110
+ </div>
111
+ <div className="mt-2">Joints:</div>
112
+ {Object.entries(lastMessage.joints).map(([joint, value]) => (
113
+ <div key={joint} className="ml-4">
114
+ {joint}: {value.toFixed(4)} rad (
115
+ {((value * 180) / Math.PI).toFixed(2)}°)
116
+ </div>
117
+ ))}
118
+ </div>
119
+ </div>
120
+ )}
121
+
122
+ <div className="text-sm text-gray-400">
123
+ <div>Expected URL: ws://localhost:8000/ws/joint-data</div>
124
+ <div>Make sure your FastAPI server is running!</div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ );
129
+ };
130
+
131
+ export default WebSocketTest;
src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Accordion = AccordionPrimitive.Root
8
+
9
+ const AccordionItem = React.forwardRef<
10
+ React.ElementRef<typeof AccordionPrimitive.Item>,
11
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
12
+ >(({ className, ...props }, ref) => (
13
+ <AccordionPrimitive.Item
14
+ ref={ref}
15
+ className={cn("border-b", className)}
16
+ {...props}
17
+ />
18
+ ))
19
+ AccordionItem.displayName = "AccordionItem"
20
+
21
+ const AccordionTrigger = React.forwardRef<
22
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
23
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
24
+ >(({ className, children, ...props }, ref) => (
25
+ <AccordionPrimitive.Header className="flex">
26
+ <AccordionPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
36
+ </AccordionPrimitive.Trigger>
37
+ </AccordionPrimitive.Header>
38
+ ))
39
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40
+
41
+ const AccordionContent = React.forwardRef<
42
+ React.ElementRef<typeof AccordionPrimitive.Content>,
43
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
44
+ >(({ className, children, ...props }, ref) => (
45
+ <AccordionPrimitive.Content
46
+ ref={ref}
47
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
48
+ {...props}
49
+ >
50
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
51
+ </AccordionPrimitive.Content>
52
+ ))
53
+
54
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
55
+
56
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
src/components/ui/alert-dialog.tsx ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { buttonVariants } from "@/components/ui/button"
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root
8
+
9
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10
+
11
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
12
+
13
+ const AlertDialogOverlay = React.forwardRef<
14
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
15
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
16
+ >(({ className, ...props }, ref) => (
17
+ <AlertDialogPrimitive.Overlay
18
+ className={cn(
19
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20
+ className
21
+ )}
22
+ {...props}
23
+ ref={ref}
24
+ />
25
+ ))
26
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
27
+
28
+ const AlertDialogContent = React.forwardRef<
29
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
30
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31
+ >(({ className, ...props }, ref) => (
32
+ <AlertDialogPortal>
33
+ <AlertDialogOverlay />
34
+ <AlertDialogPrimitive.Content
35
+ ref={ref}
36
+ className={cn(
37
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ </AlertDialogPortal>
43
+ ))
44
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
45
+
46
+ const AlertDialogHeader = ({
47
+ className,
48
+ ...props
49
+ }: React.HTMLAttributes<HTMLDivElement>) => (
50
+ <div
51
+ className={cn(
52
+ "flex flex-col space-y-2 text-center sm:text-left",
53
+ className
54
+ )}
55
+ {...props}
56
+ />
57
+ )
58
+ AlertDialogHeader.displayName = "AlertDialogHeader"
59
+
60
+ const AlertDialogFooter = ({
61
+ className,
62
+ ...props
63
+ }: React.HTMLAttributes<HTMLDivElement>) => (
64
+ <div
65
+ className={cn(
66
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ )
72
+ AlertDialogFooter.displayName = "AlertDialogFooter"
73
+
74
+ const AlertDialogTitle = React.forwardRef<
75
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
76
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
77
+ >(({ className, ...props }, ref) => (
78
+ <AlertDialogPrimitive.Title
79
+ ref={ref}
80
+ className={cn("text-lg font-semibold", className)}
81
+ {...props}
82
+ />
83
+ ))
84
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
85
+
86
+ const AlertDialogDescription = React.forwardRef<
87
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
88
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
89
+ >(({ className, ...props }, ref) => (
90
+ <AlertDialogPrimitive.Description
91
+ ref={ref}
92
+ className={cn("text-sm text-muted-foreground", className)}
93
+ {...props}
94
+ />
95
+ ))
96
+ AlertDialogDescription.displayName =
97
+ AlertDialogPrimitive.Description.displayName
98
+
99
+ const AlertDialogAction = React.forwardRef<
100
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
101
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
102
+ >(({ className, ...props }, ref) => (
103
+ <AlertDialogPrimitive.Action
104
+ ref={ref}
105
+ className={cn(buttonVariants(), className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
110
+
111
+ const AlertDialogCancel = React.forwardRef<
112
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
113
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
114
+ >(({ className, ...props }, ref) => (
115
+ <AlertDialogPrimitive.Cancel
116
+ ref={ref}
117
+ className={cn(
118
+ buttonVariants({ variant: "outline" }),
119
+ "mt-2 sm:mt-0",
120
+ className
121
+ )}
122
+ {...props}
123
+ />
124
+ ))
125
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
126
+
127
+ export {
128
+ AlertDialog,
129
+ AlertDialogPortal,
130
+ AlertDialogOverlay,
131
+ AlertDialogTrigger,
132
+ AlertDialogContent,
133
+ AlertDialogHeader,
134
+ AlertDialogFooter,
135
+ AlertDialogTitle,
136
+ AlertDialogDescription,
137
+ AlertDialogAction,
138
+ AlertDialogCancel,
139
+ }
src/components/ui/alert.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "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",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
src/components/ui/aspect-ratio.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root
4
+
5
+ export { AspectRatio }
src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Avatar = React.forwardRef<
7
+ React.ElementRef<typeof AvatarPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <AvatarPrimitive.Root
11
+ ref={ref}
12
+ className={cn(
13
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ ))
19
+ Avatar.displayName = AvatarPrimitive.Root.displayName
20
+
21
+ const AvatarImage = React.forwardRef<
22
+ React.ElementRef<typeof AvatarPrimitive.Image>,
23
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
24
+ >(({ className, ...props }, ref) => (
25
+ <AvatarPrimitive.Image
26
+ ref={ref}
27
+ className={cn("aspect-square h-full w-full", className)}
28
+ {...props}
29
+ />
30
+ ))
31
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
32
+
33
+ const AvatarFallback = React.forwardRef<
34
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
35
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
36
+ >(({ className, ...props }, ref) => (
37
+ <AvatarPrimitive.Fallback
38
+ ref={ref}
39
+ className={cn(
40
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ ))
46
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47
+
48
+ export { Avatar, AvatarImage, AvatarFallback }
src/components/ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "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",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
src/components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-normal text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:size-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
src/components/ui/button.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
src/components/ui/calendar.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+ import { DayPicker } from "react-day-picker";
4
+
5
+ import { cn } from "@/lib/utils";
6
+ import { buttonVariants } from "@/components/ui/button";
7
+
8
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>;
9
+
10
+ function Calendar({
11
+ className,
12
+ classNames,
13
+ showOutsideDays = true,
14
+ ...props
15
+ }: CalendarProps) {
16
+ return (
17
+ <DayPicker
18
+ showOutsideDays={showOutsideDays}
19
+ className={cn("p-3", className)}
20
+ classNames={{
21
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
22
+ month: "space-y-4",
23
+ caption: "flex justify-center pt-1 relative items-center",
24
+ caption_label: "text-sm font-medium",
25
+ nav: "space-x-1 flex items-center",
26
+ nav_button: cn(
27
+ buttonVariants({ variant: "outline" }),
28
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
29
+ ),
30
+ nav_button_previous: "absolute left-1",
31
+ nav_button_next: "absolute right-1",
32
+ table: "w-full border-collapse space-y-1",
33
+ head_row: "flex",
34
+ head_cell:
35
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
36
+ row: "flex w-full mt-2",
37
+ cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
38
+ day: cn(
39
+ buttonVariants({ variant: "ghost" }),
40
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
41
+ ),
42
+ day_range_end: "day-range-end",
43
+ day_selected:
44
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
45
+ day_today: "bg-accent text-accent-foreground",
46
+ day_outside:
47
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
48
+ day_disabled: "text-muted-foreground opacity-50",
49
+ day_range_middle:
50
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
51
+ day_hidden: "invisible",
52
+ ...classNames,
53
+ }}
54
+ components={{
55
+ IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
56
+ IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
57
+ }}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+ Calendar.displayName = "Calendar";
63
+
64
+ export { Calendar };