AI
AI menu with commands, streaming responses in a preview or directly into the editor.
'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:
- Setup a route handler using streamText.
- Wire up useChat in your AI menu component.
Keyboard Shortcuts
Key | Description |
---|---|
Space | Open AI menu in empty block (cursor mode) |
Cmd + J | Open AI menu (cursor or selection mode) |
Escape | Close 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
- Default: Creates a basic editor with id 'ai'
'chat'
: Shows preview with accept/reject options (default)'insert'
: Directly inserts content into editor- Default:
'chat'
- Default:
false
{block}
: Markdown of blocks in selection{editor}
: Markdown of entire editor content{selection}
: Markdown of current selection{prompt}
: Actual user prompt- Default:
'{prompt}'
- Default:
undefined
Chat helpers returned by useChat.
Function to create editor instance for preview mode.
Specifies how assistant messages are handled:
Whether the AI chat is open.
Template for generating prompts. Supports placeholders:
Template for system messages. Supports same placeholders as promptTemplate
.
API
editor.aiChat.accept()
Accepts the current AI suggestion:
- Removes AI marks from the content
- Hides the AI chat interface
- Focuses the editor
editor.aiChat.insertBelow()
Inserts AI content below the current block.
Parameters
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
editor.aiChat.replaceSelection()
Replaces the current selection with AI content.
Parameters
Editor containing the content to replace with.
Handles both block selection and normal selection modes:
- In block selection: Replaces all selected blocks
- In normal selection: Replaces the current selection
editor.aiChat.reset()
Resets the chat state:
- Stops any ongoing generation
- Clears chat messages
- Removes all AI nodes from the editor
editor.aiChat.submit()
Submits a prompt to generate AI content.
Parameters
In insert mode, undoes previous AI changes before submitting.
Transforms
editor.ai.insertNodes()
Inserts AI-generated nodes with the AI mark.
Parameters
Nodes to insert with AI mark.
editor.ai.removeMarks()
Removes AI marks from nodes in the specified location.
Parameters
editor.ai.removeNodes()
Removes nodes that have the AI mark.
Parameters
editor.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