Spaces:
Running
Running
<script lang="ts"> | |
import { onMount } from 'svelte'; | |
import { robotics, video } from 'lerobot-arena-client'; | |
import type { robotics as roboticsTypes, video as videoTypes } from 'lerobot-arena-client'; | |
// Get data from load function | |
let { data } = $props(); | |
let workspaceId = data.workspaceId; | |
// State | |
let roboticsRooms = $state<roboticsTypes.RoomInfo[]>([]); | |
let videoRooms = $state<videoTypes.RoomInfo[]>([]); | |
let loading = $state<boolean>(true); | |
let roboticsError = $state<string>(''); | |
let videoError = $state<string>(''); | |
let roboticsClient: robotics.RoboticsClientCore; | |
let videoClient: video.VideoClientCore; | |
// Debug info | |
let debugInfo = $state<{ | |
lastRefresh: string; | |
refreshCount: number; | |
responseTime: number; | |
apiCalls: number; | |
}>({ | |
lastRefresh: '', | |
refreshCount: 0, | |
responseTime: 0, | |
apiCalls: 0 | |
}); | |
async function loadRooms() { | |
const startTime = Date.now(); | |
debugInfo.refreshCount++; | |
debugInfo.apiCalls += 2; | |
try { | |
loading = true; | |
roboticsError = ''; | |
videoError = ''; | |
// Load robotics rooms | |
try { | |
roboticsClient = new robotics.RoboticsClientCore('http://localhost:8000'); | |
roboticsRooms = await roboticsClient.listRooms(workspaceId); | |
} catch (err) { | |
roboticsError = 'Failed to load robotics rooms'; | |
console.error('Failed to load robotics rooms:', err); | |
} | |
// Load video rooms | |
try { | |
videoClient = new video.VideoClientCore('http://localhost:8000'); | |
videoRooms = await videoClient.listRooms(workspaceId); | |
} catch (err) { | |
videoError = 'Failed to load video rooms'; | |
console.error('Failed to load video rooms:', err); | |
} | |
debugInfo.responseTime = Date.now() - startTime; | |
} finally { | |
loading = false; | |
debugInfo.lastRefresh = new Date().toLocaleTimeString(); | |
} | |
} | |
async function createRoboticsRoom() { | |
const roomId = prompt('Enter robotics room ID:'); | |
if (!roomId?.trim()) return; | |
debugInfo.apiCalls++; | |
try { | |
await roboticsClient.createRoom(workspaceId, roomId); | |
await loadRooms(); | |
} catch (err) { | |
alert('Failed to create robotics room. It might already exist.'); | |
console.error('Failed to create robotics room:', err); | |
} | |
} | |
async function createVideoRoom() { | |
const roomId = prompt('Enter video room ID:'); | |
if (!roomId?.trim()) return; | |
debugInfo.apiCalls++; | |
try { | |
await videoClient.createRoom(workspaceId, roomId); | |
await loadRooms(); | |
} catch (err) { | |
alert('Failed to create video room. It might already exist.'); | |
console.error('Failed to create video room:', err); | |
} | |
} | |
onMount(() => { | |
loadRooms(); | |
// Refresh rooms every 10 seconds | |
const interval = setInterval(loadRooms, 10000); | |
return () => clearInterval(interval); | |
}); | |
</script> | |
<svelte:head> | |
<title>Workspace {workspaceId} - LeRobot Arena</title> | |
</svelte:head> | |
<div class="mx-auto max-w-7xl"> | |
<!-- Header --> | |
<div class="mb-6 flex items-center justify-between"> | |
<div> | |
<h1 class="font-mono text-2xl font-bold text-gray-900">๐ Workspace Dashboard</h1> | |
<p class="mt-1 font-mono text-sm text-gray-600"> | |
Workspace: <span class="font-bold text-blue-600">{workspaceId}</span> | |
| Manage robotics and video rooms | |
</p> | |
</div> | |
<div class="flex space-x-3"> | |
<button | |
onclick={loadRooms} | |
disabled={loading} | |
class={[ | |
'rounded border px-3 py-2 font-mono text-sm', | |
loading ? 'bg-gray-100 text-gray-500' : 'bg-gray-100 hover:bg-gray-200' | |
]} | |
> | |
{loading ? '๐ Loading...' : '๐ Refresh'} | |
</button> | |
<a href="/" class="rounded border bg-gray-100 px-3 py-2 font-mono text-sm hover:bg-gray-200"> | |
โ All Workspaces | |
</a> | |
</div> | |
</div> | |
<!-- Debug Info --> | |
<div class="mb-6 rounded border bg-gray-900 p-4 font-mono text-sm text-green-400"> | |
<div class="mb-2 font-bold">WORKSPACE DEBUG - {workspaceId}</div> | |
<div class="grid grid-cols-2 gap-4 md:grid-cols-4"> | |
<div>Last Refresh: {debugInfo.lastRefresh}</div> | |
<div>Refresh Count: {debugInfo.refreshCount}</div> | |
<div>API Calls: {debugInfo.apiCalls}</div> | |
<div>Response Time: {debugInfo.responseTime}ms</div> | |
</div> | |
<div class="mt-2"> | |
Robotics Rooms: {roboticsRooms.length} | Video Rooms: {videoRooms.length} | Loading: {loading | |
? 'YES' | |
: 'NO'} | |
</div> | |
{#if roboticsError || videoError} | |
<div class="mt-2 text-red-400"> | |
{#if roboticsError}Robotics Error: {roboticsError}{/if} | |
{#if videoError}Video Error: {videoError}{/if} | |
</div> | |
{/if} | |
</div> | |
<!-- Quick Actions --> | |
<div class="mb-6 rounded border p-4"> | |
<h2 class="mb-4 font-mono text-lg font-semibold">๐ Quick Actions</h2> | |
<div class="grid grid-cols-2 gap-3 md:grid-cols-4"> | |
<a | |
href="/{workspaceId}/robotics" | |
class={[ | |
'rounded border px-4 py-2 text-center font-mono', | |
'bg-blue-600 text-white hover:bg-blue-700' | |
]} | |
> | |
๐ค Robotics | |
</a> | |
<a | |
href="/{workspaceId}/video" | |
class={[ | |
'rounded border px-4 py-2 text-center font-mono', | |
'bg-purple-600 text-white hover:bg-purple-700' | |
]} | |
> | |
๐น Video | |
</a> | |
<a | |
href="/{workspaceId}/robotics/producer" | |
class={['rounded border px-4 py-2 text-center font-mono', 'bg-green-100 hover:bg-green-200']} | |
> | |
๐ฎ Robot Producer | |
</a> | |
<a | |
href="/{workspaceId}/video/producer" | |
class={['rounded border px-4 py-2 text-center font-mono', 'bg-pink-100 hover:bg-pink-200']} | |
> | |
๐น Video Producer | |
</a> | |
</div> | |
</div> | |
<!-- Rooms Overview --> | |
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2"> | |
<!-- Robotics Rooms --> | |
<div class="rounded border p-4"> | |
<div class="mb-4 flex items-center justify-between"> | |
<h2 class="font-mono text-lg font-semibold">๐ค Robotics Rooms</h2> | |
<div class="flex space-x-2"> | |
<button | |
onclick={createRoboticsRoom} | |
class="rounded border bg-blue-100 px-3 py-1 font-mono text-sm text-blue-700 hover:bg-blue-200" | |
> | |
โ Create | |
</button> | |
<a | |
href="/{workspaceId}/robotics" | |
class="rounded border bg-gray-100 px-3 py-1 font-mono text-sm hover:bg-gray-200" | |
> | |
View All | |
</a> | |
</div> | |
</div> | |
{#if roboticsError} | |
<div class="rounded border border-red-200 bg-red-50 p-4"> | |
<p class="font-mono text-sm text-red-700">{roboticsError}</p> | |
</div> | |
{:else if roboticsRooms.length === 0} | |
<div class="py-8 text-center"> | |
<div class="mb-2 text-4xl text-gray-400">๐ค</div> | |
<p class="font-mono text-gray-500">No robotics rooms yet</p> | |
<button | |
onclick={createRoboticsRoom} | |
class="mt-2 rounded border bg-blue-600 px-3 py-1 font-mono text-sm text-white hover:bg-blue-700" | |
> | |
Create First Room | |
</button> | |
</div> | |
{:else} | |
<div class="space-y-3"> | |
{#each roboticsRooms.slice(0, 3) as room} | |
<div class="rounded border bg-gray-50 p-3"> | |
<div class="flex items-center justify-between"> | |
<div> | |
<h3 class="font-mono font-medium">{room.id}</h3> | |
<p class="font-mono text-sm text-gray-600"> | |
๐ฅ {room.participants.total} participants | |
</p> | |
</div> | |
<div class="flex space-x-2"> | |
<a | |
href="/{workspaceId}/robotics/consumer?room={room.id}" | |
class="rounded border bg-blue-100 px-2 py-1 font-mono text-xs text-blue-700 hover:bg-blue-200" | |
> | |
๐๏ธ Monitor | |
</a> | |
{#if !room.participants.producer} | |
<a | |
href="/{workspaceId}/robotics/producer?room={room.id}" | |
class="rounded border bg-green-100 px-2 py-1 font-mono text-xs text-green-700 hover:bg-green-200" | |
> | |
๐ฎ Control | |
</a> | |
{/if} | |
</div> | |
</div> | |
</div> | |
{/each} | |
{#if roboticsRooms.length > 3} | |
<div class="text-center"> | |
<a | |
href="/{workspaceId}/robotics" | |
class="font-mono text-sm text-blue-600 hover:text-blue-800" | |
> | |
... and {roboticsRooms.length - 3} more | |
</a> | |
</div> | |
{/if} | |
</div> | |
{/if} | |
</div> | |
<!-- Video Rooms --> | |
<div class="rounded border p-4"> | |
<div class="mb-4 flex items-center justify-between"> | |
<h2 class="font-mono text-lg font-semibold">๐น Video Rooms</h2> | |
<div class="flex space-x-2"> | |
<button | |
onclick={createVideoRoom} | |
class="rounded border bg-purple-100 px-3 py-1 font-mono text-sm text-purple-700 hover:bg-purple-200" | |
> | |
โ Create | |
</button> | |
<a | |
href="/{workspaceId}/video" | |
class="rounded border bg-gray-100 px-3 py-1 font-mono text-sm hover:bg-gray-200" | |
> | |
View All | |
</a> | |
</div> | |
</div> | |
{#if videoError} | |
<div class="rounded border border-red-200 bg-red-50 p-4"> | |
<p class="font-mono text-sm text-red-700">{videoError}</p> | |
</div> | |
{:else if videoRooms.length === 0} | |
<div class="py-8 text-center"> | |
<div class="mb-2 text-4xl text-gray-400">๐น</div> | |
<p class="font-mono text-gray-500">No video rooms yet</p> | |
<button | |
onclick={createVideoRoom} | |
class="mt-2 rounded border bg-purple-600 px-3 py-1 font-mono text-sm text-white hover:bg-purple-700" | |
> | |
Create First Room | |
</button> | |
</div> | |
{:else} | |
<div class="space-y-3"> | |
{#each videoRooms.slice(0, 3) as room} | |
<div class="rounded border bg-gray-50 p-3"> | |
<div class="flex items-center justify-between"> | |
<div> | |
<h3 class="font-mono font-medium">{room.id}</h3> | |
<p class="font-mono text-sm text-gray-600"> | |
๐ฅ {room.participants.total} participants | |
{#if room.participants.producer} | |
| ๐ด Streaming | |
{/if} | |
</p> | |
</div> | |
<div class="flex space-x-2"> | |
<a | |
href="/{workspaceId}/video/consumer?room={room.id}" | |
class="rounded border bg-purple-100 px-2 py-1 font-mono text-xs text-purple-700 hover:bg-purple-200" | |
> | |
๐บ Watch | |
</a> | |
{#if !room.participants.producer} | |
<a | |
href="/{workspaceId}/video/producer?room={room.id}" | |
class="rounded border bg-pink-100 px-2 py-1 font-mono text-xs text-pink-700 hover:bg-pink-200" | |
> | |
๐น Stream | |
</a> | |
{/if} | |
</div> | |
</div> | |
</div> | |
{/each} | |
{#if videoRooms.length > 3} | |
<div class="text-center"> | |
<a | |
href="/{workspaceId}/video" | |
class="font-mono text-sm text-purple-600 hover:text-purple-800" | |
> | |
... and {videoRooms.length - 3} more | |
</a> | |
</div> | |
{/if} | |
</div> | |
{/if} | |
</div> | |
</div> | |
</div> |