SVeditor Docs
AI

AI Overview

Understand SVeditor AI text processing, streaming autocomplete, proofreading, and change review features.

AI Overview

SVeditor provides three AI extension modules:

ExtensionDescription
ai-basicText processing (shorten, extend, fix grammar, translate), streaming content, autocomplete
ai-suggestionProofreading and writing suggestions
ai-changesAI change diff review and accept/reject

Configuration

AI extensions use a callback-driven configuration pattern — you provide the request handlers, and SVeditor manages the UI, streaming, and editor integration.

SVeditor.create({
  el: '#editor',
  extensionsOptions: {
    ai: {
      enabled: true,
      aiConfig: {
        /**
         * Handle streaming AI requests.
         * Return a ReadableStream<Uint8Array> of plain text chunks.
         */
        onStreamRequest: async (options) => {
          const res = await fetch('/api/ai/stream', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              action: options.action,
              text: options.text,
            }),
            signal: options.aborter?.signal,
          });
          return res.body;
        },

        /**
         * Handle non-streaming AI requests.
         * Return the complete response as a string.
         */
        onCompletionRequest: async (options) => {
          const res = await fetch('/api/ai/completion', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              action: options.action,
              text: options.text,
            }),
            signal: options.aborter?.signal,
          });
          const data = await res.json();
          return data.content;
        },
      },

      // Lifecycle callbacks
      onLoading: (context) => {
        console.log(`AI processing: ${context.action}`);
      },
      onSuccess: (context) => {
        console.log(`AI completed: ${context.action}`);
      },
      onError: (error, context) => {
        console.error(`AI failed: ${context.action}`, error);
      },
    },
  },
});

AiTextResolverOptions

Both onStreamRequest and onCompletionRequest receive an options object:

interface AiTextResolverOptions {
  /** The Tiptap editor instance */
  editor: Editor;

  /**
   * The AI action type.
   * Built-in values: 'shorten' | 'extend' | 'fix-grammar' | 'simplify' |
   *                  'complete' | 'improve-writing' | 'translate'
   */
  action: string;

  /** The text content to process */
  text: string;

  /** Text format options */
  textOptions: {
    format?: 'plain-text' | 'rich-text';
  };

  /** Extension configuration */
  extensionOptions: AiOptions;

  /** AbortController for cancelling the request */
  aborter?: AbortController;
}

Supported Actions

ActionDescription
shortenShorten the text while preserving its core meaning
extendElaborate and expand the text with more detail
fix-grammarCorrect grammar, spelling, and punctuation
simplifySimplify language for broader accessibility
completeContinue and complete the text naturally
improve-writingImprove clarity, flow, and style
translateTranslate to Chinese, or to English if in Chinese

Playground example

The auto-save demo shows AI as a layered integration — not only toolbar actions. Point extensionsOptions.ai at your completion API, then combine ai, aiSuggestion, and aiChanges with auto-save, uploads, and export in one editor.

const suggestionRules = [
  {
    id: 'grammar',
    title: 'Grammar',
    prompt:
      'Fix grammar, spelling, and punctuation errors while preserving the original meaning. Return only the revised text.',
    color: '#dc2626',
    backgroundColor: 'rgba(220, 38, 38, 0.10)',
    displayAsDiff: true,
  },
  {
    id: 'style',
    title: 'Style',
    prompt:
      'Improve clarity and flow for a product-facing audience. Return only the revised text.',
    color: '#2563eb',
    backgroundColor: 'rgba(37, 99, 235, 0.12)',
    displayAsDiff: true,
  },
];

SVeditor.create({
  el: '#editor',
  extensionsOptions: {
    autoSave: {
      onFetch: () => fetchDoc(docId),
      onSaveContent: (content) => saveDoc(docId, { content }),
      debounceMs: 900,
    },
    meta: {
      onFetchMeta: () => fetchMeta(docId),
      onSaveMeta: (meta) => saveDoc(docId, { meta }),
    },
    versionHistory: {
      enabled: true,
      autoSnapshot: {
        enabled: true,
        debounceMs: 2600,
      },
    },
    image: {
      proxyUrl: '',
      onUpload: uploadImage,
    },
    attachment: {
      proxyUrl: '',
      onUpload: uploadAttachment,
    },
    wechatCopy: {
      enabled: true,
    },
    ai: {
      enabled: true,
      aiConfig: {
        apiUrl: '/api/sdk-demo/api/ai',
      },
    },
    aiChanges: {
      enabled: true,
    },
    aiSuggestion: {
      enabled: true,
      rules: suggestionRules,
      loadOnStart: true,
      reloadOnUpdate: true,
      debounceTimeout: 2200,
      resolver: resolveSdkDemoSuggestions,
    },
  },
});

