Spaces:
Running
Running
Disable Inference
Browse files- .env.example +1 -1
- README.md +2 -2
- SPEC.md +0 -99
- external/RobotHub-InferenceServer +1 -1
- src/lib/components/3d/elements/compute/ComputeGridItem.svelte +1 -1
- src/lib/components/3d/elements/compute/Computes.svelte +13 -2
- src/lib/components/3d/elements/compute/modal/AISessionConnectionModal.svelte +101 -79
- src/lib/components/3d/elements/compute/status/ComputeBoxUIKit.svelte +1 -1
- src/lib/components/3d/elements/compute/status/ComputeInputBoxUIKit.svelte +2 -2
- src/lib/components/3d/elements/compute/status/ComputeOutputBoxUIKit.svelte +2 -2
- src/lib/components/3d/elements/compute/status/ComputeStatusBillboard.svelte +18 -2
- src/lib/components/3d/elements/compute/status/RobotInputBoxUIKit.svelte +2 -2
- src/lib/components/3d/elements/compute/status/RobotOutputBoxUIKit.svelte +2 -2
- src/lib/components/3d/elements/compute/status/VideoInputBoxUIKit.svelte +2 -2
- src/lib/components/3d/elements/robot/RobotGridItem.svelte +11 -5
- src/lib/components/3d/elements/robot/Robots.svelte +6 -0
- src/lib/components/3d/elements/robot/URDF/primitives/UrdfJoint.svelte +0 -1
- src/lib/components/3d/elements/robot/URDF/primitives/UrdfLink.svelte +0 -3
- src/lib/components/3d/elements/robot/URDF/primitives/UrdfThree.svelte +0 -1
- src/lib/components/3d/elements/robot/modal/InputConnectionModal.svelte +35 -22
- src/lib/components/3d/elements/robot/modal/OutputConnectionModal.svelte +27 -16
- src/lib/components/3d/elements/robot/status/ConnectionFlowBoxUIkit.svelte +22 -2
- src/lib/components/3d/elements/robot/status/OutputBoxUIKit.svelte +1 -1
- src/lib/components/3d/elements/robot/status/RobotStatusBillboard.svelte +9 -3
- src/lib/components/3d/elements/video/VideoGridItem.svelte +0 -1
- src/lib/components/3d/elements/video/Videos.svelte +7 -0
- src/lib/components/3d/elements/video/modal/VideoInputConnectionModal.svelte +27 -14
- src/lib/components/3d/elements/video/modal/VideoOutputConnectionModal.svelte +29 -16
- src/lib/components/3d/elements/video/status/VideoConnectionFlowBoxUIKit.svelte +25 -8
- src/lib/components/3d/elements/video/status/VideoStatusBillboard.svelte +3 -3
- src/lib/components/interface/overlay/AddAIButton.svelte +50 -90
- src/lib/components/interface/overlay/AddRobotButton.svelte +15 -8
- src/lib/components/interface/overlay/AddSensorButton.svelte +7 -0
- src/lib/components/interface/overlay/Overlay.svelte +20 -25
- src/lib/components/interface/overlay/SettingsSheet.svelte +1 -2
- src/lib/components/interface/overlay/WorkspaceIdButton.svelte +1 -1
- src/lib/configs/robotUrdfConfig.ts +4 -0
- src/lib/elements/compute/RemoteCompute.svelte.ts +2 -1
- src/lib/elements/compute/RemoteComputeManager.svelte.ts +138 -1
- src/lib/elements/compute/index.ts +2 -1
- src/lib/elements/robot/RobotManager.svelte.ts +2 -3
- src/lib/elements/robot/components/RobotItem.svelte +0 -1
- src/lib/elements/video/VideoManager.svelte.ts +8 -0
- src/lib/types/urdf.ts +5 -0
.env.example
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
PUBLIC_TRANSPORT_SERVER_URL=https://blanchon-robothub-transportserver.hf.space/api
|
| 2 |
-
PUBLIC_INFERENCE_SERVER_URL=https://blanchon-robothub-
|
|
|
|
| 1 |
PUBLIC_TRANSPORT_SERVER_URL=https://blanchon-robothub-transportserver.hf.space/api
|
| 2 |
+
PUBLIC_INFERENCE_SERVER_URL=https://blanchon-robothub-inferenceserver.hf.space/api
|
README.md
CHANGED
|
@@ -106,7 +106,7 @@ The **workspace-id** in the URL hash ties all three services together. Share `h
|
|
| 106 |
2. Click *Add Robot* → spawns an SO-100 6-DoF arm (URDF).
|
| 107 |
3. Click *Add Sensor → Camera* → creates a virtual camera element.
|
| 108 |
4. Click *Add Model → ACT* → spawns a *Compute* block.
|
| 109 |
-
5. On the Compute block choose *Create Session* – select model path (
|
| 110 |
6. Connect:
|
| 111 |
• *Video Input* – local webcam → `front` room.
|
| 112 |
• *Robot Input* – robot → *joint-input* room (producer).
|
|
@@ -244,7 +244,7 @@ Typical lifecycle:
|
|
| 244 |
```jsonc
|
| 245 |
{
|
| 246 |
"session_id": "pick_place_demo",
|
| 247 |
-
"policy_path": "
|
| 248 |
"camera_names": ["front", "wrist"],
|
| 249 |
"transport_server_url": "http://localhost:8000",
|
| 250 |
"workspace_id": "<existing-or-new>" // optional
|
|
|
|
| 106 |
2. Click *Add Robot* → spawns an SO-100 6-DoF arm (URDF).
|
| 107 |
3. Click *Add Sensor → Camera* → creates a virtual camera element.
|
| 108 |
4. Click *Add Model → ACT* → spawns a *Compute* block.
|
| 109 |
+
5. On the Compute block choose *Create Session* – select model path (`LaetusH/act_so101_beyond`) and cameras (`front`).
|
| 110 |
6. Connect:
|
| 111 |
• *Video Input* – local webcam → `front` room.
|
| 112 |
• *Robot Input* – robot → *joint-input* room (producer).
|
|
|
|
| 244 |
```jsonc
|
| 245 |
{
|
| 246 |
"session_id": "pick_place_demo",
|
| 247 |
+
"policy_path": "LaetusH/act_so101_beyond",
|
| 248 |
"camera_names": ["front", "wrist"],
|
| 249 |
"transport_server_url": "http://localhost:8000",
|
| 250 |
"workspace_id": "<existing-or-new>" // optional
|
SPEC.md
DELETED
|
@@ -1,99 +0,0 @@
|
|
| 1 |
-
# Robot USB Connection System - Summary
|
| 2 |
-
|
| 3 |
-
## Core Components
|
| 4 |
-
|
| 5 |
-
### USBProducer (Output)
|
| 6 |
-
**Purpose**: Sends software commands to control physical robot hardware
|
| 7 |
-
- Receives normalized joint commands from software
|
| 8 |
-
- Converts normalized values to raw servo positions
|
| 9 |
-
- Sends position commands to physical servos
|
| 10 |
-
- Locks servos for precise software control
|
| 11 |
-
|
| 12 |
-
### USBConsumer (Input)
|
| 13 |
-
**Purpose**: Reads physical robot movements and translates to software commands
|
| 14 |
-
- Continuously monitors physical servo positions
|
| 15 |
-
- Converts raw servo values to normalized percentages
|
| 16 |
-
- Detects movement changes and broadcasts updates
|
| 17 |
-
- Keeps servos unlocked for manual manipulation
|
| 18 |
-
|
| 19 |
-
## Key Features
|
| 20 |
-
|
| 21 |
-
### Calibration System (`USBCalibrationPanel.svelte`)
|
| 22 |
-
- **Requirement**: All USB connections must be calibrated before use
|
| 23 |
-
- **Process**: Records min/max physical range for each servo by manual movement
|
| 24 |
-
- **Result**: Establishes mapping between raw servo values (0-4095) and normalized values
|
| 25 |
-
|
| 26 |
-
### Normalized Communication Protocol
|
| 27 |
-
- **Standard Joints**: -100% to +100% range (bipolar)
|
| 28 |
-
- **Gripper/Jaw**: 0% to +100% range (unipolar)
|
| 29 |
-
- **Benefits**: Consistent software interface across different robots
|
| 30 |
-
- **Conversion**: Automatic normalization/denormalization based on calibration data
|
| 31 |
-
|
| 32 |
-
### Multi-Port Support
|
| 33 |
-
- **Capability**: Multiple independent USB connections simultaneously
|
| 34 |
-
- **Independence**: Each connection has its own calibration and configuration
|
| 35 |
-
- **Use Case**: Control multiple robots or multiple connections to same robot
|
| 36 |
-
|
| 37 |
-
### Batch Operations
|
| 38 |
-
- **Sync Read**: Read multiple servo positions simultaneously
|
| 39 |
-
- **Sync Write**: Send commands to multiple servos in batched operations
|
| 40 |
-
- **Performance**: Reduces USB communication overhead and latency
|
| 41 |
-
|
| 42 |
-
## Key Constraints
|
| 43 |
-
|
| 44 |
-
### Connection Exclusivity
|
| 45 |
-
- **Input**: Only one consumer (input source) active per robot at a time
|
| 46 |
-
- **Output**: Multiple producers (output destinations) can be active simultaneously
|
| 47 |
-
- **Rationale**: Prevents conflicting input commands while allowing broadcast to multiple destinations
|
| 48 |
-
|
| 49 |
-
### Servo Locking Strategy
|
| 50 |
-
- **Consumer Mode**: Servos unlocked → manual movement possible + position reading
|
| 51 |
-
- **Producer Mode**: Servos locked → software control only, no manual movement
|
| 52 |
-
- **Safety**: Prevents mechanical conflicts between manual and software control
|
| 53 |
-
|
| 54 |
-
### Calibration Dependency
|
| 55 |
-
- **Mandatory**: USB connections cannot establish without valid calibration
|
| 56 |
-
- **Per-Connection**: Each USB port requires independent calibration
|
| 57 |
-
- **Safety**: Prevents commanding impossible positions or damaging hardware
|
| 58 |
-
|
| 59 |
-
## Additional System Requirements (from SPEC.md analysis)
|
| 60 |
-
|
| 61 |
-
### Connection Management
|
| 62 |
-
- **Auto-Detection**: System should detect available USB ports automatically
|
| 63 |
-
- **Status Monitoring**: Real-time connection health and error reporting
|
| 64 |
-
- **Graceful Disconnection**: Safe servo unlocking on disconnect/error
|
| 65 |
-
|
| 66 |
-
### Error Handling & Recovery
|
| 67 |
-
- **Port Conflicts**: Queue management to prevent "Port is busy" errors
|
| 68 |
-
- **Retry Logic**: Automatic retry with backoff for failed servo commands
|
| 69 |
-
- **Connection Recovery**: Automatic reconnection attempts after USB disconnect
|
| 70 |
-
|
| 71 |
-
### User Interface Integration
|
| 72 |
-
- **Modal Management**: Unified connection setup through calibration panels
|
| 73 |
-
- **Status Display**: Visual indicators for connection state and calibration status
|
| 74 |
-
- **Manual Control**: Direct joint manipulation when no input consumer active
|
| 75 |
-
|
| 76 |
-
### Performance Optimizations
|
| 77 |
-
- **Polling Rates**: Configurable update frequencies for different use cases
|
| 78 |
-
- **Change Detection**: Only broadcast updates when values actually change
|
| 79 |
-
- **Queueing**: Serial command processing to prevent USB port conflicts
|
| 80 |
-
|
| 81 |
-
## System Architecture Concepts
|
| 82 |
-
|
| 83 |
-
### Bidirectional Data Flow
|
| 84 |
-
```
|
| 85 |
-
Physical Robot ←→ USB Hardware ←→ Calibration Layer ←→ Normalized Interface ←→ Software
|
| 86 |
-
```
|
| 87 |
-
|
| 88 |
-
### Connection Patterns
|
| 89 |
-
- **Teaching Mode**: USB Consumer only (read robot movements)
|
| 90 |
-
- **Control Mode**: USB Producer only (software controls robot)
|
| 91 |
-
- **Bidirectional**: Both consumer and producer (full interaction)
|
| 92 |
-
- **Broadcasting**: Multiple producers (send to hardware + remote systems)
|
| 93 |
-
|
| 94 |
-
### Value Transformation Pipeline
|
| 95 |
-
```
|
| 96 |
-
Raw Servo (0-4095) ←→ Calibrated Range ←→ Normalized (-100/+100) ←→ Software Commands
|
| 97 |
-
```
|
| 98 |
-
|
| 99 |
-
This system provides a robust, safe, and flexible interface for robot hardware control while maintaining consistency across different robot configurations and use cases.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
external/RobotHub-InferenceServer
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
Subproject commit
|
|
|
|
| 1 |
+
Subproject commit be2822ebd8b95c11d982e7f93594016c2285954f
|
src/lib/components/3d/elements/compute/ComputeGridItem.svelte
CHANGED
|
@@ -16,7 +16,6 @@
|
|
| 16 |
$props();
|
| 17 |
|
| 18 |
const { onPointerEnter, onPointerLeave, hovering } = useCursor();
|
| 19 |
-
interactivity();
|
| 20 |
|
| 21 |
let isToggled = $state(false);
|
| 22 |
|
|
@@ -24,6 +23,7 @@
|
|
| 24 |
event.stopPropagation();
|
| 25 |
isToggled = !isToggled;
|
| 26 |
}
|
|
|
|
| 27 |
</script>
|
| 28 |
|
| 29 |
<T.Group
|
|
|
|
| 16 |
$props();
|
| 17 |
|
| 18 |
const { onPointerEnter, onPointerLeave, hovering } = useCursor();
|
|
|
|
| 19 |
|
| 20 |
let isToggled = $state(false);
|
| 21 |
|
|
|
|
| 23 |
event.stopPropagation();
|
| 24 |
isToggled = !isToggled;
|
| 25 |
}
|
| 26 |
+
|
| 27 |
</script>
|
| 28 |
|
| 29 |
<T.Group
|
src/lib/components/3d/elements/compute/Computes.svelte
CHANGED
|
@@ -2,11 +2,13 @@
|
|
| 2 |
import { onMount } from "svelte";
|
| 3 |
import { remoteComputeManager } from "$lib/elements/compute/RemoteComputeManager.svelte";
|
| 4 |
import AISessionConnectionModal from "@/components/3d/elements/compute/modal/AISessionConnectionModal.svelte";
|
|
|
|
| 5 |
import VideoInputConnectionModal from "@/components/3d/elements/compute/modal/VideoInputConnectionModal.svelte";
|
| 6 |
import RobotInputConnectionModal from "@/components/3d/elements/compute/modal/RobotInputConnectionModal.svelte";
|
| 7 |
import RobotOutputConnectionModal from "@/components/3d/elements/compute/modal/RobotOutputConnectionModal.svelte";
|
| 8 |
import ComputeGridItem from "@/components/3d/elements/compute/ComputeGridItem.svelte";
|
| 9 |
import type { RemoteCompute } from "$lib/elements/compute/RemoteCompute.svelte";
|
|
|
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
workspaceId: string;
|
|
@@ -14,6 +16,7 @@
|
|
| 14 |
let { workspaceId }: Props = $props();
|
| 15 |
|
| 16 |
let isAISessionModalOpen = $state(false);
|
|
|
|
| 17 |
let isVideoInputModalOpen = $state(false);
|
| 18 |
let isRobotInputModalOpen = $state(false);
|
| 19 |
let isRobotOutputModalOpen = $state(false);
|
|
@@ -64,6 +67,11 @@
|
|
| 64 |
|
| 65 |
return () => clearInterval(interval);
|
| 66 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
</script>
|
| 68 |
|
| 69 |
{#each remoteComputeManager.computes as compute (compute.id)}
|
|
@@ -76,7 +84,7 @@
|
|
| 76 |
{/each}
|
| 77 |
|
| 78 |
{#if selectedCompute}
|
| 79 |
-
<!-- Inference Session
|
| 80 |
<AISessionConnectionModal bind:open={isAISessionModalOpen} compute={selectedCompute} {workspaceId} />
|
| 81 |
<!-- Video Input Connection Modal -->
|
| 82 |
<VideoInputConnectionModal bind:open={isVideoInputModalOpen} compute={selectedCompute} {workspaceId} />
|
|
@@ -84,4 +92,7 @@
|
|
| 84 |
<RobotInputConnectionModal bind:open={isRobotInputModalOpen} compute={selectedCompute} {workspaceId} />
|
| 85 |
<!-- Robot Output Connection Modal -->
|
| 86 |
<RobotOutputConnectionModal bind:open={isRobotOutputModalOpen} compute={selectedCompute} {workspaceId} />
|
| 87 |
-
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import { onMount } from "svelte";
|
| 3 |
import { remoteComputeManager } from "$lib/elements/compute/RemoteComputeManager.svelte";
|
| 4 |
import AISessionConnectionModal from "@/components/3d/elements/compute/modal/AISessionConnectionModal.svelte";
|
| 5 |
+
import AIModelConfigurationModal from "@/components/3d/elements/compute/modal/AIModelConfigurationModal.svelte";
|
| 6 |
import VideoInputConnectionModal from "@/components/3d/elements/compute/modal/VideoInputConnectionModal.svelte";
|
| 7 |
import RobotInputConnectionModal from "@/components/3d/elements/compute/modal/RobotInputConnectionModal.svelte";
|
| 8 |
import RobotOutputConnectionModal from "@/components/3d/elements/compute/modal/RobotOutputConnectionModal.svelte";
|
| 9 |
import ComputeGridItem from "@/components/3d/elements/compute/ComputeGridItem.svelte";
|
| 10 |
import type { RemoteCompute } from "$lib/elements/compute/RemoteCompute.svelte";
|
| 11 |
+
import { interactivity } from "@threlte/extras";
|
| 12 |
|
| 13 |
interface Props {
|
| 14 |
workspaceId: string;
|
|
|
|
| 16 |
let { workspaceId }: Props = $props();
|
| 17 |
|
| 18 |
let isAISessionModalOpen = $state(false);
|
| 19 |
+
let isAIConfigModalOpen = $state(false);
|
| 20 |
let isVideoInputModalOpen = $state(false);
|
| 21 |
let isRobotInputModalOpen = $state(false);
|
| 22 |
let isRobotOutputModalOpen = $state(false);
|
|
|
|
| 67 |
|
| 68 |
return () => clearInterval(interval);
|
| 69 |
});
|
| 70 |
+
interactivity({
|
| 71 |
+
filter: (hits, state) => {
|
| 72 |
+
return hits.slice(0, 1);
|
| 73 |
+
}
|
| 74 |
+
});
|
| 75 |
</script>
|
| 76 |
|
| 77 |
{#each remoteComputeManager.computes as compute (compute.id)}
|
|
|
|
| 84 |
{/each}
|
| 85 |
|
| 86 |
{#if selectedCompute}
|
| 87 |
+
<!-- Inference Session Configuration Modal (for existing computes without sessions) -->
|
| 88 |
<AISessionConnectionModal bind:open={isAISessionModalOpen} compute={selectedCompute} {workspaceId} />
|
| 89 |
<!-- Video Input Connection Modal -->
|
| 90 |
<VideoInputConnectionModal bind:open={isVideoInputModalOpen} compute={selectedCompute} {workspaceId} />
|
|
|
|
| 92 |
<RobotInputConnectionModal bind:open={isRobotInputModalOpen} compute={selectedCompute} {workspaceId} />
|
| 93 |
<!-- Robot Output Connection Modal -->
|
| 94 |
<RobotOutputConnectionModal bind:open={isRobotOutputModalOpen} compute={selectedCompute} {workspaceId} />
|
| 95 |
+
{/if}
|
| 96 |
+
|
| 97 |
+
<!-- AI Model Configuration Modal (for creating new models) -->
|
| 98 |
+
<AIModelConfigurationModal bind:open={isAIConfigModalOpen} {workspaceId} />
|
src/lib/components/3d/elements/compute/modal/AISessionConnectionModal.svelte
CHANGED
|
@@ -5,8 +5,8 @@
|
|
| 5 |
import { Badge } from "@/components/ui/badge";
|
| 6 |
import { Input } from "@/components/ui/input";
|
| 7 |
import { Label } from "@/components/ui/label";
|
| 8 |
-
import
|
| 9 |
-
import { remoteComputeManager } from "$lib/elements/compute
|
| 10 |
import type { RemoteCompute } from "$lib/elements/compute//RemoteCompute.svelte";
|
| 11 |
import type { AISessionConfig } from "$lib/elements/compute//RemoteComputeManager.svelte";
|
| 12 |
import { settings } from "$lib/runes/settings.svelte";
|
|
@@ -22,9 +22,12 @@
|
|
| 22 |
|
| 23 |
let isConnecting = $state(false);
|
| 24 |
let sessionId = $state("");
|
| 25 |
-
let policyPath = $state("
|
| 26 |
-
let cameraNames = $state("
|
| 27 |
-
let
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
// Auto-generate session ID when modal opens
|
| 30 |
$effect(() => {
|
|
@@ -33,6 +36,18 @@
|
|
| 33 |
}
|
| 34 |
});
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
async function handleCreateSession() {
|
| 37 |
if (!compute) return;
|
| 38 |
|
|
@@ -41,6 +56,11 @@
|
|
| 41 |
return;
|
| 42 |
}
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
isConnecting = true;
|
| 45 |
try {
|
| 46 |
const cameras = cameraNames
|
|
@@ -51,17 +71,19 @@
|
|
| 51 |
cameras.push("front");
|
| 52 |
}
|
| 53 |
|
| 54 |
-
const
|
| 55 |
sessionId: sessionId.trim(),
|
|
|
|
| 56 |
policyPath: policyPath.trim(),
|
| 57 |
cameraNames: cameras,
|
| 58 |
transportServerUrl: settings.transportServerUrl,
|
| 59 |
-
workspaceId:
|
|
|
|
| 60 |
};
|
| 61 |
|
| 62 |
-
const result = await remoteComputeManager.createSession(compute.id,
|
| 63 |
if (result.success) {
|
| 64 |
-
toast.success(
|
| 65 |
open = false;
|
| 66 |
} else {
|
| 67 |
toast.error(`Failed to create session: ${result.error}`);
|
|
@@ -140,11 +162,11 @@
|
|
| 140 |
<Dialog.Title
|
| 141 |
class="flex items-center gap-2 text-lg font-bold text-slate-900 dark:text-slate-100"
|
| 142 |
>
|
| 143 |
-
<span class="icon
|
| 144 |
-
|
| 145 |
</Dialog.Title>
|
| 146 |
<Dialog.Description class="text-sm text-slate-600 dark:text-slate-400">
|
| 147 |
-
Configure and manage
|
| 148 |
</Dialog.Description>
|
| 149 |
</Dialog.Header>
|
| 150 |
|
|
@@ -154,7 +176,7 @@
|
|
| 154 |
class="flex items-center justify-between rounded-lg border border-purple-300/30 bg-purple-100/20 p-3 dark:border-purple-500/30 dark:bg-purple-900/20"
|
| 155 |
>
|
| 156 |
<div class="flex items-center gap-2">
|
| 157 |
-
<span class="icon
|
| 158 |
<span class="text-sm font-medium text-purple-700 dark:text-purple-300"
|
| 159 |
>Session Status</span
|
| 160 |
>
|
|
@@ -325,84 +347,84 @@
|
|
| 325 |
</Card.Header>
|
| 326 |
<Card.Content>
|
| 327 |
<div class="space-y-4">
|
| 328 |
-
<div class="
|
| 329 |
-
<
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
<
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
/>
|
| 350 |
-
</div>
|
| 351 |
</div>
|
| 352 |
|
| 353 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
<div class="space-y-2">
|
| 355 |
-
<Label for="
|
| 356 |
-
|
| 357 |
-
>
|
| 358 |
-
<
|
| 359 |
-
id="
|
| 360 |
-
bind:value={
|
| 361 |
-
placeholder="
|
| 362 |
class="border-slate-300 bg-slate-50 text-slate-900 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100"
|
|
|
|
| 363 |
/>
|
| 364 |
<p class="text-xs text-slate-600 dark:text-slate-400">
|
| 365 |
-
|
| 366 |
</p>
|
| 367 |
</div>
|
| 368 |
-
|
| 369 |
-
<Label for="transportServerUrl" class="text-purple-700 dark:text-purple-300"
|
| 370 |
-
>Transport Server URL</Label
|
| 371 |
-
>
|
| 372 |
-
<Input
|
| 373 |
-
id="transportServerUrl"
|
| 374 |
-
value={settings.transportServerUrl}
|
| 375 |
-
disabled
|
| 376 |
-
placeholder="http://localhost:8000"
|
| 377 |
-
class="cursor-not-allowed border-slate-300 bg-slate-50 text-slate-900 opacity-60 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100"
|
| 378 |
-
title="Change this value in the settings panel"
|
| 379 |
-
/>
|
| 380 |
-
<p class="text-xs text-slate-600 dark:text-slate-400">
|
| 381 |
-
Configure in settings panel
|
| 382 |
-
</p>
|
| 383 |
-
</div>
|
| 384 |
-
</div>
|
| 385 |
|
| 386 |
-
<div class="
|
| 387 |
-
<
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
<
|
| 394 |
-
|
| 395 |
-
</
|
| 396 |
</div>
|
| 397 |
|
| 398 |
-
<
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
|
|
|
|
|
|
| 402 |
inputs, joint inputs, and joint outputs in the inference server communication
|
| 403 |
system.
|
| 404 |
-
</
|
| 405 |
-
</
|
| 406 |
|
| 407 |
<Button
|
| 408 |
variant="default"
|
|
@@ -428,7 +450,7 @@
|
|
| 428 |
class="rounded border border-slate-300 bg-slate-100/30 p-2 text-xs text-slate-600 dark:border-slate-700 dark:bg-slate-800/30 dark:text-slate-500"
|
| 429 |
>
|
| 430 |
<span class="icon-[mdi--information] mr-1 size-3"></span>
|
| 431 |
-
Inference Sessions require a trained
|
| 432 |
inputs, robot joint states, and control outputs in the inference server system.
|
| 433 |
</div>
|
| 434 |
</div>
|
|
|
|
| 5 |
import { Badge } from "@/components/ui/badge";
|
| 6 |
import { Input } from "@/components/ui/input";
|
| 7 |
import { Label } from "@/components/ui/label";
|
| 8 |
+
import { Textarea } from "@/components/ui/textarea";
|
| 9 |
+
import { remoteComputeManager, MODEL_TYPES } from "$lib/elements/compute";
|
| 10 |
import type { RemoteCompute } from "$lib/elements/compute//RemoteCompute.svelte";
|
| 11 |
import type { AISessionConfig } from "$lib/elements/compute//RemoteComputeManager.svelte";
|
| 12 |
import { settings } from "$lib/runes/settings.svelte";
|
|
|
|
| 22 |
|
| 23 |
let isConnecting = $state(false);
|
| 24 |
let sessionId = $state("");
|
| 25 |
+
let policyPath = $state("");
|
| 26 |
+
let cameraNames = $state("");
|
| 27 |
+
let languageInstruction = $state("");
|
| 28 |
+
|
| 29 |
+
// Use the compute's model type (can't be changed here)
|
| 30 |
+
const modelConfig = $derived(MODEL_TYPES[compute.modelType]);
|
| 31 |
|
| 32 |
// Auto-generate session ID when modal opens
|
| 33 |
$effect(() => {
|
|
|
|
| 36 |
}
|
| 37 |
});
|
| 38 |
|
| 39 |
+
// Set defaults when modal opens
|
| 40 |
+
$effect(() => {
|
| 41 |
+
if (open && modelConfig) {
|
| 42 |
+
if (!policyPath) {
|
| 43 |
+
policyPath = modelConfig.defaultPolicyPath;
|
| 44 |
+
}
|
| 45 |
+
if (!cameraNames) {
|
| 46 |
+
cameraNames = modelConfig.defaultCameraNames.join(", ");
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
});
|
| 50 |
+
|
| 51 |
async function handleCreateSession() {
|
| 52 |
if (!compute) return;
|
| 53 |
|
|
|
|
| 56 |
return;
|
| 57 |
}
|
| 58 |
|
| 59 |
+
if (modelConfig.requiresLanguageInstruction && !languageInstruction.trim()) {
|
| 60 |
+
toast.error("Language instruction is required for this model type");
|
| 61 |
+
return;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
isConnecting = true;
|
| 65 |
try {
|
| 66 |
const cameras = cameraNames
|
|
|
|
| 71 |
cameras.push("front");
|
| 72 |
}
|
| 73 |
|
| 74 |
+
const sessionConfig: AISessionConfig = {
|
| 75 |
sessionId: sessionId.trim(),
|
| 76 |
+
modelType: compute.modelType,
|
| 77 |
policyPath: policyPath.trim(),
|
| 78 |
cameraNames: cameras,
|
| 79 |
transportServerUrl: settings.transportServerUrl,
|
| 80 |
+
workspaceId: workspaceId,
|
| 81 |
+
languageInstruction: languageInstruction.trim() || undefined
|
| 82 |
};
|
| 83 |
|
| 84 |
+
const result = await remoteComputeManager.createSession(compute.id, sessionConfig);
|
| 85 |
if (result.success) {
|
| 86 |
+
toast.success(`${modelConfig.label} session created: ${sessionId}`);
|
| 87 |
open = false;
|
| 88 |
} else {
|
| 89 |
toast.error(`Failed to create session: ${result.error}`);
|
|
|
|
| 162 |
<Dialog.Title
|
| 163 |
class="flex items-center gap-2 text-lg font-bold text-slate-900 dark:text-slate-100"
|
| 164 |
>
|
| 165 |
+
<span class="{modelConfig.icon} size-5 text-purple-500 dark:text-purple-400"></span>
|
| 166 |
+
{modelConfig.label} Session - {compute.name || "No Compute Selected"}
|
| 167 |
</Dialog.Title>
|
| 168 |
<Dialog.Description class="text-sm text-slate-600 dark:text-slate-400">
|
| 169 |
+
Configure and manage {modelConfig.label} inference sessions for robot control
|
| 170 |
</Dialog.Description>
|
| 171 |
</Dialog.Header>
|
| 172 |
|
|
|
|
| 176 |
class="flex items-center justify-between rounded-lg border border-purple-300/30 bg-purple-100/20 p-3 dark:border-purple-500/30 dark:bg-purple-900/20"
|
| 177 |
>
|
| 178 |
<div class="flex items-center gap-2">
|
| 179 |
+
<span class="{modelConfig.icon} size-4 text-purple-500 dark:text-purple-400"></span>
|
| 180 |
<span class="text-sm font-medium text-purple-700 dark:text-purple-300"
|
| 181 |
>Session Status</span
|
| 182 |
>
|
|
|
|
| 347 |
</Card.Header>
|
| 348 |
<Card.Content>
|
| 349 |
<div class="space-y-4">
|
| 350 |
+
<div class="space-y-2">
|
| 351 |
+
<Label for="sessionId" class="text-purple-700 dark:text-purple-300"
|
| 352 |
+
>Session ID</Label
|
| 353 |
+
>
|
| 354 |
+
<Input
|
| 355 |
+
id="sessionId"
|
| 356 |
+
bind:value={sessionId}
|
| 357 |
+
placeholder="my-session-01"
|
| 358 |
+
class="border-slate-300 bg-slate-50 text-slate-900 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100"
|
| 359 |
+
/>
|
| 360 |
+
</div>
|
| 361 |
+
<div class="space-y-2">
|
| 362 |
+
<Label for="policyPath" class="text-purple-700 dark:text-purple-300"
|
| 363 |
+
>Policy Path</Label
|
| 364 |
+
>
|
| 365 |
+
<Input
|
| 366 |
+
id="policyPath"
|
| 367 |
+
bind:value={policyPath}
|
| 368 |
+
placeholder={modelConfig.defaultPolicyPath}
|
| 369 |
+
class="border-slate-300 bg-slate-50 text-slate-900 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100"
|
| 370 |
+
/>
|
|
|
|
|
|
|
| 371 |
</div>
|
| 372 |
|
| 373 |
+
<div class="space-y-2">
|
| 374 |
+
<Label for="cameraNames" class="text-purple-700 dark:text-purple-300"
|
| 375 |
+
>Camera Names</Label
|
| 376 |
+
>
|
| 377 |
+
<Input
|
| 378 |
+
id="cameraNames"
|
| 379 |
+
bind:value={cameraNames}
|
| 380 |
+
placeholder="front, wrist, overhead"
|
| 381 |
+
class="border-slate-300 bg-slate-50 text-slate-900 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100"
|
| 382 |
+
/>
|
| 383 |
+
<p class="text-xs text-slate-600 dark:text-slate-400">
|
| 384 |
+
Comma-separated camera names
|
| 385 |
+
</p>
|
| 386 |
+
</div>
|
| 387 |
+
|
| 388 |
+
{#if modelConfig.requiresLanguageInstruction}
|
| 389 |
<div class="space-y-2">
|
| 390 |
+
<Label for="languageInstruction" class="text-purple-700 dark:text-purple-300">
|
| 391 |
+
Language Instruction
|
| 392 |
+
</Label>
|
| 393 |
+
<Textarea
|
| 394 |
+
id="languageInstruction"
|
| 395 |
+
bind:value={languageInstruction}
|
| 396 |
+
placeholder="Pick up the red cup and place it on the table"
|
| 397 |
class="border-slate-300 bg-slate-50 text-slate-900 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100"
|
| 398 |
+
rows={3}
|
| 399 |
/>
|
| 400 |
<p class="text-xs text-slate-600 dark:text-slate-400">
|
| 401 |
+
Natural language instruction for the task (required for {modelConfig.label})
|
| 402 |
</p>
|
| 403 |
</div>
|
| 404 |
+
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
+
<div class="rounded-lg border border-green-300/30 bg-green-100/20 p-3 dark:border-green-500/30 dark:bg-green-900/20">
|
| 407 |
+
<div class="flex items-center gap-2">
|
| 408 |
+
<span class="icon-[mdi--folder] size-4 text-green-600 dark:text-green-400"></span>
|
| 409 |
+
<span class="text-sm font-medium text-green-700 dark:text-green-300">
|
| 410 |
+
Workspace: {workspaceId}
|
| 411 |
+
</span>
|
| 412 |
+
</div>
|
| 413 |
+
<p class="mt-1 text-xs text-green-600 dark:text-green-400">
|
| 414 |
+
Session will be created in the current workspace
|
| 415 |
+
</p>
|
| 416 |
</div>
|
| 417 |
|
| 418 |
+
<div
|
| 419 |
+
class="rounded-lg border border-slate-300 bg-slate-50 p-3 dark:border-slate-600 dark:bg-slate-800"
|
| 420 |
+
>
|
| 421 |
+
<div class="text-xs text-slate-600 dark:text-slate-400">
|
| 422 |
+
<span class="icon-[mdi--lightbulb] size-3"></span>
|
| 423 |
+
<strong>Tip:</strong> This will create a new {modelConfig.label} inference session with dedicated rooms for camera
|
| 424 |
inputs, joint inputs, and joint outputs in the inference server communication
|
| 425 |
system.
|
| 426 |
+
</div>
|
| 427 |
+
</div>
|
| 428 |
|
| 429 |
<Button
|
| 430 |
variant="default"
|
|
|
|
| 450 |
class="rounded border border-slate-300 bg-slate-100/30 p-2 text-xs text-slate-600 dark:border-slate-700 dark:bg-slate-800/30 dark:text-slate-500"
|
| 451 |
>
|
| 452 |
<span class="icon-[mdi--information] mr-1 size-3"></span>
|
| 453 |
+
Inference Sessions require a trained {modelConfig.label} and create dedicated communication rooms for video
|
| 454 |
inputs, robot joint states, and control outputs in the inference server system.
|
| 455 |
</div>
|
| 456 |
</div>
|
src/lib/components/3d/elements/compute/status/ComputeBoxUIKit.svelte
CHANGED
|
@@ -38,7 +38,7 @@
|
|
| 38 |
<StatusContent
|
| 39 |
title={compute.name}
|
| 40 |
subtitle={compute.statusInfo.statusText}
|
| 41 |
-
color="rgb(
|
| 42 |
variant="primary"
|
| 43 |
/>
|
| 44 |
</BaseStatusBox>
|
|
|
|
| 38 |
<StatusContent
|
| 39 |
title={compute.name}
|
| 40 |
subtitle={compute.statusInfo.statusText}
|
| 41 |
+
color="rgb(107, 33, 168)"
|
| 42 |
variant="primary"
|
| 43 |
/>
|
| 44 |
</BaseStatusBox>
|
src/lib/components/3d/elements/compute/status/ComputeInputBoxUIKit.svelte
CHANGED
|
@@ -43,7 +43,7 @@
|
|
| 43 |
<StatusContent
|
| 44 |
title={`${Object.keys(compute.inputConnections.cameras).length} Cameras`}
|
| 45 |
subtitle="Joint States"
|
| 46 |
-
color="rgb(
|
| 47 |
variant="primary"
|
| 48 |
/>
|
| 49 |
|
|
@@ -60,7 +60,7 @@
|
|
| 60 |
fontSize={12}
|
| 61 |
/>
|
| 62 |
|
| 63 |
-
<StatusContent title="Setup Required" color="rgb(
|
| 64 |
|
| 65 |
<StatusButton
|
| 66 |
text="Add Session"
|
|
|
|
| 43 |
<StatusContent
|
| 44 |
title={`${Object.keys(compute.inputConnections.cameras).length} Cameras`}
|
| 45 |
subtitle="Joint States"
|
| 46 |
+
color="rgb(21, 128, 61)"
|
| 47 |
variant="primary"
|
| 48 |
/>
|
| 49 |
|
|
|
|
| 60 |
fontSize={12}
|
| 61 |
/>
|
| 62 |
|
| 63 |
+
<StatusContent title="Setup Required" color="rgb(34, 197, 94)" variant="secondary" />
|
| 64 |
|
| 65 |
<StatusButton
|
| 66 |
text="Add Session"
|
src/lib/components/3d/elements/compute/status/ComputeOutputBoxUIKit.svelte
CHANGED
|
@@ -42,7 +42,7 @@
|
|
| 42 |
<StatusContent
|
| 43 |
title={compute.isRunning ? "Active" : "Ready"}
|
| 44 |
subtitle="Commands"
|
| 45 |
-
color="rgb(
|
| 46 |
variant="primary"
|
| 47 |
/>
|
| 48 |
|
|
@@ -64,7 +64,7 @@
|
|
| 64 |
|
| 65 |
<StatusContent
|
| 66 |
title={!compute.hasSession ? "Need Session" : "Configure"}
|
| 67 |
-
color="rgb(
|
| 68 |
variant="secondary"
|
| 69 |
/>
|
| 70 |
|
|
|
|
| 42 |
<StatusContent
|
| 43 |
title={compute.isRunning ? "Active" : "Ready"}
|
| 44 |
subtitle="Commands"
|
| 45 |
+
color="rgb(37, 99, 235)"
|
| 46 |
variant="primary"
|
| 47 |
/>
|
| 48 |
|
|
|
|
| 64 |
|
| 65 |
<StatusContent
|
| 66 |
title={!compute.hasSession ? "Need Session" : "Configure"}
|
| 67 |
+
color="rgb(59, 130, 246)"
|
| 68 |
variant="secondary"
|
| 69 |
/>
|
| 70 |
|
src/lib/components/3d/elements/compute/status/ComputeStatusBillboard.svelte
CHANGED
|
@@ -4,6 +4,8 @@
|
|
| 4 |
import { Root, Container } from "threlte-uikit";
|
| 5 |
import ComputeConnectionFlowBoxUIKit from "./ComputeConnectionFlowBoxUIKit.svelte";
|
| 6 |
import type { RemoteCompute } from "$lib/elements/compute//RemoteCompute.svelte";
|
|
|
|
|
|
|
| 7 |
|
| 8 |
interface Props {
|
| 9 |
compute: RemoteCompute;
|
|
@@ -11,6 +13,8 @@
|
|
| 11 |
onVideoInputBoxClick: (compute: RemoteCompute) => void;
|
| 12 |
onRobotInputBoxClick: (compute: RemoteCompute) => void;
|
| 13 |
onRobotOutputBoxClick: (compute: RemoteCompute) => void;
|
|
|
|
|
|
|
| 14 |
}
|
| 15 |
|
| 16 |
let {
|
|
@@ -18,10 +22,19 @@
|
|
| 18 |
visible = true,
|
| 19 |
onVideoInputBoxClick,
|
| 20 |
onRobotInputBoxClick,
|
| 21 |
-
onRobotOutputBoxClick
|
|
|
|
|
|
|
| 22 |
}: Props = $props();
|
| 23 |
|
| 24 |
interactivity();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
</script>
|
| 26 |
|
| 27 |
<T.Group
|
|
@@ -31,7 +44,6 @@
|
|
| 31 |
rotation={[-Math.PI / 2, 0, 0]}
|
| 32 |
scale={[0.1, 0.1, 0.1]}
|
| 33 |
pointerEvents="listener"
|
| 34 |
-
{visible}
|
| 35 |
>
|
| 36 |
<Billboard>
|
| 37 |
<Root name={`compute-status-billboard-${compute.id}`}>
|
|
@@ -41,6 +53,10 @@
|
|
| 41 |
alignItems="center"
|
| 42 |
justifyContent="center"
|
| 43 |
padding={20}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
>
|
| 45 |
<ComputeConnectionFlowBoxUIKit
|
| 46 |
{compute}
|
|
|
|
| 4 |
import { Root, Container } from "threlte-uikit";
|
| 5 |
import ComputeConnectionFlowBoxUIKit from "./ComputeConnectionFlowBoxUIKit.svelte";
|
| 6 |
import type { RemoteCompute } from "$lib/elements/compute//RemoteCompute.svelte";
|
| 7 |
+
import { Tween } from "svelte/motion";
|
| 8 |
+
import { cubicOut } from "svelte/easing";
|
| 9 |
|
| 10 |
interface Props {
|
| 11 |
compute: RemoteCompute;
|
|
|
|
| 13 |
onVideoInputBoxClick: (compute: RemoteCompute) => void;
|
| 14 |
onRobotInputBoxClick: (compute: RemoteCompute) => void;
|
| 15 |
onRobotOutputBoxClick: (compute: RemoteCompute) => void;
|
| 16 |
+
duration?: number;
|
| 17 |
+
delay?: number;
|
| 18 |
}
|
| 19 |
|
| 20 |
let {
|
|
|
|
| 22 |
visible = true,
|
| 23 |
onVideoInputBoxClick,
|
| 24 |
onRobotInputBoxClick,
|
| 25 |
+
onRobotOutputBoxClick,
|
| 26 |
+
duration = 100,
|
| 27 |
+
delay = 0
|
| 28 |
}: Props = $props();
|
| 29 |
|
| 30 |
interactivity();
|
| 31 |
+
|
| 32 |
+
const tweenedScale = Tween.of(() => {
|
| 33 |
+
return visible ? 1 : 0;
|
| 34 |
+
}, { duration: duration, easing: cubicOut, delay: delay });
|
| 35 |
+
const tweenedOpacity = Tween.of(() => {
|
| 36 |
+
return visible ? 1 : 0;
|
| 37 |
+
}, { duration: duration, easing: cubicOut, delay: delay });
|
| 38 |
</script>
|
| 39 |
|
| 40 |
<T.Group
|
|
|
|
| 44 |
rotation={[-Math.PI / 2, 0, 0]}
|
| 45 |
scale={[0.1, 0.1, 0.1]}
|
| 46 |
pointerEvents="listener"
|
|
|
|
| 47 |
>
|
| 48 |
<Billboard>
|
| 49 |
<Root name={`compute-status-billboard-${compute.id}`}>
|
|
|
|
| 53 |
alignItems="center"
|
| 54 |
justifyContent="center"
|
| 55 |
padding={20}
|
| 56 |
+
transformScaleX={tweenedScale.current}
|
| 57 |
+
transformScaleY={tweenedScale.current}
|
| 58 |
+
transformScaleZ={tweenedScale.current}
|
| 59 |
+
opacity={tweenedOpacity.current}
|
| 60 |
>
|
| 61 |
<ComputeConnectionFlowBoxUIKit
|
| 62 |
{compute}
|
src/lib/components/3d/elements/compute/status/RobotInputBoxUIKit.svelte
CHANGED
|
@@ -41,7 +41,7 @@
|
|
| 41 |
<StatusContent
|
| 42 |
title="Joint States"
|
| 43 |
subtitle="6 DOF Robot"
|
| 44 |
-
color="rgb(
|
| 45 |
variant="primary"
|
| 46 |
/>
|
| 47 |
|
|
@@ -57,7 +57,7 @@
|
|
| 57 |
fontSize={11}
|
| 58 |
/>
|
| 59 |
|
| 60 |
-
<StatusContent title="Setup Robot" color="rgb(
|
| 61 |
|
| 62 |
<StatusButton
|
| 63 |
icon={ICON["icon-[mdi--plus]"].svg}
|
|
|
|
| 41 |
<StatusContent
|
| 42 |
title="Joint States"
|
| 43 |
subtitle="6 DOF Robot"
|
| 44 |
+
color="rgb(180, 83, 9)"
|
| 45 |
variant="primary"
|
| 46 |
/>
|
| 47 |
|
|
|
|
| 57 |
fontSize={11}
|
| 58 |
/>
|
| 59 |
|
| 60 |
+
<StatusContent title="Setup Robot" color="rgb(245, 158, 11)" variant="secondary" />
|
| 61 |
|
| 62 |
<StatusButton
|
| 63 |
icon={ICON["icon-[mdi--plus]"].svg}
|
src/lib/components/3d/elements/compute/status/RobotOutputBoxUIKit.svelte
CHANGED
|
@@ -32,7 +32,7 @@
|
|
| 32 |
<StatusContent
|
| 33 |
title={compute.isRunning ? "AI Commands Active" : "Session Ready"}
|
| 34 |
subtitle="Motor Control"
|
| 35 |
-
color="rgb(
|
| 36 |
variant="primary"
|
| 37 |
/>
|
| 38 |
|
|
@@ -55,7 +55,7 @@
|
|
| 55 |
|
| 56 |
<StatusContent
|
| 57 |
title={!compute.hasSession ? 'Need Session' : 'Click to Configure'}
|
| 58 |
-
color="rgb(
|
| 59 |
variant="secondary"
|
| 60 |
/>
|
| 61 |
|
|
|
|
| 32 |
<StatusContent
|
| 33 |
title={compute.isRunning ? "AI Commands Active" : "Session Ready"}
|
| 34 |
subtitle="Motor Control"
|
| 35 |
+
color="rgb(37, 99, 235)"
|
| 36 |
variant="primary"
|
| 37 |
/>
|
| 38 |
|
|
|
|
| 55 |
|
| 56 |
<StatusContent
|
| 57 |
title={!compute.hasSession ? 'Need Session' : 'Click to Configure'}
|
| 58 |
+
color="rgb(59, 130, 246)"
|
| 59 |
variant="secondary"
|
| 60 |
/>
|
| 61 |
|
src/lib/components/3d/elements/compute/status/VideoInputBoxUIKit.svelte
CHANGED
|
@@ -35,7 +35,7 @@
|
|
| 35 |
<!-- Camera Streams -->
|
| 36 |
<StatusContent
|
| 37 |
title={`${Object.keys(compute.inputConnections.cameras).length} Cameras`}
|
| 38 |
-
color="rgb(
|
| 39 |
variant="primary"
|
| 40 |
/>
|
| 41 |
|
|
@@ -53,7 +53,7 @@
|
|
| 53 |
|
| 54 |
<StatusContent
|
| 55 |
title="Setup Video"
|
| 56 |
-
color="rgb(
|
| 57 |
variant="secondary"
|
| 58 |
/>
|
| 59 |
|
|
|
|
| 35 |
<!-- Camera Streams -->
|
| 36 |
<StatusContent
|
| 37 |
title={`${Object.keys(compute.inputConnections.cameras).length} Cameras`}
|
| 38 |
+
color="rgb(21, 128, 61)"
|
| 39 |
variant="primary"
|
| 40 |
/>
|
| 41 |
|
|
|
|
| 53 |
|
| 54 |
<StatusContent
|
| 55 |
title="Setup Video"
|
| 56 |
+
color="rgb(34, 197, 94)"
|
| 57 |
variant="secondary"
|
| 58 |
/>
|
| 59 |
|
src/lib/components/3d/elements/robot/RobotGridItem.svelte
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
import RobotStatusBillboard from "@/components/3d/elements/robot/status/RobotStatusBillboard.svelte";
|
| 7 |
import type { Robot } from "$lib/elements/robot/Robot.svelte.js";
|
| 8 |
import { createUrdfRobot } from "$lib/elements/robot/createRobot.svelte";
|
| 9 |
-
import {
|
| 10 |
import type { RobotUrdfConfig } from "$lib/types/urdf";
|
| 11 |
import { onMount } from "svelte";
|
| 12 |
import type IUrdfRobot from "@/components/3d/elements/robot/URDF/interfaces/IUrdfRobot.js";
|
|
@@ -66,6 +66,7 @@
|
|
| 66 |
// Batch update all joints that have changed (or all joints on initial sync)
|
| 67 |
let updatedCount = 0;
|
| 68 |
robot.jointArray.forEach((joint) => {
|
|
|
|
| 69 |
if (isInitialSync || Math.abs((lastJointValues[joint.name] || 0) - joint.value) > threshold) {
|
| 70 |
lastJointValues[joint.name] = joint.value;
|
| 71 |
const urdfJoint = findUrdfJoint(urdfRobotState, joint.name);
|
|
@@ -92,7 +93,7 @@
|
|
| 92 |
});
|
| 93 |
});
|
| 94 |
|
| 95 |
-
function findUrdfJoint(robot:
|
| 96 |
// Search through the robot's joints array
|
| 97 |
if (robot.joints && Array.isArray(robot.joints)) {
|
| 98 |
for (const joint of robot.joints) {
|
|
@@ -105,7 +106,7 @@
|
|
| 105 |
}
|
| 106 |
|
| 107 |
const { onPointerEnter, onPointerLeave, hovering } = useCursor();
|
| 108 |
-
|
| 109 |
|
| 110 |
let isToggled = $state(false);
|
| 111 |
|
|
@@ -123,7 +124,13 @@
|
|
| 123 |
scale={[10, 10, 10]}
|
| 124 |
rotation={[-Math.PI / 2, 0, 0]}
|
| 125 |
>
|
| 126 |
-
<T.Group onpointerenter={
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
{#if urdfRobotState}
|
| 128 |
{#each getRootLinks(urdfRobotState) as link}
|
| 129 |
<UrdfLink
|
|
@@ -144,7 +151,6 @@
|
|
| 144 |
nameHeight={0.1}
|
| 145 |
showLine={$hovering || isToggled}
|
| 146 |
opacity={1}
|
| 147 |
-
isInteractive={false}
|
| 148 |
/>
|
| 149 |
{/each}
|
| 150 |
{:else}
|
|
|
|
| 6 |
import RobotStatusBillboard from "@/components/3d/elements/robot/status/RobotStatusBillboard.svelte";
|
| 7 |
import type { Robot } from "$lib/elements/robot/Robot.svelte.js";
|
| 8 |
import { createUrdfRobot } from "$lib/elements/robot/createRobot.svelte";
|
| 9 |
+
import { type IntersectionEvent, useCursor } from "@threlte/extras";
|
| 10 |
import type { RobotUrdfConfig } from "$lib/types/urdf";
|
| 11 |
import { onMount } from "svelte";
|
| 12 |
import type IUrdfRobot from "@/components/3d/elements/robot/URDF/interfaces/IUrdfRobot.js";
|
|
|
|
| 66 |
// Batch update all joints that have changed (or all joints on initial sync)
|
| 67 |
let updatedCount = 0;
|
| 68 |
robot.jointArray.forEach((joint) => {
|
| 69 |
+
if (!urdfRobotState) return;
|
| 70 |
if (isInitialSync || Math.abs((lastJointValues[joint.name] || 0) - joint.value) > threshold) {
|
| 71 |
lastJointValues[joint.name] = joint.value;
|
| 72 |
const urdfJoint = findUrdfJoint(urdfRobotState, joint.name);
|
|
|
|
| 93 |
});
|
| 94 |
});
|
| 95 |
|
| 96 |
+
function findUrdfJoint(robot: IUrdfRobot, jointName: string): any {
|
| 97 |
// Search through the robot's joints array
|
| 98 |
if (robot.joints && Array.isArray(robot.joints)) {
|
| 99 |
for (const joint of robot.joints) {
|
|
|
|
| 106 |
}
|
| 107 |
|
| 108 |
const { onPointerEnter, onPointerLeave, hovering } = useCursor();
|
| 109 |
+
|
| 110 |
|
| 111 |
let isToggled = $state(false);
|
| 112 |
|
|
|
|
| 124 |
scale={[10, 10, 10]}
|
| 125 |
rotation={[-Math.PI / 2, 0, 0]}
|
| 126 |
>
|
| 127 |
+
<T.Group onpointerenter={(event) => {
|
| 128 |
+
event.stopPropagation();
|
| 129 |
+
onPointerEnter();
|
| 130 |
+
}} onpointerleave={(event) => {
|
| 131 |
+
event.stopPropagation();
|
| 132 |
+
onPointerLeave();
|
| 133 |
+
}} onclick={handleClick}>
|
| 134 |
{#if urdfRobotState}
|
| 135 |
{#each getRootLinks(urdfRobotState) as link}
|
| 136 |
<UrdfLink
|
|
|
|
| 151 |
nameHeight={0.1}
|
| 152 |
showLine={$hovering || isToggled}
|
| 153 |
opacity={1}
|
|
|
|
| 154 |
/>
|
| 155 |
{/each}
|
| 156 |
{:else}
|
src/lib/components/3d/elements/robot/Robots.svelte
CHANGED
|
@@ -7,6 +7,7 @@
|
|
| 7 |
import type { Robot } from "$lib/elements/robot/Robot.svelte.js";
|
| 8 |
import { generateName } from "$lib/utils/generateName";
|
| 9 |
import RobotGridItem from "@/components/3d/elements/robot/RobotGridItem.svelte";
|
|
|
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
workspaceId: string;
|
|
@@ -64,6 +65,11 @@
|
|
| 64 |
console.error("❌ Error during cleanup:", error);
|
| 65 |
});
|
| 66 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
</script>
|
| 68 |
|
| 69 |
{#each robotManager.robots as robot (robot.id)}
|
|
|
|
| 7 |
import type { Robot } from "$lib/elements/robot/Robot.svelte.js";
|
| 8 |
import { generateName } from "$lib/utils/generateName";
|
| 9 |
import RobotGridItem from "@/components/3d/elements/robot/RobotGridItem.svelte";
|
| 10 |
+
import { interactivity } from "@threlte/extras";
|
| 11 |
|
| 12 |
interface Props {
|
| 13 |
workspaceId: string;
|
|
|
|
| 65 |
console.error("❌ Error during cleanup:", error);
|
| 66 |
});
|
| 67 |
});
|
| 68 |
+
interactivity({
|
| 69 |
+
filter: (hits, state) => {
|
| 70 |
+
return hits.slice(0, 1);
|
| 71 |
+
}
|
| 72 |
+
});
|
| 73 |
</script>
|
| 74 |
|
| 75 |
{#each robotManager.robots as robot (robot.id)}
|
src/lib/components/3d/elements/robot/URDF/primitives/UrdfJoint.svelte
CHANGED
|
@@ -145,7 +145,6 @@
|
|
| 145 |
{nameHeight}
|
| 146 |
{showLine}
|
| 147 |
opacity={1}
|
| 148 |
-
isInteractive={true}
|
| 149 |
/>
|
| 150 |
{/if}
|
| 151 |
|
|
|
|
| 145 |
{nameHeight}
|
| 146 |
{showLine}
|
| 147 |
opacity={1}
|
|
|
|
| 148 |
/>
|
| 149 |
{/if}
|
| 150 |
|
src/lib/components/3d/elements/robot/URDF/primitives/UrdfLink.svelte
CHANGED
|
@@ -28,7 +28,6 @@
|
|
| 28 |
nameHeight?: number;
|
| 29 |
showLine?: boolean;
|
| 30 |
opacity?: number;
|
| 31 |
-
isInteractive?: boolean;
|
| 32 |
}
|
| 33 |
|
| 34 |
let {
|
|
@@ -49,7 +48,6 @@
|
|
| 49 |
nameHeight = 0.1,
|
| 50 |
showLine = true,
|
| 51 |
opacity = 0.7,
|
| 52 |
-
isInteractive = false
|
| 53 |
}: Props = $props();
|
| 54 |
|
| 55 |
let showPointCloud = false;
|
|
@@ -85,7 +83,6 @@
|
|
| 85 |
{jointIndicatorColor}
|
| 86 |
{showLine}
|
| 87 |
{opacity}
|
| 88 |
-
{isInteractive}
|
| 89 |
{showVisual}
|
| 90 |
{showCollision}
|
| 91 |
{visualOpacity}
|
|
|
|
| 28 |
nameHeight?: number;
|
| 29 |
showLine?: boolean;
|
| 30 |
opacity?: number;
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
let {
|
|
|
|
| 48 |
nameHeight = 0.1,
|
| 49 |
showLine = true,
|
| 50 |
opacity = 0.7,
|
|
|
|
| 51 |
}: Props = $props();
|
| 52 |
|
| 53 |
let showPointCloud = false;
|
|
|
|
| 83 |
{jointIndicatorColor}
|
| 84 |
{showLine}
|
| 85 |
{opacity}
|
|
|
|
| 86 |
{showVisual}
|
| 87 |
{showCollision}
|
| 88 |
{visualOpacity}
|
src/lib/components/3d/elements/robot/URDF/primitives/UrdfThree.svelte
CHANGED
|
@@ -62,7 +62,6 @@
|
|
| 62 |
{nameHeight}
|
| 63 |
showLine={false}
|
| 64 |
opacity={1}
|
| 65 |
-
isInteractive={false}
|
| 66 |
/>
|
| 67 |
{/each}
|
| 68 |
</T.Group>
|
|
|
|
| 62 |
{nameHeight}
|
| 63 |
showLine={false}
|
| 64 |
opacity={1}
|
|
|
|
| 65 |
/>
|
| 66 |
{/each}
|
| 67 |
</T.Group>
|
src/lib/components/3d/elements/robot/modal/InputConnectionModal.svelte
CHANGED
|
@@ -9,6 +9,7 @@
|
|
| 9 |
import type { Robot } from "$lib/elements/robot/Robot.svelte.js";
|
| 10 |
import { robotManager } from "$lib/elements/robot/RobotManager.svelte.js";
|
| 11 |
import USBCalibrationPanel from "$lib/elements/robot/calibration/USBCalibrationPanel.svelte";
|
|
|
|
| 12 |
|
| 13 |
interface Props {
|
| 14 |
workspaceId: string;
|
|
@@ -181,9 +182,9 @@
|
|
| 181 |
showUSBCalibration = false;
|
| 182 |
pendingUSBConnection = null;
|
| 183 |
isConnecting = false;
|
| 184 |
-
|
| 185 |
// Clean up the uncalibrated USB consumer
|
| 186 |
-
robot.removeConsumer().catch(err => {
|
| 187 |
console.error("Failed to clean up USB consumer after calibration cancel:", err);
|
| 188 |
});
|
| 189 |
}
|
|
@@ -261,9 +262,7 @@
|
|
| 261 |
onCancel={onCalibrationCancel}
|
| 262 |
/>
|
| 263 |
{:else}
|
| 264 |
-
<div class="text-center text-slate-400">
|
| 265 |
-
No USB drivers require calibration
|
| 266 |
-
</div>
|
| 267 |
{/each}
|
| 268 |
</Card.Content>
|
| 269 |
</Card.Root>
|
|
@@ -370,13 +369,14 @@
|
|
| 370 |
<div class="flex items-center justify-between">
|
| 371 |
<div>
|
| 372 |
<Card.Title
|
| 373 |
-
class="flex items-center gap-2 text-base text-purple-700 dark:text-purple-200"
|
| 374 |
>
|
| 375 |
<span class="icon-[mdi--cloud-sync] size-4"></span>
|
| 376 |
-
Remote
|
| 377 |
</Card.Title>
|
| 378 |
<Card.Description class="text-xs text-purple-600/70 dark:text-purple-300/70">
|
| 379 |
-
Receive commands from AI systems, remote users, or other software
|
|
|
|
| 380 |
</Card.Description>
|
| 381 |
</div>
|
| 382 |
<Button
|
|
@@ -499,11 +499,36 @@
|
|
| 499 |
>
|
| 500 |
{room.id}
|
| 501 |
</p>
|
| 502 |
-
<div class="flex gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 503 |
<span>{room.has_producer ? "📤 Has Output" : "📥 No Output"}</span>
|
| 504 |
<span>👥 {room.participants?.total || 0} users</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
</div>
|
|
|
|
| 506 |
</div>
|
|
|
|
| 507 |
<Button
|
| 508 |
variant="secondary"
|
| 509 |
size="sm"
|
|
@@ -514,7 +539,7 @@
|
|
| 514 |
disabled={isConnecting || robot.hasConsumer}
|
| 515 |
class="h-6 shrink-0 bg-purple-500 px-2 text-xs hover:bg-purple-600 disabled:opacity-50 dark:bg-purple-600 dark:hover:bg-purple-700"
|
| 516 |
>
|
| 517 |
-
<span class="icon-[mdi--login]
|
| 518 |
Join as Input
|
| 519 |
</Button>
|
| 520 |
</div>
|
|
@@ -532,18 +557,6 @@
|
|
| 532 |
{/if}
|
| 533 |
</Card.Content>
|
| 534 |
</Card.Root>
|
| 535 |
-
|
| 536 |
-
<!-- Help Information -->
|
| 537 |
-
<Alert.Root
|
| 538 |
-
class="border-slate-300 bg-slate-100/30 dark:border-slate-700 dark:bg-slate-800/30"
|
| 539 |
-
>
|
| 540 |
-
<span class="icon-[mdi--help-circle] size-4 text-slate-600 dark:text-slate-400"></span>
|
| 541 |
-
<Alert.Title class="text-slate-700 dark:text-slate-300">Input Sources</Alert.Title>
|
| 542 |
-
<Alert.Description class="text-xs text-slate-600 dark:text-slate-400">
|
| 543 |
-
<strong>USB:</strong> Read physical movements • <strong>Remote:</strong> Receive network
|
| 544 |
-
commands • Only one active at a time
|
| 545 |
-
</Alert.Description>
|
| 546 |
-
</Alert.Root>
|
| 547 |
{/if}
|
| 548 |
</div>
|
| 549 |
</div>
|
|
|
|
| 9 |
import type { Robot } from "$lib/elements/robot/Robot.svelte.js";
|
| 10 |
import { robotManager } from "$lib/elements/robot/RobotManager.svelte.js";
|
| 11 |
import USBCalibrationPanel from "$lib/elements/robot/calibration/USBCalibrationPanel.svelte";
|
| 12 |
+
import { settings } from "@/runes/settings.svelte";
|
| 13 |
|
| 14 |
interface Props {
|
| 15 |
workspaceId: string;
|
|
|
|
| 182 |
showUSBCalibration = false;
|
| 183 |
pendingUSBConnection = null;
|
| 184 |
isConnecting = false;
|
| 185 |
+
|
| 186 |
// Clean up the uncalibrated USB consumer
|
| 187 |
+
robot.removeConsumer().catch((err) => {
|
| 188 |
console.error("Failed to clean up USB consumer after calibration cancel:", err);
|
| 189 |
});
|
| 190 |
}
|
|
|
|
| 262 |
onCancel={onCalibrationCancel}
|
| 263 |
/>
|
| 264 |
{:else}
|
| 265 |
+
<div class="text-center text-slate-400">No USB drivers require calibration</div>
|
|
|
|
|
|
|
| 266 |
{/each}
|
| 267 |
</Card.Content>
|
| 268 |
</Card.Root>
|
|
|
|
| 369 |
<div class="flex items-center justify-between">
|
| 370 |
<div>
|
| 371 |
<Card.Title
|
| 372 |
+
class="flex items-center gap-2 pb-1 text-base text-purple-700 dark:text-purple-200"
|
| 373 |
>
|
| 374 |
<span class="icon-[mdi--cloud-sync] size-4"></span>
|
| 375 |
+
Remote Control
|
| 376 |
</Card.Title>
|
| 377 |
<Card.Description class="text-xs text-purple-600/70 dark:text-purple-300/70">
|
| 378 |
+
Receive commands from AI systems, remote users, or other software from anywhere
|
| 379 |
+
in the world
|
| 380 |
</Card.Description>
|
| 381 |
</div>
|
| 382 |
<Button
|
|
|
|
| 499 |
>
|
| 500 |
{room.id}
|
| 501 |
</p>
|
| 502 |
+
<div class="flex items-center gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 503 |
<span>{room.has_producer ? "📤 Has Output" : "📥 No Output"}</span>
|
| 504 |
<span>👥 {room.participants?.total || 0} users</span>
|
| 505 |
+
<!-- Monitoring links -->
|
| 506 |
+
<div class="flex gap-1">
|
| 507 |
+
<a
|
| 508 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/robotics/consumer?room=${room.id}`}
|
| 509 |
+
target="_blank"
|
| 510 |
+
rel="noopener noreferrer"
|
| 511 |
+
class="inline-flex items-center gap-1 rounded bg-blue-500/10 px-1.5 py-0.5 text-xs text-blue-600 hover:bg-blue-500/20 dark:bg-blue-400/10 dark:text-blue-400 dark:hover:bg-blue-400/20"
|
| 512 |
+
title="Monitor Consumer"
|
| 513 |
+
>
|
| 514 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 515 |
+
Consumer
|
| 516 |
+
</a>
|
| 517 |
+
<a
|
| 518 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/robotics/producer?room=${room.id}`}
|
| 519 |
+
target="_blank"
|
| 520 |
+
rel="noopener noreferrer"
|
| 521 |
+
class="inline-flex items-center gap-1 rounded bg-green-500/10 px-1.5 py-0.5 text-xs text-green-600 hover:bg-green-500/20 dark:bg-green-400/10 dark:text-green-400 dark:hover:bg-green-400/20"
|
| 522 |
+
title="Monitor Producer"
|
| 523 |
+
>
|
| 524 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 525 |
+
Producer
|
| 526 |
+
</a>
|
| 527 |
+
</div>
|
| 528 |
</div>
|
| 529 |
+
|
| 530 |
</div>
|
| 531 |
+
|
| 532 |
<Button
|
| 533 |
variant="secondary"
|
| 534 |
size="sm"
|
|
|
|
| 539 |
disabled={isConnecting || robot.hasConsumer}
|
| 540 |
class="h-6 shrink-0 bg-purple-500 px-2 text-xs hover:bg-purple-600 disabled:opacity-50 dark:bg-purple-600 dark:hover:bg-purple-700"
|
| 541 |
>
|
| 542 |
+
<span class="icon-[mdi--login] size-5 h-full w-full"></span>
|
| 543 |
Join as Input
|
| 544 |
</Button>
|
| 545 |
</div>
|
|
|
|
| 557 |
{/if}
|
| 558 |
</Card.Content>
|
| 559 |
</Card.Root>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
{/if}
|
| 561 |
</div>
|
| 562 |
</div>
|
src/lib/components/3d/elements/robot/modal/OutputConnectionModal.svelte
CHANGED
|
@@ -340,13 +340,13 @@
|
|
| 340 |
<div class="flex items-center justify-between">
|
| 341 |
<div>
|
| 342 |
<Card.Title
|
| 343 |
-
class="flex items-center gap-2 text-base text-orange-700 dark:text-orange-200"
|
| 344 |
>
|
| 345 |
<span class="icon-[mdi--cloud-sync] size-4"></span>
|
| 346 |
-
Remote
|
| 347 |
</Card.Title>
|
| 348 |
<Card.Description class="text-xs text-orange-600/70 dark:text-orange-300/70">
|
| 349 |
-
Broadcast robot movements to remote systems
|
| 350 |
</Card.Description>
|
| 351 |
</div>
|
| 352 |
<Button
|
|
@@ -441,9 +441,32 @@
|
|
| 441 |
>
|
| 442 |
{room.id}
|
| 443 |
</p>
|
| 444 |
-
<div class="flex gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 445 |
<span>{room.has_producer ? "🔴 Occupied" : "🟢 Available"}</span>
|
| 446 |
<span>👥 {room.participants?.total || 0} users</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
</div>
|
| 448 |
</div>
|
| 449 |
{#if !room.has_producer}
|
|
@@ -525,18 +548,6 @@
|
|
| 525 |
</Card.Content>
|
| 526 |
</Card.Root>
|
| 527 |
{/if}
|
| 528 |
-
|
| 529 |
-
<!-- Help Information -->
|
| 530 |
-
<Alert.Root
|
| 531 |
-
class="border-slate-300 bg-slate-100/30 dark:border-slate-700 dark:bg-slate-800/30"
|
| 532 |
-
>
|
| 533 |
-
<span class="icon-[mdi--help-circle] size-4 text-slate-600 dark:text-slate-400"></span>
|
| 534 |
-
<Alert.Title class="text-slate-700 dark:text-slate-300">Output Sources</Alert.Title>
|
| 535 |
-
<Alert.Description class="text-xs text-slate-600 dark:text-slate-400">
|
| 536 |
-
<strong>USB:</strong> Control physical hardware • <strong>Remote:</strong> Broadcast to
|
| 537 |
-
network • Multiple outputs can be active
|
| 538 |
-
</Alert.Description>
|
| 539 |
-
</Alert.Root>
|
| 540 |
{/if}
|
| 541 |
</div>
|
| 542 |
</div>
|
|
|
|
| 340 |
<div class="flex items-center justify-between">
|
| 341 |
<div>
|
| 342 |
<Card.Title
|
| 343 |
+
class="flex items-center gap-2 text-base text-orange-700 dark:text-orange-200 pb-1"
|
| 344 |
>
|
| 345 |
<span class="icon-[mdi--cloud-sync] size-4"></span>
|
| 346 |
+
Remote Control
|
| 347 |
</Card.Title>
|
| 348 |
<Card.Description class="text-xs text-orange-600/70 dark:text-orange-300/70">
|
| 349 |
+
Broadcast robot movements to remote robots or AI systems from anywhere in the world
|
| 350 |
</Card.Description>
|
| 351 |
</div>
|
| 352 |
<Button
|
|
|
|
| 441 |
>
|
| 442 |
{room.id}
|
| 443 |
</p>
|
| 444 |
+
<div class="flex items-center gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 445 |
<span>{room.has_producer ? "🔴 Occupied" : "🟢 Available"}</span>
|
| 446 |
<span>👥 {room.participants?.total || 0} users</span>
|
| 447 |
+
<!-- Monitoring links -->
|
| 448 |
+
<div class="flex gap-1">
|
| 449 |
+
<a
|
| 450 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/robotics/consumer?room=${room.id}`}
|
| 451 |
+
target="_blank"
|
| 452 |
+
rel="noopener noreferrer"
|
| 453 |
+
class="inline-flex items-center gap-1 rounded bg-blue-500/10 px-1.5 py-0.5 text-xs text-blue-600 hover:bg-blue-500/20 dark:bg-blue-400/10 dark:text-blue-400 dark:hover:bg-blue-400/20"
|
| 454 |
+
title="Monitor Consumer"
|
| 455 |
+
>
|
| 456 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 457 |
+
Consumer
|
| 458 |
+
</a>
|
| 459 |
+
<a
|
| 460 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/robotics/producer?room=${room.id}`}
|
| 461 |
+
target="_blank"
|
| 462 |
+
rel="noopener noreferrer"
|
| 463 |
+
class="inline-flex items-center gap-1 rounded bg-green-500/10 px-1.5 py-0.5 text-xs text-green-600 hover:bg-green-500/20 dark:bg-green-400/10 dark:text-green-400 dark:hover:bg-green-400/20"
|
| 464 |
+
title="Monitor Producer"
|
| 465 |
+
>
|
| 466 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 467 |
+
Producer
|
| 468 |
+
</a>
|
| 469 |
+
</div>
|
| 470 |
</div>
|
| 471 |
</div>
|
| 472 |
{#if !room.has_producer}
|
|
|
|
| 548 |
</Card.Content>
|
| 549 |
</Card.Root>
|
| 550 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
{/if}
|
| 552 |
</div>
|
| 553 |
</div>
|
src/lib/components/3d/elements/robot/status/ConnectionFlowBoxUIkit.svelte
CHANGED
|
@@ -5,21 +5,41 @@
|
|
| 5 |
import InputBoxUIKit from "./InputBoxUIKit.svelte";
|
| 6 |
import RobotBoxUIKit from "./RobotBoxUIKit.svelte";
|
| 7 |
import OutputBoxUIKit from "./OutputBoxUIKit.svelte";
|
|
|
|
|
|
|
| 8 |
|
| 9 |
interface Props {
|
|
|
|
| 10 |
robot: Robot;
|
| 11 |
onInputBoxClick: (robot: Robot) => void;
|
| 12 |
onRobotBoxClick: (robot: Robot) => void;
|
| 13 |
onOutputBoxClick: (robot: Robot) => void;
|
|
|
|
|
|
|
| 14 |
}
|
| 15 |
|
| 16 |
-
let { robot, onInputBoxClick, onRobotBoxClick, onOutputBoxClick }: Props = $props();
|
| 17 |
|
| 18 |
const inputColor = "rgb(34, 197, 94)";
|
| 19 |
const outputColor = "rgb(59, 130, 246)";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
</script>
|
| 21 |
|
| 22 |
-
<Container
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
<!-- Input Box -->
|
| 24 |
<InputBoxUIKit {robot} {onInputBoxClick} />
|
| 25 |
|
|
|
|
| 5 |
import InputBoxUIKit from "./InputBoxUIKit.svelte";
|
| 6 |
import RobotBoxUIKit from "./RobotBoxUIKit.svelte";
|
| 7 |
import OutputBoxUIKit from "./OutputBoxUIKit.svelte";
|
| 8 |
+
import { Tween } from "svelte/motion";
|
| 9 |
+
import { cubicOut } from "svelte/easing";
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
+
visible: boolean;
|
| 13 |
robot: Robot;
|
| 14 |
onInputBoxClick: (robot: Robot) => void;
|
| 15 |
onRobotBoxClick: (robot: Robot) => void;
|
| 16 |
onOutputBoxClick: (robot: Robot) => void;
|
| 17 |
+
duration?: number;
|
| 18 |
+
delay?: number;
|
| 19 |
}
|
| 20 |
|
| 21 |
+
let { visible, robot, onInputBoxClick, onRobotBoxClick, onOutputBoxClick, duration = 100, delay = 0 }: Props = $props();
|
| 22 |
|
| 23 |
const inputColor = "rgb(34, 197, 94)";
|
| 24 |
const outputColor = "rgb(59, 130, 246)";
|
| 25 |
+
|
| 26 |
+
const tweenedScale = Tween.of(() => {
|
| 27 |
+
return visible ? 1 : 0;
|
| 28 |
+
}, { duration: duration, easing: cubicOut, delay: delay });
|
| 29 |
+
const tweenedOpacity = Tween.of(() => {
|
| 30 |
+
return visible ? 1 : 0;
|
| 31 |
+
}, { duration: duration, easing: cubicOut, delay: delay });
|
| 32 |
</script>
|
| 33 |
|
| 34 |
+
<Container
|
| 35 |
+
flexDirection="row"
|
| 36 |
+
alignItems="center"
|
| 37 |
+
gap={12}
|
| 38 |
+
transformScaleX={tweenedScale.current}
|
| 39 |
+
transformScaleY={tweenedScale.current}
|
| 40 |
+
transformScaleZ={tweenedScale.current}
|
| 41 |
+
opacity={tweenedOpacity.current}
|
| 42 |
+
>
|
| 43 |
<!-- Input Box -->
|
| 44 |
<InputBoxUIKit {robot} {onInputBoxClick} />
|
| 45 |
|
src/lib/components/3d/elements/robot/status/OutputBoxUIKit.svelte
CHANGED
|
@@ -41,7 +41,7 @@
|
|
| 41 |
<!-- Outputs Count -->
|
| 42 |
<StatusContent
|
| 43 |
title={`${robot.outputDriverCount} Outputs Active`}
|
| 44 |
-
color=
|
| 45 |
variant="primary"
|
| 46 |
/>
|
| 47 |
|
|
|
|
| 41 |
<!-- Outputs Count -->
|
| 42 |
<StatusContent
|
| 43 |
title={`${robot.outputDriverCount} Outputs Active`}
|
| 44 |
+
color={outputColor}
|
| 45 |
variant="primary"
|
| 46 |
/>
|
| 47 |
|
src/lib/components/3d/elements/robot/status/RobotStatusBillboard.svelte
CHANGED
|
@@ -26,7 +26,7 @@
|
|
| 26 |
interactivity();
|
| 27 |
</script>
|
| 28 |
|
| 29 |
-
{#if visible}
|
| 30 |
<T.Group
|
| 31 |
position.z={0.35}
|
| 32 |
rotation={[Math.PI / 2, 0, 0]}
|
|
@@ -43,7 +43,13 @@
|
|
| 43 |
justifyContent="center"
|
| 44 |
padding={20}
|
| 45 |
>
|
| 46 |
-
<ConnectionFlowBoxUIkit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</Container>
|
| 48 |
</Root>
|
| 49 |
</Billboard>
|
|
@@ -81,4 +87,4 @@
|
|
| 81 |
</HTML>
|
| 82 |
</Billboard> -->
|
| 83 |
</T.Group>
|
| 84 |
-
{/if}
|
|
|
|
| 26 |
interactivity();
|
| 27 |
</script>
|
| 28 |
|
| 29 |
+
<!-- {#if visible} -->
|
| 30 |
<T.Group
|
| 31 |
position.z={0.35}
|
| 32 |
rotation={[Math.PI / 2, 0, 0]}
|
|
|
|
| 43 |
justifyContent="center"
|
| 44 |
padding={20}
|
| 45 |
>
|
| 46 |
+
<ConnectionFlowBoxUIkit
|
| 47 |
+
{visible}
|
| 48 |
+
{robot}
|
| 49 |
+
{onInputBoxClick}
|
| 50 |
+
{onRobotBoxClick}
|
| 51 |
+
{onOutputBoxClick}
|
| 52 |
+
/>
|
| 53 |
</Container>
|
| 54 |
</Root>
|
| 55 |
</Billboard>
|
|
|
|
| 87 |
</HTML>
|
| 88 |
</Billboard> -->
|
| 89 |
</T.Group>
|
| 90 |
+
<!-- {/if} -->
|
src/lib/components/3d/elements/video/VideoGridItem.svelte
CHANGED
|
@@ -16,7 +16,6 @@
|
|
| 16 |
let { video, workspaceId, onCameraMove, onInputBoxClick, onOutputBoxClick }: Props = $props();
|
| 17 |
|
| 18 |
const { onPointerEnter, onPointerLeave } = useCursor();
|
| 19 |
-
interactivity();
|
| 20 |
|
| 21 |
let isToggled = $state(false);
|
| 22 |
let hovering = $state(false);
|
|
|
|
| 16 |
let { video, workspaceId, onCameraMove, onInputBoxClick, onOutputBoxClick }: Props = $props();
|
| 17 |
|
| 18 |
const { onPointerEnter, onPointerLeave } = useCursor();
|
|
|
|
| 19 |
|
| 20 |
let isToggled = $state(false);
|
| 21 |
let hovering = $state(false);
|
src/lib/components/3d/elements/video/Videos.svelte
CHANGED
|
@@ -7,6 +7,7 @@
|
|
| 7 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
| 8 |
import { generateName } from "$lib/utils/generateName";
|
| 9 |
import VideoGridItem from "@/components/3d/elements/video/VideoGridItem.svelte";
|
|
|
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
workspaceId: string;
|
|
@@ -27,6 +28,12 @@
|
|
| 27 |
selectedVideo = video;
|
| 28 |
isOutputModalOpen = true;
|
| 29 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
</script>
|
| 31 |
|
| 32 |
{#each videoManager.videos as video (video.id)}
|
|
|
|
| 7 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
| 8 |
import { generateName } from "$lib/utils/generateName";
|
| 9 |
import VideoGridItem from "@/components/3d/elements/video/VideoGridItem.svelte";
|
| 10 |
+
import { interactivity } from "@threlte/extras";
|
| 11 |
|
| 12 |
interface Props {
|
| 13 |
workspaceId: string;
|
|
|
|
| 28 |
selectedVideo = video;
|
| 29 |
isOutputModalOpen = true;
|
| 30 |
}
|
| 31 |
+
|
| 32 |
+
interactivity({
|
| 33 |
+
filter: (hits, state) => {
|
| 34 |
+
return hits.slice(0, 1);
|
| 35 |
+
}
|
| 36 |
+
});
|
| 37 |
</script>
|
| 38 |
|
| 39 |
{#each videoManager.videos as video (video.id)}
|
src/lib/components/3d/elements/video/modal/VideoInputConnectionModal.svelte
CHANGED
|
@@ -7,6 +7,7 @@
|
|
| 7 |
import { toast } from "svelte-sonner";
|
| 8 |
import { videoManager } from "$lib/elements/video/VideoManager.svelte";
|
| 9 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
|
|
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
workspaceId: string;
|
|
@@ -355,10 +356,10 @@
|
|
| 355 |
<div class="flex items-center justify-between">
|
| 356 |
<div>
|
| 357 |
<Card.Title
|
| 358 |
-
class="flex items-center gap-2 text-base text-purple-700 dark:text-purple-200"
|
| 359 |
>
|
| 360 |
<span class="icon-[mdi--cloud-download] size-4"></span>
|
| 361 |
-
Remote
|
| 362 |
</Card.Title>
|
| 363 |
<Card.Description class="text-xs text-purple-600/70 dark:text-purple-300/70">
|
| 364 |
Receive video streams from remote cameras or AI systems
|
|
@@ -483,13 +484,36 @@
|
|
| 483 |
>
|
| 484 |
{room.id}
|
| 485 |
</p>
|
| 486 |
-
<div class="flex gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 487 |
<span
|
| 488 |
>{room.participants?.producer
|
| 489 |
? "📹 Has Output"
|
| 490 |
: "📭 No Output"}</span
|
| 491 |
>
|
| 492 |
<span>👥 {room.participants?.consumers?.length || 0} inputs</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
</div>
|
| 494 |
</div>
|
| 495 |
{#if room.participants?.producer}
|
|
@@ -529,17 +553,6 @@
|
|
| 529 |
</Card.Content>
|
| 530 |
</Card.Root>
|
| 531 |
|
| 532 |
-
<!-- Help Information -->
|
| 533 |
-
<Alert.Root
|
| 534 |
-
class="border-slate-300 bg-slate-100/30 dark:border-slate-700 dark:bg-slate-800/30"
|
| 535 |
-
>
|
| 536 |
-
<span class="icon-[mdi--help-circle] size-4 text-slate-600 dark:text-slate-400"></span>
|
| 537 |
-
<Alert.Title class="text-slate-700 dark:text-slate-300">Video Input Sources</Alert.Title>
|
| 538 |
-
<Alert.Description class="text-xs text-slate-600 dark:text-slate-400">
|
| 539 |
-
<strong>Camera:</strong> Local device camera • <strong>Remote:</strong> Video streams from
|
| 540 |
-
rooms • Only one active at a time
|
| 541 |
-
</Alert.Description>
|
| 542 |
-
</Alert.Root>
|
| 543 |
</div>
|
| 544 |
</div>
|
| 545 |
</Dialog.Content>
|
|
|
|
| 7 |
import { toast } from "svelte-sonner";
|
| 8 |
import { videoManager } from "$lib/elements/video/VideoManager.svelte";
|
| 9 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
| 10 |
+
import { settings } from "$lib/runes/settings.svelte";
|
| 11 |
|
| 12 |
interface Props {
|
| 13 |
workspaceId: string;
|
|
|
|
| 356 |
<div class="flex items-center justify-between">
|
| 357 |
<div>
|
| 358 |
<Card.Title
|
| 359 |
+
class="flex items-center gap-2 text-base text-purple-700 dark:text-purple-200 pb-1"
|
| 360 |
>
|
| 361 |
<span class="icon-[mdi--cloud-download] size-4"></span>
|
| 362 |
+
Remote Control
|
| 363 |
</Card.Title>
|
| 364 |
<Card.Description class="text-xs text-purple-600/70 dark:text-purple-300/70">
|
| 365 |
Receive video streams from remote cameras or AI systems
|
|
|
|
| 484 |
>
|
| 485 |
{room.id}
|
| 486 |
</p>
|
| 487 |
+
<div class="flex items-center gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 488 |
<span
|
| 489 |
>{room.participants?.producer
|
| 490 |
? "📹 Has Output"
|
| 491 |
: "📭 No Output"}</span
|
| 492 |
>
|
| 493 |
<span>👥 {room.participants?.consumers?.length || 0} inputs</span>
|
| 494 |
+
<!-- Monitoring links -->
|
| 495 |
+
<div class="flex gap-1">
|
| 496 |
+
<a
|
| 497 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/video/consumer?room=${room.id}`}
|
| 498 |
+
target="_blank"
|
| 499 |
+
rel="noopener noreferrer"
|
| 500 |
+
class="inline-flex items-center gap-1 rounded bg-blue-500/10 px-1.5 py-0.5 text-xs text-blue-600 hover:bg-blue-500/20 dark:bg-blue-400/10 dark:text-blue-400 dark:hover:bg-blue-400/20"
|
| 501 |
+
title="Monitor Consumer"
|
| 502 |
+
>
|
| 503 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 504 |
+
Consumer
|
| 505 |
+
</a>
|
| 506 |
+
<a
|
| 507 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/video/producer?room=${room.id}`}
|
| 508 |
+
target="_blank"
|
| 509 |
+
rel="noopener noreferrer"
|
| 510 |
+
class="inline-flex items-center gap-1 rounded bg-green-500/10 px-1.5 py-0.5 text-xs text-green-600 hover:bg-green-500/20 dark:bg-green-400/10 dark:text-green-400 dark:hover:bg-green-400/20"
|
| 511 |
+
title="Monitor Producer"
|
| 512 |
+
>
|
| 513 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 514 |
+
Producer
|
| 515 |
+
</a>
|
| 516 |
+
</div>
|
| 517 |
</div>
|
| 518 |
</div>
|
| 519 |
{#if room.participants?.producer}
|
|
|
|
| 553 |
</Card.Content>
|
| 554 |
</Card.Root>
|
| 555 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
</div>
|
| 557 |
</div>
|
| 558 |
</Dialog.Content>
|
src/lib/components/3d/elements/video/modal/VideoOutputConnectionModal.svelte
CHANGED
|
@@ -7,6 +7,7 @@
|
|
| 7 |
import { toast } from "svelte-sonner";
|
| 8 |
import { videoManager } from "$lib/elements/video/VideoManager.svelte";
|
| 9 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
|
|
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
workspaceId: string;
|
|
@@ -92,7 +93,6 @@
|
|
| 92 |
try {
|
| 93 |
isConnecting = true;
|
| 94 |
error = null;
|
| 95 |
-
const roomId = customRoomId.trim() || video.id;
|
| 96 |
const result = await videoManager.startVideoOutputAsProducer(workspaceId, video.id);
|
| 97 |
if (result.success) {
|
| 98 |
customRoomId = "";
|
|
@@ -366,10 +366,10 @@
|
|
| 366 |
<div class="flex items-center justify-between">
|
| 367 |
<div>
|
| 368 |
<Card.Title
|
| 369 |
-
class="flex items-center gap-2 text-base text-purple-700 dark:text-purple-200"
|
| 370 |
>
|
| 371 |
<span class="icon-[mdi--cloud-upload] size-4"></span>
|
| 372 |
-
Remote
|
| 373 |
</Card.Title>
|
| 374 |
<Card.Description class="text-xs text-purple-600/70 dark:text-purple-300/70">
|
| 375 |
Broadcast video stream to remote systems and users
|
|
@@ -483,7 +483,7 @@
|
|
| 483 |
: "No rooms available. Create one to get started."}
|
| 484 |
</div>
|
| 485 |
{:else}
|
| 486 |
-
{#each videoManager.rooms as room}
|
| 487 |
<div
|
| 488 |
class="rounded border border-slate-300 bg-slate-50/50 p-2 dark:border-slate-600 dark:bg-slate-800/50"
|
| 489 |
>
|
|
@@ -494,13 +494,36 @@
|
|
| 494 |
>
|
| 495 |
{room.id}
|
| 496 |
</p>
|
| 497 |
-
<div class="flex gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 498 |
<span
|
| 499 |
>{room.participants?.producer
|
| 500 |
? "🔴 Has Output"
|
| 501 |
: "🟢 Available"}</span
|
| 502 |
>
|
| 503 |
<span>👥 {room.participants?.consumers?.length || 0} inputs</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
</div>
|
| 505 |
</div>
|
| 506 |
{#if !room.participants?.producer}
|
|
@@ -540,17 +563,7 @@
|
|
| 540 |
</Card.Content>
|
| 541 |
</Card.Root>
|
| 542 |
|
| 543 |
-
|
| 544 |
-
<Alert.Root
|
| 545 |
-
class="border-slate-300 bg-slate-100/30 dark:border-slate-700 dark:bg-slate-800/30"
|
| 546 |
-
>
|
| 547 |
-
<span class="icon-[mdi--help-circle] size-4 text-slate-600 dark:text-slate-400"></span>
|
| 548 |
-
<Alert.Title class="text-slate-700 dark:text-slate-300">Video Output Options</Alert.Title>
|
| 549 |
-
<Alert.Description class="text-xs text-slate-600 dark:text-slate-400">
|
| 550 |
-
<strong>Recording:</strong> Save locally • <strong>Remote:</strong> Broadcast to rooms •
|
| 551 |
-
Only one active at a time
|
| 552 |
-
</Alert.Description>
|
| 553 |
-
</Alert.Root>
|
| 554 |
</div>
|
| 555 |
</div>
|
| 556 |
</Dialog.Content>
|
|
|
|
| 7 |
import { toast } from "svelte-sonner";
|
| 8 |
import { videoManager } from "$lib/elements/video/VideoManager.svelte";
|
| 9 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
| 10 |
+
import { settings } from "$lib/runes/settings.svelte";
|
| 11 |
|
| 12 |
interface Props {
|
| 13 |
workspaceId: string;
|
|
|
|
| 93 |
try {
|
| 94 |
isConnecting = true;
|
| 95 |
error = null;
|
|
|
|
| 96 |
const result = await videoManager.startVideoOutputAsProducer(workspaceId, video.id);
|
| 97 |
if (result.success) {
|
| 98 |
customRoomId = "";
|
|
|
|
| 366 |
<div class="flex items-center justify-between">
|
| 367 |
<div>
|
| 368 |
<Card.Title
|
| 369 |
+
class="flex items-center gap-2 text-base text-purple-700 dark:text-purple-200 pb-1"
|
| 370 |
>
|
| 371 |
<span class="icon-[mdi--cloud-upload] size-4"></span>
|
| 372 |
+
Remote Control
|
| 373 |
</Card.Title>
|
| 374 |
<Card.Description class="text-xs text-purple-600/70 dark:text-purple-300/70">
|
| 375 |
Broadcast video stream to remote systems and users
|
|
|
|
| 483 |
: "No rooms available. Create one to get started."}
|
| 484 |
</div>
|
| 485 |
{:else}
|
| 486 |
+
{#each videoManager.rooms as room (room.id)}
|
| 487 |
<div
|
| 488 |
class="rounded border border-slate-300 bg-slate-50/50 p-2 dark:border-slate-600 dark:bg-slate-800/50"
|
| 489 |
>
|
|
|
|
| 494 |
>
|
| 495 |
{room.id}
|
| 496 |
</p>
|
| 497 |
+
<div class="flex items-center gap-3 text-xs text-slate-600 dark:text-slate-400">
|
| 498 |
<span
|
| 499 |
>{room.participants?.producer
|
| 500 |
? "🔴 Has Output"
|
| 501 |
: "🟢 Available"}</span
|
| 502 |
>
|
| 503 |
<span>👥 {room.participants?.consumers?.length || 0} inputs</span>
|
| 504 |
+
<!-- Monitoring links -->
|
| 505 |
+
<div class="flex gap-1">
|
| 506 |
+
<a
|
| 507 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/video/consumer?room=${room.id}`}
|
| 508 |
+
target="_blank"
|
| 509 |
+
rel="noopener noreferrer"
|
| 510 |
+
class="inline-flex items-center gap-1 rounded bg-blue-500/10 px-1.5 py-0.5 text-xs text-blue-600 hover:bg-blue-500/20 dark:bg-blue-400/10 dark:text-blue-400 dark:hover:bg-blue-400/20"
|
| 511 |
+
title="Monitor Consumer"
|
| 512 |
+
>
|
| 513 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 514 |
+
Consumer
|
| 515 |
+
</a>
|
| 516 |
+
<a
|
| 517 |
+
href={`${settings.transportServerUrl.replace('/api', '')}/${workspaceId}/video/producer?room=${room.id}`}
|
| 518 |
+
target="_blank"
|
| 519 |
+
rel="noopener noreferrer"
|
| 520 |
+
class="inline-flex items-center gap-1 rounded bg-green-500/10 px-1.5 py-0.5 text-xs text-green-600 hover:bg-green-500/20 dark:bg-green-400/10 dark:text-green-400 dark:hover:bg-green-400/20"
|
| 521 |
+
title="Monitor Producer"
|
| 522 |
+
>
|
| 523 |
+
<span class="icon-[mdi--monitor-eye] size-3"></span>
|
| 524 |
+
Producer
|
| 525 |
+
</a>
|
| 526 |
+
</div>
|
| 527 |
</div>
|
| 528 |
</div>
|
| 529 |
{#if !room.participants?.producer}
|
|
|
|
| 563 |
</Card.Content>
|
| 564 |
</Card.Root>
|
| 565 |
|
| 566 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
</div>
|
| 568 |
</div>
|
| 569 |
</Dialog.Content>
|
src/lib/components/3d/elements/video/status/VideoConnectionFlowBoxUIKit.svelte
CHANGED
|
@@ -5,38 +5,55 @@
|
|
| 5 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
| 6 |
import { Container } from "threlte-uikit";
|
| 7 |
import { StatusArrow } from "$lib/components/3d/ui";
|
|
|
|
|
|
|
| 8 |
|
| 9 |
interface Props {
|
|
|
|
| 10 |
video: VideoInstance;
|
| 11 |
onInputBoxClick: (video: VideoInstance) => void;
|
| 12 |
onOutputBoxClick: (video: VideoInstance) => void;
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
-
let { video, onInputBoxClick, onOutputBoxClick }: Props = $props();
|
| 16 |
|
| 17 |
const inputColor = "rgb(34, 197, 94)";
|
| 18 |
const outputColor = "rgb(59, 130, 246)";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</script>
|
| 20 |
|
| 21 |
-
<Container
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
<!-- Input Video Box -->
|
| 23 |
<InputVideoBoxUIKit {video} handleClick={() => onInputBoxClick(video)} />
|
| 24 |
|
| 25 |
<!-- Arrow 1: Input to Video -->
|
| 26 |
-
<StatusArrow
|
| 27 |
-
color={inputColor}
|
| 28 |
-
opacity={video.hasInput ? 1 : 0.5}
|
| 29 |
-
/>
|
| 30 |
|
| 31 |
<!-- Video Box -->
|
| 32 |
<VideoBoxUIKit {video} />
|
| 33 |
|
| 34 |
<!-- Arrow 2: Video to Output -->
|
| 35 |
-
<StatusArrow
|
| 36 |
color={outputColor}
|
| 37 |
opacity={video.hasInput && video.hasOutput ? 1 : video.hasInput && video.canOutput ? 0.7 : 0.5}
|
| 38 |
/>
|
| 39 |
|
| 40 |
<!-- Output Box -->
|
| 41 |
<OutputVideoBoxUIKit {video} handleClick={() => onOutputBoxClick(video)} />
|
| 42 |
-
</Container>
|
|
|
|
| 5 |
import type { VideoInstance } from "$lib/elements/video/VideoManager.svelte";
|
| 6 |
import { Container } from "threlte-uikit";
|
| 7 |
import { StatusArrow } from "$lib/components/3d/ui";
|
| 8 |
+
import { Tween } from "svelte/motion";
|
| 9 |
+
import { cubicOut } from "svelte/easing";
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
+
visible: boolean;
|
| 13 |
video: VideoInstance;
|
| 14 |
onInputBoxClick: (video: VideoInstance) => void;
|
| 15 |
onOutputBoxClick: (video: VideoInstance) => void;
|
| 16 |
+
duration?: number;
|
| 17 |
+
delay?: number;
|
| 18 |
}
|
| 19 |
|
| 20 |
+
let { visible, video, onInputBoxClick, onOutputBoxClick, duration = 100, delay = 0 }: Props = $props();
|
| 21 |
|
| 22 |
const inputColor = "rgb(34, 197, 94)";
|
| 23 |
const outputColor = "rgb(59, 130, 246)";
|
| 24 |
+
|
| 25 |
+
const tweenedScale = Tween.of(() => {
|
| 26 |
+
return visible ? 1 : 0;
|
| 27 |
+
}, { duration: duration, easing: cubicOut, delay: delay });
|
| 28 |
+
const tweenedOpacity = Tween.of(() => {
|
| 29 |
+
return visible ? 1 : 0;
|
| 30 |
+
}, { duration: duration, easing: cubicOut, delay: delay });
|
| 31 |
</script>
|
| 32 |
|
| 33 |
+
<Container
|
| 34 |
+
flexDirection="row"
|
| 35 |
+
alignItems="center"
|
| 36 |
+
gap={12}
|
| 37 |
+
transformScaleX={tweenedScale.current}
|
| 38 |
+
transformScaleY={tweenedScale.current}
|
| 39 |
+
transformScaleZ={tweenedScale.current}
|
| 40 |
+
opacity={tweenedOpacity.current}
|
| 41 |
+
>
|
| 42 |
<!-- Input Video Box -->
|
| 43 |
<InputVideoBoxUIKit {video} handleClick={() => onInputBoxClick(video)} />
|
| 44 |
|
| 45 |
<!-- Arrow 1: Input to Video -->
|
| 46 |
+
<StatusArrow color={inputColor} opacity={video.hasInput ? 1 : 0.5} />
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
<!-- Video Box -->
|
| 49 |
<VideoBoxUIKit {video} />
|
| 50 |
|
| 51 |
<!-- Arrow 2: Video to Output -->
|
| 52 |
+
<StatusArrow
|
| 53 |
color={outputColor}
|
| 54 |
opacity={video.hasInput && video.hasOutput ? 1 : video.hasInput && video.canOutput ? 0.7 : 0.5}
|
| 55 |
/>
|
| 56 |
|
| 57 |
<!-- Output Box -->
|
| 58 |
<OutputVideoBoxUIKit {video} handleClick={() => onOutputBoxClick(video)} />
|
| 59 |
+
</Container>
|
src/lib/components/3d/elements/video/status/VideoStatusBillboard.svelte
CHANGED
|
@@ -18,7 +18,7 @@
|
|
| 18 |
interactivity();
|
| 19 |
</script>
|
| 20 |
|
| 21 |
-
{#if visible}
|
| 22 |
<T.Group
|
| 23 |
onpointerdown={(e) => e.stopPropagation()}
|
| 24 |
onpointerup={(e) => e.stopPropagation()}
|
|
@@ -39,7 +39,7 @@
|
|
| 39 |
justifyContent="center"
|
| 40 |
padding={20}
|
| 41 |
>
|
| 42 |
-
<VideoConnectionFlowBoxUIKit {video} {onInputBoxClick} {onOutputBoxClick} />
|
| 43 |
</Container>
|
| 44 |
</Root>
|
| 45 |
</Billboard>
|
|
@@ -73,4 +73,4 @@
|
|
| 73 |
</HTML>
|
| 74 |
</Billboard> -->
|
| 75 |
</T.Group>
|
| 76 |
-
{/if}
|
|
|
|
| 18 |
interactivity();
|
| 19 |
</script>
|
| 20 |
|
| 21 |
+
<!-- {#if visible} -->
|
| 22 |
<T.Group
|
| 23 |
onpointerdown={(e) => e.stopPropagation()}
|
| 24 |
onpointerup={(e) => e.stopPropagation()}
|
|
|
|
| 39 |
justifyContent="center"
|
| 40 |
padding={20}
|
| 41 |
>
|
| 42 |
+
<VideoConnectionFlowBoxUIKit {visible} {video} {onInputBoxClick} {onOutputBoxClick} />
|
| 43 |
</Container>
|
| 44 |
</Root>
|
| 45 |
</Billboard>
|
|
|
|
| 73 |
</HTML>
|
| 74 |
</Billboard> -->
|
| 75 |
</T.Group>
|
| 76 |
+
<!-- {/if} -->
|
src/lib/components/interface/overlay/AddAIButton.svelte
CHANGED
|
@@ -1,81 +1,55 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
import { Button } from "@/components/ui/button";
|
| 3 |
import * as DropdownMenu from "@/components/ui/dropdown-menu";
|
| 4 |
-
import { toast } from "svelte-sonner";
|
| 5 |
import { cn } from "$lib/utils";
|
| 6 |
-
import {
|
| 7 |
-
import
|
| 8 |
|
| 9 |
interface Props {
|
|
|
|
| 10 |
open?: boolean;
|
| 11 |
}
|
| 12 |
|
| 13 |
-
let { open = $bindable() }: Props = $props();
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
{ id: "pi0", label: "Pi0", icon: "icon-[mdi--brain]", enabled: false },
|
| 18 |
-
{ id: "nano-vla", label: "Nano VLA", icon: "icon-[mdi--brain]", enabled: false },
|
| 19 |
-
{ id: "nvidia-groot", label: "Nvidia Groot", icon: "icon-[mdi--robot-outline]", enabled: false }
|
| 20 |
-
];
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
// Basic validation
|
| 25 |
-
if (!aiType) return;
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
toast.success(`Created ${formatAIType(aiType)} compute: ${computeName}`);
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
} catch (error) {
|
| 38 |
-
console.error("AI creation failed:", error);
|
| 39 |
-
toast.error("Failed to create AI compute");
|
| 40 |
-
}
|
| 41 |
}
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
-
function
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
return "Pi0";
|
| 51 |
-
case "nano-vla":
|
| 52 |
-
return "Nano VLA";
|
| 53 |
-
case "nvidia-groot":
|
| 54 |
-
return "Nvidia Groot";
|
| 55 |
-
default:
|
| 56 |
-
return aiType;
|
| 57 |
-
}
|
| 58 |
}
|
| 59 |
|
| 60 |
-
function
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
return "Lightweight AI model";
|
| 64 |
-
case "nano-vla":
|
| 65 |
-
return "Vision-language-action model";
|
| 66 |
-
case "nvidia-groot":
|
| 67 |
-
return "Humanoid robotics model";
|
| 68 |
-
default:
|
| 69 |
-
return "AI model";
|
| 70 |
-
}
|
| 71 |
}
|
| 72 |
</script>
|
| 73 |
|
| 74 |
-
<!-- Main Add Button (
|
| 75 |
<Button
|
| 76 |
variant="default"
|
| 77 |
size="sm"
|
| 78 |
-
onclick={
|
|
|
|
| 79 |
class="group rounded-r-none border-0 bg-purple-500 text-white transition-all duration-200 hover:bg-purple-400 dark:bg-purple-600 dark:hover:bg-purple-500"
|
| 80 |
>
|
| 81 |
<span
|
|
@@ -93,6 +67,7 @@
|
|
| 93 |
{#snippet child({ props })}
|
| 94 |
<Button
|
| 95 |
{...props}
|
|
|
|
| 96 |
variant="default"
|
| 97 |
size="sm"
|
| 98 |
class={cn(
|
|
@@ -112,44 +87,29 @@
|
|
| 112 |
{/snippet}
|
| 113 |
</DropdownMenu.Trigger>
|
| 114 |
|
| 115 |
-
<DropdownMenu.Content
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
<DropdownMenu.GroupHeading
|
| 121 |
-
class="text-xs font-semibold tracking-wider text-purple-100 uppercase dark:text-purple-200"
|
| 122 |
>
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
class=
|
| 129 |
-
|
| 130 |
-
"data-highlighted:bg-purple-600 dark:bg-purple-600 dark:data-highlighted:bg-purple-700"
|
| 131 |
-
]}
|
| 132 |
-
onclick={async () => await addAI(ai.id)}
|
| 133 |
-
disabled={!ai.enabled}
|
| 134 |
-
>
|
| 135 |
-
<span
|
| 136 |
-
class={[
|
| 137 |
-
ai.icon,
|
| 138 |
-
"mr-3 size-4 text-purple-100 transition-colors duration-200 dark:text-purple-200"
|
| 139 |
-
]}
|
| 140 |
-
></span>
|
| 141 |
-
<div class="flex flex-1 flex-col">
|
| 142 |
-
<span class="font-medium text-white transition-colors duration-200"
|
| 143 |
-
>{formatAIType(ai.id)}</span
|
| 144 |
-
>
|
| 145 |
-
<span
|
| 146 |
-
class="text-xs text-purple-100 transition-colors duration-200 dark:text-purple-200"
|
| 147 |
-
>
|
| 148 |
-
{getAIDescription(ai.id)}
|
| 149 |
-
</span>
|
| 150 |
</div>
|
| 151 |
-
</
|
| 152 |
-
|
| 153 |
-
|
| 154 |
</DropdownMenu.Content>
|
| 155 |
</DropdownMenu.Root>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
import { Button } from "@/components/ui/button";
|
| 3 |
import * as DropdownMenu from "@/components/ui/dropdown-menu";
|
|
|
|
| 4 |
import { cn } from "$lib/utils";
|
| 5 |
+
import { MODEL_TYPES, type ModelType } from "$lib/elements/compute";
|
| 6 |
+
import AIModelConfigurationModal from "@/components/3d/elements/compute/modal/AIModelConfigurationModal.svelte";
|
| 7 |
|
| 8 |
interface Props {
|
| 9 |
+
workspaceId: string;
|
| 10 |
open?: boolean;
|
| 11 |
}
|
| 12 |
|
| 13 |
+
let { open = $bindable(), workspaceId }: Props = $props();
|
| 14 |
|
| 15 |
+
let isConfigModalOpen = $state(false);
|
| 16 |
+
let selectedModelType = $state<ModelType>('act');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
// Get available model types
|
| 19 |
+
const availableModels = Object.values(MODEL_TYPES).filter(model => model.enabled);
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
function openConfigModal(modelType: ModelType) {
|
| 22 |
+
selectedModelType = modelType;
|
| 23 |
+
isConfigModalOpen = true;
|
| 24 |
+
open = false; // Close the dropdown
|
| 25 |
+
}
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
function quickAddACT() {
|
| 28 |
+
openConfigModal('act');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
+
function formatModelType(modelType: string): string {
|
| 32 |
+
const config = MODEL_TYPES[modelType as ModelType];
|
| 33 |
+
return config ? config.label : modelType;
|
| 34 |
}
|
| 35 |
|
| 36 |
+
function getModelDescription(modelType: string): string {
|
| 37 |
+
const config = MODEL_TYPES[modelType as ModelType];
|
| 38 |
+
return config ? config.description : "AI model";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
+
function getModelIcon(modelType: string): string {
|
| 42 |
+
const config = MODEL_TYPES[modelType as ModelType];
|
| 43 |
+
return config ? config.icon : "icon-[mdi--brain]";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
</script>
|
| 46 |
|
| 47 |
+
<!-- Main Add Button (ACT Model - Quick Add) -->
|
| 48 |
<Button
|
| 49 |
variant="default"
|
| 50 |
size="sm"
|
| 51 |
+
onclick={quickAddACT}
|
| 52 |
+
disabled={true}
|
| 53 |
class="group rounded-r-none border-0 bg-purple-500 text-white transition-all duration-200 hover:bg-purple-400 dark:bg-purple-600 dark:hover:bg-purple-500"
|
| 54 |
>
|
| 55 |
<span
|
|
|
|
| 67 |
{#snippet child({ props })}
|
| 68 |
<Button
|
| 69 |
{...props}
|
| 70 |
+
disabled={true}
|
| 71 |
variant="default"
|
| 72 |
size="sm"
|
| 73 |
class={cn(
|
|
|
|
| 87 |
{/snippet}
|
| 88 |
</DropdownMenu.Trigger>
|
| 89 |
|
| 90 |
+
<DropdownMenu.Content class="w-64 bg-slate-100 border-slate-300 dark:bg-slate-900 dark:border-slate-600">
|
| 91 |
+
{#each availableModels as model}
|
| 92 |
+
<DropdownMenu.Item
|
| 93 |
+
class="flex items-center gap-3 p-3 cursor-pointer hover:bg-purple-100 dark:hover:bg-purple-900/30"
|
| 94 |
+
onclick={() => openConfigModal(model.id)}
|
|
|
|
|
|
|
| 95 |
>
|
| 96 |
+
<span class="{model.icon} size-5 text-purple-500 dark:text-purple-400"></span>
|
| 97 |
+
<div class="flex-1">
|
| 98 |
+
<div class="font-medium text-slate-900 dark:text-slate-100">
|
| 99 |
+
{model.label}
|
| 100 |
+
</div>
|
| 101 |
+
<div class="text-xs text-slate-600 dark:text-slate-400">
|
| 102 |
+
{model.description}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
</div>
|
| 104 |
+
</div>
|
| 105 |
+
</DropdownMenu.Item>
|
| 106 |
+
{/each}
|
| 107 |
</DropdownMenu.Content>
|
| 108 |
</DropdownMenu.Root>
|
| 109 |
+
|
| 110 |
+
<!-- Configuration Modal -->
|
| 111 |
+
<AIModelConfigurationModal
|
| 112 |
+
bind:open={isConfigModalOpen}
|
| 113 |
+
{workspaceId}
|
| 114 |
+
initialModelType={selectedModelType}
|
| 115 |
+
/>
|
src/lib/components/interface/overlay/AddRobotButton.svelte
CHANGED
|
@@ -44,17 +44,23 @@
|
|
| 44 |
}
|
| 45 |
}
|
| 46 |
|
| 47 |
-
async function
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
</script>
|
| 52 |
|
| 53 |
-
<!-- Main Add Button (
|
| 54 |
<Button
|
| 55 |
variant="default"
|
| 56 |
size="sm"
|
| 57 |
-
onclick={
|
| 58 |
class="group rounded-r-none border-0 bg-emerald-500 text-white transition-all duration-200 hover:bg-emerald-400 dark:bg-emerald-600 dark:hover:bg-emerald-500"
|
| 59 |
>
|
| 60 |
<span
|
|
@@ -103,6 +109,7 @@
|
|
| 103 |
</DropdownMenu.GroupHeading>
|
| 104 |
|
| 105 |
{#each robotTypes as robotType}
|
|
|
|
| 106 |
<DropdownMenu.Item
|
| 107 |
class={[
|
| 108 |
"group group cursor-pointer bg-emerald-500 text-white transition-all duration-200",
|
|
@@ -112,19 +119,19 @@
|
|
| 112 |
>
|
| 113 |
<span
|
| 114 |
class={[
|
| 115 |
-
|
| 116 |
"mr-3 size-4 text-emerald-100 transition-colors duration-200 dark:text-emerald-200"
|
| 117 |
]}
|
| 118 |
></span>
|
| 119 |
<div class="flex flex-1 flex-col">
|
| 120 |
<span class="font-medium text-white transition-colors duration-200"
|
| 121 |
-
>{robotType.replace(/-/g, " ").toUpperCase()}</span
|
| 122 |
>
|
| 123 |
<span class="text-xs text-emerald-100 transition-colors duration-200 dark:text-emerald-200">
|
| 124 |
-
{
|
| 125 |
</span>
|
| 126 |
</div>
|
| 127 |
-
{#if
|
| 128 |
<Badge
|
| 129 |
variant="secondary"
|
| 130 |
class="ml-2 bg-emerald-600 text-xs text-emerald-100 group-data-highlighted:bg-emerald-300 group-data-highlighted:text-emerald-900 dark:bg-emerald-700 dark:text-emerald-100 dark:group-data-highlighted:bg-emerald-400 dark:group-data-highlighted:text-emerald-900"
|
|
|
|
| 44 |
}
|
| 45 |
}
|
| 46 |
|
| 47 |
+
async function quickAddDefault() {
|
| 48 |
+
const defaultRobotType = robotTypes.find(type => robotUrdfConfigMap[type].isDefault);
|
| 49 |
+
if (defaultRobotType) {
|
| 50 |
+
await addRobot(defaultRobotType);
|
| 51 |
+
} else {
|
| 52 |
+
// Fallback to first robot type if no default is set
|
| 53 |
+
await addRobot(robotTypes[0]);
|
| 54 |
+
}
|
| 55 |
}
|
| 56 |
|
| 57 |
</script>
|
| 58 |
|
| 59 |
+
<!-- Main Add Button (Default Robot) -->
|
| 60 |
<Button
|
| 61 |
variant="default"
|
| 62 |
size="sm"
|
| 63 |
+
onclick={quickAddDefault}
|
| 64 |
class="group rounded-r-none border-0 bg-emerald-500 text-white transition-all duration-200 hover:bg-emerald-400 dark:bg-emerald-600 dark:hover:bg-emerald-500"
|
| 65 |
>
|
| 66 |
<span
|
|
|
|
| 109 |
</DropdownMenu.GroupHeading>
|
| 110 |
|
| 111 |
{#each robotTypes as robotType}
|
| 112 |
+
{@const urdfConfig = robotUrdfConfigMap[robotType]}
|
| 113 |
<DropdownMenu.Item
|
| 114 |
class={[
|
| 115 |
"group group cursor-pointer bg-emerald-500 text-white transition-all duration-200",
|
|
|
|
| 119 |
>
|
| 120 |
<span
|
| 121 |
class={[
|
| 122 |
+
urdfConfig.icon || "icon-[mdi--robot-industrial]",
|
| 123 |
"mr-3 size-4 text-emerald-100 transition-colors duration-200 dark:text-emerald-200"
|
| 124 |
]}
|
| 125 |
></span>
|
| 126 |
<div class="flex flex-1 flex-col">
|
| 127 |
<span class="font-medium text-white transition-colors duration-200"
|
| 128 |
+
>{urdfConfig.displayName || robotType.replace(/-/g, " ").toUpperCase()}</span
|
| 129 |
>
|
| 130 |
<span class="text-xs text-emerald-100 transition-colors duration-200 dark:text-emerald-200">
|
| 131 |
+
{urdfConfig.description || "Robot"}
|
| 132 |
</span>
|
| 133 |
</div>
|
| 134 |
+
{#if urdfConfig.isDefault}
|
| 135 |
<Badge
|
| 136 |
variant="secondary"
|
| 137 |
class="ml-2 bg-emerald-600 text-xs text-emerald-100 group-data-highlighted:bg-emerald-300 group-data-highlighted:text-emerald-900 dark:bg-emerald-700 dark:text-emerald-100 dark:group-data-highlighted:bg-emerald-400 dark:group-data-highlighted:text-emerald-900"
|
src/lib/components/interface/overlay/AddSensorButton.svelte
CHANGED
|
@@ -29,6 +29,13 @@
|
|
| 29 |
enabled: true,
|
| 30 |
isDefault: true
|
| 31 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
{
|
| 33 |
id: 'lidar',
|
| 34 |
label: 'Lidar',
|
|
|
|
| 29 |
enabled: true,
|
| 30 |
isDefault: true
|
| 31 |
},
|
| 32 |
+
{
|
| 33 |
+
id: 'depth-camera',
|
| 34 |
+
label: 'Depth Camera',
|
| 35 |
+
description: 'Depth Camera Sensor',
|
| 36 |
+
icon: 'icon-[mdi--camera]',
|
| 37 |
+
enabled: false
|
| 38 |
+
},
|
| 39 |
{
|
| 40 |
id: 'lidar',
|
| 41 |
label: 'Lidar',
|
src/lib/components/interface/overlay/Overlay.svelte
CHANGED
|
@@ -26,49 +26,44 @@
|
|
| 26 |
</script>
|
| 27 |
|
| 28 |
<div class="select-none">
|
| 29 |
-
<!-- Button Bar Container -->
|
| 30 |
-
<div class="fixed top-
|
| 31 |
-
<!--
|
| 32 |
-
<div class="flex items-center
|
| 33 |
<!-- Logo/Favicon -->
|
| 34 |
<div class="flex items-center justify-center">
|
| 35 |
-
<!-- From /favicon_1024.png -->
|
| 36 |
<img
|
| 37 |
src="/favicon_1024.png"
|
| 38 |
alt="Logo"
|
| 39 |
draggable="false"
|
| 40 |
-
class="h-
|
| 41 |
/>
|
| 42 |
</div>
|
| 43 |
-
|
| 44 |
-
|
|
|
|
| 45 |
<AddRobotButton bind:open={addRobotDropdownMenuOpen} />
|
| 46 |
</div>
|
| 47 |
-
</div>
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
<!-- Add sensor button and dropdown menu -->
|
| 52 |
-
<div class="flex items-center justify-center">
|
| 53 |
<AddSensorButton bind:open={addSensorDropdownMenuOpen} />
|
| 54 |
</div>
|
| 55 |
-
</div>
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
<div class="flex items-center justify-center">
|
| 61 |
-
<AddAIButton bind:open={addAIDropdownMenuOpen} />
|
| 62 |
</div>
|
| 63 |
</div>
|
| 64 |
-
</div>
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
|
|
|
| 72 |
</div>
|
| 73 |
</div>
|
| 74 |
<SettingsSheet bind:open={settingsOpen} />
|
|
|
|
| 26 |
</script>
|
| 27 |
|
| 28 |
<div class="select-none">
|
| 29 |
+
<!-- Responsive Button Bar Container -->
|
| 30 |
+
<div class="fixed top-2 left-2 right-2 z-50 flex flex-wrap items-center justify-between gap-1 select-none md:top-4 md:left-4 md:right-4 md:gap-2">
|
| 31 |
+
<!-- Left Group: Logo + Add Buttons -->
|
| 32 |
+
<div class="flex items-center gap-1 flex-wrap md:gap-2">
|
| 33 |
<!-- Logo/Favicon -->
|
| 34 |
<div class="flex items-center justify-center">
|
|
|
|
| 35 |
<img
|
| 36 |
src="/favicon_1024.png"
|
| 37 |
alt="Logo"
|
| 38 |
draggable="false"
|
| 39 |
+
class="h-8 w-8 invert-0 filter dark:invert md:h-10 md:w-10"
|
| 40 |
/>
|
| 41 |
</div>
|
| 42 |
+
|
| 43 |
+
<!-- Add Robot Button Group -->
|
| 44 |
+
<div class="flex items-center justify-center overflow-hidden rounded-lg">
|
| 45 |
<AddRobotButton bind:open={addRobotDropdownMenuOpen} />
|
| 46 |
</div>
|
|
|
|
| 47 |
|
| 48 |
+
<!-- Add Sensor Button Group - Hidden on very small screens -->
|
| 49 |
+
<div class="hidden min-[480px]:flex items-center justify-center overflow-hidden rounded-lg">
|
|
|
|
|
|
|
| 50 |
<AddSensorButton bind:open={addSensorDropdownMenuOpen} />
|
| 51 |
</div>
|
|
|
|
| 52 |
|
| 53 |
+
<!-- Add AI Button Group - Hidden on small screens -->
|
| 54 |
+
<div class="hidden min-[560px]:flex items-center justify-center overflow-hidden rounded-lg">
|
| 55 |
+
<AddAIButton bind:open={addAIDropdownMenuOpen} workspaceId={workspaceId} />
|
|
|
|
|
|
|
| 56 |
</div>
|
| 57 |
</div>
|
|
|
|
| 58 |
|
| 59 |
+
<!-- Right Group: Workspace ID + Settings -->
|
| 60 |
+
<div class="flex items-center gap-1 md:gap-2">
|
| 61 |
+
<!-- Workspace ID Button -->
|
| 62 |
+
<WorkspaceIdButton {workspaceId} bind:open={workspaceIdMenuOpen} />
|
| 63 |
|
| 64 |
+
<!-- Settings Button -->
|
| 65 |
+
<SettingsButton bind:open={settingsOpen} />
|
| 66 |
+
</div>
|
| 67 |
</div>
|
| 68 |
</div>
|
| 69 |
<SettingsSheet bind:open={settingsOpen} />
|
src/lib/components/interface/overlay/SettingsSheet.svelte
CHANGED
|
@@ -130,8 +130,7 @@
|
|
| 130 |
>Inference Server URL</Label
|
| 131 |
>
|
| 132 |
<p class="text-xs text-slate-600 dark:text-slate-400">
|
| 133 |
-
URL for the remote AI inference server
|
| 134 |
-
sessions
|
| 135 |
</p>
|
| 136 |
</div>
|
| 137 |
<div class="flex gap-2">
|
|
|
|
| 130 |
>Inference Server URL</Label
|
| 131 |
>
|
| 132 |
<p class="text-xs text-slate-600 dark:text-slate-400">
|
| 133 |
+
URL for the remote AI inference server to run policies using remote compute resources
|
|
|
|
| 134 |
</p>
|
| 135 |
</div>
|
| 136 |
<div class="flex gap-2">
|
src/lib/components/interface/overlay/WorkspaceIdButton.svelte
CHANGED
|
@@ -128,7 +128,7 @@
|
|
| 128 |
class="rounded-lg border border-slate-300 bg-slate-50 p-2 dark:border-slate-600 dark:bg-slate-800"
|
| 129 |
>
|
| 130 |
<div class="font-mono text-sm break-all text-slate-800 dark:text-slate-200">
|
| 131 |
-
https://blanchon-robothub-frontend.hf.space
|
| 132 |
class="rounded bg-blue-100 px-1 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
| 133 |
>#{workspaceId}</span
|
| 134 |
>
|
|
|
|
| 128 |
class="rounded-lg border border-slate-300 bg-slate-50 p-2 dark:border-slate-600 dark:bg-slate-800"
|
| 129 |
>
|
| 130 |
<div class="font-mono text-sm break-all text-slate-800 dark:text-slate-200">
|
| 131 |
+
https://blanchon-robothub-frontend.hf.space/<span
|
| 132 |
class="rounded bg-blue-100 px-1 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
| 133 |
>#{workspaceId}</span
|
| 134 |
>
|
src/lib/configs/robotUrdfConfig.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { RobotUrdfConfig } from "$lib/types/urdf";
|
|
| 3 |
export const robotUrdfConfigMap: { [key: string]: RobotUrdfConfig } = {
|
| 4 |
"so-arm100": {
|
| 5 |
urdfUrl: "/robots/so-100/so_arm100.urdf",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
jointNameIdMap: {
|
| 7 |
Rotation: 1,
|
| 8 |
Pitch: 2,
|
|
|
|
| 3 |
export const robotUrdfConfigMap: { [key: string]: RobotUrdfConfig } = {
|
| 4 |
"so-arm100": {
|
| 5 |
urdfUrl: "/robots/so-100/so_arm100.urdf",
|
| 6 |
+
displayName: "SO ARM 100",
|
| 7 |
+
description: "6-DOF Robotic Arm",
|
| 8 |
+
icon: "icon-[ix--robotic-arm]",
|
| 9 |
+
isDefault: true,
|
| 10 |
jointNameIdMap: {
|
| 11 |
Rotation: 1,
|
| 12 |
Pitch: 2,
|
src/lib/elements/compute/RemoteCompute.svelte.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import type { Positionable, Position3D } from '$lib/types/positionable.js';
|
| 2 |
-
import type { AISessionConfig, AISessionResponse } from './RemoteComputeManager.svelte';
|
| 3 |
|
| 4 |
export type ComputeStatus = 'disconnected' | 'ready' | 'running' | 'stopped' | 'initializing';
|
| 5 |
|
|
@@ -10,6 +10,7 @@ export class RemoteCompute implements Positionable {
|
|
| 10 |
position = $state<Position3D>({ x: 0, y: 0, z: 0 });
|
| 11 |
name = $state<string>('');
|
| 12 |
status = $state<ComputeStatus>('disconnected');
|
|
|
|
| 13 |
|
| 14 |
// Session data
|
| 15 |
sessionId = $state<string | null>(null);
|
|
|
|
| 1 |
import type { Positionable, Position3D } from '$lib/types/positionable.js';
|
| 2 |
+
import type { AISessionConfig, AISessionResponse, ModelType } from './RemoteComputeManager.svelte';
|
| 3 |
|
| 4 |
export type ComputeStatus = 'disconnected' | 'ready' | 'running' | 'stopped' | 'initializing';
|
| 5 |
|
|
|
|
| 10 |
position = $state<Position3D>({ x: 0, y: 0, z: 0 });
|
| 11 |
name = $state<string>('');
|
| 12 |
status = $state<ComputeStatus>('disconnected');
|
| 13 |
+
modelType = $state<ModelType>('act'); // Default to ACT model
|
| 14 |
|
| 15 |
// Session data
|
| 16 |
sessionId = $state<string | null>(null);
|
src/lib/elements/compute/RemoteComputeManager.svelte.ts
CHANGED
|
@@ -17,12 +17,86 @@ import type {
|
|
| 17 |
CreateSessionResponse
|
| 18 |
} from '@robothub/inference-server-client';
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
export interface AISessionConfig {
|
| 21 |
sessionId: string;
|
|
|
|
| 22 |
policyPath: string;
|
| 23 |
cameraNames: string[];
|
| 24 |
transportServerUrl: string;
|
| 25 |
workspaceId?: string;
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
export interface AISessionResponse {
|
|
@@ -84,7 +158,68 @@ export class RemoteComputeManager {
|
|
| 84 |
}
|
| 85 |
|
| 86 |
/**
|
| 87 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
*/
|
| 89 |
createCompute(id?: string, name?: string, position?: Position3D): RemoteCompute {
|
| 90 |
const computeId = id || generateName();
|
|
@@ -150,6 +285,8 @@ export class RemoteComputeManager {
|
|
| 150 |
camera_names: config.cameraNames,
|
| 151 |
transport_server_url: config.transportServerUrl,
|
| 152 |
workspace_id: config.workspaceId || undefined,
|
|
|
|
|
|
|
| 153 |
};
|
| 154 |
|
| 155 |
const response = await createSessionSessionsPost({
|
|
|
|
| 17 |
CreateSessionResponse
|
| 18 |
} from '@robothub/inference-server-client';
|
| 19 |
|
| 20 |
+
export type ModelType = 'act' | 'diffusion' | 'smolvla' | 'pi0' | 'groot' | 'custom';
|
| 21 |
+
|
| 22 |
+
export interface ModelTypeConfig {
|
| 23 |
+
id: ModelType;
|
| 24 |
+
label: string;
|
| 25 |
+
icon: string;
|
| 26 |
+
description: string;
|
| 27 |
+
defaultPolicyPath: string;
|
| 28 |
+
defaultCameraNames: string[];
|
| 29 |
+
requiresLanguageInstruction?: boolean;
|
| 30 |
+
enabled: boolean;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export const MODEL_TYPES: Record<ModelType, ModelTypeConfig> = {
|
| 34 |
+
act: {
|
| 35 |
+
id: 'act',
|
| 36 |
+
label: 'ACT Model',
|
| 37 |
+
icon: 'icon-[mdi--brain]',
|
| 38 |
+
description: 'Action Chunking with Transformers',
|
| 39 |
+
defaultPolicyPath: 'LaetusH/act_so101_beyond',
|
| 40 |
+
defaultCameraNames: ['front'],
|
| 41 |
+
enabled: true
|
| 42 |
+
},
|
| 43 |
+
diffusion: {
|
| 44 |
+
id: 'diffusion',
|
| 45 |
+
label: 'Diffusion Policy',
|
| 46 |
+
icon: 'icon-[mdi--creation]',
|
| 47 |
+
description: 'Diffusion-based robot control',
|
| 48 |
+
defaultPolicyPath: 'diffusion_policy/default',
|
| 49 |
+
defaultCameraNames: ['front', 'wrist'],
|
| 50 |
+
enabled: true
|
| 51 |
+
},
|
| 52 |
+
smolvla: {
|
| 53 |
+
id: 'smolvla',
|
| 54 |
+
label: 'SmolVLA',
|
| 55 |
+
icon: 'icon-[mdi--eye-outline]',
|
| 56 |
+
description: 'Small Vision-Language-Action model',
|
| 57 |
+
defaultPolicyPath: 'smolvla/latest',
|
| 58 |
+
defaultCameraNames: ['front'],
|
| 59 |
+
requiresLanguageInstruction: true,
|
| 60 |
+
enabled: true
|
| 61 |
+
},
|
| 62 |
+
pi0: {
|
| 63 |
+
id: 'pi0',
|
| 64 |
+
label: 'Pi0',
|
| 65 |
+
icon: 'icon-[mdi--pi]',
|
| 66 |
+
description: 'Lightweight robotics model',
|
| 67 |
+
defaultPolicyPath: 'pi0/base',
|
| 68 |
+
defaultCameraNames: ['front'],
|
| 69 |
+
enabled: true
|
| 70 |
+
},
|
| 71 |
+
groot: {
|
| 72 |
+
id: 'groot',
|
| 73 |
+
label: 'NVIDIA Groot',
|
| 74 |
+
icon: 'icon-[mdi--robot-outline]',
|
| 75 |
+
description: 'Humanoid robotics foundation model',
|
| 76 |
+
defaultPolicyPath: 'nvidia/groot',
|
| 77 |
+
defaultCameraNames: ['front', 'left', 'right'],
|
| 78 |
+
requiresLanguageInstruction: true,
|
| 79 |
+
enabled: false // Not yet implemented
|
| 80 |
+
},
|
| 81 |
+
custom: {
|
| 82 |
+
id: 'custom',
|
| 83 |
+
label: 'Custom Model',
|
| 84 |
+
icon: 'icon-[mdi--cog]',
|
| 85 |
+
description: 'Custom model configuration',
|
| 86 |
+
defaultPolicyPath: '',
|
| 87 |
+
defaultCameraNames: ['front'],
|
| 88 |
+
enabled: true
|
| 89 |
+
}
|
| 90 |
+
};
|
| 91 |
+
|
| 92 |
export interface AISessionConfig {
|
| 93 |
sessionId: string;
|
| 94 |
+
modelType: ModelType;
|
| 95 |
policyPath: string;
|
| 96 |
cameraNames: string[];
|
| 97 |
transportServerUrl: string;
|
| 98 |
workspaceId?: string;
|
| 99 |
+
languageInstruction?: string;
|
| 100 |
}
|
| 101 |
|
| 102 |
export interface AISessionResponse {
|
|
|
|
| 158 |
}
|
| 159 |
|
| 160 |
/**
|
| 161 |
+
* Get available model types
|
| 162 |
+
*/
|
| 163 |
+
get availableModelTypes(): ModelTypeConfig[] {
|
| 164 |
+
return Object.values(MODEL_TYPES).filter(model => model.enabled);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
/**
|
| 168 |
+
* Get model type configuration
|
| 169 |
+
*/
|
| 170 |
+
getModelTypeConfig(modelType: ModelType): ModelTypeConfig | undefined {
|
| 171 |
+
return MODEL_TYPES[modelType];
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
/**
|
| 175 |
+
* Create a new AI compute instance with full configuration
|
| 176 |
+
*/
|
| 177 |
+
async createComputeWithSession(
|
| 178 |
+
config: AISessionConfig,
|
| 179 |
+
computeId?: string,
|
| 180 |
+
computeName?: string,
|
| 181 |
+
position?: Position3D
|
| 182 |
+
): Promise<{ success: boolean; error?: string; compute?: RemoteCompute }> {
|
| 183 |
+
const finalComputeId = computeId || generateName();
|
| 184 |
+
|
| 185 |
+
// Check if compute already exists
|
| 186 |
+
if (this._computes.find(c => c.id === finalComputeId)) {
|
| 187 |
+
return { success: false, error: `Compute with ID ${finalComputeId} already exists` };
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
try {
|
| 191 |
+
// Create compute instance
|
| 192 |
+
const compute = new RemoteCompute(finalComputeId, computeName);
|
| 193 |
+
compute.modelType = config.modelType;
|
| 194 |
+
|
| 195 |
+
// Set position (from position manager if not provided)
|
| 196 |
+
compute.position = position || positionManager.getNextPosition();
|
| 197 |
+
|
| 198 |
+
// Add to reactive array
|
| 199 |
+
this._computes.push(compute);
|
| 200 |
+
|
| 201 |
+
// Create the session immediately
|
| 202 |
+
const sessionResult = await this.createSession(compute.id, config);
|
| 203 |
+
if (!sessionResult.success) {
|
| 204 |
+
// Remove compute if session creation failed
|
| 205 |
+
await this.removeCompute(compute.id);
|
| 206 |
+
return { success: false, error: sessionResult.error };
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
console.log(`Created compute ${finalComputeId} with ${config.modelType} model at position (${compute.position.x.toFixed(1)}, ${compute.position.y.toFixed(1)}, ${compute.position.z.toFixed(1)}). Total computes: ${this._computes.length}`);
|
| 210 |
+
|
| 211 |
+
return { success: true, compute };
|
| 212 |
+
} catch (error) {
|
| 213 |
+
console.error('Failed to create compute with session:', error);
|
| 214 |
+
return {
|
| 215 |
+
success: false,
|
| 216 |
+
error: error instanceof Error ? error.message : String(error)
|
| 217 |
+
};
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
/**
|
| 222 |
+
* Create a new AI compute instance (legacy method)
|
| 223 |
*/
|
| 224 |
createCompute(id?: string, name?: string, position?: Position3D): RemoteCompute {
|
| 225 |
const computeId = id || generateName();
|
|
|
|
| 285 |
camera_names: config.cameraNames,
|
| 286 |
transport_server_url: config.transportServerUrl,
|
| 287 |
workspace_id: config.workspaceId || undefined,
|
| 288 |
+
policy_type: config.modelType, // Use model type as policy type
|
| 289 |
+
language_instruction: config.languageInstruction || undefined,
|
| 290 |
};
|
| 291 |
|
| 292 |
const response = await createSessionSessionsPost({
|
src/lib/elements/compute/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
export { RemoteComputeManager, remoteComputeManager } from './RemoteComputeManager.svelte.js';
|
| 2 |
export { RemoteCompute } from './RemoteCompute.svelte.js';
|
| 3 |
-
export type { AISessionConfig, AISessionResponse, AISessionStatus } from './RemoteComputeManager.svelte.js';
|
|
|
|
| 4 |
export type { ComputeStatus } from './RemoteCompute.svelte.js';
|
|
|
|
| 1 |
export { RemoteComputeManager, remoteComputeManager } from './RemoteComputeManager.svelte.js';
|
| 2 |
export { RemoteCompute } from './RemoteCompute.svelte.js';
|
| 3 |
+
export type { AISessionConfig, AISessionResponse, AISessionStatus, ModelType, ModelTypeConfig } from './RemoteComputeManager.svelte.js';
|
| 4 |
+
export { MODEL_TYPES } from './RemoteComputeManager.svelte.js';
|
| 5 |
export type { ComputeStatus } from './RemoteCompute.svelte.js';
|
src/lib/elements/robot/RobotManager.svelte.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { positionManager } from '$lib/utils/positionManager.js';
|
|
| 8 |
import { settings } from '$lib/runes/settings.svelte';
|
| 9 |
import { robotics } from '@robothub/transport-server-client';
|
| 10 |
import type { robotics as roboticsTypes } from '@robothub/transport-server-client';
|
|
|
|
| 11 |
|
| 12 |
export class RobotManager {
|
| 13 |
private _robots = $state<Robot[]>([]);
|
|
@@ -135,9 +136,7 @@ export class RobotManager {
|
|
| 135 |
*/
|
| 136 |
async createSO100Robot(id?: string, position?: Position3D): Promise<Robot> {
|
| 137 |
const robotId = id || `so100-${Date.now()}`;
|
| 138 |
-
const urdfConfig
|
| 139 |
-
urdfUrl: "/robots/so-100/so_arm100.urdf"
|
| 140 |
-
};
|
| 141 |
|
| 142 |
return this.createRobotFromUrdf(robotId, urdfConfig, position);
|
| 143 |
}
|
|
|
|
| 8 |
import { settings } from '$lib/runes/settings.svelte';
|
| 9 |
import { robotics } from '@robothub/transport-server-client';
|
| 10 |
import type { robotics as roboticsTypes } from '@robothub/transport-server-client';
|
| 11 |
+
import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
|
| 12 |
|
| 13 |
export class RobotManager {
|
| 14 |
private _robots = $state<Robot[]>([]);
|
|
|
|
| 136 |
*/
|
| 137 |
async createSO100Robot(id?: string, position?: Position3D): Promise<Robot> {
|
| 138 |
const robotId = id || `so100-${Date.now()}`;
|
| 139 |
+
const urdfConfig = robotUrdfConfigMap["so-arm100"];
|
|
|
|
|
|
|
| 140 |
|
| 141 |
return this.createRobotFromUrdf(robotId, urdfConfig, position);
|
| 142 |
}
|
src/lib/elements/robot/components/RobotItem.svelte
CHANGED
|
@@ -137,7 +137,6 @@
|
|
| 137 |
nameHeight={0.1}
|
| 138 |
showLine={isHovered || isSelected}
|
| 139 |
opacity={1}
|
| 140 |
-
isInteractive={false}
|
| 141 |
/>
|
| 142 |
{/each}
|
| 143 |
</T.Group>
|
|
|
|
| 137 |
nameHeight={0.1}
|
| 138 |
showLine={isHovered || isSelected}
|
| 139 |
opacity={1}
|
|
|
|
| 140 |
/>
|
| 141 |
{/each}
|
| 142 |
</T.Group>
|
src/lib/elements/video/VideoManager.svelte.ts
CHANGED
|
@@ -34,6 +34,8 @@ export class VideoInstance implements Positionable {
|
|
| 34 |
// Output state (what this video is broadcasting)
|
| 35 |
output = $state({
|
| 36 |
active: false,
|
|
|
|
|
|
|
| 37 |
client: null as videoTypes.VideoProducer | null,
|
| 38 |
roomId: null as string | null,
|
| 39 |
});
|
|
@@ -270,6 +272,8 @@ export class VideoManager {
|
|
| 270 |
|
| 271 |
// Update output state
|
| 272 |
video.output.active = true;
|
|
|
|
|
|
|
| 273 |
video.output.client = producer;
|
| 274 |
video.output.roomId = roomId;
|
| 275 |
|
|
@@ -528,6 +532,8 @@ export class VideoManager {
|
|
| 528 |
|
| 529 |
// Update output state
|
| 530 |
video.output.active = true;
|
|
|
|
|
|
|
| 531 |
video.output.client = producer;
|
| 532 |
video.output.roomId = result.roomId;
|
| 533 |
|
|
@@ -552,6 +558,8 @@ export class VideoManager {
|
|
| 552 |
}
|
| 553 |
|
| 554 |
video.output.active = false;
|
|
|
|
|
|
|
| 555 |
video.output.client = null;
|
| 556 |
video.output.roomId = null;
|
| 557 |
|
|
|
|
| 34 |
// Output state (what this video is broadcasting)
|
| 35 |
output = $state({
|
| 36 |
active: false,
|
| 37 |
+
type: null as 'recording' | 'remote-broadcast' | null,
|
| 38 |
+
stream: null as MediaStream | null,
|
| 39 |
client: null as videoTypes.VideoProducer | null,
|
| 40 |
roomId: null as string | null,
|
| 41 |
});
|
|
|
|
| 272 |
|
| 273 |
// Update output state
|
| 274 |
video.output.active = true;
|
| 275 |
+
video.output.type = 'remote-broadcast';
|
| 276 |
+
video.output.stream = video.input.stream;
|
| 277 |
video.output.client = producer;
|
| 278 |
video.output.roomId = roomId;
|
| 279 |
|
|
|
|
| 532 |
|
| 533 |
// Update output state
|
| 534 |
video.output.active = true;
|
| 535 |
+
video.output.type = 'remote-broadcast';
|
| 536 |
+
video.output.stream = video.input.stream;
|
| 537 |
video.output.client = producer;
|
| 538 |
video.output.roomId = result.roomId;
|
| 539 |
|
|
|
|
| 558 |
}
|
| 559 |
|
| 560 |
video.output.active = false;
|
| 561 |
+
video.output.type = null;
|
| 562 |
+
video.output.stream = null;
|
| 563 |
video.output.client = null;
|
| 564 |
video.output.roomId = null;
|
| 565 |
|
src/lib/types/urdf.ts
CHANGED
|
@@ -23,4 +23,9 @@ export type RobotUrdfConfig = {
|
|
| 23 |
restPosition?: {
|
| 24 |
[jointName: string]: number;
|
| 25 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
};
|
|
|
|
| 23 |
restPosition?: {
|
| 24 |
[jointName: string]: number;
|
| 25 |
};
|
| 26 |
+
// Display metadata for UI
|
| 27 |
+
displayName?: string;
|
| 28 |
+
description?: string;
|
| 29 |
+
icon?: string;
|
| 30 |
+
isDefault?: boolean;
|
| 31 |
};
|