AI 驱动的写作辅助。

Loading...

功能特点

  • 智能命令菜单: 带有预定义 AI 命令的组合框界面,用于生成和编辑
  • 多种触发模式:
    • 光标模式: 在块末尾用空格触发
    • 选择模式: 用选中的文本触发
    • 块选择模式: 用选中的块触发
  • 响应模式:
    • 聊天模式: 预览响应并提供接受/拒绝选项
    • 插入模式: 直接插入内容并支持 markdown 流式传输
  • 智能内容处理: 针对表格、代码块和复杂结构优化的分块处理
  • 流式响应: 实时 AI 内容生成
  • Markdown 集成: 完全支持 AI 响应中的 Markdown 语法
  • 可自定义提示: 用户和系统提示的模板系统
  • 内置 Vercel AI SDK 支持: 即用型聊天 API 集成

Kit 使用

安装

添加 AI 功能最快的方法是使用 AIKit,它包含预配置的 AIPluginAIChatPlugin,以及光标覆盖和 markdown 支持及其 Plate UI 组件。

添加 Kit

import { createPlateEditor } from 'platejs/react';
import { AIKit } from '@/components/editor/plugins/ai-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...其他插件,
    ...AIKit,
  ],
});

添加 API 路由

AI 功能需要服务器端 API 端点。添加预配置的 AI 命令路由:

配置环境

确保在环境变量中设置了 OpenAI API 密钥:

.env.local
OPENAI_API_KEY="your-api-key"

手动使用

安装

pnpm add @platejs/ai @platejs/selection @platejs/markdown @platejs/basic-nodes

添加插件

import { AIPlugin, AIChatPlugin } from '@platejs/ai/react';
import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...其他插件,
    ...MarkdownKit, // AI 内容处理必需
    AIPlugin,
    AIChatPlugin,
  ],
});
  • MarkdownKit: 处理带有 Markdown 语法和 MDX 支持的 AI 响应所必需。
  • AIPlugin: 用于 AI 内容管理和转换的核心插件。
  • AIChatPlugin: 处理 AI 聊天界面、流式传输和用户交互。

配置插件

创建带有基本配置的扩展 aiChatPlugin:

import type { AIChatPluginConfig } from '@platejs/ai/react';
import type { UseChatOptions } from 'ai/react';
 
import { KEYS, PathApi } from 'platejs';
import { streamInsertChunk, withAIBatch } from '@platejs/ai';
import { AIChatPlugin, AIPlugin, useChatChunk } from '@platejs/ai/react';
import { usePluginOption } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
import { AILoadingBar, AIMenu } from '@/components/ui/ai-menu';
import { AIAnchorElement, AILeaf } from '@/components/ui/ai-node';
 
export const aiChatPlugin = AIChatPlugin.extend({
  options: {
    chatOptions: {
      api: '/api/ai/command',
      body: {},
    } as UseChatOptions,
  },
  render: {
    afterContainer: AILoadingBar,
    afterEditable: AIMenu,
    node: AIAnchorElement,
  },
  shortcuts: { show: { keys: 'mod+j' } },
});
 
const plugins = [
  // ...其他插件,
  ...MarkdownKit,
  AIPlugin.withComponent(AILeaf),
  aiChatPlugin,
];
  • chatOptions: Vercel AI SDK useChat 钩子的配置。
  • render: AI 界面的 UI 组件。
  • shortcuts: 键盘快捷键(Cmd+J 显示 AI 菜单)。

使用 useHooks 添加流式传输

useChatChunk 钩子实时处理流式 AI 响应,处理内容插入和块管理。它监控聊天状态并处理传入的文本块,在它们到达时将它们插入编辑器:

