Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR (3.x): Define chat command provider for slash commands #1278

Open
dlqqq opened this issue Mar 17, 2025 · 0 comments
Open

FR (3.x): Define chat command provider for slash commands #1278

dlqqq opened this issue Mar 17, 2025 · 0 comments
Assignees
Labels
enhancement New feature or request

Comments

@dlqqq
Copy link
Member

dlqqq commented Mar 17, 2025

Problem

jupyterlab-chat v0.8.0 has now been released. Notably, this release replaces the old autocomplete framework with the new chat commands framework introduced by jupyterlab/jupyter-chat#161.

However, the main (3.x) branch needs to be updated to use the new chat commands framework.

Proposed Solution

Update jupyterlab-chat to 0.8.1:

  1. Update the version range of jupyterlab-chat to jupyterlab-chat>=0.8.1,<0.9.0 in packages/jupyter-ai/pyproject.toml.
  2. Update the version range of @jupyter/chat to "@jupyter/chat": "^0.8.1" in packages/jupyter-ai/package.json.
  3. Remove packages/jupyter-ai/src/slash-autocompletion.tsx and delete the chat_autocompletion plugin in packages/jupyter-ai/src/index.ts. You can also remove the import { IAutocompletionRegistry } from '@jupyter/chat' statement since that no longer is available in 0.8.0.

Define a new plugin that registers a chat command provider

  1. Create a new folder: packages/jupyter-ai/src/chat-commands/.
  2. Create a new file titled slash-commands.ts. For now, you can use the reference implementation below. You may have to update it to conform to the new command provider interface defined here: Add new InputModel class for managing input state jupyter-chat#171
Reference code (click to expand)
/*
 * Copyright (c) Jupyter Development Team.
 * Distributed under the terms of the Modified BSD License.
 */

import { JupyterFrontEndPlugin } from '@jupyterlab/application';
import {
  IChatCommandProvider,
  IChatCommandRegistry,
  IInputModel,
  ChatCommand
} from '@jupyter/chat';

export class SlashCommandProvider implements IChatCommandProvider {
  public id: string = '@jupyter-ai/core:slash-command-provider';
  private _slash_commands: ChatCommand[] = [
    { name: '/ask', providerId: this.id, replaceWith: '/ask ' },
    { name: '/learn', providerId: this.id, replaceWith: '/learn ' },
    { name: '/help', providerId: this.id, replaceWith: '/help ' }
  ];

  // regex used to test the current word
  private _regex: RegExp = /^\/\w*/;

  async getChatCommands(inputModel: IInputModel) {
    const match = inputModel.currentWord.match(this._regex)?.[0];
    if (!match) {
      return [];
    }

    const commands = this._slash_commands.filter(cmd =>
      cmd.name.startsWith(match)
    );
    return commands;
  }

  async handleChatCommand(
    command: ChatCommand,
    inputModel: IInputModel
  ): Promise<void> {
    // no handling needed because `replaceWith` is set in each command.
    return;
  }
}

export const slashCommandPlugin: JupyterFrontEndPlugin<void> = {
  id: '@jupyter-ai/core:slash-command-plugin',
  description:
    'Adds Jupyter AI slash commands to the chat commands menu.',
  autoStart: true,
  requires: [IChatCommandRegistry],
  activate: (app, registry: IChatCommandRegistry) => {
    registry.addProvider(new SlashCommandProvider());
  }

};

  1. Provide this plugin by adding it to the list of default exports in packages/jupyter-ai/src/index.ts. The list of exports should look like this:
 export default [
   plugin,
   statusItemPlugin,
   completionPlugin,
-  chat_autocompletion
+  slashCommandPlugin
 ];
  1. Re-build the lab extension frontend via jlpm build and refresh your browser. You can test the functionality by typing the / character and see if the menu pops up.

Updating the slash command provider to use the API

  1. Update async getChatCommands() to call AiService.listSlashCommands(). This should be self-explanatory.
  2. Update the returned list of commands to include icons for each slash command. The default list of icons can be found here:
    const DEFAULT_SLASH_COMMAND_ICONS: Record<string, JSX.Element> = {
    ask: <FindInPage />,
    clear: <HideSource />,
    export: <Download />,
    fix: <AutoFixNormal />,
    generate: <MenuBook />,
    help: <Help />,
    learn: <School />,
    unknown: <MoreHoriz />
    };
  3. (optional challenge) Inside SlashCommandProvider.constructor(), create a Promise that fetches the list of slash commands and processes it into a list of the type ChatCommand[]. This should be saved as a private attribute like this._slashCommands: Promise<ChatCommand[]> = .... Then, getChatCommands() can just call return await this._chatCommands. This ensures that a network request is only made once, and all calls to this command provider just return a cached value, improving performance substantially.

Feel free to open a PR anyways if you get stuck on step 3). I'll share more guidance in my review if necessary.

Additional context

@keerthi-swarna This will be a more challenging assignment, since it requires an understanding of how JupyterLab plugins work. This doc will be useful: https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#application-plugins

Essentially, every lab extension is a set of "plugins". Each plugin can do two things simultaneously:

  1. It can provide a token that other plugins can require/depend upon. This allows a plugin to provide a service to other plugins.
  2. It can require tokens provided by another plugins. This allows a plugin to use services provided by other plugins.

In JupyterLab, the entire frontend is defined using this plugin architecture. This means that an extensions' plugins can override default plugins provided by jupyterlab, and use other plugins provided by jupyterlab.

JupyterLab automatically initializes each of the plugins in the correct order, presumably by performing a topological sort on a directed acyclic graph (DAG). Understanding what a topological sort does may help you understand how plugins are initialized in JupyterLab.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: Active
Development

No branches or pull requests

2 participants