Skip to content

Instantly share code, notes, and snippets.

@kabeer11000
Created September 25, 2025 10:30
Show Gist options
  • Select an option

  • Save kabeer11000/1f1b3f3d6a8d2f11b74504c179794daa to your computer and use it in GitHub Desktop.

Select an option

Save kabeer11000/1f1b3f3d6a8d2f11b74504c179794daa to your computer and use it in GitHub Desktop.

Revisions

  1. kabeer11000 created this gist Sep 25, 2025.
    266 changes: 266 additions & 0 deletions DeltaEditor.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,266 @@
    Creating a Document Editor with AI and delta changes a faster way.

    ## 1. **Use Tiptap's Transaction API**

    Instead of replacing the entire document, create transactions that apply specific changes:

    ```javascript
    // Your chatbot returns structured commands
    const chatbotResponse = {
    message: "I've added a security section and updated the introduction.",
    edits: [
    {
    type: "insertHeading",
    position: 150,
    level: 2,
    text: "API Security Best Practices"
    },
    {
    type: "insertParagraph",
    position: 151,
    text: "Implement rate limiting and input validation..."
    },
    {
    type: "replaceText",
    from: 45,
    to: 67,
    text: "comprehensive security protocols"
    }
    ]
    };

    // Apply changes using Tiptap transactions
    function applyEdits(editor, edits) {
    const tr = editor.state.tr;

    edits.forEach(edit => {
    switch(edit.type) {
    case 'insertHeading':
    tr.insert(edit.position, editor.schema.nodes.heading.create(
    { level: edit.level },
    editor.schema.text(edit.text)
    ));
    break;

    case 'insertParagraph':
    tr.insert(edit.position, editor.schema.nodes.paragraph.create(
    {},
    editor.schema.text(edit.text)
    ));
    break;

    case 'replaceText':
    tr.replaceWith(edit.from, edit.to, editor.schema.text(edit.text));
    break;
    }
    });

    editor.view.dispatch(tr);
    }
    ```

    ## 2. **Position-Based Updates with Node IDs**

    Add IDs to your document structure for reliable targeting:

    ```javascript
    // Configure Tiptap with node IDs
    const editor = new Editor({
    extensions: [
    StarterKit,
    // Add ID extension for trackable nodes
    Document.extend({
    addGlobalAttributes() {
    return [
    {
    types: ['heading', 'paragraph', 'bulletList'],
    attributes: {
    id: {
    default: null,
    parseHTML: element => element.getAttribute('data-id'),
    renderHTML: attributes => {
    if (!attributes.id) return {};
    return { 'data-id': attributes.id };
    },
    },
    },
    },
    ];
    },
    }),
    ],
    });

    // Chatbot references nodes by ID
    const edits = [
    {
    type: "updateNodeById",
    nodeId: "intro-paragraph-1",
    content: "Updated introduction text..."
    },
    {
    type: "insertAfterNode",
    nodeId: "section-2-heading",
    nodeType: "paragraph",
    content: "New paragraph after section 2..."
    }
    ];
    ```

    ## 3. **Semantic Section Management**

    Create helper functions to work with document sections:

    ```javascript
    class DocumentManager {
    constructor(editor) {
    this.editor = editor;
    }

    findNodeById(id) {
    let foundNode = null;
    this.editor.state.doc.descendants((node, pos) => {
    if (node.attrs.id === id) {
    foundNode = { node, pos };
    return false; // stop iteration
    }
    });
    return foundNode;
    }

    insertAfterNode(nodeId, nodeType, content) {
    const found = this.findNodeById(nodeId);
    if (!found) return;

    const tr = this.editor.state.tr;
    const insertPos = found.pos + found.node.nodeSize;

    const newNode = this.editor.schema.nodes[nodeType].create(
    { id: `generated-${Date.now()}` },
    this.editor.schema.text(content)
    );

    tr.insert(insertPos, newNode);
    this.editor.view.dispatch(tr);
    }

    updateNodeContent(nodeId, newContent) {
    const found = this.findNodeById(nodeId);
    if (!found) return;

    const tr = this.editor.state.tr;
    const newNode = found.node.type.create(
    found.node.attrs,
    this.editor.schema.text(newContent)
    );

    tr.replaceWith(found.pos, found.pos + found.node.nodeSize, newNode);
    this.editor.view.dispatch(tr);
    }
    }

    // Usage
    const docManager = new DocumentManager(editor);

    // Apply chatbot edits
    chatbotEdits.forEach(edit => {
    switch(edit.type) {
    case 'updateSection':
    docManager.updateNodeContent(edit.sectionId, edit.content);
    break;
    case 'addAfterSection':
    docManager.insertAfterNode(edit.afterId, edit.nodeType, edit.content);
    break;
    }
    });
    ```

    ## 4. **Range-Based Updates**

    For more complex edits, use Tiptap's range utilities:

    ```javascript
    import { findChildren } from '@tiptap/core';

    function applyRangeEdit(editor, edit) {
    const { from, to, content, nodeType = 'paragraph' } = edit;

    const tr = editor.state.tr;

    // Create new content
    const nodes = content.map(text =>
    editor.schema.nodes[nodeType].create({}, editor.schema.text(text))
    );

    // Replace range with new nodes
    tr.replaceWith(from, to, nodes);
    editor.view.dispatch(tr);
    }

    // Chatbot can specify ranges to replace
    const edit = {
    type: "replaceRange",
    from: 100,
    to: 250,
    content: [
    "Updated first paragraph...",
    "New second paragraph..."
    ]
    };
    ```

    ## 5. **Integration with RAG System**

    Structure your RAG prompt to return Tiptap-compatible edits:

    ```javascript
    const prompt = `
    You are editing a document using Tiptap editor. Return your response as JSON with:
    1. "message": Your conversational response
    2. "edits": Array of edit operations
    Available edit types:
    - insertAfterNode: {type, afterNodeId, nodeType, content, id}
    - updateNode: {type, nodeId, content}
    - replaceRange: {type, from, to, content}
    - insertAtPosition: {type, position, nodeType, content, id}
    Current document structure: ${getDocumentStructure()}
    User request: ${userMessage}
    `;

    // Process RAG response
    async function handleChatbotResponse(userMessage) {
    const response = await callRAG(prompt);
    const parsed = JSON.parse(response);

    // Show conversational response
    displayChatMessage(parsed.message);

    // Apply document edits
    applyEditsToDocument(parsed.edits);
    }
    ```

    ## 6. **Track Changes for Undo/Redo**

    Tiptap handles this automatically, but you can add custom tracking:

    ```javascript
    // Before applying edits
    const beforeState = editor.state;

    applyEdits(editor, chatbotEdits);

    // Add to custom history if needed
    addToChangeHistory({
    type: 'chatbot-edit',
    before: beforeState,
    after: editor.state,
    timestamp: Date.now()
    });
    ```

    This approach lets you make precise, incremental changes to your Tiptap document without regenerating the entire content. The editor's transaction system ensures smooth updates and maintains undo/redo functionality.

    Would you like me to elaborate on any specific part or help you implement a particular edit type?