import { RemoteCompute } from './RemoteCompute.svelte'; import type { Position3D } from '$lib/types/positionable.js'; import { generateName } from '$lib/utils/generateName.js'; import { positionManager } from '$lib/utils/positionManager.js'; import { rootGet, healthCheckHealthGet, listSessionsSessionsGet, createSessionSessionsPost, startInferenceSessionsSessionIdStartPost, stopInferenceSessionsSessionIdStopPost, deleteSessionSessionsSessionIdDelete } from '@robothub/inference-server-client'; import { settings } from '$lib/runes/settings.svelte'; import type { CreateSessionRequest, CreateSessionResponse } from '@robothub/inference-server-client'; export type ModelType = 'act' | 'diffusion' | 'smolvla' | 'pi0' | 'groot' | 'custom'; export interface ModelTypeConfig { id: ModelType; label: string; icon: string; description: string; defaultPolicyPath: string; defaultCameraNames: string[]; requiresLanguageInstruction?: boolean; enabled: boolean; } export const MODEL_TYPES: Record = { act: { id: 'act', label: 'ACT Model', icon: 'icon-[mdi--brain]', description: 'Action Chunking with Transformers', defaultPolicyPath: 'LaetusH/act_so101_beyond', defaultCameraNames: ['front'], enabled: true }, diffusion: { id: 'diffusion', label: 'Diffusion Policy', icon: 'icon-[mdi--creation]', description: 'Diffusion-based robot control', defaultPolicyPath: 'diffusion_policy/default', defaultCameraNames: ['front', 'wrist'], enabled: true }, smolvla: { id: 'smolvla', label: 'SmolVLA', icon: 'icon-[mdi--eye-outline]', description: 'Small Vision-Language-Action model', defaultPolicyPath: 'smolvla/latest', defaultCameraNames: ['front'], requiresLanguageInstruction: true, enabled: true }, pi0: { id: 'pi0', label: 'Pi0', icon: 'icon-[mdi--pi]', description: 'Lightweight robotics model', defaultPolicyPath: 'pi0/base', defaultCameraNames: ['front'], enabled: true }, groot: { id: 'groot', label: 'NVIDIA Groot', icon: 'icon-[mdi--robot-outline]', description: 'Humanoid robotics foundation model', defaultPolicyPath: 'nvidia/groot', defaultCameraNames: ['front', 'left', 'right'], requiresLanguageInstruction: true, enabled: false // Not yet implemented }, custom: { id: 'custom', label: 'Custom Model', icon: 'icon-[mdi--cog]', description: 'Custom model configuration', defaultPolicyPath: '', defaultCameraNames: ['front'], enabled: true } }; export interface AISessionConfig { sessionId: string; modelType: ModelType; policyPath: string; cameraNames: string[]; transportServerUrl: string; workspaceId?: string; languageInstruction?: string; } export interface AISessionResponse { workspace_id: string; camera_room_ids: Record; joint_input_room_id: string; joint_output_room_id: string; } export interface AISessionStatus { session_id: string; status: 'initializing' | 'ready' | 'running' | 'stopped'; policy_path: string; camera_names: string[]; workspace_id: string; rooms: { workspace_id: string; camera_room_ids: Record; joint_input_room_id: string; joint_output_room_id: string; }; stats: { inference_count: number; commands_sent: number; joints_received: number; images_received: Record; errors: number; actions_in_queue: number; }; inference_stats?: { inference_count: number; total_inference_time: number; average_inference_time: number; average_fps: number; is_loaded: boolean; device: string; }; error_message?: string; } export class RemoteComputeManager { private _computes = $state([]); constructor() { // No client initialization needed anymore } // Reactive getters get computes(): RemoteCompute[] { return this._computes; } get computeCount(): number { return this._computes.length; } get runningComputes(): RemoteCompute[] { return this._computes.filter(compute => compute.status === 'running'); } /** * Get available model types */ get availableModelTypes(): ModelTypeConfig[] { return Object.values(MODEL_TYPES).filter(model => model.enabled); } /** * Get model type configuration */ getModelTypeConfig(modelType: ModelType): ModelTypeConfig | undefined { return MODEL_TYPES[modelType]; } /** * Create a new AI compute instance with full configuration */ async createComputeWithSession( config: AISessionConfig, computeId?: string, computeName?: string, position?: Position3D ): Promise<{ success: boolean; error?: string; compute?: RemoteCompute }> { const finalComputeId = computeId || generateName(); // Check if compute already exists if (this._computes.find(c => c.id === finalComputeId)) { return { success: false, error: `Compute with ID ${finalComputeId} already exists` }; } try { // Create compute instance const compute = new RemoteCompute(finalComputeId, computeName); compute.modelType = config.modelType; // Set position (from position manager if not provided) compute.position = position || positionManager.getNextPosition(); // Add to reactive array this._computes.push(compute); // Create the session immediately const sessionResult = await this.createSession(compute.id, config); if (!sessionResult.success) { // Remove compute if session creation failed await this.removeCompute(compute.id); return { success: false, error: sessionResult.error }; } 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}`); return { success: true, compute }; } catch (error) { console.error('Failed to create compute with session:', error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Create a new AI compute instance (legacy method) */ createCompute(id?: string, name?: string, position?: Position3D): RemoteCompute { const computeId = id || generateName(); // Check if compute already exists if (this._computes.find(c => c.id === computeId)) { throw new Error(`Compute with ID ${computeId} already exists`); } // Create compute instance const compute = new RemoteCompute(computeId, name); // Set position (from position manager if not provided) compute.position = position || positionManager.getNextPosition(); // Add to reactive array this._computes.push(compute); console.log(`Created compute ${computeId} at position (${compute.position.x.toFixed(1)}, ${compute.position.y.toFixed(1)}, ${compute.position.z.toFixed(1)}). Total computes: ${this._computes.length}`); return compute; } /** * Get compute by ID */ getCompute(id: string): RemoteCompute | undefined { return this._computes.find(c => c.id === id); } /** * Remove a compute instance */ async removeCompute(id: string): Promise { const computeIndex = this._computes.findIndex(c => c.id === id); if (computeIndex === -1) return; const compute = this._computes[computeIndex]; // Clean up compute resources await this.stopSession(id); await this.deleteSession(id); // Remove from reactive array this._computes.splice(computeIndex, 1); console.log(`Removed compute ${id}. Remaining computes: ${this._computes.length}`); } /** * Create an Inference Session */ async createSession(computeId: string, config: AISessionConfig): Promise<{ success: boolean; error?: string; data?: AISessionResponse }> { const compute = this.getCompute(computeId); if (!compute) { return { success: false, error: `Compute ${computeId} not found` }; } try { const request: CreateSessionRequest = { session_id: config.sessionId, policy_path: config.policyPath, camera_names: config.cameraNames, transport_server_url: config.transportServerUrl, workspace_id: config.workspaceId || undefined, policy_type: config.modelType, // Use model type as policy type language_instruction: config.languageInstruction || undefined, }; const response = await createSessionSessionsPost({ body: request, baseUrl: settings.inferenceServerUrl }); if (!response.data) { throw new Error('Failed to create session - no data returned'); } const data: CreateSessionResponse = response.data; // Update compute with session info compute.sessionId = config.sessionId; compute.status = 'ready'; compute.sessionConfig = config; compute.sessionData = { workspace_id: data.workspace_id, camera_room_ids: data.camera_room_ids, joint_input_room_id: data.joint_input_room_id, joint_output_room_id: data.joint_output_room_id }; return { success: true, data: compute.sessionData }; } catch (error) { console.error(`Failed to create session for compute ${computeId}:`, error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Start inference for a session */ async startSession(computeId: string): Promise<{ success: boolean; error?: string }> { const compute = this.getCompute(computeId); if (!compute || !compute.sessionId) { return { success: false, error: 'No session to start' }; } try { await startInferenceSessionsSessionIdStartPost({ path: { session_id: compute.sessionId }, baseUrl: settings.inferenceServerUrl }); compute.status = 'running'; return { success: true }; } catch (error) { console.error(`Failed to start session for compute ${computeId}:`, error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Stop inference for a session */ async stopSession(computeId: string): Promise<{ success: boolean; error?: string }> { const compute = this.getCompute(computeId); if (!compute || !compute.sessionId) { return { success: false, error: 'No session to stop' }; } try { await stopInferenceSessionsSessionIdStopPost({ path: { session_id: compute.sessionId }, baseUrl: settings.inferenceServerUrl }); compute.status = 'stopped'; return { success: true }; } catch (error) { console.error(`Failed to stop session for compute ${computeId}:`, error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Delete a session */ async deleteSession(computeId: string): Promise<{ success: boolean; error?: string }> { const compute = this.getCompute(computeId); if (!compute || !compute.sessionId) { return { success: true }; // Already deleted } try { await deleteSessionSessionsSessionIdDelete({ path: { session_id: compute.sessionId }, baseUrl: settings.inferenceServerUrl }); // Reset compute session info compute.sessionId = null; compute.status = 'disconnected'; compute.sessionConfig = null; compute.sessionData = null; return { success: true }; } catch (error) { console.error(`Failed to delete session for compute ${computeId}:`, error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Get session status */ async getSessionStatus(computeId: string): Promise<{ success: boolean; data?: AISessionStatus; error?: string }> { const compute = this.getCompute(computeId); if (!compute || !compute.sessionId) { return { success: false, error: 'No session found' }; } try { // Get all sessions and find the one we want const response = await listSessionsSessionsGet({ baseUrl: settings.inferenceServerUrl }); if (!response.data) { throw new Error('Failed to get sessions list'); } const session = response.data.find(s => s.session_id === compute.sessionId); if (!session) { throw new Error(`Session ${compute.sessionId} not found`); } // Update compute status compute.status = session.status as 'initializing' | 'ready' | 'running' | 'stopped'; // Convert to AISessionStatus format const sessionStatus: AISessionStatus = { session_id: session.session_id, status: session.status as 'initializing' | 'ready' | 'running' | 'stopped', policy_path: session.policy_path, camera_names: session.camera_names, workspace_id: session.workspace_id, rooms: session.rooms as any, stats: session.stats as any, inference_stats: session.inference_stats as any, error_message: session.error_message || undefined }; return { success: true, data: sessionStatus }; } catch (error) { console.error(`Failed to get session status for compute ${computeId}:`, error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Check AI server health */ async checkServerHealth(): Promise<{ success: boolean; data?: any; error?: string }> { try { const healthResponse = await rootGet({ baseUrl: settings.inferenceServerUrl }); if (!healthResponse.data) { return { success: false, error: 'Server is not healthy' }; } // Get detailed health info const detailedHealthResponse = await healthCheckHealthGet({ baseUrl: settings.inferenceServerUrl }); return { success: true, data: detailedHealthResponse.data }; } catch (error) { console.error('Failed to check AI server health:', error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Clean up all computes */ async destroy(): Promise { const cleanupPromises = this._computes.map(async (compute) => { await this.stopSession(compute.id); await this.deleteSession(compute.id); }); await Promise.allSettled(cleanupPromises); this._computes.length = 0; } } // Global compute manager instance export const remoteComputeManager = new RemoteComputeManager();