export const aiChatPlugin = AIChatPlugin.extend({
  // ... 之前的选项
  useHooks: ({ editor, getOption }) => {
    const mode = usePluginOption(
      { key: KEYS.aiChat } as AIChatPluginConfig,
      'mode'
    );
 
    useChatChunk({
      onChunk: ({ chunk, isFirst, nodes }) => {
        if (isFirst && mode == 'insert') {
          editor.tf.withoutSaving(() => {
            editor.tf.insertNodes(
              {
                children: [{ text: '' }],
                type: KEYS.aiChat,
              },
              {
                at: PathApi.next(editor.selection!.focus.path.slice(0, 1)),
              }
            );
          });
          editor.setOption(AIChatPlugin, 'streaming', true);
        }
 
        if (mode === 'insert' && nodes.length > 0) {
          withAIBatch(
            editor,
            () => {
              if (!getOption('streaming')) return;
              editor.tf.withScrolling(() => {
                streamInsertChunk(editor, chunk, {
                  textProps: {
                    ai: true,
                  },
                });
              });
            },
            { split: isFirst }
          );
        }
      },
      onFinish: () => {
        editor.setOption(AIChatPlugin, 'streaming', false);
        editor.setOption(AIChatPlugin, '_blockChunks', '');
        editor.setOption(AIChatPlugin, '_blockPath', null);
      },
    });
  },
});
  • onChunk: 处理每个流式块,在第一个块创建 AI 节点并实时插入内容
  • onFinish: 响应完成时清理流式状态
  • 使用 withAIBatchstreamInsertChunk 进行优化的内容插入

系统提示

系统提示定义了 AI 的角色和行为。您可以在扩展插件中自定义 systemTemplate:

export const customAIChatPlugin = AIChatPlugin.extend({
  options: {
    systemTemplate: ({ isBlockSelecting, isSelecting }) => {
      const customSystem = `你是一个专门从事代码和 API 文档的技术文档助手。
 
规则:
- 提供准确、结构良好的技术内容
- 使用适当的代码格式和语法高亮
- 包含相关示例和最佳实践
- 保持一致的文档风格
- 重要:除非明确要求,否则不要删除或修改自定义 MDX 标签。
- 重要:区分指令和问题。`;
 
      return isBlockSelecting
        ? `${customSystem}
- <Selection> 表示用户选择并想要修改或询问的完整文本块。
- 你的响应应该是对整个 <Selection> 的直接替换。
- 除非另有明确指示,否则保持所选块的整体结构和格式。
<Selection>
{block}
</Selection>`
        : isSelecting
          ? `${customSystem}
- <Block> 是包含用户选择的文本块,提供上下文。
- <Selection> 是用户在块中选择并想要修改或询问的特定文本。
- 考虑 <Block> 提供的上下文,但只修改 <Selection>。
<Block>
{block}
</Block>
<Selection>
{selection}
</Selection>`
          : `${customSystem}
- <Block> 是用户当前正在处理的文本块。
 
<Block>
{block}
</Block>`;
    },
    // ...其他选项
  },
}),

用户提示

自定义用户提示在扩展插件中的格式和上下文:

export const customAIChatPlugin = AIChatPlugin.extend({
  options: {
    promptTemplate: ({ isBlockSelecting, isSelecting }) => {
      return isBlockSelecting
        ? `<Reminder>
如果是问题,请提供关于 <Selection> 的有帮助且简洁的回答。
如果是指令,请仅提供替换整个 <Selection> 的内容。不要解释。
分析并改进以下内容块,保持结构和清晰度。
永远不要写入 <Block> 或 <Selection>。
</Reminder>
{prompt} 关于 <Selection>`
        : isSelecting
          ? `<Reminder>
如果是问题,请提供关于 <Selection> 的有帮助且简洁的回答。
如果是指令,请仅提供替换 <Selection> 的文本。不要解释。
确保它无缝融入 <Block>。如果 <Block> 为空,写一个随机句子。
永远不要写入 <Block> 或 <Selection>。
</Reminder>
{prompt} 关于 <Selection>`
          : `<Reminder>
重要:永远不要写入 <Block>。
自然地继续或改进内容。
</Reminder>
{prompt}`;
    },
    // ...其他选项
  },
}),

添加 API 路由

创建一个针对不同内容类型优化的流式 API 路由处理程序:

app/api/ai/command/route.ts
import type { TextStreamPart, ToolSet } from 'ai';
import type { NextRequest } from 'next/server';
 
import { createOpenAI } from '@ai-sdk/openai';
import { InvalidArgumentError } from '@ai-sdk/provider';
import { delay as originalDelay } from '@ai-sdk/provider-utils';
import { convertToCoreMessages, streamText } from 'ai';
import { NextResponse } from 'next/server';
 
const CHUNKING_REGEXPS = {
  line: /\n+/m,
  list: /.{8}/m,
  word: /\S+\s+/m,
};
 
export async function POST(req: NextRequest) {
  const { apiKey: key, messages, system } = await req.json();
 
  const apiKey = key || process.env.OPENAI_API_KEY;
 
  if (!apiKey) {
    return NextResponse.json(
      { error: '缺少 OpenAI API 密钥。' },
      { status: 401 }
    );
  }
 
  const openai = createOpenAI({ apiKey });
 
  let isInCodeBlock = false;
  let isInTable = false;
  let isInList = false;
  let isInLink = false;
 
  try {
    const result = streamText({
      experimental_transform: smoothStream({
        chunking: (buffer) => {
          // 检测内容类型以优化分块
          if (/```[^\s]+/.test(buffer)) {
            isInCodeBlock = true;
          } else if (isInCodeBlock && buffer.includes('```')) {
            isInCodeBlock = false;
          }
 
          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];
        },
        delayInMs: () => (isInCodeBlock || isInTable ? 100 : 30),
      }),
      maxTokens: 2048,
      messages: convertToCoreMessages(messages),
      model: openai('gpt-4o'),
      system: system,
    });
 
    return result.toDataStreamResponse();
  } catch {
    return NextResponse.json(
      { error: '处理 AI 请求失败' },
      { status: 500 }
    );
  }
}
 
// 用于优化分块的平滑流实现
function smoothStream<TOOLS extends ToolSet>({
  _internal: { delay = originalDelay } = {},
  chunking = 'word',
  delayInMs = 10,
}: {
  _internal?: {
    delay?: (delayInMs: number | null) => Promise<void>;
  };
  chunking?: ChunkDetector | RegExp | 'line' | 'word';
  delayInMs?: delayer | number | null;
} = {}): (options: {
  tools: TOOLS;
}) => TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>> {
  let detectChunk: ChunkDetector;
 
  if (typeof chunking === 'function') {
    detectChunk = (buffer) => {
      const match = chunking(buffer);
      if (match == null) return null;
      if (match.length === 0) {
        throw new Error(`分块函数必须返回非空字符串。`);
      }
      if (!buffer.startsWith(match)) {
        throw new Error(
          `分块函数必须返回缓冲区前缀的匹配项。`
        );
      }
      return match;
    };
  } else {
    const chunkingRegex =
      typeof chunking === 'string' ? CHUNKING_REGEXPS[chunking] : chunking;
 
    if (chunkingRegex == null) {
      throw new InvalidArgumentError({
        argument: 'chunking',
        message: `分块必须是 "word" 或 "line" 或 RegExp。收到: ${chunking}`,
      });
    }
 
    detectChunk = (buffer) => {
      const match = chunkingRegex.exec(buffer);
      if (!match) return null;
      return buffer.slice(0, match.index) + match?.[0];
    };
  }
 
  return () => {
    let buffer = '';
 
    return new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({
      async transform(chunk, controller) {
        if (chunk.type !== 'text-delta') {
          if (buffer.length > 0) {
            controller.enqueue({ textDelta: buffer, type: 'text-delta' });
            buffer = '';
          }
          controller.enqueue(chunk);
          return;
        }
 
        buffer += chunk.textDelta;
        let match;
 
        while ((match = detectChunk(buffer)) != null) {
          controller.enqueue({ textDelta: match, type: 'text-delta' });
          buffer = buffer.slice(match.length);
 
          const _delayInMs =
            typeof delayInMs === 'number'
              ? delayInMs
              : (delayInMs?.(buffer) ?? 10);
 
          await delay(_delayInMs);
        }
      },
    });
  };
}

