Proofreader workflow
Build a proofreader that detects and corrects spelling mistakes in your Tiptap documents.
See the source code on GitHub.
Tech stack
- React + Next.js
- AI SDK by Vercel + OpenAI models
- Tiptap AI Toolkit
Project overview
This demo uses the AI Toolkit's Proofreader workflow to make a series of small edits to the document in real-time.
Installation
Create a Next.js project:
npx create-next-app@latest proofreaderInstall the core Tiptap packages and the Vercel AI SDK for OpenAI:
npm install @tiptap/react @tiptap/starter-kit ai @ai-sdk/react @ai-sdk/openai zod uuidInstall the Tiptap AI Toolkit:
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-definitionsServer setup
Create an API endpoint that uses the Vercel AI SDK to call the OpenAI model.
If your backend is in another programming language than TypeScript, see this guide.
Inside the API endpoint, create and configure the proofreader workflow, using the createProofreaderWorkflow function. The workflow includes a ready-to-use system prompt that instructs the AI model on how to generate the content.
Additionally, you need include these two properties in the user message:
content: The content of the document to be proofread (see the client-side setup section on how to obtain it)task: The task to be performed by the AI. For example,Correct all grammar and spelling mistakes.context: (Optional) Additional context or background information related to the task.
As the AI model generates its response, the API endpoint streams the suggestions to the client.
// app/api/proofreader/route.ts
import { openai } from '@ai-sdk/openai'
import { createProofreaderWorkflow } from '@tiptap-pro/ai-toolkit-tool-definitions'
import { Output, streamText } from 'ai'
export async function POST(req: Request) {
const { content } = await req.json()
// Create and configure the proofreader workflow (with the default settings).
// It includes the ready-to-use system prompt and the output schema.
const workflow = createProofreaderWorkflow()
const result = streamText({
model: openai('gpt-5-mini'),
// System prompt
system: workflow.systemPrompt,
// User message
prompt: JSON.stringify({
content,
task: 'Correct all grammar and spelling mistakes',
context: 'This is a formal business document',
}),
output: Output.object({ schema: workflow.zodOutputSchema }),
// If you use gpt-5-mini, set the reasoning effort to minimal to improve the
// response time.
providerOptions: {
openai: {
reasoningEffort: 'minimal',
},
},
})
return result.toTextStreamResponse()
}Client setup
Create a React component that renders the editor and applies the edits in real-time.
First, when the proofreading process starts, call the tiptapRead method of the AI Toolkit to read the document. The method returns the content in a format that is optimized for fast, precise edits.
Then, call the API endpoint to start the workflow. The component uses Vercel AI SDK's useObject hook to handle streaming, so that the response is received bit by bit and the edits are applied in real-time.
Every time the response changes, call the proofreaderWorkflow method of the AI Toolkit to apply the edits to the document in real-time.
The option mode: 'preview' allows you to preview the edits before they are applied to the document. Otherwise, all the edits are applied immediately, without showing a preview.
// app/proofreader/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, proofreaderWorkflowOutputSchema } from '@tiptap-pro/ai-toolkit'
import { useEffect, useRef, useState } from 'react'
import { v4 as uuid } from 'uuid'
export default function Page() {
const editor = useEditor({
immediatelyRender: false,
extensions: [StarterKit, AiToolkit],
content: `<h1>Grammar Check Demo</h1><p>This is a excelent editor for writng documents. It have many feature's that makes it very powerfull.</p>`,
})
const [isReviewing, setIsReviewing] = useState(false)
const [workflowId, setWorkflowId] = useState('')
const { submit, isLoading, object } = useObject({
api: '/api/proofreader',
schema: proofreaderWorkflowOutputSchema,
onFinish: () => {
setIsReviewing(true)
},
})
const operations = object?.operations ?? []
// Stream partial results as they arrive
useEffect(() => {
if (!editor || !operations) return
const toolkit = getAiToolkit(editor)
toolkit.proofreaderWorkflow({
operations,
workflowId,
reviewOptions: {
mode: 'preview',
},
hasFinished: !isLoading,
})
}, [operations, workflowId, editor, isLoading])
if (!editor) return null
const checkGrammar = () => {
const toolkit = getAiToolkit(editor)
// Obtain the content of the document to be proofread
const { content } = toolkit.tiptapRead()
// Each workflow must have a unique ID
setWorkflowId(uuid())
// Call the API endpoint to start the workflow
submit({ content })
}
return (
<div>
<EditorContent editor={editor} />
{!isReviewing && (
<button onClick={checkGrammar} disabled={isLoading}>
{isLoading ? 'Checking...' : 'Check Grammar'}
</button>
)}
{isReviewing && (
<div>
<p>Corrections are highlighted in the document above.</p>
<button
onClick={() => {
const toolkit = getAiToolkit(editor)
toolkit.acceptAllSuggestions()
setIsReviewing(false)
}}
>
Accept all
</button>
<button
onClick={() => {
const toolkit = getAiToolkit(editor)
toolkit.rejectAllSuggestions()
setIsReviewing(false)
}}
>
Reject all
</button>
</div>
)}
</div>
)
}End result
With additional CSS styles, the result is a polished proofreader application with real-time grammar checking:
See the source code on GitHub.
Proofread part of the document.
To proofread a sub-section of the document, set the range option. This argument takes a Range with the region where the document will be proofread.
For example, to proofread the selected content:
// Create a Range with the area where
// the document will be proofread
const range = editor.state.selection
// Read only the selected part of the document
const { content } = toolkit.tiptapRead({
range,
})
const operations = callApi(content)
// Apply operations in the same range
toolkit.proofreaderWorkflow({
operations,
range,
})For large documents, you can use the tiptapReadChunks method to split the document into chunks and process them in parallel. Each chunk includes a Range with the region of that chunk.
Show a review UI
Configure the review options to show proofreading suggestions that users can accept or reject.
toolkit.proofreaderWorkflow({
operations,
workflowId,
// Show suggestions in preview mode
reviewOptions: { mode: 'preview' },
hasFinished: true,
})
// Accept all suggestions
toolkit.acceptAllSuggestions()See the Suggestions guide to learn more about suggestions.
Related guides
- API reference of the
tiptapReadmethod - API reference of the
tiptapReadChunksmethod - API reference of the
proofreaderWorkflowmethod - Suggestions: Learn about reviewing the document and displaying suggestions in the editor.