SVeditor 文档
AI 能力

AI 能力概览

了解 SVeditor 的 AI 文本处理、流式自动补全、校对建议及修改审阅能力。

AI 能力概览

SVeditor 提供三个 AI 扩展模块:

扩展说明
ai-basic文本处理(缩写/扩写/语法修复/翻译)、流式内容、自动补全
ai-suggestion校对与写作建议
ai-changesAI 修改差异对比与审阅(接受/拒绝)

配置方式

AI 扩展采用回调函数配置模式——你提供请求处理函数,SVeditor 负责 UI 渲染、流式传输和编辑器集成。

SVeditor.create({
  el: '#editor',
  extensionsOptions: {
    ai: {
      enabled: true,
      aiConfig: {
        /**
         * 处理流式 AI 请求。
         * 返回纯文本块的 ReadableStream<Uint8Array>。
         */
        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;
        },

        /**
         * 处理非流式 AI 请求。
         * 返回完整响应字符串。
         */
        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;
        },
      },

      // 生命周期回调
      onLoading: (context) => {
        console.log(`AI 处理中: ${context.action}`);
      },
      onSuccess: (context) => {
        console.log(`AI 完成: ${context.action}`);
      },
      onError: (error, context) => {
        console.error(`AI 失败: ${context.action}`, error);
      },
    },
  },
});

AiTextResolverOptions 参数

onStreamRequestonCompletionRequest 均接收一个 options 对象:

interface AiTextResolverOptions {
  /** Tiptap 编辑器实例 */
  editor: Editor;

  /**
   * AI 操作类型。
   * 内置值:'shorten' | 'extend' | 'fix-grammar' | 'simplify' |
   *         'complete' | 'improve-writing' | 'translate'
   */
  action: string;

  /** 要处理的文本内容 */
  text: string;

  /** 文本格式选项 */
  textOptions: {
    format?: 'plain-text' | 'rich-text';
  };

  /** 扩展配置 */
  extensionOptions: AiOptions;

  /** 用于取消请求的 AbortController */
  aborter?: AbortController;
}

支持的操作类型

操作说明
shorten缩短文本,保留核心含义
extend扩展文本,增加更多细节
fix-grammar修正语法、拼写和标点
simplify简化语言,提升可读性
complete自然续写补全文本
improve-writing改善表达清晰度和行文流畅度
translate中译英或英译中

Playground 示例

自动保存演示 展示 AI 的分层接入 — 不仅是工具栏按钮。将 extensionsOptions.ai 指向你的补全 API,可在同一编辑器中组合 aiaiSuggestionaiChanges、自动保存、上传与导出。

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,
    },
  },
});

完整实现示例

步骤一:创建请求处理函数

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

const SYSTEM_PROMPTS: Record<string, string> = {
  shorten: '请缩短以下文本,保留核心含义。只返回缩短后的文本。',
  extend: '请扩展以下文本,添加更多细节和上下文。只返回扩展后的文本。',
  'fix-grammar': '请修正以下文本中的所有语法、拼写和标点错误。只返回修正后的文本。',
  simplify: '请简化以下文本,使其更易理解。只返回简化后的文本。',
  complete: '请自然地续写以下文本。只返回续写的内容。',
  'improve-writing': '请改善以下文本的写作风格、表达清晰度和行文流畅度。只返回改进后的文本。',
  translate: '如果是中文,请翻译成英文;如果是英文,请翻译成中文。只返回翻译结果。',
};

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 {
                // 跳过格式错误的 SSE 事件
              }
            }
          }
        }
        controller.close();
      } catch (error) {
        controller.error(error);
      }
    },
  });
}

export const aiConfig = {
  onStreamRequest: async (options: AiTextResolverOptions) => {
    const { action, text, aborter } = options;
    const systemPrompt = SYSTEM_PROMPTS[action] ?? '处理以下文本。';

    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 请求失败: ${res.status}`);
    if (!res.body) throw new Error('无响应体');

    return parseSSEStream(res.body);
  },

  onCompletionRequest: async (options: AiTextResolverOptions) => {
    const { action, text, aborter } = options;
    const systemPrompt = SYSTEM_PROMPTS[action] ?? '处理以下文本。';

    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 请求失败: ${res.status}`);
    const data = await res.json();
    return data?.choices?.[0]?.message?.content ?? null;
  },
};

步骤二:配置编辑器

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

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

兼容的 AI 服务

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',
};

阿里云通义千问

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

其他 OpenAI 兼容 API

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

注意事项

  1. 回调必须通过抛出异常来处理错误 — SDK 捕获抛出的错误并路由到 onError。HTTP 错误时不要返回 null,而应 throw

  2. 流式响应需要纯文本块格式onStreamRequest 必须返回纯文本块的 ReadableStream<Uint8Array>(不是 SSE 格式)。如果你的 API 返回 SSE,请使用上面的 parseSSEStream 辅助函数。

  3. 请求可以被取消 — 务必将 options.aborter?.signal 传给 fetch。SDK 在用户关闭 AI 面板或触发新操作时会取消进行中的请求。

  4. onCompletionRequest 是可选的 — 未提供时,所有 AI 操作均使用 onStreamRequest