然后在 .env.local 中设置你的 OPENAI_API_KEY

添加工具栏按钮

你可以在工具栏中添加 AIToolbarButton 来打开 AI 菜单。

键盘快捷键

KeyDescription
Space

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

Cmd + J

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

Escape关闭 AI 菜单

Plate Plus

自定义

添加自定义 AI 命令

你可以通过向 aiChatItems 对象添加新项目并更新菜单状态项目来扩展 AI 菜单。

简单自定义命令

添加一个提交自定义提示的基本命令:

// 添加到你的 ai-menu.tsx aiChatItems 对象
summarizeInBullets: {
  icon: <ListIcon />,
  label: '以要点形式总结',
  value: 'summarizeInBullets',
  onSelect: ({ editor }) => {
    void editor.getApi(AIChatPlugin).aiChat.submit({
      prompt: '将此内容总结为要点',
    });
  },
},

复杂逻辑命令

创建在提交前具有客户端逻辑的命令:

generateTOC: {
  icon: <BookIcon />,
  label: '生成目录',
  value: 'generateTOC',
  onSelect: ({ editor }) => {
    // 检查文档是否有标题
    const headings = editor.api.nodes({
      match: (n) => ['h1', 'h2', 'h3'].includes(n.type as string),
    });
 
    if (headings.length === 0) {
      void editor.getApi(AIChatPlugin).aiChat.submit({
        mode: 'insert',
        prompt: '为此文档创建带有示例标题的目录',
      });
    } else {
      void editor.getApi(AIChatPlugin).aiChat.submit({
        mode: 'insert',
        prompt: '根据现有标题生成目录',
      });
    }
  },
},

理解菜单状态

AI 菜单根据用户选择和 AI 响应状态适应不同的上下文:

const menuState = React.useMemo(() => {
  // 如果 AI 已经响应,显示建议操作
  if (messages && messages.length > 0) {
    return isSelecting ? 'selectionSuggestion' : 'cursorSuggestion';
  }
  
  // 如果还没有 AI 响应,显示命令操作
  return isSelecting ? 'selectionCommand' : 'cursorCommand';
}, [isSelecting, messages]);

菜单状态:

  • cursorCommand:无选择,无 AI 响应 → 显示生成命令(继续写作、总结等)
  • selectionCommand:文本已选择,无 AI 响应 → 显示编辑命令(改进写作、修正拼写等)
  • cursorSuggestion:无选择,AI 已响应 → 显示建议操作(接受、丢弃、重试)
  • selectionSuggestion:文本已选择,AI 已响应 → 显示替换操作(替换选择、在下方插入等)

更新菜单状态

menuStateItems 中的适当菜单状态添加自定义命令:

const menuStateItems: Record<EditorChatState, { items: any[] }[]> = {
  cursorCommand: [
    {
      items: [
        aiChatItems.generateTOC,
        aiChatItems.summarizeInBullets,
        // ... 现有项目
      ],
    },
  ],
  selectionCommand: [
    {
      items: [
        aiChatItems.summarizeInBullets, // 也适用于选中的文本
        // ... 现有项目
      ],
    },
  ],
  // ... 其他状态
};

切换 AI 模型

在 API 路由中配置不同的 AI 模型和提供商:

app/api/ai/command/route.ts
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
 
export async function POST(req: NextRequest) {
  const { model = 'gpt-4o', provider = 'openai', ...rest } = await req.json();
 
  let aiProvider;
  
  switch (provider) {
    case 'anthropic':
      aiProvider = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
      break;
    case 'openai':
    default:
      aiProvider = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
      break;
  }
 
  const result = streamText({
    model: aiProvider(model),
    // ... 其他选项
  });
 
  return result.toDataStreamResponse();
}

aiChatPlugin 中配置模型:

export const aiChatPlugin = AIChatPlugin.extend({
  options: {
    chatOptions: {
      api: '/api/ai/command',
      body: {
        model: 'gpt-4o-mini', // 或 'claude-4-sonnet'
        provider: 'openai',   // 或 'anthropic'
      },
    },
    // ... 其他选项
  },
});

有关更多 AI 提供商和模型,请参阅 Vercel AI SDK 文档

自定义流式优化

使用自定义分块策略优化特定内容类型的流式性能:

app/api/ai/command/route.ts
const customChunking = (buffer: string) => {
  // 检测 JSON 内容以进行较慢的分块
  if (buffer.includes('{') && buffer.includes('}')) {
    const jsonMatch = /\{[^}]*\}/g.exec(buffer);
    if (jsonMatch) {
      return buffer.slice(0, jsonMatch.index + jsonMatch[0].length);
    }
  }
 
  // 检测代码块以进行基于行的分块
  if (buffer.includes('```')) {
    const lineMatch = /\n+/m.exec(buffer);
    return lineMatch ? buffer.slice(0, lineMatch.index + lineMatch[0].length) : null;
  }
 
  // 默认单词分块
  const wordMatch = /\S+\s+/m.exec(buffer);
  return wordMatch ? buffer.slice(0, wordMatch.index + wordMatch[0].length) : null;
};
 
// 在 streamText 配置中使用
const result = streamText({
  experimental_transform: smoothStream({
    chunking: customChunking,
    delayInMs: (buffer) => {
      // 复杂内容较慢,简单文本较快
      return buffer.includes('```') || buffer.includes('{') ? 80 : 20;
    },
  }),
  // ... 其他选项
});

安全注意事项

实现 AI 功能的安全最佳实践:

app/api/ai/command/route.ts
export async function POST(req: NextRequest) {
  const { messages, system } = await req.json();
 
  // 验证请求结构
  if (!messages || !Array.isArray(messages)) {
    return NextResponse.json({ error: '无效的消息' }, { status: 400 });
  }
 
  // 内容长度验证
  const totalContent = messages.map(m => m.content).join('');
  if (totalContent.length > 50000) {
    return NextResponse.json({ error: '内容过长' }, { status: 413 });
  }
 
  // 速率限制(使用您首选的解决方案实现)
  // await rateLimit(req);
 
  // 内容过滤(可选)
  // const filteredMessages = await filterContent(messages);
 
  // 处理 AI 请求...
}

安全指南:

  • 验证输入:始终验证和清理用户提示
  • 速率限制:在 AI 端点上实现速率限制
  • 内容过滤:考虑对响应进行内容过滤
  • API 密钥安全:切勿在客户端暴露 API 密钥
  • 用户隐私:注意发送给 AI 模型的数据

插件

AIPlugin

核心插件,扩展编辑器以支持 AI 内容管理功能。

Options

  • node object

    AI 叶子元素的节点配置。

    • isLeaf: true:AI 内容被视为叶子节点
    • isDecoration: false:不用于装饰

AIChatPlugin

主要插件,支持 AI 聊天操作、流式传输和用户界面交互。

Options

Collapse all
  • chatOptions UseChatOptions

    Vercel AI SDK useChat 钩子的配置选项。

    • api:AI 请求的 API 端点
    • body:额外的请求体参数
  • mode optional 'chat' | 'insert'

    指定如何处理助手消息:

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

    AI 聊天界面是否打开。

    • 默认值: false
  • streaming optional boolean

    AI 响应是否正在流式传输。

    • 默认值: false
  • promptTemplate optional (props: EditorPromptParams) => string

    生成用户提示的模板。支持占位符:

    • {block}:选择中块的 Markdown
    • {editor}:整个编辑器内容的 Markdown
    • {selection}:当前选择的 Markdown
    • {prompt}:实际用户提示
    • 默认值: '{prompt}'
  • systemTemplate optional (props: EditorPromptParams) => string | void

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

  • aiEditor optional SlateEditor | null

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

    • 默认值: null
  • chat optional Partial<UseChatHelpers>

    useChat 钩子返回的聊天助手。

