Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions packages/backend/src/registries/CommandRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**********************************************************************
* Copyright (C) 2026 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
import { type Disposable, commands } from '@podman-desktop/api';
import type { InferenceServer } from '@shared/models/IInference';
import type { Conversation } from '@shared/models/IPlaygroundMessage';
import type { PlaygroundV2Manager } from '../managers/playgroundV2Manager';
import type { InferenceManager } from '../managers/inference/inferenceManager';
import {
MODEL_NAVIGATE_COMMAND,
PLAYGROUND_NAVIGATE_COMMAND,
RECIPE_NAVIGATE_COMMAND,
SERVICE_NAVIGATE_COMMAND,
} from './NavigationRegistry';
import type { CatalogManager } from '../managers/catalogManager';
import type { ModelsManager } from '../managers/modelsManager';
import type { ModelInfo } from '@shared/models/IModelInfo';
import type { Recipe } from '@shared/models/IRecipe';

export const LIST_RESOURCES_COMMAND = 'ai-lab.command.list-resources';

interface NavigationResourceInfo {
title: string;
icon: string;
command: string;
id: string;
hidden: boolean;
}
export class CommandRegistry implements Disposable {
#disposables: Disposable[] = [];

constructor(
private inferenceManager: InferenceManager,
private playgroundManager: PlaygroundV2Manager,
private modelsManager: ModelsManager,
private catalogManager: CatalogManager,
) {}

init(): void {
this.#disposables.push(commands.registerCommand(LIST_RESOURCES_COMMAND, this.listResources.bind(this)));
}

dispose(): void {
this.#disposables.forEach(disposable => disposable.dispose());
}

/**
* Lists all available resources (running inference servers, created playgrounds)
*/
public async listResources(): Promise<NavigationResourceInfo[]> {
// Build resource list
const resources: NavigationResourceInfo[] = [];

const inferenceServers: InferenceServer[] = this.inferenceManager.getServers();
inferenceServers.forEach((server: InferenceServer) => {
const containerId = server.container.containerId;
const model: ModelInfo | undefined = server.models?.[0];
const modelName: string = model ? `(${model.name})` : '';
const serviceName: string = `${containerId.substring(0, 12)}`;

resources.push({
title: `Service > ${serviceName}${modelName}`,
icon: 'fas fa-rocket',
command: SERVICE_NAVIGATE_COMMAND,
id: containerId,
hidden: false,
});
});

const conversations: Conversation[] = this.playgroundManager.getConversations();
conversations.forEach((conversation: Conversation) => {
resources.push({
title: `Playground > ${conversation.name}`,
icon: 'fas fa-message',
command: PLAYGROUND_NAVIGATE_COMMAND,
id: conversation.id,
hidden: false,
});
});

const models: ModelInfo[] = this.modelsManager.getModelsInfo();
models.forEach((model: ModelInfo) => {
resources.push({
title: `Model > ${model.name}`,
icon: 'fas fa-book-open',
command: MODEL_NAVIGATE_COMMAND,
id: model.id,
hidden: true,
});
});

const recipes: Recipe[] = this.catalogManager.getRecipes();
recipes.forEach((recipe: Recipe) => {
resources.push({
title: `Recipe > ${recipe.name}`,
icon: 'fas fa-book-open',
command: RECIPE_NAVIGATE_COMMAND,
id: recipe.id,
hidden: true,
});
});

return resources;
}
}
200 changes: 199 additions & 1 deletion packages/backend/src/registries/NavigationRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
* Copyright (C) 2024-2026 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,12 +19,48 @@ import { type Disposable, navigation, type WebviewPanel, commands } from '@podma
import { MSG_NAVIGATION_ROUTE_UPDATE } from '@shared/Messages';
import type { RpcExtension } from '@shared/messages/MessageProxy';

// Route identifiers and commands
export const DASHBOARD_ROUTE = 'dashboard';
export const DASHBOARD_NAVIGATE_COMMAND = 'ai-lab.navigation.dashboard';

export const RECIPES_ROUTE = 'recipes';
export const RECIPES_NAVIGATE_COMMAND = 'ai-lab.navigation.recipes';

export const RECIPE_START_ROUTE = 'recipe.start';
export const RECIPE_START_NAVIGATE_COMMAND = 'ai-lab.navigation.recipe.start';

export const APPLICATIONS_ROUTE = 'applications';
export const APPLICATIONS_NAVIGATE_COMMAND = 'ai-lab.navigation.applications';

export const MODELS_ROUTE = 'models';
export const MODELS_NAVIGATE_COMMAND = 'ai-lab.navigation.models';

export const PLAYGROUNDS_ROUTE = 'playgrounds';
export const PLAYGROUNDS_NAVIGATE_COMMAND = 'ai-lab.navigation.playgrounds';

export const SERVICES_ROUTE = 'services';
export const SERVICES_NAVIGATE_COMMAND = 'ai-lab.navigation.services';

