Streaming AI Responses

The AI Toolkit can consume a Server-Sent Events stream instead of waiting for the full response. Tokens appear as they arrive, the same way ChatGPT / Claude / Gemini surfaces do. The server implementation lives in RichTextBox.AspNetCore(MapRichTextBoxUploads() automatically maps /richtextbox/ai/stream); OpenAI and Anthropic resolvers implementIStreamingRichTextBoxAiResolver.

Click a button below to call the streaming endpoint. Tokens render live inside the editor.

Example code

<div id="stream_editor">
    <p>Click a button below to call the streaming endpoint. Tokens render live inside the editor.</p>
</div>

<button type="button" onclick="runStream('Summarize this document in three bullet points.')">Summarize</button>
<button type="button" onclick="runStream('Proofread the document, fix only grammar and punctuation.')">Proofread</button>
<button type="button" id="abortBtn" onclick="abortStream()" disabled>Abort</button>
<div id="streamStatus"></div>

<script>
    var streamEditor = new RichTextEditor("#stream_editor", { toolbar: "default" });
    var currentStream = null;

    function runStream(prompt) {
        if (!streamEditor.aiToolkit || !streamEditor.aiToolkit.streamRequest) {
            document.getElementById("streamStatus").textContent = "streamRequest helper not available.";
            return;
        }
        var html = streamEditor.getHTMLCode();
        var accumulator = "";
        document.getElementById("streamStatus").textContent = "Opening stream...";
        document.getElementById("abortBtn").disabled = false;

        currentStream = streamEditor.aiToolkit.streamRequest({
            url: "/richtextbox/ai/stream",
            body: { mode: "chat", prompt: prompt, documentText: html, hasSelection: false },
            onDelta: function (delta, full) {
                accumulator += delta;
                document.getElementById("streamStatus").textContent = "Streaming... " + accumulator.length + " chars";
            },
            onDone: function () {
                streamEditor.insertHTML("<hr/><p><strong>AI &mdash; streamed response:</strong></p><p>" + accumulator.replace(/\n/g, "<br/>") + "</p>");
                document.getElementById("streamStatus").textContent = "Done.";
                document.getElementById("abortBtn").disabled = true;
                currentStream = null;
            },
            onError: function (err) {
                document.getElementById("streamStatus").textContent = "Error: " + err.message;
                document.getElementById("abortBtn").disabled = true;
            }
        });
    }

    function abortStream() {
        if (currentStream) currentStream.abort();
        document.getElementById("streamStatus").textContent = "Aborted by user.";
        document.getElementById("abortBtn").disabled = true;
    }
</script>