Providers, streaming & DOCX export
Server-side companions to the AI Toolkit, all shipped in the RichTextBox.AspNetCore NuGet package (1.0.0-preview.11+). One-line DI to wire a real provider, SSE streaming end-to-end, and an HTML → DOCX endpoint.
Built-in provider resolvers
Three drop-in IRichTextBoxAiResolver implementations using HttpClientFactory, cancellation, and ILogger<T>. Pick one and register in Program.cs:
using RichTextBox.AiResolvers;
builder.Services.AddRichTextBox();
builder.Services.AddRichTextBoxOpenAiResolver(opts =>
{
opts.ApiKey = builder.Configuration["OpenAI:ApiKey"];
opts.Model = "gpt-4o-mini";
});
Swap AddRichTextBoxOpenAiResolver for AddRichTextBoxAnthropicResolver or AddRichTextBoxAzureOpenAiResolver. All three share AiResolverOptions:
public class AiResolverOptions
{
public string ApiKey { get; set; } // required
public string? Model { get; set; } // provider-specific default
public string? BaseUrl { get; set; } // for proxies / Ollama / Groq
public int MaxTokens { get; set; } = 1024;
public double Temperature { get; set; } = 0.4;
public string? SystemSuffix { get; set; } // appended to every mode's system prompt
public TimeSpan Timeout { get; set; } = 60s;
}
Azure has three extras on AzureOpenAiResolverOptions: Endpoint (https://my-resource.openai.azure.com), DeploymentName (not model), ApiVersion (optional).
The OpenAiResolver works with any OpenAI-compatible endpoint — override BaseUrl for Groq, Together.ai, or local Ollama.
Streaming responses (Server-Sent Events)
MapRichTextBoxUploads() also maps POST /richtextbox/ai/stream (configurable via RichTextBoxOptions.AiStreamEndpoint). OpenAI and Anthropic resolvers implement IStreamingRichTextBoxAiResolver; non-streaming providers fall back to a single data frame transparently.
Wire format
data: <json-encoded delta string>\n\n
data: <json-encoded delta string>\n\n
...
event: response
data: { ...RichTextBoxAiResponse payload... }\n\n
event: done
data: {}\n\n
Client
var stream = editor.aiToolkit.streamRequest({
url: "/richtextbox/ai/stream",
body: { mode: "rewrite", selectionText: "..." },
onDelta: function (chunk, full) { /* render progressively */ },
onResponse: function (payload) { /* final structured result */ },
onDone: function (fullText) { },
onError: function (err) { }
});
// stream.abort() — cancel mid-response
// stream.promise — resolves with the concatenated full text
The helper uses fetch + ReadableStream. Browsers without streaming fall back to XHR against the non-streaming endpoint automatically.
DOCX export
MapRichTextBoxUploads() also maps POST /richtextbox/export/docx (configurable via DocxExportEndpoint). Built on MIT-licensed DocumentFormat.OpenXml. No external service.
Request
POST /richtextbox/export/docx
Content-Type: application/json
{
"html": "<h1>Memo</h1><p>Body.</p>",
"title": "Memo",
"fileName": "memo.docx"
}
Response
Binary stream with Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document and Content-Disposition: attachment; filename=memo.docx.
Supported HTML
- Paragraphs,
h1–h6, blockquote, pre, hr - Ordered & unordered nested lists
- Tables (with header rows)
- Inline:
b/strong,i/em,u,s/del,sub,sup,code,br - Hyperlinks, inline images (data URIs only — external URLs skipped for safety)
- Style attributes:
font-weight,font-style,text-decoration,color,background-color,text-align
Client helper
editor.aiToolkit.exportDocx({
url: "/richtextbox/export/docx",
fileName: "report.docx",
title: "Quarterly report"
});
Custom resolvers
For Bedrock, Gemini, your own model, or anything else — implement IRichTextBoxAiResolver (and optionally IStreamingRichTextBoxAiResolver) and register your class. The rest of the AI Toolkit surface is provider-agnostic.
public sealed class MyBedrockResolver : IRichTextBoxAiResolver
{
public async ValueTask<RichTextBoxAiResponse> ResolveAsync(
RichTextBoxAiRequest request, CancellationToken cancellationToken = default)
{
// ...call Bedrock, map response to RichTextBoxAiResponseBuilder...
}
}
builder.Services.AddRichTextBox();
builder.Services.AddSingleton<IRichTextBoxAiResolver, MyBedrockResolver>();