Docs
Copilot

Copilot

AI-powered assistant that provides intelligent suggestions and autocompletion for enhanced content creation.

🤖 Copilot

  1. Position your cursor at the end of a paragraph where you want to add or modify text.
  1. Press Control + Space to trigger Copilot.
  1. Choose from the suggested completions:
  • Tab:Accept the entire suggested completion
  • Command + Right Arrow: Complete one character at a time
  • Escape: Cancel the Copilot

Features

  • Offers intelligent autocompletion to enhance content creation and editing.
  • Provides two modes: debounce and shortcut.
    1. Shortcut mode is the default, triggered by pressing Control+Space.
    2. Debounce mode can be automatically triggered when the selection changes or during typing.

Installation

npm install @udecode/plate-ai

Usage

// ...
import { CopilotPlugin } from '@udecode/plate-ai/react';
 
const editor = usePlateEditor({
  id: 'ai-demo',
  override: {
    components: PlateUI,
  },
  plugins: [
    ...commonPlugins,
    CopilotPlugin.configure({
    options: {
      fetchSuggestion: async ({ abortSignal, prompt }) => {
 
        // Currently, we are using a mock function to simulate the api request
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve(MENTIONABLES[Math.floor(Math.random() * 41)].text);
          }, 100);
        });
      },
      hoverCard: AiCopilotHoverCard,
      query: {
        allow: [
          ParagraphPlugin.key,
          BlockquotePlugin.key,
          HEADING_KEYS.h1,
          HEADING_KEYS.h2,
          HEADING_KEYS.h3,
          HEADING_KEYS.h4,
          HEADING_KEYS.h5,
          HEADING_KEYS.h6,
        ],
      },
    },
});
  ],
  value: aiValue,
});

Integrate with your backend

options.fetchSuggestion is an asynchronous function that you need to implement to fetch suggestions from your backend. This function is crucial for integrating the Copilot feature with your own AI model or service.

The function receives an object with two properties:

  • abortSignal: An AbortSignal object that allows you to cancel the request if needed. This is particularly useful in debounce mode to abort the request if the user continues typing or changes the selection.
  • prompt: A string containing the text of the node where the user's cursor is currently positioned. This serves as the context for generating suggestions.

The function should return a Promise that resolves to a string. This string will be the suggestion inserted into the editor.

Here's a more detailed example of how you might implement this function:

 
 fetchSuggestion: async ({ abortSignal, prompt }) => {
    const system = `Please continue writing a sentence based on the prompt.
      Important Note: Please do not answer any questions. Directly provide the continuation
      of the content without including any part of the prompt and don't write more than one sentence:
      finish current sentence or write a new one`
 
    const response = await fetch('https://your-api-endpoint.com/api/v1/generate-text', {
      method: 'POST',
      body: JSON.stringify({ prompt,system }),
      // pass the abortSignal to the fetch request
      signal: abortSignal,
    });
 
    const data = await response.json();
 
 
    // data.suggestion should be a string
    return data.suggestion;
},
 

Copilot state

The Copilot plugin maintains its own state to manage the suggestion process. The state can be either 'idle' or 'completed'. Here's a breakdown of what each state means:

type CopilotState = 'error' | 'idle' | 'pending' | 'success';
  • idle: This is the default state. It indicates that the Copilot is not currently providing a suggestion or has finished processing the previous suggestion.
  • completed: This state indicates that the Copilot has generated a suggestion and it's ready to be inserted into the editor. You'll see this suggestion as gray text in the editor in this state.

You can access the current state of the Copilot using:

const copilotState = editor.getOptions(CopilotPlugin).copilotState;

Conflict with Other Plugins

The Tab key is a popular and frequently used key in text editing, which can lead to conflicts.

Conflict with Indent Plugin

The IndentPlugin and IndentListPlugin have a similar conflict with Copilot Plugin.

As a workaround, you can place the Copilot Plugin before the these two plugins in your plugin configuration.

Or set the priority of Copilot Plugin to a higher value than Indent Plugin see priority.

Here's an example of how to order your plugins:

const editor = usePlateEditor({
  id: 'ai-demo',
  override: {
    components: PlateUI,
  },
  plugins: [
    MarkdownPlugin.configure({ options: { indentList: true } }),
    // CopilotPlugin should be before indent plugin
    CopilotPlugin,
    IndentPlugin.extend({
      inject: {
        targetPlugins: [
          ParagraphPlugin.key,
          HEADING_KEYS.h1,
          HEADING_KEYS.h2,
          HEADING_KEYS.h3,
          HEADING_KEYS.h4,
          HEADING_KEYS.h5,
          HEADING_KEYS.h6,
          BlockquotePlugin.key,
          CodeBlockPlugin.key,
          TogglePlugin.key,
        ],
      },
    }),
    IndentListPlugin.extend({
      inject: {
        targetPlugins: [
          ParagraphPlugin.key,
          HEADING_KEYS.h1,
          HEADING_KEYS.h2,
          HEADING_KEYS.h3,
          HEADING_KEYS.h4,
          HEADING_KEYS.h5,
          HEADING_KEYS.h6,
          BlockquotePlugin.key,
          CodeBlockPlugin.key,
          TogglePlugin.key,
        ],
      },
      options: {
        listStyleTypes: {
          fire: {
            liComponent: FireLiComponent,
            markerComponent: FireMarker,
            type: 'fire',
          },
          todo: {
            liComponent: TodoLi,
            markerComponent: TodoMarker,
            type: 'todo',
          },
        },
      },
    }),
    ...otherPlugins,
  ],
  value: copilotValue,
});

It's important to note that when using IndentListPlugin instead of ListPlugin, you should configure the MarkdownPlugin with the indentList: true option.

This is necessary because the LLM generates Markdown, which needs to be converted to Plate nodes.If your LLM just generate the plain text, you can ignore this.

Here's how you can set it up:

MarkdownPlugin.configure({ options: { indentList: true } }),

Conflict with Tabbable Plugin

When using both the Tabbable Plugin and the Copilot feature in your editor, you may encounter conflicts with the Tab key functionality. This is because both features utilize the same key binding.

To resolve this conflict and ensure smooth operation of both features, you can configure the Tabbable Plugin to be disabled when the cursor is at the end of a paragraph. This allows the Copilot feature to function as intended when you press Tab at the end of a line.

Here's how you can modify the Tabbable Plugin configuration:

  1. Visit the Tabbable Plugin documentation for more details on handling conflicts.
  2. Implement the following configuration to disable the Tabbable Plugin when the cursor is at the end of a paragraph:
TabbablePlugin.configure(({ editor }) => ({
  options: {
    query: () => {
      // return false when cursor is in the end of the paragraph
      if (isSelectionAtBlockStart(editor) || isSelectionAtBlockEnd(editor))
        return false;
 
      return !someNode(editor, {
        match: (n) => {
          return !!(
            n.type &&
            ([
              CodeBlockPlugin.key,
              ListItemPlugin.key,
              TablePlugin.key,
            ].includes(n.type as any) ||
              n[IndentListPlugin.key])
          );
        },
      });
    },
  },
}));

Plate Plus

In Plate Plus, We using debounce mode by default. That's mean you will see the suggestion automatically without pressing Control+Space.

We also provide a new style of hover card that is more user-friendly.You can hover on the suggestion to see the hover card.

All of the backend setup is available in Potion template.

API

editor.getApi(CopilotPlugin).copilot.abortCopilot();

Aborts the ongoing API request and removes any suggestion text currently displayed.

Returns

Collapse all

    This function does not return a value.

editor.getApi(CopilotPlugin).copilot.setCopilot

Sets the suggestion text to the editor.

Parameters

Collapse all

    The ID of the node to set the suggestion text.

    The suggestion text to set.

Utils functions

withoutAbort

Temporarily disables the abort functionality of the Copilot plugin, by default any apply will cause the Copilot to remove the suggestion text and abort the request.

Parameters

Collapse all

    The Plate editor instance.

    The function to execute without abort.

Returns

Collapse all

    This function does not return a value.

Usage example:

import { withoutAbort } from '@udecode/plate-ai/react';
 
withoutAbort(editor, () => {
  // Perform operations without the risk of abort
  // For example, setting copilot suggestions
});