使用 AI 工具包的样式建议

当 AI 对文档进行更改时,您可以在编辑器中将这些更改显示为建议。在本指南中,您将学习如何通过修改样式来自定义建议的外观,并在其中显示自定义元素(例如菜单)。

为建议添加 CSS 样式

当使用默认的 renderDecorations 函数时,会应用以下类名:

  • .tiptap-ai-suggestion:应用于被建议范围覆盖的内容。
  • .tiptap-ai-suggestion--selected:当光标位于建议范围内时应用。
  • .tiptap-ai-suggestion--change-group:当建议是多个更改组成的组而非单个更改时应用。
  • .tiptap-ai-suggestion-sub-change:应用于更改组中的单个子更改。
  • .tiptap-ai-suggestion-diff:应用于显示在建议范围旁边的差异部件。
  • .tiptap-ai-suggestion-diff--selected:当光标位于建议范围内时,应用于差异部件。
  • .tiptap-ai-suggestion-diff--change-group:当差异部件是多个更改组成的组而非单个更改时应用。
  • .tiptap-ai-suggestion-diff-sub-change:应用于差异更改组中的单个子更改。

您可以使用上述类来样式化建议,或提供自定义的 renderDecorations 函数以完全控制 UI。

CSS 样式示例

我们提供示例 CSS 样式供您快速上手。

预览模式

预览模式中使用这些样式,以便在更改插入文档之前预览它们。

/* 将删除文字高亮为红色 */
.tiptap-ai-suggestion,
.tiptap-ai-suggestion > * {
  background-color: oklch(80.8% 0.114 19.571);
  color: oklch(0.396 0.141 25.723);
}

/* 更改组的背景色较浅(子更改变为较强高亮) */
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group,
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group > *:not(.tiptap-ai-suggestion-sub-change) {
  background-color: oklch(0.936 0.032 17.717);
}

/* 行内更改组内的子更改高亮 */
.tiptap-ai-suggestion-sub-change {
  background-color: oklch(80.8% 0.114 19.571);
}

/* 将插入文字高亮为绿色 */
.tiptap-ai-suggestion-diff,
.tiptap-ai-suggestion-diff > * {
  background-color: oklch(87.1% 0.15 154.449);
}

/* 差异更改组的背景色较浅(子更改变为较强高亮) */
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group,
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group
  > *:not(.tiptap-ai-suggestion-diff-sub-change) {
  background-color: oklch(0.962 0.044 156.743);
}

/* 替换差异部件内的子更改高亮 */
.tiptap-ai-suggestion-diff-sub-change {
  background-color: oklch(87.1% 0.15 154.449);
}

/* 正确渲染表格行插入 */
.tiptap-ai-suggestion-diff:has(tr) {
  display: contents;
}

.tiptap-ai-suggestion-diff:has(tr) td,
.tiptap-ai-suggestion-diff:has(tr) th {
  background-color: oklch(87.1% 0.15 154.449);
}

审阅模式 / 文档比较

审阅模式中使用这些样式,用于在文档中插入后审阅更改。

显示由文档比较功能生成的建议时也使用这些样式。

/* 将插入文字高亮为绿色 */
.tiptap-ai-suggestion,
.tiptap-ai-suggestion > * {
  background-color: oklch(87.1% 0.15 154.449);
}

/* 更改组的背景色较浅(子更改变为较强高亮) */
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group,
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group > *:not(.tiptap-ai-suggestion-sub-change) {
  background-color: oklch(0.962 0.044 156.743);
}

/* 行内更改组内的子更改高亮 */
.tiptap-ai-suggestion-sub-change {
  background-color: oklch(87.1% 0.15 154.449);
}

/* 将删除文字高亮为红色 */
.tiptap-ai-suggestion-diff,
.tiptap-ai-suggestion-diff > * {
  background-color: oklch(80.8% 0.114 19.571);
  color: oklch(0.396 0.141 25.723);
}

/* 差异更改组的背景色较浅(子更改变为较强高亮) */
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group,
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group
  > *:not(.tiptap-ai-suggestion-diff-sub-change) {
  background-color: oklch(0.936 0.032 17.717);
}

/* 替换差异部件内的子更改高亮 */
.tiptap-ai-suggestion-diff-sub-change {
  background-color: oklch(80.8% 0.114 19.571);
}

/* 正确渲染表格行删除 */
.tiptap-ai-suggestion-diff:has(tr) {
  display: contents;
}

.tiptap-ai-suggestion-diff:has(tr) td,
.tiptap-ai-suggestion-diff:has(tr) th {
  background-color: oklch(80.8% 0.114 19.571);
}

在建议预览中将自定义节点视图设为只读

当建议预览渲染自定义节点视图时,这些节点视图会接收到标记它们属于 AI 工具包建议的装饰。使用 isAiToolkitSuggestionNodeView 来检测该状态,并禁用节点视图中的编辑控件。

