---
title: "AI 菜单"
description: "基于智能 AI 的内容编辑和生成上下文菜单。更多详情请查看文档。"
canonical_url: "https://tiptap.zhcndoc.com/ui-components/components/ai-menu"
---

# AI 菜单

基于智能 AI 的内容编辑和生成上下文菜单。更多详情请查看文档。

一个功能完备、基于 AI 的 Tiptap 编辑器上下文菜单。提供智能的内容编辑、生成和转换功能，支持浮动菜单定位和可自定义的 AI 操作。

> **Interactive demo:** [ai menu](https://template.tiptap.dev/preview/tiptap-ui/ai-menu)

## 安装

通过 Tiptap CLI 添加该组件：

```bash
npx @tiptap/cli@latest add ai-menu
```

## 组件

### `<AiMenu />`

一个全面的 AI 菜单，提供上下文的编辑和生成能力。

#### 使用示例

```tsx
import { EditorContent, EditorContext, useEditor } from '@tiptap/react'

// --- Tiptap 核心扩展 ---
import { StarterKit } from '@tiptap/starter-kit'
import { Ai } from '@tiptap-pro/extension-ai'
import { UiState } from '@/components/tiptap-extension/ui-state-extension'

import { HorizontalRule } from '@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension'
import { Selection } from '@tiptap/extensions'
import { AiProvider, useAi } from '@/components/contexts/ai-context'

// --- Tiptap UI ---
import { AiMenu } from '@/components/tiptap-ui/ai-menu'
import { AiAskButton } from '@/components/tiptap-ui/ai-ask-button'

// --- UI 基础组件 ---
import { ButtonGroup } from '@/components/tiptap-ui-primitive/button'

// --- 工具 ---
import { TIPTAP_AI_APP_ID } from '@/lib/tiptap-collab-utils'

// --- Tiptap 节点样式 ---
import '@/components/tiptap-node/blockquote-node/blockquote-node.scss'
import '@/components/tiptap-node/code-block-node/code-block-node.scss'
import '@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss'
import '@/components/tiptap-node/heading-node/heading-node.scss'
import '@/components/tiptap-node/paragraph-node/paragraph-node.scss'

export const AiMenuExample = () => {
  return (
    <AiProvider>
      <AiEditorWrapper />
    </AiProvider>
  )
}

const AiEditorWrapper = () => {
  const { aiToken } = useAi()

  if (!aiToken) {
    return <div className="tiptap-editor-wrapper">正在加载 AI...</div>
  }

  return <AiEditor aiToken={aiToken} />
}

const AiEditor = ({ aiToken }: { aiToken: string }) => {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [
      StarterKit.configure({
        horizontalRule: false,
      }),
      HorizontalRule,
      Selection,
      UiState,
      Ai.configure({
        token: aiToken,
        autocompletion: false,
        showDecorations: true,
        hideDecorationsOnStreamEnd: false,
        onLoading: (context) => {
          context.editor.commands.aiGenerationSetIsLoading(true)
          context.editor.commands.aiGenerationHasMessage(false)
        },
        onChunk: (context) => {
          context.editor.commands.aiGenerationSetIsLoading(true)
          context.editor.commands.aiGenerationHasMessage(true)
        },
        onSuccess: (context) => {
          const hasMessage = !!context.response
          context.editor.commands.aiGenerationSetIsLoading(false)
          context.editor.commands.aiGenerationHasMessage(hasMessage)
        },
      }),
    ],
    content: `
<p>今天，我们探索 AI 如何改变创意工作流程。从写作辅助到智能摘要，我们手中的工具快速演进。但我们如何负责任地使用它们？</p>
<p>本文将展示 AI 如何增强——而非替代——人类创造力的真实案例。</p>
        `,
  })

  return (
    <EditorContext.Provider value={{ editor }}>
      <div className="controls-bar">
        <div className="control-item">
          <ButtonGroup orientation="horizontal">
            <AiAskButton />
          </ButtonGroup>
        </div>
      </div>

      <EditorContent editor={editor} role="presentation" className="control-showcase">
        <AiMenu anchorToSelection />
      </EditorContent>
    </EditorContext.Provider>
  )
}
```

#### 属性

| 名称                  | 类型               | 默认值         | 描述                                         |
| ------------------- | ---------------- | ----------- | ------------------------------------------ |
| `editor`            | `Editor \| null` | `undefined` | Tiptap 编辑器实例                               |
| `anchorToSelection` | `boolean`        | `false`     | 若为 `true`，菜单锚点定位于当前文本选区，覆盖整个编辑器宽度，并动态响应滚动。 |

### `<AiMenuStateProvider />`

管理 AI 菜单状态的状态提供器，覆盖整个应用。

#### 使用示例

```tsx
import { AiMenuStateProvider } from '@/components/tiptap-ui/ai-menu'

function App() {
  return <AiMenuStateProvider>{/* 你的编辑器组件 */}</AiMenuStateProvider>
}
```

### `<AiMenuContent />`

渲染 AI 菜单界面的主内容组件。

#### 属性

| 名称                  | 类型               | 默认值         | 描述                                         |
| ------------------- | ---------------- | ----------- | ------------------------------------------ |
| `editor`            | `Editor \| null` | `undefined` | Tiptap 编辑器实例                               |
| `anchorToSelection` | `boolean`        | `false`     | 若为 `true`，菜单锚点定位于当前文本选区，覆盖整个编辑器宽度，并动态响应滚动。 |

## Hooks

### `useAiMenuState()`

用于访问和管理 AI 菜单状态的 Hook。

#### 使用示例

```tsx
import { useAiMenuState } from '@/components/tiptap-ui/ai-menu'

function MyComponent() {
  const { state, updateState, setFallbackAnchor, reset } = useAiMenuState()

  const handleOpenMenu = () => {
    updateState({ isOpen: true, shouldShowInput: true })
  }

  const handleCloseMenu = () => {
    reset()
  }

  return (
    <button onClick={state.isOpen ? handleCloseMenu : handleOpenMenu}>
      {state.isOpen ? '关闭 AI 菜单' : '打开 AI 菜单'}
    </button>
  )
}
```

#### 返回值

| 名称                  | 类型                                              | 描述         |
| ------------------- | ----------------------------------------------- | ---------- |
| `state`             | `AiMenuState`                                   | 当前 AI 菜单状态 |
| `updateState`       | `(updates: Partial<AiMenuState>) => void`       | 更新菜单状态的函数  |
| `setFallbackAnchor` | `(element: HTMLElement \| null, rect?) => void` | 设置备用定位锚点   |
| `reset`             | `() => void`                                    | 重置菜单状态为初始值 |

### `useAiContentTracker()`

用于追踪编辑器中 AI 生成内容变化的 Hook。支持两种定位模式：**流式模式**（当 `anchorToSelection` 为 `true` 并且 AI 正在加载时），使用 `ResizeObserver` 实时调整锚点位置；**静态模式**则基于 AI 元素位置建立带滚动感知的虚拟锚点。

#### 使用示例

```tsx
import { useAiContentTracker } from '@/components/tiptap-ui/ai-menu'

function MyComponent() {
  const { editor } = useTiptapEditor()

  useAiContentTracker({
    editor,
    aiGenerationActive: true,
    setAnchorElement: (el) => store.setAnchorElement(el),
    anchorToSelection: true,
  })

  return <div>带 AI 内容跟踪的组件</div>
}
```

#### 参数

| 名称                   | 类型                               | 默认值     | 描述                            |
| -------------------- | -------------------------------- | ------- | ----------------------------- |
| `editor`             | `Editor \| null`                 | —       | Tiptap 编辑器实例                  |
| `aiGenerationActive` | `boolean`                        | —       | AI 生成内容是否处于活跃状态               |
| `setAnchorElement`   | `(element: HTMLElement) => void` | —       | 设置浮动菜单锚点元素的回调函数               |
| `anchorToSelection`  | `boolean`                        | `false` | 是否使用覆盖整个编辑器宽度的虚拟锚点，并支持滚动及流式调整 |

### `useTextSelectionTracker()`

用于追踪文本选择变化以调整菜单定位的 Hook。当 `anchorToSelection` 为 `true` 时，基于选区的垂直位置创建覆盖编辑器宽度的虚拟锚点，替代原生选区 DOM 元素。

#### 使用示例

```tsx
import { useTextSelectionTracker } from '@/components/tiptap-ui/ai-menu'

function MyComponent() {
  const { editor } = useTiptapEditor()

  useTextSelectionTracker({
    editor,
    aiGenerationActive: true,
    showMenuAtElement: (el) => show(el),
    setMenuVisible: (visible) => updateState({ isOpen: visible }),
    anchorToSelection: true,
  })

  return <div>带选择跟踪的组件</div>
}
```

#### 参数

| 名称                   | 类型                                                              | 默认值     | 描述                         |
| -------------------- | --------------------------------------------------------------- | ------- | -------------------------- |
| `editor`             | `Editor \| null`                                                | —       | Tiptap 编辑器实例               |
| `aiGenerationActive` | `boolean`                                                       | —       | AI 生成内容是否处于活跃状态            |
| `showMenuAtElement`  | `(element: HTMLElement) => void`                                | —       | 显示菜单于指定元素的回调               |
| `setMenuVisible`     | `(visible: boolean) => void`                                    | —       | 设置菜单可见状态的回调                |
| `onSelectionChange`  | `(element: HTMLElement \| null, rect: DOMRect \| null) => void` | —       | 选区变化时的可选回调                 |
| `prevent`            | `boolean`                                                       | `false` | 若为 `true`，阻止追踪             |
| `anchorToSelection`  | `boolean`                                                       | `false` | 是否使用覆盖编辑器宽度的虚拟锚点，基于选区的垂直位置 |

## 子组件

### `<AiMenuItems />`

按类别分组展示可用 AI 操作的组件。

#### 使用示例

```tsx
import { AiMenuItems } from '@/components/tiptap-ui/ai-menu'

function CustomAiMenu() {
  const { editor } = useTiptapEditor()

  return (
    <AiMenuItems
      editor={editor}
      availableActions={['improveWriting', 'aiFixSpellingAndGrammar', 'summarize']}
    />
  )
}
```

### `<AiMenuActions />`

渲染 AI 菜单相关操作按钮（接受、重新生成等）的组件。

#### 使用示例

```tsx
import { AiMenuActions } from '@/components/tiptap-ui/ai-menu'

function CustomAiMenu() {
  const { editor } = useTiptapEditor()

  return (
    <AiMenuActions
      editor={editor}
      onAccept={() => console.log('AI 内容已接受')}
      onRegenerate={() => console.log('重新生成 AI 内容')}
    />
  )
}
```

### `<AiMenuInputTextarea />`

用于自定义 AI 提示语的输入组件。

#### 使用示例

```tsx
import { AiMenuInputTextarea } from '@/components/tiptap-ui/ai-menu'

function CustomPromptInput() {
  const handleSubmit = (prompt: string) => {
    console.log('用户提示:', prompt)
  }

  return (
    <AiMenuInputTextarea
      onSubmit={handleSubmit}
      placeholder="请向 AI 提问以帮助完善内容..."
    />
  )
}
```

## 工具函数

### `getContextAndInsertAt(editor)`

确定上下文及 AI 操作插入点的工具函数。

```tsx
import { getContextAndInsertAt } from '@/components/tiptap-ui/ai-menu'

const { context, insertAt, isSelection } = getContextAndInsertAt(editor)

if (isSelection) {
  // 处理基于选择的 AI 操作
  editor.chain().aiEdit({ prompt: '改进这段文字', insertAt }).run()
} else {
  // 处理基于插入点的 AI 操作
  editor.chain().aiGenerate({ prompt: '写一段关于 AI 的内容', insertAt }).run()
}
```

### `findPrioritizedAIElement(editor)`

查找最合适用于 AI 菜单定位的 DOM 元素。

```tsx
import { findPrioritizedAIElement } from '@/components/tiptap-ui/ai-menu'

const targetElement = findPrioritizedAIElement(editor)
if (targetElement) {
  // 将菜单相对于此元素定位
}
```

### `getSelectionRangeRect(editor)`

使用浏览器的 Selection API 获取当前文本选区的边界矩形。若选区为空，则返回 `null`。当原生方法失效时，使用 ProseMirror 坐标做备选。

```tsx
import { getSelectionRangeRect } from '@/components/tiptap-ui/ai-menu'

const rect = getSelectionRangeRect(editor)
if (rect) {
  // 使用 rect 进行定位
}
```

### `createEditorWidthAnchorRect(editorDom, sourceRect)`

基于给定的源矩形，在编辑器内容区（排除内边距和边框）创建一条覆盖编辑器宽度、位置在源矩形垂直位置的锚点矩形。

```tsx
import { createEditorWidthAnchorRect } from '@/components/tiptap-ui/ai-menu'

const selectionRect = getSelectionRangeRect(editor)
if (selectionRect) {
  const anchorRect = createEditorWidthAnchorRect(editor.view.dom, selectionRect)
  // anchorRect 覆盖编辑器内容区宽度，垂直对应选区位置
}
```

### `createVirtualAnchor(rect, referenceElement?)`

创建一个虚拟锚点元素，其 `getBoundingClientRect()` 方法返回传入的矩形。当提供了 `referenceElement` 时，锚点会存储相对于该元素的偏移，并在每次调用时动态计算视口坐标，从而实现支持滚动感知的定位。

```tsx
import { createVirtualAnchor, createEditorWidthAnchorRect } from '@/components/tiptap-ui/ai-menu'

const anchorRect = createEditorWidthAnchorRect(editor.view.dom, selectionRect)
const anchor = createVirtualAnchor(anchorRect, editor.view.dom)

// 锚点的 getBoundingClientRect() 会随着编辑器滚动实时更新
store.setAnchorElement(anchor)
```

### `getEditorContentRect(editorDom)`

计算编辑器元素的内容区矩形（排除内边距和边框），返回包含 `left` 和 `width` 属性的对象。

```tsx
import { getEditorContentRect } from '@/components/tiptap-ui/ai-menu'

const { left, width } = getEditorContentRect(editor.view.dom)
```

## AI 操作

AI 菜单提供若干内置操作，按类别组织：

### 编辑类操作

- **调整语气**：改变所选文字的语气
- **拼写与语法修正**：纠正拼写和语法错误
- **扩展**：扩展所选内容
- **缩短**：使内容更简洁
- **简化语言**：使用更简单、更清晰的语言
- **改进写作**：增强整体写作质量
- **表情符号化**：为内容添加相关表情符号

### 写作类操作

- **继续写作**：生成内容的延续部分
- **摘要**：对所选文本进行总结
- **翻译到**：将内容翻译成不同语言

## 状态管理

### `AiMenuState` 接口

```tsx
interface AiMenuState {
  isOpen: boolean
  tone?: string
  language: string
  shouldShowInput: boolean
  inputIsFocused: boolean
  fallbackAnchor: {
    element: HTMLElement | null
    rect: DOMRect | null
  }
}
```

### 状态更新

可以使用 `updateState` 函数更新 AI 菜单状态：

```tsx
const { updateState } = useAiMenuState()

// 打开菜单并聚焦输入框
updateState({
  isOpen: true,
  shouldShowInput: true,
  inputIsFocused: true,
})

// 设置翻译语言
updateState({ language: 'es' })

// 设置内容调整语气
updateState({ tone: 'professional' })
```

## 依赖与要求

### 依赖包

- `@tiptap/react` - Tiptap React 核心集成
- `@tiptap-pro/extension-ai` - 内容生成的 AI 扩展
- `@tiptap/starter-kit` - 基础 Tiptap 扩展
- `react-hotkeys-hook` - 键盘快捷键管理

### 扩展

- `ui-state-extension` - 管理 AI 操作的 UI 状态
- `selection-extension` - 增强文本选择处理

### 相关组件

- `use-tiptap-editor`（Hook）
- `use-ui-editor-state`（Hook）
- `menu`（基础组件）
- `button`, `button-group`（基础组件）
- `card`（基础组件）
- `combobox`（基础组件）
- `tiptap-utils`（工具库）
- `sparkles-icon`, `stop-circle-2-icon`（图标）

## 配置

### AI 提供者配置示例

```tsx
import { AiProvider } from '@/contexts/ai-context'

function App() {
  return (
    <AiProvider token="your-ai-token">
      <YourEditor />
    </AiProvider>
  )
}
```
