Spaces:
Running
Running
<script lang="ts"> | |
import * as Dialog from "@/components/ui/dialog"; | |
import { Button } from "@/components/ui/button"; | |
import * as Card from "@/components/ui/card"; | |
import * as Alert from "@/components/ui/alert"; | |
import { Badge } from "@/components/ui/badge"; | |
import { Separator } from "@/components/ui/separator"; | |
import { toast } from "svelte-sonner"; | |
import { robotManager } from "$lib/robot/RobotManager.svelte"; | |
import { getApiBaseUrl, getWebSocketBaseUrl } from "$lib/utils/config"; | |
import type { Robot } from "$lib/robot/Robot.svelte"; | |
interface Props { | |
open: boolean; | |
robot: Robot | null; | |
} | |
let { open = $bindable(), robot }: Props = $props(); | |
// Robot selection modal state for server slaves | |
let showRobotSelectionModal = $state(false); | |
let availableServerRobots = $state<{ id: string; name: string; robot_type: string }[]>([]); | |
let selectedServerRobotId = $state<string>(""); | |
// Get URLs from configuration | |
const apiBaseUrl = getApiBaseUrl(); | |
const wsBaseUrl = getWebSocketBaseUrl(); | |
// Slave connection functions | |
async function connectMockSlave() { | |
if (!robot) return; | |
try { | |
await robotManager.connectMockSlave(robot.id, 50); | |
} catch (err) { | |
toast.error("Failed to Connect Mock Slave", { | |
description: `Could not connect mock slave: ${err}` | |
}); | |
console.error(err); | |
} | |
} | |
async function connectUSBSlave() { | |
if (!robot) return; | |
try { | |
await robotManager.connectUSBSlave(robot.id); | |
} catch (err) { | |
toast.error("Failed to Connect USB Slave", { | |
description: `Could not connect USB slave: ${err}` | |
}); | |
console.error(err); | |
} | |
} | |
async function connectRemoteServerSlave() { | |
if (!robot) return; | |
try { | |
// First, fetch available robots from the server | |
const response = await fetch(`${apiBaseUrl}/api/robots`); | |
if (!response.ok) { | |
throw new Error(`Server responded with ${response.status}: ${response.statusText}`); | |
} | |
const robots = await response.json(); | |
if (robots.length === 0) { | |
toast.error("No Server Robots Available", { | |
description: "No robots available on the server. Create a robot on the server first." | |
}); | |
return; | |
} | |
// Show modal for robot selection | |
availableServerRobots = robots; | |
selectedServerRobotId = robots[0]?.id || ""; | |
showRobotSelectionModal = true; | |
} catch (err) { | |
toast.error("Failed to Fetch Server Robots", { | |
description: `Could not fetch server robots: ${err}` | |
}); | |
console.error(err); | |
} | |
} | |
async function confirmRobotSelection() { | |
if (!robot || !selectedServerRobotId) return; | |
try { | |
await robotManager.connectRemoteServerSlave( | |
robot.id, | |
wsBaseUrl, | |
undefined, | |
selectedServerRobotId | |
); | |
// Close modal | |
showRobotSelectionModal = false; | |
} catch (err) { | |
toast.error("Failed to Connect Remote Server Slave", { | |
description: `Could not connect remote server slave: ${err}` | |
}); | |
console.error(err); | |
} | |
} | |
function cancelRobotSelection() { | |
showRobotSelectionModal = false; | |
selectedServerRobotId = ""; | |
} | |
async function disconnectSlave(slaveId: string) { | |
if (!robot) return; | |
try { | |
await robotManager.disconnectSlave(robot.id, slaveId); | |
} catch (err) { | |
toast.error("Failed to Disconnect Slave", { | |
description: `Could not disconnect slave: ${err}` | |
}); | |
console.error(err); | |
} | |
} | |
</script> | |
<Dialog.Root bind:open> | |
<Dialog.Content | |
class="max-h-[80vh] max-w-xl overflow-y-auto border-slate-600 bg-slate-900 text-slate-100" | |
> | |
<Dialog.Header class="pb-3"> | |
<Dialog.Title class="flex items-center gap-2 text-lg font-bold text-slate-100"> | |
<span class="icon-[mdi--devices] size-5 text-blue-400"></span> | |
Slave Connection | |
</Dialog.Title> | |
<Dialog.Description class="text-sm text-slate-400"> | |
Configure slave targets for robot {robot?.id} | |
</Dialog.Description> | |
</Dialog.Header> | |
{#if robot} | |
<div class="space-y-4"> | |
<!-- Current Status - Compact --> | |
<div | |
class="flex items-center justify-between rounded-lg border border-blue-500/30 bg-blue-900/20 p-3" | |
> | |
<div class="flex items-center gap-2"> | |
<span class="icon-[fa6-solid--ear-listen] size-4 text-blue-400"></span> | |
<span class="text-sm font-medium text-blue-300">Slave Status</span> | |
</div> | |
<Badge variant="default" class="bg-blue-600 text-xs"> | |
{robot.connectedSlaves.length} / {robot.slaves.length} Connected | |
</Badge> | |
</div> | |
<!-- Slave Controls --> | |
<Card.Root class="border-blue-500/30 bg-blue-500/5"> | |
<Card.Header class="pb-2"> | |
<Card.Title class="flex items-center gap-2 text-base text-blue-200"> | |
<span class="icon-[mdi--devices] size-4"></span> | |
Connection Options | |
</Card.Title> | |
</Card.Header> | |
<Card.Content class="space-y-3"> | |
<div class="space-y-2"> | |
<Button | |
variant="secondary" | |
onclick={connectMockSlave} | |
class="h-8 w-full bg-yellow-600 text-sm text-white hover:bg-yellow-700" | |
> | |
<span class="icon-[mdi--robot-confused] mr-2 size-4"></span> | |
Add Mock Slave | |
</Button> | |
<Button | |
variant="secondary" | |
onclick={connectUSBSlave} | |
class="h-8 w-full bg-green-600 text-sm text-white hover:bg-green-700" | |
> | |
<span class="icon-[mdi--usb] mr-2 size-4"></span> | |
Add USB Slave | |
</Button> | |
<Button | |
variant="secondary" | |
onclick={connectRemoteServerSlave} | |
class="h-8 w-full bg-purple-600 text-sm text-white hover:bg-purple-700" | |
> | |
<span class="icon-[mdi--cloud] mr-2 size-4"></span> | |
Add Remote Server Slave | |
</Button> | |
</div> | |
{#if robot.slaves.length > 0} | |
<Separator /> | |
<div class="space-y-2"> | |
<p class="text-xs font-medium text-blue-300">Connected Slaves:</p> | |
<div class="max-h-32 space-y-1 overflow-y-auto"> | |
{#each robot.slaves as slave} | |
<div class="flex items-center justify-between rounded-md bg-slate-700/50 p-2"> | |
<div class="flex items-center gap-2"> | |
<span class="icon-[mdi--circle] size-2 text-green-400"></span> | |
<span class="text-sm text-slate-300">{slave.name}</span> | |
<Badge variant="outline" class="text-xs">{slave.id.slice(0, 8)}</Badge> | |
</div> | |
<Button | |
variant="destructive" | |
size="sm" | |
onclick={() => disconnectSlave(slave.id)} | |
class="h-6 px-2 text-xs" | |
> | |
<span class="icon-[mdi--close] size-3"></span> | |
</Button> | |
</div> | |
{/each} | |
</div> | |
</div> | |
{/if} | |
</Card.Content> | |
</Card.Root> | |
<!-- Quick Info --> | |
<div class="rounded border border-slate-700 bg-slate-800/30 p-2 text-xs text-slate-500"> | |
<span class="icon-[mdi--information] mr-1 size-3"></span> | |
Slaves are output targets. Multiple can be connected simultaneously. | |
</div> | |
</div> | |
{/if} | |
</Dialog.Content> | |
</Dialog.Root> | |
<!-- Robot Selection Modal for Remote Server Slaves --> | |
{#if showRobotSelectionModal} | |
<Dialog.Root open={true}> | |
<Dialog.Content class="max-w-md border-slate-600 bg-slate-900 text-slate-100"> | |
<Dialog.Header> | |
<Dialog.Title class="flex items-center gap-2 text-slate-100"> | |
<span class="icon-[ix--robotic-arm] size-5"></span> | |
Select Server Robot | |
</Dialog.Title> | |
<Dialog.Description class="text-slate-400"> | |
Choose which server robot to connect as a slave target | |
</Dialog.Description> | |
</Dialog.Header> | |
<div class="space-y-4"> | |
<div class="space-y-2"> | |
<label for="server-robot-select" class="text-sm font-medium text-slate-300" | |
>Available robots on server:</label | |
> | |
<select | |
bind:value={selectedServerRobotId} | |
class="w-full rounded-md border border-slate-600 bg-slate-700 px-3 py-2 text-sm text-slate-100" | |
id="server-robot-select" | |
> | |
{#each availableServerRobots as serverRobot} | |
<option value={serverRobot.id}> | |
{serverRobot.name} ({serverRobot.id}) - {serverRobot.robot_type} | |
</option> | |
{/each} | |
</select> | |
</div> | |
<Alert.Root> | |
<span class="icon-[mdi--information] size-4"></span> | |
<Alert.Description> | |
This will connect your local robot <strong>"{robot?.id}"</strong> as a slave to receive commands | |
from the selected server robot. | |
</Alert.Description> | |
</Alert.Root> | |
</div> | |
<Dialog.Footer class="flex justify-end gap-3"> | |
<Button variant="outline" onclick={cancelRobotSelection}>Cancel</Button> | |
<Button | |
onclick={confirmRobotSelection} | |
disabled={!selectedServerRobotId} | |
class="bg-purple-600 hover:bg-purple-700" | |
> | |
<span class="icon-[mdi--link] mr-1 size-4"></span> | |
Connect Slave | |
</Button> | |
</Dialog.Footer> | |
</Dialog.Content> | |
</Dialog.Root> | |
{/if} | |