export const INFERENCE_CREATE_ROUTE = 'inference.create';
export const INFERENCE_CREATE_NAVIGATE_COMMAND = 'ai-lab.navigation.inference.create';

export const LLAMASTACK_ROUTE = 'llamastack';
export const LLAMASTACK_NAVIGATE_COMMAND = 'ai-lab.navigation.llamastack';

export const LOCAL_SERVER_ROUTE = 'localserver';
export const LOCAL_SERVER_NAVIGATE_COMMAND = 'ai-lab.navigation.localserver';

export const ABOUT_INSTRUCTLAB_ROUTE = 'about-instructlab';
export const ABOUT_INSTRUCTLAB_NAVIGATE_COMMAND = 'ai-lab.navigation.about-instructlab';

export const INSTRUCTLAB_ROUTE = 'instructlab';
export const INSTRUCTLAB_NAVIGATE_COMMAND = 'ai-lab.navigation.instructlab';

export const SERVICE_NAVIGATE_COMMAND = 'ai-lab.navigation.service';
export const PLAYGROUND_NAVIGATE_COMMAND = 'ai-lab.navigation.playground';
export const RECIPE_NAVIGATE_COMMAND = 'ai-lab.navigation.recipe';
export const MODEL_NAVIGATE_COMMAND = 'ai-lab.navigation.model';

export class NavigationRegistry implements Disposable {
#disposables: Disposable[] = [];
#route: string | undefined = undefined;
Expand All @@ -51,6 +87,112 @@ export class NavigationRegistry implements Disposable {
commands.registerCommand(INFERENCE_CREATE_NAVIGATE_COMMAND, this.navigateToInferenceCreate.bind(this)),
);
this.#disposables.push(navigation.register(INFERENCE_CREATE_ROUTE, INFERENCE_CREATE_NAVIGATE_COMMAND));

// Register Dashboard
this.#disposables.push(commands.registerCommand(DASHBOARD_NAVIGATE_COMMAND, this.navigateToDashboard.bind(this)));
this.#disposables.push(
navigation.register(DASHBOARD_ROUTE, DASHBOARD_NAVIGATE_COMMAND, {
title: 'Dashboard',
icon: 'fas fa-house',
}),
);

// Register Recipes Catalog
this.#disposables.push(commands.registerCommand(RECIPES_NAVIGATE_COMMAND, this.navigateToRecipes.bind(this)));
this.#disposables.push(
navigation.register(RECIPES_ROUTE, RECIPES_NAVIGATE_COMMAND, {
title: 'Recipe Catalog',
icon: 'fas fa-book-open',
}),
);

// Register Applications
this.#disposables.push(
commands.registerCommand(APPLICATIONS_NAVIGATE_COMMAND, this.navigateToApplications.bind(this)),
);
this.#disposables.push(
navigation.register(APPLICATIONS_ROUTE, APPLICATIONS_NAVIGATE_COMMAND, {
title: 'Running',
icon: 'fas fa-server',
}),
);

// Register Models Catalog
this.#disposables.push(commands.registerCommand(MODELS_NAVIGATE_COMMAND, this.navigateToModels.bind(this)));
this.#disposables.push(
navigation.register(MODELS_ROUTE, MODELS_NAVIGATE_COMMAND, {
title: 'Catalog',
icon: 'fas fa-book-open',
}),
);

// Register Services
this.#disposables.push(commands.registerCommand(SERVICES_NAVIGATE_COMMAND, this.navigateToServices.bind(this)));
this.#disposables.push(
navigation.register(SERVICES_ROUTE, SERVICES_NAVIGATE_COMMAND, {
title: 'Services',
icon: 'fas fa-rocket',
}),
);

// Register Playgrounds
this.#disposables.push(
commands.registerCommand(PLAYGROUNDS_NAVIGATE_COMMAND, this.navigateToPlaygrounds.bind(this)),
);
this.#disposables.push(
navigation.register(PLAYGROUNDS_ROUTE, PLAYGROUNDS_NAVIGATE_COMMAND, {
title: 'Playgrounds',
icon: 'fas fa-message',
}),
);

// Register Llama Stack
this.#disposables.push(commands.registerCommand(LLAMASTACK_NAVIGATE_COMMAND, this.navigateToLlamaStack.bind(this)));
this.#disposables.push(
navigation.register(LLAMASTACK_ROUTE, LLAMASTACK_NAVIGATE_COMMAND, {
title: 'Llama Stack',
icon: 'fas fa-rocket',
}),
);

// Register Local Server
this.#disposables.push(
commands.registerCommand(LOCAL_SERVER_NAVIGATE_COMMAND, this.navigateToLocalServer.bind(this)),
);
this.#disposables.push(
navigation.register(LOCAL_SERVER_ROUTE, LOCAL_SERVER_NAVIGATE_COMMAND, {
title: 'Local Server',
icon: 'fas fa-gear',
}),
);

