AI

AI menu with commands, streaming responses in a preview or directly into the editor.

Loading...
Files
components/demo.tsx
'use client';

import React from 'react';

import { Plate } from '@udecode/plate-common/react';

import { editorPlugins } from '@/components/editor/plugins/editor-plugins';
import { useCreateEditor } from '@/components/editor/use-create-editor';
import { Editor, EditorContainer } from '@/components/plate-ui/editor';

import { DEMO_VALUES } from './values/demo-values';

export default function Demo({ id }: { id: string }) {
  const editor = useCreateEditor({
    plugins: [...editorPlugins],
    value: DEMO_VALUES[id],
  });

  return (
    <Plate editor={editor}>
      <EditorContainer variant="demo">
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

Features

  • Combobox menu with predefined commands:
    • Generate: continue writing, add summary, explain
    • Edit: improve writing, make it longer or shorter, fix spelling & grammar, simplify language
  • Three trigger modes:
    • Cursor mode: trigger at block end
    • Selection mode: trigger with selected text
    • Block selection mode: trigger with selected blocks
  • Streaming responses in preview or direct editor insertion
  • Markdown support
  • Built-in support for Vercel AI SDK chat API

Installation

npm install @udecode/plate-ai @udecode/plate-selection @udecode/plate-markdown @udecode/plate-basic-marks

Usage

Plugins

import { withProps } from '@udecode/cn';
import { AIChatPlugin, AIPlugin } from '@udecode/plate-ai/react';
import {
  BoldPlugin,
  CodePlugin,
  ItalicPlugin,
  StrikethroughPlugin,
  UnderlinePlugin,
} from '@udecode/plate-basic-marks/react';
import { PlateLeaf, createPlateEditor } from '@udecode/plate-common/react';
import { LinkPlugin } from '@udecode/plate-link/react';
import { MarkdownPlugin } from '@udecode/plate-markdown';
export const createAIEditor = () => {
  const editor = createPlateEditor({
    id: 'ai',
    override: {
      components: {
        [BoldPlugin.key]: withProps(PlateLeaf, { as: 'strong' }),
        [CodePlugin.key]: CodeLeaf,
        [ItalicPlugin.key]: withProps(PlateLeaf, { as: 'em' }),
        [LinkPlugin.key]: LinkElement,
        [StrikethroughPlugin.key]: withProps(PlateLeaf, { as: 's' }),
        [UnderlinePlugin.key]: withProps(PlateLeaf, { as: 'u' }),
      },
    },
    plugins: [
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
      StrikethroughPlugin,
      CodePlugin,
    ],
    value: [{ children: [{ text: '' }], type: 'p' }],
  });
 
  return editor;
};
 
const systemCommon = `\
You are an advanced AI-powered note-taking assistant, designed to enhance productivity and creativity in note management.
Respond directly to user prompts with clear, concise, and relevant content. Maintain a neutral, helpful tone.
 
Rules:
- <Document> is the entire note the user is working on.
- <Reminder> is a reminder of how you should reply to INSTRUCTIONS. It does not apply to questions.
- Anything else is the user prompt.
- Your response should be tailored to the user's prompt, providing precise assistance to optimize note management.
- For INSTRUCTIONS: Follow the <Reminder> exactly. Provide ONLY the content to be inserted or replaced. No explanations or comments.
- For QUESTIONS: Provide a helpful and concise answer. You may include brief explanations if necessary.
- CRITICAL: Distinguish between INSTRUCTIONS and QUESTIONS. Instructions typically ask you to modify or add content. Questions ask for information or clarification.
`;
 
const systemDefault = `\
${systemCommon}
- <Block> is the current block of text the user is working on.
- Ensure your output can seamlessly fit into the existing <Block> structure.
- CRITICAL: Provide only a single block of text. DO NOT create multiple paragraphs or separate blocks.
<Block>
{block}
</Block>
`;
 
const systemSelecting = `\
${systemCommon}
- <Block> is the block of text containing the user's selection, providing context.
- Ensure your output can seamlessly fit into the existing <Block> structure.
- <Selection> is the specific text the user has selected in the block and wants to modify or ask about.
- Consider the context provided by <Block>, but only modify <Selection>. Your response should be a direct replacement for <Selection>.
<Block>
{block}
</Block>
<Selection>
{selection}
</Selection>
`;
 
const systemBlockSelecting = `\
${systemCommon}
- <Selection> represents the full blocks of text the user has selected and wants to modify or ask about.
- Your response should be a direct replacement for the entire <Selection>.
- Maintain the overall structure and formatting of the selected blocks, unless explicitly instructed otherwise.
- CRITICAL: Provide only the content to replace <Selection>. Do not add additional blocks or change the block structure unless specifically requested.
<Selection>
{block}
</Selection>
`;
 
const userDefault = `<Reminder>
CRITICAL: DO NOT use block formatting. You can only use inline formatting.
CRITICAL: DO NOT start new lines or paragraphs.
NEVER write <Block>.
</Reminder>
{prompt}`;
 
const userSelecting = `<Reminder>
If this is a question, provide a helpful and concise answer about <Selection>.
If this is an instruction, provide ONLY the text to replace <Selection>. No explanations.
Ensure it fits seamlessly within <Block>. If <Block> is empty, write ONE random sentence.
NEVER write <Block> or <Selection>.
</Reminder>
{prompt} about <Selection>`;
 
const userBlockSelecting = `<Reminder>
If this is a question, provide a helpful and concise answer about <Selection>.
If this is an instruction, provide ONLY the content to replace the entire <Selection>. No explanations.
Maintain the overall structure unless instructed otherwise.
NEVER write <Block> or <Selection>.
</Reminder>
{prompt} about <Selection>`;
 
export const PROMPT_TEMPLATES = {
  systemBlockSelecting,
  systemDefault,
  systemSelecting,
  userBlockSelecting,
  userDefault,
  userSelecting,
};
 
const plugins = [
  // ...otherPlugins,
  MarkdownPlugin.configure({ options: { indentList: true } }),
  AIPlugin,
  AIChatPlugin.configure({
    options: {
      createAIEditor,
      promptTemplate: ({ isBlockSelecting, isSelecting }) => {
        return isBlockSelecting
          ? PROMPT_TEMPLATES.userBlockSelecting
          : isSelecting
            ? PROMPT_TEMPLATES.userSelecting
            : PROMPT_TEMPLATES.userDefault;
      },
      systemTemplate: ({ isBlockSelecting, isSelecting }) => {
        return isBlockSelecting
          ? PROMPT_TEMPLATES.systemBlockSelecting
          : isSelecting
            ? PROMPT_TEMPLATES.systemSelecting
            : PROMPT_TEMPLATES.systemDefault;
      },
    },
    render: { afterEditable: () => <AIMenu /> },
  }),
];

AI SDK

This plugin is depending on the ai package:

Keyboard Shortcuts

KeyDescription
Space

Open AI menu in empty block (cursor mode)

Cmd + J

Open AI menu (cursor or selection mode)

EscapeClose AI menu

Examples

Plate UI

Refer to the preview above.

Plate Plus

Plugins

AIPlugin

Extends the editor with AI transforms.

AIChatPlugin

Enables chat operations and streaming text generation in the editor.

Options

Collapse all

    Chat helpers returned by useChat.

    Function to create editor instance for preview mode.

    • Default: Creates a basic editor with id 'ai'

    Specifies how assistant messages are handled:

    • 'chat': Shows preview with accept/reject options (default)
    • 'insert': Directly inserts content into editor
    • Default: 'chat'

    Whether the AI chat is open.

    • Default: false

    Template for generating prompts. Supports placeholders:

    • {block}: Markdown of blocks in selection
    • {editor}: Markdown of entire editor content
    • {selection}: Markdown of current selection
    • {prompt}: Actual user prompt
    • Default: '{prompt}'

    Template for system messages. Supports same placeholders as promptTemplate.

    • Default: undefined

API

api.aiChat.accept()

Accepts the current AI suggestion:

  • Removes AI marks from the content
  • Hides the AI chat interface
  • Focuses the editor

api.aiChat.insertBelow()

Inserts AI content below the current block.

Parameters

Collapse all

    Editor containing the content to insert.

Handles both block selection and normal selection modes:

  • In block selection: Inserts after the last selected block
  • In normal selection: Inserts after the current block

api.aiChat.replaceSelection()

Replaces the current selection with AI content.

Parameters

Collapse all

    Editor containing the content to replace with.

Handles different selection modes:

  • Single block selection: Replaces the selected block, applying its formatting to all inserted content
  • Multiple block selection: Replaces all selected blocks, preserving the original formatting unless forceUniformFormatting is enabled
  • Normal selection: Replaces the current selection while maintaining surrounding context

api.aiChat.reset()

Resets the chat state:

  • Stops any ongoing generation
  • Clears chat messages
  • Removes all AI nodes from the editor

api.aiChat.submit()

Submits a prompt to generate AI content.

Parameters

Collapse all

In insert mode, undoes previous AI changes before submitting.

Transforms

tf.ai.insertNodes()

Inserts AI-generated nodes with the AI mark.

Parameters

Collapse all

    Nodes to insert with AI mark.

tf.ai.removeMarks()

Removes AI marks from nodes in the specified location.

Parameters

Collapse all

tf.ai.removeNodes()

Removes nodes that have the AI mark.

Parameters

Collapse all

tf.ai.undo()

Special undo operation for AI changes:

  • Undoes the last operation if it was AI-generated
  • Removes the redo stack entry to prevent redoing AI operations