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 implement
IStreamingRichTextBoxAiResolver.
Click a button below to call the streaming endpoint. Tokens render live inside the editor.
Example code
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css" />
<script type="text/javascript" src="/richtexteditor/rte.js"></script>
<script type="text/javascript" src='/richtexteditor/plugins/all_plugins.js'></script>
<div id="stream_editor">
<p>Click a button below to call the streaming endpoint. Tokens render live inside the editor.</p>
</div>
<div style="display:flex; gap:8px; margin-top:10px;">
<button type="button" onclick="runStream('Summarize this document in three bullet points.')" style="padding:6px 14px; border-radius:999px; background:#0f172a; color:#fff; border:0; cursor:pointer;">Summarize</button>
<button type="button" onclick="runStream('Proofread the document, fix only grammar and punctuation.')" style="padding:6px 14px; border-radius:999px; background:#fff; color:#0f172a; border:1px solid #e2e8f0; cursor:pointer;">Proofread</button>
<button type="button" id="abortBtn" onclick="abortStream()" disabled style="padding:6px 14px; border-radius:999px; background:#fff; color:#64748b; border:1px solid #e2e8f0; cursor:pointer;">Abort</button>
</div>
<div id="streamStatus" style="margin-top:10px; font-size:12.5px; color:#64748b;"></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 — 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>