LeRobot-Arena / src /lib /components /interface /overlay /MasterConnectionModal.svelte
blanchon's picture
Mostly UI Update
18b0fa5
<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 { toast } from "svelte-sonner";
import { robotManager } from "$lib/robot/RobotManager.svelte";
import { 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();
// Get URLs from configuration
const wsBaseUrl = getWebSocketBaseUrl();
// Master connection functions
async function connectDemoSequences() {
if (!robot) return;
try {
await robotManager.connectDemoSequences(robot.id);
} catch (err) {
toast.error("Failed to Connect Demo Sequences", {
description: `Could not connect demo sequences: ${err}`
});
console.error(err);
}
}
async function connectRemoteServerMaster() {
if (!robot) return;
try {
const config: import("$lib/types/robotDriver").MasterDriverConfig = {
type: "remote-server",
url: wsBaseUrl,
apiKey: undefined,
pollInterval: 100
};
await robotManager.connectMaster(robot.id, config);
} catch (err) {
toast.error("Failed to Connect Remote Server", {
description: `Could not connect remote server: ${err}`
});
console.error(err);
}
}
async function connectUSBMaster() {
if (!robot) return;
try {
await robotManager.connectUSBMaster(robot.id, {
pollInterval: 200,
smoothing: true
});
} catch (err) {
toast.error("Failed to Connect USB Master", {
description: `Could not connect USB master: ${err}`
});
console.error(err);
}
}
async function disconnectMaster() {
if (!robot) return;
try {
await robotManager.disconnectMaster(robot.id);
} catch (err) {
toast.error("Failed to Disconnect Master", {
description: `Could not disconnect master: ${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--account-supervisor] size-5 text-green-400"></span>
Master Connection
</Dialog.Title>
<Dialog.Description class="text-sm text-slate-400">
Configure master sources 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-green-500/30 bg-green-900/20 p-3"
>
<div class="flex items-center gap-2">
<span class="icon-[mdi--speak] size-4 text-green-400"></span>
<span class="text-sm font-medium text-green-300">Master Status</span>
</div>
{#if robot.controlState.hasActiveMaster}
<Badge variant="default" class="bg-green-600 text-xs">
{robot.controlState.masterName}
</Badge>
{:else}
<Badge variant="outline" class="text-xs text-slate-400">None Connected</Badge>
{/if}
</div>
<!-- Master Controls -->
<Card.Root class="border-green-500/30 bg-green-500/5">
<Card.Header>
<Card.Title class="flex items-center gap-2 text-base text-green-200">
<span class="icon-[mdi--account-supervisor] size-4"></span>
Connection Options
</Card.Title>
</Card.Header>
<Card.Content class="space-y-2">
{#if robot.controlState.hasActiveMaster}
<div class="rounded-lg border border-green-500/30 bg-green-900/20 p-2">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-green-300">
{robot.controlState.masterName}
</p>
<p class="text-xs text-green-400/70">Currently active</p>
</div>
<Button
variant="destructive"
size="sm"
onclick={disconnectMaster}
class="h-7 px-2 text-xs"
>
<span class="icon-[mdi--close-circle] mr-1 size-3"></span>
Disconnect
</Button>
</div>
</div>
{:else}
<div class="space-y-2">
<Button
variant="secondary"
onclick={connectDemoSequences}
class="h-8 w-full bg-orange-600 text-sm text-white hover:bg-orange-700"
>
<span class="icon-[mdi--play-circle] mr-2 size-4"></span>
Demo Sequences
</Button>
<Button
variant="secondary"
onclick={connectRemoteServerMaster}
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>
Remote Server
</Button>
<Button
variant="secondary"
onclick={connectUSBMaster}
class="h-8 w-full bg-blue-600 text-sm text-white hover:bg-blue-700"
>
<span class="icon-[mdi--usb] mr-2 size-4"></span>
USB Master
</Button>
</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>
Masters are input sources. Only one can be active at a time.
</div>
</div>
{/if}
</Dialog.Content>
</Dialog.Root>