Store and regenerate responses

The AI extension stores the current state in it’s extension storage under editor.storage.ai || editor.storage.aiAdvanced (depending on if you are using the extension-ai or extension-ai-advanced extension). This storage is used to keep track of the current state of the AI response, as well as any past responses.

keytypedefinition
state'loading' | 'error' | 'idle'While the AI is generating a response, the state is set to loading. After the response is generated, the state is set to idle. When there is an error, the state is set to error
responsestring | undefinedThe most recent message generated by the AI. When idle, if this is a string, it is the previous generated message, if undefined, no message has been generated. When loading, this will be a string of what the AI has generated so far (if streaming the response). When error, this is undefined
errorError | undefinedThe error generated, only ever set in the error state
generatedWith{ action: TextAction; options: TextOptions; range: undefined | Range; } | undefinedThe options that describe what the last generated response was generated with range is only ever set if inserting the content into the editor
pastResponsesstring[]Stores previously generated responses (on success), most recent first. Cleared when the response is accepted/rejected.

You can use this storage to read out the current state of AI responses like:

const aiStorage = editor.storage.ai || editor.storage.aiAdvanced

if (aiStorage.response.state === 'error') {
  // The error that occurred
  aiStorage.response.error
}

if (aiStorage.response.state === 'loading') {
  // The message that is currently being processed
  aiStorage.response.message
}

if (aiStorage.response.state === 'idle') {
  if (aiStorage.response.message) {
    // The successful response
    aiStorage.response.message
  } else {
    // No response has been requested yet
  }
}

Using AI Storage

Want to leverage the Tiptap Content AI's ability to generate results but, not have the results available outside of the editor? You can use insert: false on any AI TextOption and it will store the result into the extension.

const chatMessage = 'Hello, how are you?'

editor
  .chain()
  .aiTextPrompt({
    text: chatMessage,
    stream: true,
    insert: false,
    html: true,
  })
  .run()

From there, you can use the aiAccept, aiReject, and aiRegenerate commands

aiAccept

This command is meant to be ran when the user has accepted the AI response, it will insert the response into the editor by default and it’s behavior changes depending on the provided options.

keytypedefinition
insertAtnumber | { from: number, to: number }When a number, accept the response and insert it into the start of the editor. When { from: number, to: number }, accept the response and replace the content from position from to position to with the AI response
appendbooleanIf true, instead of replacing the current selection, append to it

The default behavior with no provided options is to, accept the response and insert it into the editor, replacing the current selection

// Accept the response and insert it into the editor
editor.chain().aiAccept().run()

// Accept the response and insert it into the editor at the start
editor.chain().aiAccept({ insertAt: 0 }).run()

// Accept the response and insert it into the editor at the end
editor.chain().aiAccept({ insertAt: editor.state.doc.content.size }).run()

// Accept the response and append it to the current selection
editor.chain().aiAccept({ append: true }).run()

aiRegenerate

This command is meant to be ran when the user wants the to retry the AI response, it will use all the same options as the previous AI text operation and add to the (editor.storage.ai || editor.storage.aiAdvanced).pastResponses array

keytypedefinition
insertbooleanWhether to insert the regenerated response into the editor
insertAtnumber | { from: number, to: number }If not specified,the regenerated response will be inserted where the previous response was. When a number, regenerate the response and insert it into the start of the editor. When { from: number, to: number }, regenerate the response and replace the content from position from to position to with the AI response

The default behavior with no provided options is to, regenerate the response and insert it into the editor, replacing the current selection

// Regenerate the response and insert it into the editor
editor.chain().aiRegenerate().run()

// Regenerate the response and insert it into the editor at the start
editor.chain().aiRegenerate({ insertAt: 0 }).run()

// Regenerate the response and insert it into the editor at the end
editor.chain().aiRegenerate({ insertAt: editor.state.doc.content.size }).run()

// Regenerate the response and append it to the current selection
editor.chain().aiRegenerate({ append: true }).run()

aiReject

This command is meant to be ran when the user has rejected the AI response, it will reset the extension’s state to the initial idle state and clear all (editor.storage.ai || editor.storage.aiAdvanced).pastResponses

keytypedefinition
type'reset' | 'pause'Whether to reset the AI to the idle state. Or just pause the current response. Default is 'reset'
editor.chain().aiReject().run()

// Will not clear out editor.storage.ai || editor.storage.aiAdvanced, useful for keeping current response in the editor storage
editor.chain().aiReject({ type: 'pause' }).run()

Advanced Example

One use-case of extension storage could be to render a preview of the AI generated content.

To render a preview of what a chat would look like in your editor, we can use your editor’s schema to generate the html that would be generated. With this HTML you can display a preview of that content in an element

// Display the response as HTML
import { tryParseToTiptapHTML } from '@tiptap-pro/extension-ai'

// try to parse the current message as HTML, and null if it could not be parsed
tryToParseToHTML((editor.storage.ai || editor.storage.aiAdvanced).response.message, editor)

// try to parse a previous response as HTML, and null if it could not be parsed
tryToParseToHTML((editor.storage.ai || editor.storage.aiAdvanced).pastResponses[0], editor)

// For example in React
function PreviewComponent({ editor }) {
  const htmlResponse = tryToParseToHTML(
    (editor.storage.ai || editor.storage.aiAdvanced).response.message,
    editor,
  )
  /* This is safe since we've parsed the content with prose-mirror first */
  return <div dangerouslySetInnerHTML={{ __html: htmlResponse }}></div>
}

See our demo below for a full example of how a chat preview could work.