Skip to content

如何基于现有富文本编辑器(如 Slate、TipTap、Monaco)扩展支持 AIGC 插件?比如一键生成段落或续写?

一、核心架构设计

二、编辑器实现方案

Slate.js 实现

typescript
// 注册AI命令扩展
const withAIGC = (editor: Editor) => {
    const { insertText } = editor;

    editor.aigenerate = async (type: 'paragraph' | 'continue') => {
        const selection = editor.selection;
        if (!selection) return;

        // 获取上下文文本
        const context = Editor.string(editor, Editor.above(editor)?.trim() || '';
        
        // 调用AI服务
        const aiResult = await fetchAICompletion({
            prompt: type === 'continue' ? context : '',
            mode: type
        });

        // 插入结果
        Transforms.insertText(editor, aiResult, { at: selection });
    };

    return editor;
};

// 使用示例
<button onClick={() => ReactEditor.aigenerate(editor, 'paragraph')}>
    生成段落
</button>

三、关键技术实现

1. 上下文感知获取

javascript
// 获取光标前后各200字符作为上下文
function getEditorContext(editor) {
    const { selection } = editor;
    const range = {
        anchor: { path: selection.anchor.path, offset: Math.max(0, selection.anchor.offset - 200) },
        focus: { path: selection.focus.path, offset: selection.focus.offset + 200 }
    };
    return Editor.string(editor, range);
}

2. 节流请求优化

typescript
const aiRequestQueue = new Map();

async function throttledAIRequest(key, prompt) {
    if (aiRequestQueue.has(key)) {
        return aiRequestQueue.get(key);
    }
    
    const promise = fetchAICompletion(prompt)
        .finally(() => aiRequestQueue.delete(key));
    
    aiRequestQueue.set(key, promise);
    return promise;
}

3. Markdown兼容处理

javascript
// 转换AI生成的Markdown为编辑器节点
function parseAIContent(content) {
    const md = new Remarkable();
    const tokens = md.parse(content, {});

    return tokens.map(token => {
        if (token.type === 'paragraph_open') {
            return { type: 'paragraph', children: [] };
        }
        // ...其他转换规则
    });
}

四、用户体验优化

1. 渐进式加载动画

tsx
const [isGenerating, setIsGenerating] = useState(false);

const handleGenerate = async () => {
    setIsGenerating(true);
    try {
        const result = await editor.aigenerate();
        // 逐字插入效果
        for (let i = 0; i < result.length; i++) {
            await new Promise(r => setTimeout(r, 20));
            Transforms.insertText(editor, result[i], { at: editor.selection });
        }
    } finally {
        setIsGenerating(false);
    }
};

2. 多候选结果处理

javascript
// 显示候选结果浮层
function showAIOptions(editor, results) {
    const popup = document.createElement('div');
    popup.className = 'ai-options-popup';

    results.forEach((text, i) => {
        const option = document.createElement('div');
        option.textContent = text.substr(0, 50);
        option.onclick = () => {
            Transforms.insertText(editor, text);
            document.body.removeChild(popup);
        };
        popup.appendChild(option);
    });

    // 定位到光标下方
    const [node, path] = Editor.node(editor, editor.selection);
    const domNode = ReactEditor.toDOMNode(editor, node);
    const rect = domNode.getBoundingClientRect();
    popup.style.top = `${rect.bottom}px`;
    popup.style.left = `${rect.left}px`;
    document.body.appendChild(popup);
}

3. 错误恢复机制

typescript
// 失败时保留草稿并重试
async function safeAIGenerate(editor, retries = 3) {
    const originalText = Editor.string(editor, editor.selection);
    try {
        return await editor.aigenerate();
    } catch (error) {
        if (retries > 0) {
            Transforms.insertText(editor, originalText);
            return safeAIGenerate(editor, retries - 1);
        }
        throw error;
    }
}

五、生产环境注意事项

  1. API安全

    • 使用JWT签名请求
    • 限制用户每分钟调用次数
    javascript
    // 请求签名示例
    const signRequest = (prompt) => {
        const nonce = crypto.randomUUID();
        const signature = crypto.createHmac('sha256', SECRET)
            .update(`${prompt}:${nonce}`)
            .digest('hex');
        return { prompt, nonce, signature };
    };
  2. 本地缓存

    typescript
    // 使用IndexedDB缓存常见请求
    const cacheAIResponse = async (prompt, result) => {
        const db = await openDB('ai-cache', 1);
        await db.put('responses', { prompt, result, timestamp: Date.now() });
    };
  3. 性能监控

    javascript
    // 埋点记录生成耗时
    const start = performance.now();
    const result = await fetchAICompletion(prompt);
    trackEvent('ai_generate', {
        duration: performance.now() - start,
        length: result.length
    });

题目要点:

  • 编辑器集成:通过扩展各编辑器的命令系统实现无缝接入
  • 上下文感知:智能获取光标周围文本作为AI提示
  • 渐进式交互:流式插入结果增强用户体验
  • 健壮性保障:错误重试和本地缓存机制
  • 安全防护:请求签名和频率限制