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();