探索 Tiptap V3 的最新功能

存储和重新生成响应

Available in Start plan

AI Generation 扩展会将当前状态存储在其扩展存储中,位置为 editor.storage.ai || editor.storage.aiAdvanced(取决于你是否使用 extension-ai 或 extension-ai-advanced 扩展)。该存储用于追踪 AI 响应的当前状态以及所有历史响应。

key类型定义
state'loading' | 'error' | 'idle'当 AI 正在生成响应时,状态为 loading。响应生成结束后,状态为 idle。发生错误时状态为 error
responsestring | undefinedAI 最近生成的消息。当状态为 idle 并且是字符串时,表示之前生成的消息;若为 undefined,表示尚未生成任何消息。状态为 loading 时,该字符串包含 AI 当前已生成的内容(流式响应场景)。状态为 error 时为 undefined
errorError | undefined当前错误,仅在错误状态时设置。
generatedWith{ action: TextAction; options: TextOptions; range: undefined | Range; } | undefined表示上一次生成响应时使用的选项对象,只有在将内容插入编辑器时会设置 range
pastResponsesstring[]存储之前生成的响应(成功时),以最新的为先。接受或拒绝响应时此列表会被清空。

你可以使用此存储读取 AI 响应的当前状态,如下示例所示:

const aiStorage = editor.storage.ai || editor.storage.aiAdvanced

if (aiStorage.response.state === 'error') {
  // 出现的错误
  aiStorage.response.error
}

if (aiStorage.response.state === 'loading') {
  // 当前正在处理的消息
  aiStorage.response.message
}

if (aiStorage.response.state === 'idle') {
  if (aiStorage.response.message) {
    // 成功的响应
    aiStorage.response.message
  } else {
    // 尚未请求响应
  }
}

使用 AI 存储

想利用 Tiptap 内容 AI 生成结果,但不想让结果直接显示在编辑器中吗?你可以在任何 AI TextOption 中使用 insert: false,这样结果会存储到扩展中,而不是插入编辑器。

const chatMessage = 'Hello, how are you?'

editor
  .chain()
  .aiTextPrompt({
    text: chatMessage,
    stream: true,
    insert: false,
    format: 'rich-text',
  })
  .run()

之后,你可以使用 aiAcceptaiRejectaiRegenerate 命令。

aiAccept

该命令在用户接受 AI 响应时执行,默认行为是将响应插入编辑器。行为可根据传入的选项调整。

key类型定义
insertAtnumber | { from: number, to: number }如果是数字,接受响应并插入到编辑器指定索引位置;如果是 { from: number, to: number },则接受响应并替换编辑器中该范围的内容。
appendboolean若为 true,则不替换当前选区,而是追加在当前选区后面。

默认行为(无选项传入)是接受响应并替换当前选区内容插入编辑器。

// 接受响应并插入编辑器
editor.chain().aiAccept().run()

// 接受响应并插入到编辑器开头
editor.chain().aiAccept({ insertAt: 0 }).run()

// 接受响应并插入到编辑器末尾
editor.chain().aiAccept({ insertAt: editor.state.doc.content.size }).run()

// 接受响应并追加到当前选区后面
editor.chain().aiAccept({ append: true }).run()

aiRegenerate

该命令在用户想要重新生成 AI 响应时执行,会使用上一次 AI 文本操作的所有相同选项,并将新的响应添加到 (editor.storage.ai || editor.storage.aiAdvanced).pastResponses 数组中。

key类型定义
insertboolean是否将重新生成的响应插入编辑器。
insertAtnumber | { from: number, to: number }如果未指定,重新生成的响应会插入到上一次响应的位置。若为数字,重新生成的响应将插入到编辑器开头;若为 { from: number, to: number },则重新生成响应替换编辑器中该范围内容。

默认行为(无选项传入)是重新生成响应并替换当前选区内容插入编辑器。

// 重新生成响应并插入编辑器
editor.chain().aiRegenerate().run()

// 重新生成响应并插入到编辑器开头
editor.chain().aiRegenerate({ insertAt: 0 }).run()

// 重新生成响应并插入到编辑器末尾
editor.chain().aiRegenerate({ insertAt: editor.state.doc.content.size }).run()

// 重新生成响应并追加到当前选区后面
editor.chain().aiRegenerate({ append: true }).run()

aiReject

该命令在用户拒绝 AI 响应时执行,会将扩展的状态重置为初始的 idle 状态,并清空 (editor.storage.ai || editor.storage.aiAdvanced).pastResponses

key类型定义
type'reset' | 'pause'是重置 AI 到 idle 状态,还是仅暂停当前响应。默认值为 'reset'
editor.chain().aiReject().run()

// 不会清空 editor.storage.ai || editor.storage.aiAdvanced,适合保留当前响应在编辑器存储中
editor.chain().aiReject({ type: 'pause' }).run()

高级示例

扩展存储的一个应用场景是渲染 AI 生成内容的预览。

为了在编辑器中渲染聊天内容的预览,我们可以使用编辑器的 schema 生成对应的 HTML。这段 HTML 可用于某个元素内展示该内容的预览。

// 以 HTML 形式显示响应
import { tryParseToTiptapHTML } from '@tiptap-pro/extension-ai'

// 尝试将当前消息解析为 HTML,无法解析时返回 null
tryToParseToHTML((editor.storage.ai || editor.storage.aiAdvanced).response.message, editor)

// 尝试将过去的响应解析为 HTML,无法解析时返回 null
tryToParseToHTML((editor.storage.ai || editor.storage.aiAdvanced).pastResponses[0], editor)

// 例如在 React 中
function PreviewComponent({ editor }) {
  const htmlResponse = tryToParseToHTML(
    (editor.storage.ai || editor.storage.aiAdvanced).response.message,
    editor,
  )
  /* 由于先用 prose-mirror 解析,风险较小 */
  return <div dangerouslySetInnerHTML={{ __html: htmlResponse }}></div>
}

请参阅下面的演示,了解聊天预览的完整示例。