// Register About InstructLab
this.#disposables.push(
commands.registerCommand(ABOUT_INSTRUCTLAB_NAVIGATE_COMMAND, this.navigateToAboutInstructLab.bind(this)),
);
this.#disposables.push(
navigation.register(ABOUT_INSTRUCTLAB_ROUTE, ABOUT_INSTRUCTLAB_NAVIGATE_COMMAND, {
title: 'About InstructLab',
icon: 'fas fa-info-circle',
}),
);

// Register InstructLab
this.#disposables.push(
commands.registerCommand(INSTRUCTLAB_NAVIGATE_COMMAND, this.navigateToInstructLab.bind(this)),
);
this.#disposables.push(
navigation.register(INSTRUCTLAB_ROUTE, INSTRUCTLAB_NAVIGATE_COMMAND, {
title: 'Try InstructLab',
icon: 'fas fa-circle-down',
}),
);

// Register navigation commands for resources
this.#disposables.push(commands.registerCommand(SERVICE_NAVIGATE_COMMAND, this.navigateToService.bind(this)));
this.#disposables.push(commands.registerCommand(PLAYGROUND_NAVIGATE_COMMAND, this.navigateToPlayground.bind(this)));
this.#disposables.push(commands.registerCommand(RECIPE_NAVIGATE_COMMAND, this.navigateToRecipe.bind(this)));
this.#disposables.push(commands.registerCommand(MODEL_NAVIGATE_COMMAND, this.navigateToModel.bind(this)));
}

/**
Expand All @@ -73,11 +215,67 @@ export class NavigationRegistry implements Disposable {
this.panel.reveal();
}

public async navigateToDashboard(): Promise<void> {
return this.updateRoute('/');
}

public async navigateToRecipes(): Promise<void> {
return this.updateRoute('/recipes');
}

public async navigateToRecipeStart(recipeId: string, trackingId: string): Promise<void> {
return this.updateRoute(`/recipe/${recipeId}/start?trackingId=${trackingId}`);
}

public async navigateToApplications(): Promise<void> {
return this.updateRoute('/applications');
}

public async navigateToModels(): Promise<void> {
return this.updateRoute('/models');
}

public async navigateToPlaygrounds(): Promise<void> {
return this.updateRoute('/playgrounds');
}

public async navigateToServices(): Promise<void> {
return this.updateRoute('/services');
}

public async navigateToInferenceCreate(trackingId: string): Promise<void> {
return this.updateRoute(`/service/create?trackingId=${trackingId}`);
}

public async navigateToLlamaStack(): Promise<void> {
return this.updateRoute('/llamastack/try');
}

public async navigateToInstructLab(): Promise<void> {
return this.updateRoute('/instructlab/try');
}

public async navigateToAboutInstructLab(): Promise<void> {
return this.updateRoute('/about-instructlab');
}

public async navigateToLocalServer(): Promise<void> {
return this.updateRoute('/local-server');
}

public async navigateToService(id: string): Promise<void> {
return this.updateRoute(`/service/${id}`);
}

public async navigateToPlayground(id: string): Promise<void> {
return this.updateRoute(`/playground/${id}`);
}

public async navigateToRecipe(id: string): Promise<void> {
return this.updateRoute(`/recipe/${id}`);
}

public async navigateToModel(id: string): Promise<void> {
return this.updateRoute(`/model/${id}`);
}
}
16 changes: 15 additions & 1 deletion packages/backend/src/studio.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**********************************************************************
* Copyright (C) 2024-2025 Red Hat, Inc.
* Copyright (C) 2024-2026 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -65,6 +65,7 @@ import { LlamaStackManager } from './managers/llama-stack/llamaStackManager';
import { OpenVINO } from './workers/provider/OpenVINO';
import { McpServerManager } from './managers/playground/McpServerManager';
import os from 'node:os';
import { CommandRegistry } from './registries/CommandRegistry';

export class Studio {
readonly #extensionContext: ExtensionContext;
Expand Down Expand Up @@ -102,6 +103,7 @@ export class Studio {
#configurationRegistry: ConfigurationRegistry | undefined;
#gpuManager: GPUManager | undefined;
#navigationRegistry: NavigationRegistry | undefined;
#commandRegistry: CommandRegistry | undefined;
#instructlabManager: InstructlabManager | undefined;
#llamaStackManager: LlamaStackManager | undefined;

Expand Down Expand Up @@ -387,6 +389,18 @@ export class Studio {
this.#snippetManager = new SnippetManager(this.#rpcExtension, this.#telemetry);
this.#snippetManager.init();

/**
* The command registry is used to register and managed the commands of the extension
*/
this.#commandRegistry = new CommandRegistry(
this.#inferenceManager,
this.#playgroundManager,
this.#modelsManager,
this.#catalogManager,
);
this.#commandRegistry.init();
this.#extensionContext.subscriptions.push(this.#commandRegistry);

/**
* The StudioApiImpl is the implementation of our API between backend and frontend
*/
Expand Down
Loading
Loading