Full Implementation Example

Step 1: Create request handlers

// src/utils/ai.ts
import type { AiTextResolverOptions } from '@wztlink1013/sveditor';

const SYSTEM_PROMPTS: Record<string, string> = {
  shorten: 'Shorten the text while keeping the main meaning. Return only the shortened text.',
  extend: 'Elaborate on the text with more detail and context. Return only the expanded text.',
  'fix-grammar': 'Fix all grammar, spelling, and punctuation errors. Return only the corrected text.',
  simplify: 'Simplify this text for easier understanding. Return only the simplified text.',
  complete: 'Continue and naturally complete this text. Return only the continuation.',
  'improve-writing': 'Improve the writing style, clarity, and flow. Return only the improved text.',
  translate: 'If the text is in Chinese, translate to English. Otherwise translate to Chinese. Return only the translation.',
};

function parseSSEStream(body: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
  const reader = body.getReader();
  const decoder = new TextDecoder();
  const encoder = new TextEncoder();

  return new ReadableStream({
    async start(controller) {
      let buffer = '';
      try {
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;

          buffer += decoder.decode(value, { stream: true });
          const lines = buffer.split('\n');
          buffer = lines.pop() ?? '';

          for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed || trimmed === 'data: [DONE]') continue;
            if (trimmed.startsWith('data: ')) {
              try {
                const data = JSON.parse(trimmed.slice(6));
                const content = data?.choices?.[0]?.delta?.content;
                if (content) controller.enqueue(encoder.encode(content));
              } catch {
                // Skip malformed SSE events
              }
            }
          }
        }
        controller.close();
      } catch (error) {
        controller.error(error);
      }
    },
  });
}

export const aiConfig = {
  onStreamRequest: async (options: AiTextResolverOptions) => {
    const { action, text, aborter } = options;
    const systemPrompt = SYSTEM_PROMPTS[action] ?? 'Process the text.';

    const res = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      },
      body: JSON.stringify({
        model: 'gpt-4',
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: text },
        ],
        stream: true,
      }),
      signal: aborter?.signal,
    });

    if (!res.ok) throw new Error(`AI request failed: ${res.status}`);
    if (!res.body) throw new Error('No response body');

    return parseSSEStream(res.body);
  },

  onCompletionRequest: async (options: AiTextResolverOptions) => {
    const { action, text, aborter } = options;
    const systemPrompt = SYSTEM_PROMPTS[action] ?? 'Process the text.';

    const res = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      },
      body: JSON.stringify({
        model: 'gpt-4',
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: text },
        ],
        stream: false,
      }),
      signal: aborter?.signal,
    });

    if (!res.ok) throw new Error(`AI request failed: ${res.status}`);
    const data = await res.json();
    return data?.choices?.[0]?.message?.content ?? null;
  },
};

Step 2: Configure the editor

import { SVeditor } from '@wztlink1013/sveditor';
import { aiConfig } from './utils/ai';

SVeditor.create({
  el: '#editor',
  extensionsOptions: {
    ai: {
      enabled: true,
      aiConfig,
    },
  },
});

Compatible AI Services

OpenAI

const AI_CONFIG = {
  apiKey: 'sk-...',
  baseURL: 'https://api.openai.com/v1',
  model: 'gpt-4',
};

Azure OpenAI

const AI_CONFIG = {
  apiKey: 'your-azure-key',
  baseURL: 'https://your-resource.openai.azure.com/openai/deployments/your-deployment',
  model: 'gpt-4',
};

Alibaba Cloud Qwen

const AI_CONFIG = {
  apiKey: 'sk-...',
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  model: 'qwen-max',
};

Any OpenAI-compatible API

const AI_CONFIG = {
  apiKey: 'your-key',
  baseURL: 'https://your-service.com/v1',
  model: 'your-model',
};

Important Notes

  1. Callback must handle errors by throwing — The SDK catches thrown errors and routes them to onError. Do not return null on HTTP errors; throw instead.

  2. Streaming requires SSE format — The onStreamRequest handler must return a ReadableStream<Uint8Array> of plain text chunks (not SSE-formatted). Use the parseSSEStream helper above if your API returns SSE.

  3. Requests can be cancelled — Always pass options.aborter?.signal to fetch. The SDK cancels ongoing requests when the user closes the AI panel or triggers a new action.

  4. onCompletionRequest is optional — If not provided, all AI actions use onStreamRequest exclusively.