useEditorAi
The React hook that runs AI requests and gives you the answer, preview, and errors.
useEditorAi is the React hook behind all the AI components. Use it if you want
to build your own AI UI. You give it your editor and a transport, and it gives
you methods like run and accept, plus state like the streamed answer.
import { useEditorAi } from "@emend/ai/react"Options
interface UseEditorAiOptions {
readonly editor: Editor | null
readonly transport: EmendTransport
readonly previewMode: "inline" | "card"
readonly limits?: Partial<EmendRequestLimits>
readonly linkProtocols?: readonly string[]
readonly contextProjection?: string | ((context: JSONContent) => string)
}| Option | Required | Purpose |
|---|---|---|
editor | Yes | Your Tiptap editor, or null while it is loading. |
transport | Yes | Sends requests to your server. See Transports. |
previewMode | Yes | inline shows the suggestion inside the editor. card lets you show it in your own UI. |
limits | No | Changes the maximum request sizes. |
linkProtocols | No | Extra link types to allow in suggestions, besides http and https. |
contextProjection | No | Plain text to send to the AI when the document cannot be turned into Markdown. Pass a string, or a function that builds the text when a request starts. |
Create the transport once
Create the transport outside your component, or wrap it in useMemo. If you
create a new one on every render, the hook starts over each time.
What the hook returns
| Field | Type | Meaning |
|---|---|---|
state | EmendAiState | What is happening now: idle, submitting, streaming, reviewing, error, or aborted. |
activeRequest | EmendAiRequest | null | The request that is running now. |
streamedMarkdown | string | The answer received so far. |
informationalMarkdown | string | The finished answer to a question. |
streamCompleted | boolean | Whether the whole answer has arrived. |
pendingProposal | EmendProposal | null | The finished edit suggestion, waiting for Accept or Reject. |
proposalMarkdown | string | null | The suggestion's text, including any changes the user made to it. |
preparation | EmendTiptapPreparation | null | Whether the suggestion can be applied, applied as plain text, or is blocked. See Content and Proposals. |
error | EmendAiError | null | An error from sending the request or receiving the answer. |
reviewError | EmendAiError | null | An error from previewing, accepting, or rejecting. |
editorState | EmendTiptapEditorState | null | The document version and the preview currently shown. |
stale | boolean | true if the document changed after the request, so the suggestion is out of date. |
Methods
| Method | Purpose |
|---|---|
run(actionId, options?) | Run an action, such as "improve", or "custom" for your own instruction. |
stop() | Stop the request that is running. |
retry() | Run the last failed or stopped request again. |
regenerate() | Ask for a new answer to the last action, using the current text. |
setProposalMarkdown(value) | Change the suggestion's text before accepting it. |
accept(confirm?) | Apply the suggestion as one change the user can undo. |
reject() | Remove the suggestion without changing the document. |
dismissInformationalResult() | Hide a finished answer to a question. |
copy() | Get the text of the current answer or suggestion. |
accept and reject return an EmendTiptapApplyResult that says whether they
worked. run, retry, and regenerate finish when the whole answer has
arrived. They do not throw. If something fails, check state and error.
Example
"use client"
import { EmendAi } from "@emend/ai/tiptap"
import { createFetchTransport } from "@emend/ai/transport"
import { useEditorAi } from "@emend/ai/react"
import { Markdown } from "@tiptap/markdown"
import { EditorContent, useEditor } from "@tiptap/react"
import StarterKit from "@tiptap/starter-kit"
const transport = createFetchTransport({ url: "/api/editor-ai" })
export function EditorWithAi() {
const editor = useEditor({
immediatelyRender: false,
extensions: [StarterKit, Markdown, EmendAi],
content: "<p>Select this sentence, then improve it.</p>",
})
const ai = useEditorAi({ editor, transport, previewMode: "inline" })
return (
<>
<EditorContent editor={editor} />
<button
type="button"
disabled={ai.state !== "idle"}
onClick={() => void ai.run("improve")}
>
Improve
</button>
{ai.pendingProposal && (
<div>
<button type="button" onClick={() => ai.accept()}>
Accept
</button>
<button type="button" onClick={ai.reject}>
Reject
</button>
</div>
)}
</>
)
}Your editor must include the EmendAi extension.
To show a different message for each error, check error.code (see
error codes), not the message text.