AI assistant

Stream completions from any model into the inline AI menu.

The AI extension adds an Ask AI entry to the toolbar and bubble menu. It opens an inline prompt with shortcuts (improve, shorten, translate, change tone …); the response streams in and can be inserted below or replace the selection.

Connect a model

Provide a completions(history, signal) function that returns an async iterable of chunks. Chunks can be plain strings, OpenAI-style choices[0].delta.content objects, or { text } objects — so any provider works.

import { AI } from 'vue-echo-editor'

AI.configure({
  completions: async function* (history, signal) {
    const res = await fetch('/api/ai', {
      method: 'POST',
      body: JSON.stringify({ messages: history }),
      headers: { 'content-type': 'application/json' },
      signal,
    })
    const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader()
    while (true) {
      const { value, done } = await reader.read()
      if (done) return
      yield value // plain text chunks
    }
  },
})

Example server with Claude

server/api/ai.post.ts
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic() // reads ANTHROPIC_API_KEY

export default defineEventHandler(async event => {
  const { messages } = await readBody(event)
  const stream = client.messages.stream({
    model: 'claude-sonnet-5-5',
    max_tokens: 2048,
    system: 'You are a writing assistant. Answer in HTML, preserving formatting.',
    messages,
  })

  return new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
          controller.enqueue(new TextEncoder().encode(chunk.delta.text))
        }
      }
      controller.close()
    },
  })
})

OpenAI-compatible SDKs

The OpenAI SDK's streaming response is already an async iterable of choices[0].delta.content chunks, so it can be returned directly (from a trusted environment):

AI.configure({
  completions: (history, signal) =>
    openai.chat.completions.create({ model: 'gpt-5', messages: history, stream: true }, { signal }),
})

Custom shortcuts

AI.configure({
  completions,
  shortcuts: [
    {
      label: 'Marketing',
      icon: 'Sparkles',
      children: [
        { label: 'Write a tagline', prompt: 'Write a short, catchy tagline for this text.' },
        { label: 'SEO title', prompt: 'Suggest an SEO-friendly title (max 60 characters).' },
      ],
    },
  ],
})

Custom shortcuts are appended to the built-in ones. Labels can be plain text or locale keys.

Behaviour

  • Responses are rendered at most once per animation frame while streaming.
  • Esc stops generation and closes the menu.
  • The conversation is kept while the menu is open, so follow-up prompts refine the previous answer.