Command Palette

Search for a command to run...

AI

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

import * as React from 'react';

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

import { editorPlugins } from '@/components/editor/plugins/editor-plugins';
import { useCreateEditor } from '@/components/editor/use-create-editor';
import { Editor, EditorContainer } from '@/components/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>
  );
}

功能特性

  • 带预设命令的组合框菜单:
    • 生成:继续写作、添加摘要、解释说明
    • 编辑:优化写作、添加表情符号、扩写/缩写、修正拼写和语法、简化语言
  • 三种触发模式:
    • 光标模式:在段落末尾触发
    • 选区模式:选中文本时触发
    • 块选区模式:选中多个段落时触发
  • 支持流式响应预览或直接插入编辑器
  • 支持Markdown语法
  • 内置Vercel AI SDK聊天API支持

安装

pnpm add @udecode/plate-ai @udecode/plate-selection @udecode/plate-markdown @udecode/plate-basic-marks

使用指南

插件配置

import { AIChatPlugin, AIPlugin } from '@udecode/plate-ai/react';
import {
  BaseBoldPlugin,
  BaseCodePlugin,
  BaseItalicPlugin,
  BaseStrikethroughPlugin,
  BaseUnderlinePlugin,
} from '@udecode/plate-basic-marks';
import { BaseBlockquotePlugin } from '@udecode/plate-block-quote';
import {
  BaseCodeBlockPlugin,
  BaseCodeLinePlugin,
  BaseCodeSyntaxPlugin,
} from '@udecode/plate-code-block';
import { BaseParagraphPlugin, createSlateEditor } from '@udecode/plate';
import { BaseHeadingPlugin, HEADING_LEVELS } from '@udecode/plate-heading';
import { BaseHorizontalRulePlugin } from '@udecode/plate-horizontal-rule';
import { BaseIndentListPlugin } from '@udecode/plate-indent-list';
import { BaseLinkPlugin } from '@udecode/plate-link';
import { MarkdownPlugin } from '@udecode/plate-markdown';
export const createAIEditor = () => {
  const editor = createSlateEditor({
    id: 'ai',
    plugins: [
      BaseBlockquotePlugin,
      BaseBoldPlugin,
      BaseCodeBlockPlugin,
      BaseCodeLinePlugin,
      BaseCodePlugin,
      BaseCodeSyntaxPlugin,
      BaseItalicPlugin,
      BaseStrikethroughPlugin,
      BaseUnderlinePlugin,
      BaseHeadingPlugin,
      BaseHorizontalRulePlugin,
      BaseLinkPlugin,
      BaseParagraphPlugin,
      BaseIndentListPlugin.extend({
        inject: {
          targetPlugins: [
            BaseParagraphPlugin.key,
            ...HEADING_LEVELS,
            BaseBlockquotePlugin.key,
            BaseCodeBlockPlugin.key,
          ],
        },
        options: {
          listStyleTypes: {
            todo: {
              liComponent: TodoLiStatic,
              markerComponent: TodoMarkerStatic,
              type: 'todo',
            },
          },
        },
      }),
      MarkdownPlugin.configure({
        options: {
          remarkPlugins: [remarkMath, remarkGfm, remarkMdx],
        },
      }),
    ],
  });
 
 
  return editor;
};
 
const systemCommon = `\
你是一个先进的AI智能笔记助手,旨在提升笔记管理的生产力和创造力。
直接响应用户提示,提供清晰、简洁且相关的内容。保持中立、有帮助的语气。
 
规则:
- <Document>代表用户正在处理的整个笔记文档
- <Reminder>是关于如何响应INSTRUCTIONS的提示,不适用于问题回答
- 其他内容均为用户提示
- 你的响应应针对用户提示,提供精确的笔记管理协助
- 对于INSTRUCTIONS:严格遵循<Reminder>。仅提供需要插入或替换的内容,不要包含解释或评论
- 对于QUESTIONS:提供有帮助且简洁的回答,必要时可包含简短说明
- 关键:区分INSTRUCTIONS和QUESTIONS。指令通常要求修改或添加内容,问题则询问信息或澄清
`;
 
const systemDefault = `\
${systemCommon}
- <Block>代表用户当前处理的文本块
- 确保输出能无缝融入现有<Block>结构
- 关键:仅提供单个文本块,不要创建多个段落或独立块
<Block>
{block}
</Block>
`;
 
const systemSelecting = `\
${systemCommon}
- <Block>包含用户选区的文本块,提供上下文
- 确保输出能无缝融入现有<Block>结构
- <Selection>是用户在块中选定的特定文本,需要修改或询问
- 考虑<Block>提供的上下文,但仅修改<Selection>。响应应直接替换<Selection>
<Block>
{block}
</Block>
<Selection>
{selection}
</Selection>
`;
 