API

api.aiChat.accept()

接受当前 AI 建议:

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

接受当前的 AI 建议:

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

api.aiChat.insertBelow()

在当前块下方插入 AI 生成的内容。

处理块选择和普通选择两种模式:

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

Parameters

Collapse all
  • sourceEditor PlateEditor

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

  • options optional object

    插入行为的选项。

Optionsobject

Collapse all
  • format optional 'all' | 'none' | 'single'

    要应用的格式:

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

api.aiChat.replaceSelection()

用 AI 生成的内容替换当前选择。

处理不同的选择模式:

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

Parameters

Collapse all
  • sourceEditor PlateEditor

    包含替换内容的编辑器。

  • options optional object

    替换行为的选项。

Optionsobject

Collapse all
  • format optional 'all' | 'none' | 'single'

    要应用的格式:

    • 'all':对所有块应用格式
    • 'none':替换时不应用格式
    • 'single':仅对第一个块应用格式
    • 默认值: 'single'

api.aiChat.reset()

重置聊天状态:

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

api.aiChat.node()

获取 AI 聊天节点条目。

Parameters

Collapse all
  • options optional EditorNodesOptions & { anchor?: boolean; streaming?: boolean }

    查找节点的选项。

OptionsEditorNodesOptions & { anchor?: boolean; streaming?: boolean }

Collapse all
  • anchor optional boolean

    为 true 时,查找类型与插件类型匹配的节点。

    • 默认值: false
  • streaming optional boolean

    为 true 时,查找流式 AI 节点。

    • 默认值: false

ReturnsNodeEntry | undefined

    找到的节点条目,如果未找到则返回 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 内容。

Parameters

Collapse all
  • options optional SubmitOptions

    提交的选项。

OptionsSubmitOptions

Collapse all
  • mode optional 'chat' | 'insert'

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

    • 默认值: 选择时为 'chat',否则为 'insert'
  • prompt optional string

    自定义提示。

    • 默认值: 如果未提供则使用聊天输入
  • system optional string

    此请求的自定义系统消息。

转换

tf.aiChat.removeAnchor()

从编辑器中移除 AI 聊天锚点节点。

Parameters

Collapse all
  • options optional EditorNodesOptions

    查找要移除节点的选项。

tf.aiChat.accept()

接受当前 AI 建议并将其集成到编辑器内容中。

tf.aiChat.insertBelow()

在当前块下方插入 AI 内容的转换。

tf.aiChat.replaceSelection()

用 AI 内容替换当前选择的转换。

tf.ai.insertNodes()

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

Parameters

Collapse all
  • nodes Descendant[]

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

  • options optional InsertNodesOptions

    插入节点的选项。

OptionsInsertNodesOptions

Collapse all
  • target optional Path

    插入的目标路径。

    • 默认值: 当前选择

tf.ai.removeMarks()

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

Parameters

Collapse all
  • options optional RemoveMarksOptions

    移除标记的选项。

OptionsRemoveMarksOptions

Collapse all
  • at optional Location

    要移除标记的位置。

    • 默认值: 整个文档

tf.ai.removeNodes()

移除带有 AI 标记的节点。

Parameters

Collapse all
  • options optional RemoveNodesOptions

    移除节点的选项。

OptionsRemoveNodesOptions

Collapse all
  • at optional Path

    要移除节点的路径。

    • 默认值: 整个文档

tf.ai.undo()

AI 更改的特殊撤销操作:

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

钩子

useAIChatEditor

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

Parameters

Collapse all
  • editor PlateEditor

    要注册的编辑器实例。

  • content string

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

  • options optional object

    内容处理的选项。

Optionsobject

Collapse all
  • memoize optional boolean

    启用带有 _memo 属性的块级记忆化。

    • 默认值: true
  • parser optional ParseMarkdownBlocksOptions

    markdown 标记解析器的选项。可以过滤特定的标记类型。

  • processor optional (processor: Processor) => Processor

    自定义 markdown 处理器的函数。

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