RobotHub-Frontend / src /lib /elements /compute /RemoteComputeManager.svelte.ts
blanchon's picture
Update
67a499d
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<ModelType, ModelTypeConfig> = {
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<string, string>;
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<string, string>;
joint_input_room_id: string;
joint_output_room_id: string;
};
stats: {
inference_count: number;
commands_sent: number;
joints_received: number;
images_received: Record<string, number>;
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<RemoteCompute[]>([]);
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<void> {
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<void> {
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();