Comments workflow

Build a workflow that allows the AI to manage comments and threads in your Tiptap documents.

See the source code on GitHub.

Tech stack

Project overview

This demo uses the AI Toolkit's Comments workflow to manage threads and comments in the document. The AI can create new threads, add comments, update comments, remove comments, and manage thread status.

Installation

Create a Next.js project:

npx create-next-app@latest comments-workflow

Install the core Tiptap packages and the Vercel AI SDK for Anthropic:

npm install @tiptap/react @tiptap/starter-kit ai @ai-sdk/react @ai-sdk/anthropic zod uuid

Install the Tiptap AI Toolkit and Comments extension:

Pro package

The AI Toolkit is a pro package. Before installation, set up access to the private NPM registry by following the private registry guide.

npm install @tiptap-pro/ai-toolkit @tiptap-pro/ai-toolkit-tool-definitions @tiptap-pro/extension-comments

Server setup

Create an API endpoint that uses the Vercel AI SDK to call the Anthropic model.

Inside the API endpoint, create and configure the Comments workflow using the createEditThreadsWorkflow function. The workflow includes a ready-to-use system prompt that instructs the AI model on how to manage comments.

The user message should include:

  • content: The content of the document (obtained from tiptapRead)
  • threads: The existing threads in the document (obtained from getThreads)
  • task: The task to be performed by the AI. For example, Add a comment suggesting improvements to the introduction.
// app/api/comments-workflow/route.ts
import { anthropic } from '@ai-sdk/anthropic'
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()

  // Create and configure the Comments workflow (with the default settings).
  // It includes the ready-to-use system prompt and the output schema.
  const workflow = createEditThreadsWorkflow()

  const result = streamText({
    model: anthropic('claude-haiku-4-5'),
    // System prompt
    system: workflow.systemPrompt,
    // User message
    prompt: JSON.stringify({
      content,
      threads,
      task,
    }),
    output: Output.object({ schema: workflow.zodOutputSchema }),
  })

  return result.toTextStreamResponse()
}

Client setup

Create a React component that renders the editor with the Comments extension and applies comment operations.

The workflow supports streaming, which means operations are applied incrementally as they arrive from the AI model, providing real-time feedback to users.

First, when the workflow starts, call the tiptapRead method to get the document content and getThreads method to get existing threads.

Then, call the API endpoint to start the workflow. Use the editThreadsWorkflow method to apply the comment operations. The isStreaming parameter ensures operations are applied incrementally as they stream in.

// 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'

// Create a simple comments provider
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>Document with Comments</h1><p>This is a sample document where AI can add and manage comments.</p>`,
  })

  const [workflowId, setWorkflowId] = useState('')
  const [task, setTask] = useState('Add a comment suggesting improvements to this document')

  const { submit, isLoading, object } = useObject({
    api: '/api/comments-workflow',
    schema: editThreadsWorkflowOutputSchema,
  })

  // Apply operations as they stream in
  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)

    // Get the document content and existing threads
    const { content } = toolkit.tiptapRead()
    const { threads } = toolkit.getThreads()

    // Call the API endpoint to start the workflow
    submit({ content, threads, task })
  }

  return (
    <div>
      <EditorContent editor={editor} />

      <input
        type="text"
        value={task}
        onChange={(e) => setTask(e.target.value)}
        placeholder="Enter task for comments..."
      />

      <button onClick={manageComments} disabled={isLoading}>
        {isLoading ? 'Processing...' : 'Manage Comments with AI'}
      </button>
    </div>
  )
}

Available operations

The Comments workflow supports the following operations:

OperationDescription
Create threadCreates a new thread at a specific location
Create commentAdds a comment to an existing thread
Update commentUpdates an existing comment
Remove commentRemoves a comment from a thread
Remove threadRemoves an entire thread
Resolve threadMarks a thread as resolved
Unresolve threadMarks a thread as unresolved

End result

With additional CSS styles, the result is a polished comments management application:

See the source code on GitHub.