import { NodeViewWrapper } from '@tiptap/react'
import { isAiToolkitSuggestionNodeView } from '@tiptap-pro/ai-toolkit'

function MathBlockNodeView({ decorations }) {
  const isAiToolkitSuggestion = isAiToolkitSuggestionNodeView(decorations)

  return (
    <NodeViewWrapper contentEditable={!isAiToolkitSuggestion}>
      {!isAiToolkitSuggestion ? <button>Edit equation</button> : null}
      {/* 在此处渲染节点视图内容。 */}
    </NodeViewWrapper>
  )
}

在建议中渲染 React 组件

您可以使用 renderDecorations 选项,结合React 门户,在建议内显示 React 组件。

首先,定义一个 React 组件并用 hook 存储 React 门户的挂载 HTML 元素。

import { useState } from 'react'

function MyComponent() {
  const [portalElement, setPortalElement] = useState<HTMLElement | null>(null)

  return null
}

然后,在 displayOptions.renderDecorations 选项里,创建一个带有 HTML 元素的小部件装饰,用作 React 门户的入口点。

const aiToolkit = getAiToolkit(editor)
// AI 工具包中生成建议的示例方法
aiToolkit.executeTool({
  reviewOptions: {
    mode: 'preview',
    displayOptions: {
      renderDecorations({ suggestion, defaultRenderDecorations }) {
        const decorations = defaultRenderDecorations()
        decorations.push(
          Decoration.widget(suggestion.range.to, () => {
            const element = document.createElement('span')
            setPortalElement(element)
            return element
          }),
        )
        return decorations
      },
    },
  },
})

最后,在 React 组件内,将任意 React 元素渲染到该门户内。

import { useState } from 'react'
import { createPortal } from 'react-dom'

function MyComponent() {
  const [portalElement, setPortalElement] = useState<HTMLElement | null>(null)

  if (portalElement) {
    return <>{createPortal(<div>Hello, world!</div>, portalElement)}</>
  }
  return null
}

选中建议时显示弹出框或提示框

您可以使用上述技术,在选中建议时显示提示框。

若要在选中建议时显示弹出框,需要使用 getCustomSuggestionDecoration 选项。此函数允许您向建议中添加自定义元素,包括弹出框。

下面是使用 React UI 库实现的简化示例。

import { useState } from 'react'
import { createPortal } from 'react-dom'

// 在 React 组件内部
function MyComponent() {
  const toolkit = getAiToolkit(editor)

  // 首先,定义 hook 存储弹出框挂载的 HTML 元素
  const [popoverElement, setPopoverElement] = useState<HTMLElement | null>(null)

  // 工具执行时,配置装饰的渲染方式
  aiToolkit.executeTool({
    reviewOptions: {
      mode: 'preview',
      displayOptions: {
        renderDecorations({ suggestion, defaultRenderDecorations }) {
          const decorations = defaultRenderDecorations()

          // 创建包含 HTML 元素的 Prosemirror 装饰
          decorations.push(
            Decoration.widget(suggestion.range.to, () => {
              const element = document.createElement('span')

              setPopoverElement(element)
              return element
            }),
          )

          return decorations
        },
      },
    },
  })

  const selectedSuggestion = toolkit.getSelectedSuggestion()

  if (popoverElement && selectedSuggestion) {
    // 将内容添加到自定义元素中。本例使用 React 门户渲染弹出框。
    return <>{createPortal(<Popover suggestion={selectedSuggestion} />, popoverElement)}</>
  }

  return null
}

我们推荐使用 Floating UI 库来显示弹出框。

在弹出框中渲染建议时,可以使用 getNextWordgetPreviousWord 工具函数,显示建议所在句子的前后单词。

import { getNextWord, getPreviousWord } from '@tiptap-pro/ai-toolkit'

// 获取句子中的前一个单词。
const { previousWord } = getPreviousWord(editor, suggestion.range.from)
// 获取句子中的后一个单词及其后面的标点符号(如果为句尾)。
const { nextWord, punctuationMark } = getNextWord(editor, suggestion.range.to)

在编辑器外的侧边栏中显示建议

您可以通过 AI 工具包的 getSuggestions 方法获取当前建议。

const toolkit = getAiToolkit(editor)
const suggestions = toolkit.getSuggestions()

然后,您可以使用这些数据在 UI 中(编辑器外)渲染建议。以下是使用 React UI 库实现的示例:

// 从 AI 工具包获取建议
const toolkit = getAiToolkit(editor)
const suggestions = toolkit.getSuggestions()

// 在 UI 中渲染建议
return (
  <div>
    {suggestions.map((suggestion) => (
      <div key={suggestion.id}>
        <p>删除内容: {toolkit.getTextRange(suggestion.range)}</p>
        <p>添加内容: {suggestion.replacementOptions[0].content.toString()}</p>
      </div>
    ))}
  </div>
)

后续步骤

  • API 参考中了解更多有关建议的信息。请查看 displayOptions 参数以了解所有自定义选项。