AI
AI menu with commands, streaming responses in a preview or directly into the editor.
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
安装
npm install @udecode/plate-ai @udecode/plate-selection @udecode/plate-markdown @udecode/plate-basic-marks
使用
插件
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
这个插件依赖于 ai 包:
- 设置一个 route handler 使用 streamText.
- 在 AI menu 组件中连接 useChat.
键盘快捷键
Key | Description |
---|---|
Space | 在空块中打开 AI 菜单 (光标模式) |
Cmd + J | 打开 AI 菜单 (光标或选择模式) |
Escape | 关闭 AI 菜单 |
Examples
Plate UI
查看上面的预览。
Plate Plus
插件
AIPlugin
为编辑器扩展 AI 转换功能。
AIChatPlugin
在编辑器中启用聊天操作和流式文本生成。
Options
Collapse all
- 默认值: 创建一个基础编辑器,id 为 'ai'
'chat'
: 显示预览并提供接受/拒绝选项(默认)'insert'
: 直接将内容插入编辑器- 默认值:
'chat'
- 默认值:
false
{block}
: 选中块的 Markdown{editor}
: 整个编辑器内容的 Markdown{selection}
: 当前选择的 Markdown{prompt}
: 实际用户提示- 默认值:
'{prompt}'
- 默认值:
undefined
由 useChat 返回的聊天助手。
创建预览模式编辑器实例的函数。
指定如何处理助手消息:
AI 聊天是否打开。
生成提示的模板。支持以下占位符:
系统消息的模板。支持与 promptTemplate
相同的占位符。
API
api.aiChat.accept()
接受当前的 AI 建议:
- 移除内容中的 AI 标记
- 隐藏 AI 聊天界面
- 聚焦编辑器
api.aiChat.insertBelow()
在当前块下方插入 AI 内容。
Parameters
Collapse all
包含要插入内容的编辑器。
同时处理块选择和普通选择模式:
- 在块选择中:在最后选中的块后插入
- 在普通选择中:在当前块后插入
api.aiChat.replaceSelection()
用 AI 内容替换当前选择。
Parameters
Collapse all
包含要替换内容的编辑器。
处理不同的选择模式:
- 单块选择:替换选中的块,将其格式应用到所有插入的内容
- 多块选择:替换所有选中的块,除非启用
forceUniformFormatting
,否则保留原始格式 - 普通选择:替换当前选择,同时保持周围上下文
api.aiChat.reset()
重置聊天状态:
- 停止任何正在进行的生成
- 清除聊天消息
- 从编辑器中移除所有 AI 节点
api.aiChat.submit()
提交提示以生成 AI 内容。
Parameters
Collapse all
在插入模式下,提交前撤销之前的 AI 更改。
Transforms
tf.ai.insertNodes()
插入带有 AI 标记的 AI 生成节点。
Parameters
Collapse all
要插入的带 AI 标记的节点。
tf.ai.removeMarks()
移除指定位置节点中的 AI 标记。
Parameters
Collapse all
tf.ai.removeNodes()
移除带有 AI 标记的节点。
Parameters
Collapse all
tf.ai.undo()
AI 更改的特殊撤销操作:
- 如果最后一个操作是 AI 生成的,则撤销它
- 移除重做栈entry以防止重做 AI 操作