In-editor AI for the <richtextbox> tag helper: Ask AI in the toolbar, a docked AI Chat panel,
an inline suggestion preview, and a persistent AI Review drawer backed by
MapRichTextBoxUploads()'s server-side endpoints. The built-in demo resolver runs here so no API key is required.
See also: Structured content demo for JSON / Markdown round-trip.
enable-ai-toolkit turns on the AI plugin for this editor only. The tag helper assigns a persistence key,
configures the shared review endpoints, and injects a server-backed demo resolver so the AI workflow works in ASP.NET Core
without adding custom page bootstrapping. If you register your own IRichTextBoxAiResolver, the same toolbar, chat,
and review UI will call your server logic instead. You can also swap IRichTextBoxAiSuggestionLedgerStore and
IRichTextBoxAiReviewLogStore if you want shared review state to live in SQL, Redis, or another backend.
<richtextbox
id="AiToolkitEditor"
name="AiToolkitEditor"
toolbar="default"
enable-ai-toolkit="true"
ai-toolkit-persistence-key="demo-ai-toolkit"
ai-toolkit-review-sync-interval="10000"
height="440px" />
The editor is provider-agnostic. Register an implementation of IRichTextBoxAiResolver in DI and the existing tag-helper,
dialog, chat, and review UI will call your server logic.
builder.Services.AddRichTextBox();
builder.Services.AddSingleton<IRichTextBoxAiResolver, MyAiResolver>();
public sealed class MyAiResolver : IRichTextBoxAiResolver
{
public ValueTask<RichTextBoxAiResponse> ResolveAsync(RichTextBoxAiRequest request, CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(
RichTextBoxAiResponseBuilder.FromOperations(
request.HasSelection ? "Selection suggestion" : "Document suggestion",
RichTextBoxAiResponseBuilder.PreviewSuggestion(
"Provider-backed rewrite goes here.",
"Generated by a custom ASP.NET Core AI resolver.")));
}
}
When you need fully custom request shaping, swap the client-side resolver instead. Every editor exposes
aiToolkit.setResolver() - the toolbar, dialog, and panels call your function and handle the UI for you.
<script>
window.addEventListener("DOMContentLoaded", function () {
var editor = window.richTextBoxEditors && window.richTextBoxEditors.AiToolkitEditor;
if (!editor || !editor.aiToolkit) return;
editor.aiToolkit.setResolver(async function (request) {
// POST to YOUR backend - the key stays server-side
var reply = await fetch("/my-ai", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
mode: request.mode, text: request.source, language: request.language
})
}).then(function (r) { return r.json(); });
return {
result: reply.text,
reason: reply.explanation,
operations: [{ type: "preview-suggestion", text: reply.text }]
};
});
});
</script>
Swap the default in-memory stores to share pending / accepted / rejected review state across requests and users.
builder.Services.AddSingleton<IRichTextBoxAiSuggestionLedgerStore, MySuggestionLedgerStore>();
builder.Services.AddSingleton<IRichTextBoxAiReviewLogStore, MyReviewLogStore>();
See the AI Provider Settings (BYOK) demo for a worked
bring-your-own-key pattern - tenant admin form, client-side resolver swap via setResolver(),
and a server-side /api/ai handler sketch that reads keys from a secrets store.