评论工作流
构建一个允许 AI 管理你 Tiptap 文档中评论和线程的工作流。
查看GitHub 上的源码。
技术栈
- React + Next.js
- Vercel 的 AI SDK + OpenAI 模型
- Tiptap AI 工具包
- Tiptap 评论扩展
项目概览
此示例使用 AI 工具包的评论工作流来管理文档中的线程和评论。AI 可以创建新线程、添加评论、更新评论、删除评论并管理线程状态。
安装
创建一个 Next.js 项目:
npx create-next-app@latest comments-workflow安装核心 Tiptap 包和用于 OpenAI 的 Vercel AI SDK:
npm install @tiptap/react @tiptap/starter-kit ai @ai-sdk/react @ai-sdk/anthropic zod uuid安装 Tiptap AI 工具包和评论扩展:
专业版包
AI 工具包是一个专业版包。安装前,请通过 阅读私有注册表指南 设置对私有 NPM 注册表的访问权限。
npm install @tiptap-pro/ai-toolkit @tiptap-pro/ai-toolkit-tool-definitions @tiptap-pro/extension-comments服务器配置
创建一个使用 Vercel AI SDK 调用 Anthropic 模型的 API 端点。如果你的后端不是 TypeScript,请参阅非 TypeScript 后端。
在 API 端点中,使用 createEditThreadsWorkflow 函数创建并配置评论工作流。该工作流包含一个可直接使用的系统提示,用于指示 AI 模型如何管理评论。
用户消息应包含:
nodes:文档的节点(由tiptapRead获得)threads:文档中已有的线程(由getThreads获得)task:AI 需要执行的任务。例如,添加一个改进引言的建议性评论。
// app/api/comments-workflow/route.ts
import { openai } from '@ai-sdk/openai'
import { createEditThreadsWorkflow } from '@tiptap-pro/ai-toolkit-tool-definitions'
import { Output, streamText } from 'ai'
export async function POST(req: Request) {
const { content, threads, task } = await req.json()
// 创建并配置评论工作流(使用默认设置)。
// 包含可直接使用的系统提示和输出模式。
const workflow = createEditThreadsWorkflow()
const result = streamText({
model: openai('gpt-5.4-mini'),
// 系统提示
system: workflow.systemPrompt,
// 用户消息
prompt: JSON.stringify({
content,
threads,
task,
}),
output: Output.object({ schema: workflow.zodOutputSchema }),
})
return result.toTextStreamResponse()
}客户端配置
创建一个 React 组件,渲染集成了评论扩展的编辑器并应用评论操作。
首先,工作流开始时调用 tiptapRead 方法获取文档内容,调用 getThreads 方法获取现有线程。
然后,调用 API 端点来启动工作流。使用 editThreadsWorkflow 方法应用评论操作。
// app/comments-workflow/page.tsx
'use client'
import { experimental_useObject as useObject } from '@ai-sdk/react'
import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { AiToolkit, getAiToolkit, editThreadsWorkflowOutputSchema } from '@tiptap-pro/ai-toolkit'
import { Comments } from '@tiptap-pro/extension-comments'
import { useEffect, useState } from 'react'
import { v4 as uuid } from 'uuid'
// 创建一个简单的评论提供者
const commentsProvider = {
threads: [],
getThreads: () => commentsProvider.threads,
createThread: (thread) => {
commentsProvider.threads.push(thread)
return thread
},
updateThread: (id, data) => {
const index = commentsProvider.threads.findIndex((t) => t.id === id)
if (index !== -1) {
commentsProvider.threads[index] = { ...commentsProvider.threads[index], ...data }
}
},
deleteThread: (id) => {
commentsProvider.threads = commentsProvider.threads.filter((t) => t.id !== id)
},
}
export default function Page() {
const editor = useEditor({
immediatelyRender: false,
extensions: [
StarterKit,
AiToolkit,
Comments.configure({
provider: commentsProvider,
}),
],
content: `<h1>含评论的文档</h1><p>这是一个示例文档,AI 可以在其中添加和管理评论。</p>`,
})
const [task, setTask] = useState('添加一个建议改进此文档的评论')
const { submit, isLoading, object } = useObject({
api: '/api/comments-workflow',
schema: editThreadsWorkflowOutputSchema,
})
const operations = object?.operations ?? []
// 实时应用传入的操作
useEffect(() => {
if (!editor || !object?.operations) return
const toolkit = getAiToolkit(editor)
toolkit.editThreadsWorkflow({
operations: object.operations,
workflowId,
isStreaming: isLoading,
})
}, [editor, object, workflowId, isLoading])
if (!editor) return null
const manageComments = () => {
setWorkflowId(uuid())
const toolkit = getAiToolkit(editor)
// 获取文档内容和现有线程
const { nodes } = toolkit.tiptapRead()
const { threads } = toolkit.getThreads()
// 调用 API 端点启动工作流
submit({ nodes, threads, task })
}
return (
<div>
<EditorContent editor={editor} />
<input
type="text"
value={task}
onChange={(e) => setTask(e.target.value)}
placeholder="请输入评论任务..."
/>
<button onClick={manageComments} disabled={isLoading}>
{isLoading ? '处理中...' : '使用 AI 管理评论'}
</button>
</div>
)
}可用操作
评论工作流支持以下操作:
| 操作 | 格式 | 描述 |
|---|---|---|
| 创建线程 | ['createThread', nodeHash, htmlContent] | 在指定位置创建新线程 |
| 创建评论 | ['createComment', threadId, content] | 向现有线程添加评论 |
| 更新评论 | ['updateComment', threadId, commentId, content] | 更新现有评论 |
| 删除评论 | ['removeComment', threadId, commentId] | 从线程中删除评论 |
| 删除线程 | ['removeThread', threadId] | 删除整个线程 |
| 解决线程 | ['resolveThread', threadId] | 标记线程为已解决 |
| 解除解决线程 | ['unresolveThread', threadId] | 标记线程为未解决 |
最终效果
通过额外的 CSS 样式,效果是一个精致的评论管理应用:
查看GitHub 上的源码。
文档部分评论
要只对文档的某一子部分添加评论,可以在 tiptapRead 上设置 range 选项。该参数接收一个带有指定区域的 Range。
例如,只对选中的内容评论:
const range = editor.state.selection
// 只读取文档中选中的部分
const { content } = toolkit.tiptapRead({ range })
const { threads } = toolkit.getThreads()
const operations = callApi(content, threads)
// 应用评论操作
toolkit.editThreadsWorkflow({ operations, workflowId })对于大型文档,可以使用 tiptapReadChunks 方法将文档拆分为多个块并行处理。每个块都包含一个带有该块区域的 Range。