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)
}
OptionRequiredPurpose
editorYesYour Tiptap editor, or null while it is loading.
transportYesSends requests to your server. See Transports.
previewModeYesinline shows the suggestion inside the editor. card lets you show it in your own UI.
limitsNoChanges the maximum request sizes.
linkProtocolsNoExtra link types to allow in suggestions, besides http and https.
contextProjectionNoPlain 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

FieldTypeMeaning
stateEmendAiStateWhat is happening now: idle, submitting, streaming, reviewing, error, or aborted.
activeRequestEmendAiRequest | nullThe request that is running now.
streamedMarkdownstringThe answer received so far.
informationalMarkdownstringThe finished answer to a question.
streamCompletedbooleanWhether the whole answer has arrived.
pendingProposalEmendProposal | nullThe finished edit suggestion, waiting for Accept or Reject.
proposalMarkdownstring | nullThe suggestion's text, including any changes the user made to it.
preparationEmendTiptapPreparation | nullWhether the suggestion can be applied, applied as plain text, or is blocked. See Content and Proposals.
errorEmendAiError | nullAn error from sending the request or receiving the answer.
reviewErrorEmendAiError | nullAn error from previewing, accepting, or rejecting.
editorStateEmendTiptapEditorState | nullThe document version and the preview currently shown.
stalebooleantrue if the document changed after the request, so the suggestion is out of date.

Methods

MethodPurpose
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.

On this page