const systemBlockSelecting = `\
${systemCommon}
- <Selection>代表用户选中的完整文本块,需要修改或询问
- 响应应直接替换整个<Selection>
- 除非明确指示,否则保持选中块的整体结构和格式
- 关键:仅提供替换<Selection>的内容。除非特别要求,不要添加额外块或改变块结构
<Selection>
{block}
</Selection>
`;
 
const userDefault = `<Reminder>
关键:不要使用块级格式,仅允许行内格式
关键:不要换行或新建段落
永远不要输出<Block>
</Reminder>
{prompt}`;
 
const userSelecting = `<Reminder>
如果是问题,提供关于<Selection>的有帮助且简洁的回答
如果是指令,仅提供替换<Selection>的文本,不要解释
确保内容能无缝融入<Block>。如果<Block>为空,写一个随机句子
永远不要输出<Block>或<Selection>
</Reminder>
{prompt} 关于 <Selection>`;
 
const userBlockSelecting = `<Reminder>
如果是问题,提供关于<Selection>的有帮助且简洁的回答
如果是指令,仅提供替换整个<Selection>的内容,不要解释
除非另有指示,否则保持整体结构
永远不要输出<Block>或<Selection>
</Reminder>
{prompt} 关于 <Selection>`;
 
export const PROMPT_TEMPLATES = {
  systemBlockSelecting,
  systemDefault,
  systemSelecting,
  userBlockSelecting,
  userDefault,
  userSelecting,
};
 
