AI Overview
Understand SVeditor AI text processing, streaming autocomplete, proofreading, and change review features.
AI Overview
SVeditor provides three AI extension modules:
| Extension | Description |
|---|---|
ai-basic | Text processing (shorten, extend, fix grammar, translate), streaming content, autocomplete |
ai-suggestion | Proofreading and writing suggestions |
ai-changes | AI 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
| Action | Description |
|---|---|
shorten | Shorten the text while preserving its core meaning |
extend | Elaborate and expand the text with more detail |
fix-grammar | Correct grammar, spelling, and punctuation |
simplify | Simplify language for broader accessibility |
complete | Continue and complete the text naturally |
improve-writing | Improve clarity, flow, and style |
translate | Translate 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
-
Callback must handle errors by throwing — The SDK catches thrown errors and routes them to
onError. Do not returnnullon HTTP errors; throw instead. -
Streaming requires SSE format — The
onStreamRequesthandler must return aReadableStream<Uint8Array>of plain text chunks (not SSE-formatted). Use theparseSSEStreamhelper above if your API returns SSE. -
Requests can be cancelled — Always pass
options.aborter?.signaltofetch. The SDK cancels ongoing requests when the user closes the AI panel or triggers a new action. -
onCompletionRequestis optional — If not provided, all AI actions useonStreamRequestexclusively.