---
title: "与白板一起使用"
description: "让服务器端 AI 代理直接在 Tiptap 白板上添加注释、绘制图表和编写文档，或者作为用户查看的建议。"
canonical_url: "https://tiptap.zhcndoc.com/ai/ai-toolkit/agents/whiteboard"
---

# 与白板一起使用

让服务器端 AI 代理直接在 Tiptap 白板上添加注释、绘制图表和编写文档，或者作为用户查看的建议。

让 AI 代理直接操作[白板](https://tiptap.zhcndoc.com/editor/whiteboard/getting-started/overview.md)。Server AI Toolkit 的白板工具可以添加便签、绘制流程图、将便签分列、连接或修改形状、删除形状，以及为较长文本添加文档框架。工具会修改白板对应的 Tiptap Cloud 文档，因此所有协作者都能实时看到更改。

> **独立产品:**
>
> [Whiteboard](https://tiptap.zhcndoc.com/editor/whiteboard/getting-started/overview.md) 是独立于 Server AI Toolkit 的 Tiptap 产品，需单独购买。

> **实验性功能:**
>
> [Whiteboard](https://tiptap.zhcndoc.com/editor/whiteboard/getting-started/overview.md) 扩展目前处于 Alpha 阶段。

> **AI 代理聊天机器人指南的后续内容:**
>
> 本指南是 [AI 代理聊天机器人指南](https://tiptap.zhcndoc.com/ai/ai-toolkit/agents/ai-agent-chatbot.md) 的后续内容。请先阅读该指南。

## 工作原理

1. AI 使用 `readBoard` 读取白板：其形状、形状之间的箭头以及用户选择的形状。
2. AI 使用其他白板工具修改白板，例如 `createShapes` 和 `createDiagram`。AI 不需要提供坐标；这些工具会调整新形状的大小，并将其放在 AI 指定或选中的形状旁边，或放在用户视口中不与其他形状重叠的位置。
3. 每个文档框架都会在自己的 Tiptap Cloud 文档中显示内容。AI 使用 `tiptapRead` 和 `tiptapEdit` 操作文档。
4. 设置 `reviewOptions` 后，更改会作为[修订跟踪](https://tiptap.zhcndoc.com/tracked-changes/getting-started/overview.md)建议提交，用户可以在白板上接受或拒绝。

## 设置白板

白板工具操作的是白板对应的 Tiptap Cloud 文档。请在白板编辑器中配置协作，参见[白板协作指南](https://tiptap.zhcndoc.com/editor/whiteboard/guides/collaboration.md)。然后从该编辑器获取上下文，并随每条聊天消息发送，做法与 [AI 代理聊天机器人指南](https://tiptap.zhcndoc.com/ai/ai-toolkit/agents/ai-agent-chatbot.md) 相同：

```ts
import { getEditorContext } from '@tiptap/ai-toolkit'

// The board's editor, with WhiteboardKit and BoardCollaboration
const boardEditorContext = getEditorContext(editor)
```

白板工具不会通过哈希定位节点，因此白板编辑器无需添加 `ServerAiToolkit` 扩展。

## 启用白板工具

在获取工具定义时启用白板工具，并使用白板的编辑器上下文：

```ts
import { getAuthHeaders } from '@/lib/server-ai-toolkit/get-auth-headers'

const response = await fetch(`${apiBaseUrl}/v4/ai/toolkit/fetch-tools`, {
  method: 'POST',
  headers: await getAuthHeaders(),
  body: JSON.stringify({
    editorContext: boardEditorContext,
    tools: {
      readBoard: true,
      createShapes: true,
      arrangeShapes: true,
      createDiagram: true,
      updateShapes: true,
      deleteShapes: true,
      connectShapes: true,
      createDocFrame: true,
    },
  }),
})
```

仅启用代理所需的工具。例如，如果没有 `deleteShapes`，AI 就无法删除形状。

白板工具采用纯文本，而不是文档内容，因此它们在每个 `format` 中的工作方式相同。它们的描述告诉 AI 如何使用它们，因此不需要此请求的 `systemPrompt`。无法通过 `stream-tool` 使用白板工具。

## 执行板卡工具

执行板卡文档上的板卡工具。 `tool.config` 告诉他们用户选择和看到的内容：

```ts
const result = await fetch(`${apiBaseUrl}/v4/ai/toolkit/execute-tool`, {
  method: 'POST',
  headers: await getAuthHeaders(documentId),
  body: JSON.stringify({
    editorContext: boardEditorContext,
    document: { type: 'cloud', id: documentId },
    user: 'ai-assistant',
    tool: {
      name: toolName,
      input,
      // What the user selected and saw when they sent the message
      config: { selectedIds, viewport },
    },
  }),
})
```

- `selectedIds` 是用户选择的形状。 `readBoard` 首先列出它们及其全文，因此人工智能知道“这些注释”意味着哪些形状。新的形状位于它们的右侧，或者当距离较近时位于它们的下方。
- `viewport` 是用户看到的白板部分（以白板为单位）。如果没有选择，新的形状就会出现在中间。

如果没有其中任何一个，新形状将位于白板上所有形状的右侧，或到达空白板的原点。缺失、`null` 或无效的值会自行回退。 AI 还可以命名 `near` 中的形状，因此摘要会显示在其图表旁边。请参阅[白板布局](https://tiptap.zhcndoc.com/ai/ai-toolkit/api-reference/rest-api.md#board-placement)。

当用户发送消息时在浏览器中读取这两个消息，并将它们一起发送：

```ts
import { getSelectedShapeIds, visibleBoardRect } from '@tiptap-pro/extension-whiteboard'

sendMessage(
  { text: input },
  {
    body: {
      editorContext: boardEditorContext,
      selectedIds: getSelectedShapeIds(editor.state),
      viewport: visibleBoardRect({
        // Your app's camera, and the stage element around the board
        camera,
        viewport: { width: stage.clientWidth, height: stage.clientHeight },
      }),
    },
  },
)
```

当用户在聊天中键入内容时，白板会保留其选择，因此您无需再次聚焦白板。

当用户在工具运行时更换白板时，`execute-tool` 可以用 `concurrent_edit_conflict` 回答 `409` 并且不进行任何更改。再次运行该工具。服务器在 Tiptap Cloud 文档的 `default` 字段中检查这一点。在另一个`field`中，它保存而不检查。当文档或编辑器上下文不是白板时，它会用 `board_schema_not_supported` 回答 `403`。

## 编写文档框架的文档

每个文档框架显示其自己的文档。 `createDocFrame` 添加一个带有新的空文档的框架并返回其 `docId`，`readBoard` 列出每个框架的 `docId`。然后，AI 使用 `tiptapRead` 和 `tiptapEdit` 读取和写入文档。

### 将文档保存在 Tiptap Cloud 中

AI 服务器将文档写入 Tiptap Cloud 中。将框架文档的扩展保留在浏览器和服务器共享的模块中，因为服务器需要相同的架构。向它们添加 `ServerAiToolkit` 扩展，因为文档工具通过哈希来定位节点：

```ts
// lib/whiteboard-docs.ts, shared by the browser and the server
import { ServerAiToolkit } from '@tiptap/ai-toolkit'
import StarterKit from '@tiptap/starter-kit'

export const docExtensions = [StarterKit.configure({ undoRedo: false }), ServerAiToolkit]

// The name of the Tiptap Cloud document of a frame, on the board `documentId`
export const frameDocumentName = (documentId: string, docId: string) => `${documentId}/doc/${docId}`
```

在浏览器中，将每个框架的编辑器连接到其 `docId` 的 Tiptap Cloud 文档：

```ts
import { Editor } from '@tiptap/core'
import { Collaboration } from '@tiptap/extension-collaboration'
import type { CreateDocEditorOptions } from '@tiptap-pro/extension-whiteboard'
import { TiptapCollabProvider } from '@tiptap-pro/provider'
import * as Y from 'yjs'
import { docExtensions, frameDocumentName } from '@/lib/whiteboard-docs'

function createEditor({ element, docId }: CreateDocEditorOptions) {
  const document = new Y.Doc()
  const provider = new TiptapCollabProvider({
    name: frameDocumentName(documentId, docId),
    appId,
    token,
    document,
  })

  return new Editor({
    element,
    extensions: [...docExtensions, Collaboration.configure({ document })],
    onDestroy: () => provider.destroy(),
  })
}
```

在 `WhiteboardKit` 的 `docs` 选项中将其用作 `createEditor`。请参阅 [`copy` 和 `start` 的文档框架 ](https://tiptap.zhcndoc.com/editor/whiteboard/guides/doc-frames.md)。 AI 添加的帧来自 Yjs，因此 `start` 不会为它们运行。 `createEditor` 通过 `docId` 打开他们的文档，并显示 AI 在那里写入的内容。

浏览器的`token` 也必须允许框架文件，而不仅仅是白板文件。允许名称以 `${documentId}/doc/` 开头的文档，并带有 [前缀约束](https://tiptap.zhcndoc.com/authentication.md#constraints):

```ts
permissions: [
  { action: 'Documents:Write', resource: documentId },
  { action: 'Documents:Write', resource: '*', constraints: { prefix: `${documentId}/doc/` } },
],
```

每个提供程序都会打开自己的连接。要通过一个连接打开所有框架的文档，请参阅[Multiplexing](https://tiptap.zhcndoc.com/hocuspocus/provider/examples.md#multiplexing).

### 为文档工具指定一个 `docId`

文档工具可处理一个文档。在服务器上，使用 `docExtensions` 从编辑器获取框架文档的编辑器上下文。为其提供 JSON 内容，因为默认内容是 HTML，需要浏览器：

```ts
import { getEditorContext } from '@tiptap/ai-toolkit'
import { Editor } from '@tiptap/core'
import { docExtensions } from '@/lib/whiteboard-docs'

// Only for the schema of the frames' documents
const docEditor = new Editor({
  extensions: docExtensions,
  content: { type: 'doc', content: [{ type: 'paragraph' }] },
})
const docEditorContext = getEditorContext(docEditor)
docEditor.destroy()
```

然后使用该编辑器上下文获取 `tiptapRead` 和 `tiptapEdit`。将 `docId` 添加到他们的输入中，并在 `docId` 的文档上执行它们：

```ts
import { tool } from 'ai'
import z from 'zod'
import { executeTool } from '@/lib/server-ai-toolkit/execute-tool'
import { getToolDefinitions } from '@/lib/server-ai-toolkit/get-tool-definitions'
import { frameDocumentName } from '@/lib/whiteboard-docs'

// tiptapRead and tiptapEdit, and the prompt about the format of the documents
const { prompt: docsPrompt, tools: docToolDefinitions } = await getToolDefinitions(docEditorContext)

const docTools = Object.fromEntries(
  docToolDefinitions.map((definition) => [
    definition.name,
    tool({
      description: definition.description,
      inputSchema: z.fromJSONSchema({
        ...definition.inputSchema,
        properties: {
          docId: {
            type: 'string',
            description: 'The docId of the doc frame, from readBoard or createDocFrame.',
          },
          ...definition.inputSchema.properties,
        },
        required: ['docId', ...(definition.inputSchema.required ?? [])],
      }),
      strict: false,
      execute: async (toolInput) => {
        const { docId, ...input } = toolInput as Record<string, unknown>

        // The docId comes from the AI, and becomes part of a document name
        if (typeof docId !== 'string' || !/^[\w-]+$/.test(docId)) {
          return { success: false, error: 'Use the docId of a doc frame, from readBoard.' }
        }

        try {
          const result = await executeTool(
            definition.name,
            input,
            frameDocumentName(documentId, docId),
            docEditorContext,
          )
          return result.tool.output
        } catch (error) {
          console.error(`Failed to execute tool ${definition.name}:`, error)
          return { error: error instanceof Error ? error.message : 'Unknown error' }
        }
      },
    }),
  ]),
)
```

将这些工具与 AI 代理使用的其他工具一起提供，并把 `docsPrompt` 加入代理指令，让 AI 了解文档格式。

白板会按文档内容绘制文档框架，但 `createDocFrame` 保存的高度来自 `lines` 输入。箭头、选区和布局使用保存的高度。只有用户直接编辑文档时，白板才会更新该高度，因此 AI 对文档的更改不会自动改变框架高度。

## 查看 AI 的更改

在白板编辑器中启用修订跟踪，参见[白板修订跟踪指南](https://tiptap.zhcndoc.com/editor/whiteboard/guides/tracked-changes.md)，并发送该编辑器的上下文。然后在执行白板工具时传入 `reviewOptions`：

```ts
reviewOptions: {
  mode: 'trackedChanges',
  trackedChangesOptions: {
    userId: 'ai-assistant',
    userMetadata: { name: 'AI Assistant' },
  },
},
```

这样白板工具就会将更改作为建议提交，而不会直接应用：

- 新形状会成为 `add` 建议。移动形状、修改颜色或类型会生成包含新旧值的 `attributeChange` 建议；替换文本时，新文本会作为建议替换原段落。
- 被删除的形状会保留在白板上，直到用户接受 `delete` 建议。通过待处理建议添加的形状会立即显示。
- 一次调用添加或删除的形状共享同一条建议，因此调用一次 `acceptSuggestion` 或 `rejectSuggestion` 即可处理全部形状。
- 每个被修改的形状和每段新文本都有各自的建议。`suggestionIds` 列出本次调用创建或继续处理的建议；查看列表中的所有建议即可审核整次调用。继续处理已有建议时，建议也会保留该形状之前的更改。
- 如果某个形状正等待其他用户审核，工具会跳过它，并在 `skipped` 中说明原因。
- 当建议新增、删除或修改形状，或仅修改形状中的文本时，`readBoard` 会在该形状上标记 `suggestion`。

如果编辑器上下文中没有启用修订跟踪，`execute-tool` 会像文档工具一样返回 `403` 和 `tracked_changes_unsupported_schema`，不会执行更改。若未提供 `reviewOptions`，工具会直接修改白板，但会跳过所有正在等待审核的形状。

对于文档框架中的文档，请将 `TrackedChanges` 添加到 `docExtensions`，确保框架编辑器和 `docEditorContext` 都启用了修订跟踪。否则，`execute-tool` 会返回 `403` 和 `tracked_changes_unsupported_schema`。然后将相同的 `reviewOptions` 添加到 `executeTool` 帮助函数的请求正文中，并传给文档工具。参见[与修订跟踪配合使用](https://tiptap.zhcndoc.com/ai/ai-toolkit/agents/tracked-changes.md)。如果 AI 在同一轮回答中创建文档，可以跳过审核，因为该文档框架本身已作为建议等待审核。

## 限制

- 这些工具仅添加白板自己的形状：便签、几何形状、自由文本、箭头和文档框架。 `readBoard` 列出具有 id 和框的每个形状。
- 服务器无法测量浏览器的字体，因此工具会估计标题、标签和文档框架的高度。该白板仅测量其自身用户的更改，因此这些高度将保持不变，直到用户编辑文本。
- 箭头是其形状中心之间的直线，因此长箭头可以穿过其他形状。箭头的标签是自由文本形状，当用户移动箭头的形状时，它会保持在原位。
- `readBoard` 最多列出 300 个形状和箭头，首先列出选定的形状。它会在 280 个字符后剪切其他形状的文本。
- 服务器通过比较整个白板，将调用写入白板的 Yjs 文档中。当调用删除一个形状并更改后面的形状时（例如释放箭头末端的 `deleteShapes`），用户同时对之间的形状进行的编辑可能会丢失或落在另一个形状上。

## 后续步骤

- 请参阅 REST API 参考中的[白板工具](https://tiptap.zhcndoc.com/ai/ai-toolkit/api-reference/rest-api.md#board-tools)
- 设置[文档框架](https://tiptap.zhcndoc.com/editor/whiteboard/guides/doc-frames.md) 和[白板上的协作](https://tiptap.zhcndoc.com/editor/whiteboard/guides/collaboration.md)