const plugins = [
  // ...其他插件
  MarkdownPlugin.configure({
    options: {
      remarkPlugins: [remarkMath, remarkGfm, remarkMdx],
    },
  }),
  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包:

  1. 使用streamText设置路由处理器
  2. AI菜单组件中集成useChat

将数据块转换为Plate节点

默认情况下,AI响应以分块方式流式传输到Plate编辑器。但这种方式在处理复杂节点(如表格和代码块)时可能存在问题。如果分块过于频繁,会导致这些重量级节点被反复替换,引发性能问题。

解决方案是使用streamText函数中的experimental_transform参数配合smoothStream函数来优化分块传输。下面的分块函数实现了默认行为:对表格、数学公式、链接和代码块使用基于行的分块,对其他内容使用基于词的分块。

    const result = streamText({
      experimental_transform: smoothStream({
        chunking: (buffer) => {
          // 检测代码块标记
          if (/```[^\s]+/.test(buffer)) {
            isInCodeBlock = true
          }else if(isInCodeBlock && buffer.includes('```') ) {
            isInCodeBlock = false
          }
          // 测试用例:不应反序列化带有markdown语法的链接
          if (buffer.includes('http')) {
            isInLink = true;
          } else if (buffer.includes('https')) {
            isInLink = true;
          } else if (buffer.includes('\n') && isInLink) {
            isInLink = false;
          }
          if (buffer.includes('*') || buffer.includes('-')) {
            isInList = true;
          } else if (buffer.includes('\n') && isInList) {
            isInList = false;
          }
          // 简单表格检测:遇到|进入表格模式,遇到双换行退出
          if (!isInTable && buffer.includes('|')) {
            isInTable = true;
          } else if (isInTable && buffer.includes('\n\n')) {
            isInTable = false;
          }
 
          // 根据内容类型选择分块策略
          let match;
 
          if (isInCodeBlock || isInTable || isInLink) {
            // 对代码块和表格使用行分块
            match = CHUNKING_REGEXPS.line.exec(buffer);
          } else if (isInList) {
            // 对列表使用列表分块
            match = CHUNKING_REGEXPS.list.exec(buffer);
          } else {
            // 对常规文本使用词分块
            match = CHUNKING_REGEXPS.word.exec(buffer);
          }
          if (!match) {
            return null;
          }
 
          return buffer.slice(0, match.index) + match?.[0];
        },
      }),
      // 其他配置项
      
      // maxTokens: 2048,
      // messages: convertToCoreMessages(messages),
      // model: openai('gpt-4o'),
      // system: system,
    });
 

快捷键

KeyDescription
Space

在空段落中打开AI菜单(光标模式)

Cmd + J

打开AI菜单(光标或选区模式)

Escape关闭AI菜单

示例

Plate UI

参考上方预览。

Plate Plus

Combobox menu with free-form prompt input

  • Additional trigger methods:
    • Block menu button
    • Slash command menu
  • Beautifully crafted UI

插件

AIPlugin

扩展编辑器以支持AI转换功能。

AIChatPlugin

此插件为实验性功能。

在编辑器中启用聊天操作和流式文本生成。

Options

Collapse all

    用于生成AI响应的编辑器实例。

    useChat返回的聊天助手。

    指定助手消息处理方式:

    • 'chat':显示带接受/拒绝选项的预览
    • 'insert':直接将内容插入编辑器
    • 默认值: 'chat'

    AI聊天界面是否打开。

    • 默认值: false

    AI响应是否正在流式传输(仅光标模式)。

    • 默认值: false

    提示模板,支持占位符:

    • {block}:选区中块的Markdown
    • {editor}:整个编辑器内容的Markdown
    • {selection}:当前选区的Markdown
    • {prompt}:实际用户提示
    • 默认值: '{prompt}'

    系统消息模板,支持与promptTemplate相同的占位符。

    内部使用,用于streamInsertChunk。

    内部使用,用于跟踪块路径。

API接口

api.aiChat.accept

接受当前AI建议:

  • 从内容中移除AI标记
  • 隐藏AI聊天界面
  • 聚焦编辑器

api.aiChat.insertBelow

在当前段落下方插入AI内容。

处理块选区和普通选区模式:

  • 块选区:在最后一个选中块后插入,应用最后一个块的格式
  • 普通选区:在当前块后插入,应用当前块的格式

Parameters

Collapse all

    包含待插入内容的编辑器。

    插入选项。

Optionsobject

Collapse all

    应用的格式:

    • 'all':应用格式到所有块
    • 'none':无格式插入
    • 'single':仅对第一个块应用格式
    • 默认值: 'single'

api.aiChat.replaceSelection

用AI内容替换当前选区。

处理不同选区模式:

  • 单块选区:替换选中块,根据format选项应用格式
  • 多块选区:替换所有选中块
    • format: 'none''single':保留原始格式
    • format: 'all':将第一个块的格式应用到所有内容
  • 普通选区:替换当前选区同时保持上下文

Parameters

Collapse all

    包含替换内容的编辑器。

    替换选项。

Optionsobject

Collapse all

    应用的格式:

    • 'all':应用格式到所有块
    • 'none':无格式插入
    • 'single':仅对第一个块应用格式
    • 默认值: 'single'

api.aiChat.reset

重置聊天状态:

  • 停止任何正在进行的生成
  • 清除聊天消息
  • 从编辑器中移除所有AI节点

api.aiChat.node

获取AI聊天节点entry。

Parameters

Collapse all

    查找节点的选项:

    • anchor:为true时查找匹配插件类型的节点
    • 默认(false)查找带有ai属性的节点

ReturnsNodeEntry | undefined

    找到的节点entry或未找到时返回undefined。

api.aiChat.reload

重新加载当前AI聊天:

  • 插入模式:撤销之前的AI更改
  • 使用当前系统提示重新加载聊天

api.aiChat.show

显示AI聊天界面:

  • 重置聊天状态
  • 清除消息
  • 将打开状态设为true

api.aiChat.hide

隐藏AI聊天界面:

  • 重置聊天状态
  • 将打开状态设为false
  • 聚焦编辑器
  • 移除AI锚点

api.aiChat.stop

停止当前AI生成:

  • 将流式状态设为false
  • 调用聊天停止函数

api.aiChat.submit

提交提示生成AI内容。

OptionsSubmitOptions

Collapse all

    使用模式。插入模式会在提交前撤销之前的AI更改。

    • 默认值: 选区时为'chat',其他为'insert'

    自定义提示。

    • 默认值: 未提供时使用聊天输入

    自定义系统消息。

转换方法

tf.aiChat.removeAnchor

移除AI聊天锚点节点。

Parameters

Collapse all

    查找要移除节点的选项。

tf.ai.insertNodes

插入带AI标记的AI生成节点。

Parameters

Collapse all

    要插入的带AI标记的节点。

    插入节点的选项。

OptionsInsertNodesOptions

Collapse all

    目标路径。

    • 默认值: 当前选区

tf.ai.removeMarks

从指定位置的节点中移除AI标记。

OptionsRemoveMarksOptions

Collapse all

    要移除标记的位置。

    • 默认值: 整个文档

tf.ai.removeNodes

移除具有AI标记的节点。

OptionsRemoveNodesOptions

Collapse all

    要移除节点的路径。

    • 默认值: 整个文档

tf.ai.undo

AI更改的特殊撤销操作:

  • 如果最后一个操作是AI生成的,则撤销该操作
  • 移除重做栈条目以防止重做AI操作

useAIChatEditor

一个在AI聊天插件中注册编辑器的钩子,并使用块级记忆化反序列化markdown内容。

Parameters

Collapse all

    要反序列化到编辑器中的markdown内容。

const AIChatEditor = ({ content }: { content: string }) => {
  const aiEditor = usePlateEditor({
    plugins: [
      // 你的编辑器插件
      MarkdownPlugin,
      // 等等...
    ],
  });
 
  useAIChatEditor(aiEditor, content, {
    // 可选的markdown解析器选项
    parser: {
      exclude: ['space'],
    },
  });
 
  return <Editor editor={aiEditor} />;
};