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 interface AISessionConfig { sessionId: string; policyPath: string; cameraNames: string[]; transportServerUrl: string; workspaceId?: 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'); } /** * Create a new AI compute instance */ 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, }; 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();