Spaces:
Running
Running
<script lang="ts"> | |
import { onMount } from 'svelte'; | |
import { robotics } from 'lerobot-arena-client'; | |
import type { robotics as roboticsTypes } from 'lerobot-arena-client'; | |
import { goto } from '$app/navigation'; | |
// Server status | |
let serverStatus = $state<'checking' | 'connected' | 'error'>('checking'); | |
let serverInfo = $state<{ rooms: number; version: string }>({ rooms: 0, version: 'Unknown' }); | |
let rooms = $state<roboticsTypes.RoomInfo[]>([]); | |
let lastError = $state<string>(''); | |
let debugInfo = $state<{ | |
lastCheck: string; | |
connectionAttempts: number; | |
responseTime: number; | |
}>({ | |
lastCheck: '', | |
connectionAttempts: 0, | |
responseTime: 0 | |
}); | |
// Workspace management | |
let workspaceInput = $state<string>(''); | |
let recentWorkspaces = $state<string[]>([]); | |
let error = $state<string>(''); | |
async function checkServerStatus() { | |
const startTime = Date.now(); | |
debugInfo.connectionAttempts++; | |
try { | |
// Use relative URL so it works in both development and production | |
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'http://localhost:7860'; | |
const apiUrl = `${baseUrl}/api`; | |
const client = new robotics.RoboticsClientCore(apiUrl); | |
const roomList = await client.listRooms(''); | |
rooms = roomList; | |
serverInfo = { rooms: roomList.length, version: '1.0.0' }; | |
serverStatus = 'connected'; | |
lastError = ''; | |
debugInfo.responseTime = Date.now() - startTime; | |
} catch (error) { | |
console.error('Server check failed:', error); | |
serverStatus = 'error'; | |
lastError = error instanceof Error ? error.message : String(error); | |
debugInfo.responseTime = Date.now() - startTime; | |
} | |
debugInfo.lastCheck = new Date().toLocaleTimeString(); | |
} | |
// Generate a new workspace ID | |
function generateWorkspaceId(): string { | |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | |
const r = Math.random() * 16 | 0; | |
const v = c === 'x' ? r : (r & 0x3 | 0x8); | |
return v.toString(16); | |
}); | |
} | |
// Load recent workspaces from localStorage | |
function loadRecentWorkspaces() { | |
try { | |
const stored = localStorage.getItem('lerobot-arena-workspaces'); | |
if (stored) { | |
recentWorkspaces = JSON.parse(stored); | |
} | |
} catch (err) { | |
console.warn('Failed to load recent workspaces:', err); | |
} | |
} | |
// Save workspace to recent list | |
function saveToRecentWorkspaces(workspaceId: string) { | |
const updated = [workspaceId, ...recentWorkspaces.filter(id => id !== workspaceId)].slice(0, 10); | |
recentWorkspaces = updated; | |
try { | |
localStorage.setItem('lerobot-arena-workspaces', JSON.stringify(updated)); | |
} catch (err) { | |
console.warn('Failed to save recent workspaces:', err); | |
} | |
} | |
// Create new workspace | |
function createNewWorkspace() { | |
const newWorkspaceId = generateWorkspaceId(); | |
saveToRecentWorkspaces(newWorkspaceId); | |
goto(`/${newWorkspaceId}`); | |
} | |
// Join existing workspace | |
function joinWorkspace() { | |
if (!workspaceInput.trim()) { | |
error = 'Please enter a workspace ID'; | |
return; | |
} | |
const workspaceId = workspaceInput.trim(); | |
saveToRecentWorkspaces(workspaceId); | |
goto(`/${workspaceId}`); | |
} | |
// Join workspace from recent list | |
function joinRecentWorkspace(workspaceId: string) { | |
saveToRecentWorkspaces(workspaceId); // Move to top of recent list | |
goto(`/${workspaceId}`); | |
} | |
// Remove workspace from recent list | |
function removeRecentWorkspace(workspaceId: string) { | |
recentWorkspaces = recentWorkspaces.filter(id => id !== workspaceId); | |
try { | |
localStorage.setItem('lerobot-arena-workspaces', JSON.stringify(recentWorkspaces)); | |
} catch (err) { | |
console.warn('Failed to save recent workspaces:', err); | |
} | |
} | |
onMount(() => { | |
checkServerStatus(); | |
// Check server status every 10 seconds | |
const interval = setInterval(checkServerStatus, 10000); | |
loadRecentWorkspaces(); | |
return () => clearInterval(interval); | |
}); | |
</script> | |
<svelte:head> | |
<title>LeRobot Arena - Workspace Selection</title> | |
</svelte:head> | |
<div class="mx-auto max-w-4xl p-4"> | |
<!-- Header --> | |
<div class="mb-8 text-center"> | |
<h1 class="font-mono text-3xl font-bold text-gray-900">π€ LeRobot Arena</h1> | |
<p class="mt-2 font-mono text-lg text-gray-600"> | |
Real-time robotics control and monitoring platform | |
</p> | |
<p class="mt-1 font-mono text-sm text-gray-500"> | |
Select or create a workspace to get started | |
</p> | |
</div> | |
<!-- Workspace Selection --> | |
<div class="mb-8 space-y-6"> | |
<!-- Create New Workspace --> | |
<div class="rounded border p-6"> | |
<div class="mb-4 text-center"> | |
<h2 class="font-mono text-xl font-semibold text-gray-900">Create New Workspace</h2> | |
<p class="mt-1 font-mono text-sm text-gray-600"> | |
Start fresh with a new isolated workspace | |
</p> | |
</div> | |
<div class="text-center"> | |
<button | |
onclick={createNewWorkspace} | |
class="rounded border bg-blue-600 px-6 py-3 font-mono text-white hover:bg-blue-700" | |
> | |
β¨ Generate New Workspace | |
</button> | |
</div> | |
</div> | |
<!-- Join Existing Workspace --> | |
<div class="rounded border p-6"> | |
<div class="mb-4 text-center"> | |
<h2 class="font-mono text-xl font-semibold text-gray-900">Join Existing Workspace</h2> | |
<p class="mt-1 font-mono text-sm text-gray-600"> | |
Enter a workspace ID to join an existing session | |
</p> | |
</div> | |
<div class="mx-auto max-w-md space-y-4"> | |
<div> | |
<label for="workspaceInput" class="mb-1 block font-mono text-sm font-medium text-gray-700"> | |
Workspace ID | |
</label> | |
<input | |
id="workspaceInput" | |
type="text" | |
bind:value={workspaceInput} | |
placeholder="Enter workspace ID (UUID format)" | |
class="w-full rounded border border-gray-300 px-3 py-2 font-mono focus:border-blue-500 focus:ring-blue-500" | |
onkeydown={(e) => { | |
if (e.key === 'Enter') { | |
joinWorkspace(); | |
} | |
}} | |
/> | |
</div> | |
<button | |
onclick={joinWorkspace} | |
disabled={!workspaceInput.trim()} | |
class={[ | |
'w-full rounded border px-4 py-2 font-mono', | |
workspaceInput.trim() | |
? 'bg-green-600 text-white hover:bg-green-700' | |
: 'bg-gray-200 text-gray-500' | |
]} | |
> | |
π Join Workspace | |
</button> | |
{#if error} | |
<div class="mt-2 rounded border border-red-200 bg-red-50 p-3"> | |
<p class="font-mono text-sm text-red-700">{error}</p> | |
</div> | |
{/if} | |
</div> | |
</div> | |
<!-- Recent Workspaces --> | |
{#if recentWorkspaces.length > 0} | |
<div class="rounded border p-6"> | |
<div class="mb-4 text-center"> | |
<h2 class="font-mono text-xl font-semibold text-gray-900">Recent Workspaces</h2> | |
<p class="mt-1 font-mono text-sm text-gray-600"> | |
Quick access to your previously used workspaces | |
</p> | |
</div> | |
<div class="space-y-3"> | |
{#each recentWorkspaces as workspaceId} | |
<div class="flex items-center justify-between rounded border bg-gray-50 p-3"> | |
<div> | |
<div class="font-mono text-sm font-medium">{workspaceId}</div> | |
<div class="font-mono text-xs text-gray-500">UUID Workspace</div> | |
</div> | |
<div class="flex space-x-2"> | |
<button | |
onclick={() => joinRecentWorkspace(workspaceId)} | |
class="rounded border bg-blue-100 px-3 py-1 font-mono text-sm text-blue-700 hover:bg-blue-200" | |
> | |
π Open | |
</button> | |
<button | |
onclick={() => removeRecentWorkspace(workspaceId)} | |
class="rounded border bg-red-100 px-3 py-1 font-mono text-sm text-red-700 hover:bg-red-200" | |
> | |
ποΈ | |
</button> | |
</div> | |
</div> | |
{/each} | |
</div> | |
</div> | |
{/if} | |
</div> | |
<!-- About Workspaces --> | |
<div class="rounded border bg-blue-50 p-6"> | |
<h3 class="mb-3 font-mono text-lg font-semibold text-blue-900">About Workspaces</h3> | |
<div class="space-y-2 font-mono text-sm text-blue-800"> | |
<p>π <strong>Isolation:</strong> Each workspace provides a secure, isolated environment for your robotics sessions</p> | |
<p>π <strong>Rooms:</strong> Create multiple rooms within a workspace for different robot configurations</p> | |
<p>π₯ <strong>Sharing:</strong> Share the workspace ID with others to collaborate on the same robots</p> | |
<p>π <strong>Real-time:</strong> All participants in a workspace see live updates from producers</p> | |
</div> | |
</div> | |
</div> | |