AI Toolkit
Ask AI, AI Chat, inline preview, and an AI Review drawer - built on a single aiToolkitResolver hook so you can run the built-in demo resolver today and swap in your own backend tomorrow without changing the UI.
Overview
AI Toolkit adds three related surfaces to the editor:
- Ask AI - a toolbar-launched dialog with seven one-click modes (proofread, rewrite, shorten, expand, summarize, translate, justify), a selection/document scope switch, and an inline preview before applying.
- AI Chat - a docked side panel for multi-turn requests. Scope-aware, with quick prompts and copy/apply/insert/replace actions on every AI response.
- AI Review - a persistent queue of pending, accepted, and rejected suggestions. Optional server-ledger sync for cross-session review.
All three share the same aiToolkitResolver hook and the same operation contract, so a suggestion previewed in one surface can be accepted, rejected, or escalated in another.
Demo mode. With no resolver configured, the plugin ships a deterministic defaultResolveAction so you can run Ask AI, Chat, and Review locally with no API key, no network call, and predictable output.
Install
Load the AI Toolkit stylesheet and script after the core editor bundle. The plugin auto-injects an Ask AI button into the default, basic, full, and mobile toolbar presets when it loads.
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css" />
<link rel="stylesheet" href="/richtexteditor/plugins/aitoolkit.css" />
<script src="/richtexteditor/rte-config.js"></script>
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<!-- aitoolkit.js is bundled inside all_plugins.js.
Include it separately only if you are loading plugins piecemeal. -->
<div id="editor"></div>
<script>
var editor = new RichTextEditor("#editor", {
toolbar: "full",
aiToolkitPersistenceKey: "my-editor-ai"
});
</script>
Order matters. rte-config.js must load before rte.js; aitoolkit.css must be present any time the AI button can render (even with the default toolbar) or the AI dropdown menu will size incorrectly.
Three surfaces
Toolbar-first dialog. Source + result panes, mode picker, scope toggle, before/after preview, operation plan, apply / replace / insert / reject buttons.
Docked panel. Scope-aware messaging, quick prompts, message history, and per-response apply / insert / replace actions.
Persistent drawer. Pending / accepted / rejected queue, batch controls, and optional remote ledger sync.
Built-in modes
Each mode is a { id, title, description } entry in config.aiToolkitDialogModes. They appear in the Ask AI dialog's Action dropdown and are callable through runQuickAction(id).
| id | Title | Default behavior |
|---|---|---|
proofread | Proofread | Normalizes spacing, punctuation, and capitalization while preserving meaning. |
rewrite | Rewrite | Removes filler phrases (very, really, in order to, due to the fact that) and tightens wording. |
shorten | Shorten | Keeps the first ~60% of the words, minimum 8. |
expand | Expand | Appends a supporting sentence below the source. |
summarize | Summarize | Builds a bullet summary from the first ~45% of the words. |
translate | Translate | Produces a language-labeled draft; target language is passed as request.language. |
justify | Justify edit | Same as rewrite, plus a reason field explaining the change. |
These are deterministic stubs for the demo resolver. Once you wire aiToolkitResolver, your function decides how each mode is handled. You can also register new modes - see Custom actions.
The aiToolkitResolver hook
This is the extension point. The resolver receives a structured request and returns a result (or a Promise that resolves to one). The editor handles rendering, preview, review, and apply.
var editor = new RichTextEditor("#editor", {
toolbar: "full",
aiToolkitResolver: async function (request) {
var reply = await fetch("/api/ai", {
method: "POST",
body: JSON.stringify({
mode: request.mode,
text: request.source,
language: request.language
})
}).then(function (r) { return r.json(); });
return {
result: reply.text, // string: the AI output
reason: reply.explanation, // string: why this change
operations: [{ // structured plan (optional)
type: "preview-suggestion",
text: reply.text,
reason: reply.explanation
}]
};
}
});
Request shape
| Field | Type | Meaning |
|---|---|---|
actionId | string | Action the user invoked (matches a registered action or mode id). |
mode | string | Dialog mode - proofread, rewrite, translate, etc. |
source | string | Text to operate on. Selection text if present; otherwise whole-document text. |
prompt | string | User prompt text (for chat mode). |
language | string | Target language for translate mode. Empty for others. |
scope | string | "selection" or "document". |
selectionText | string | Selection text (may be empty). |
documentText | string | Whole-document text. |
snapshot | object | Selection snapshot (hasSelection, rangeHTML, text, wholeText, ...). |
editor | RichTextEditor | The editor instance. |
contract | object | Full list of supported operation types (for validation). |
Response shape
Return a plain object with any of the following fields. All are optional; omit what you don't need.
| Field | Type | Meaning |
|---|---|---|
result | string | Raw AI output. Shown in the result pane and used as the default suggestion body. |
reason | string | Why the AI recommends this change. Surfaces in the "Why this suggestion" panel. |
message | string | Chat-style reply (AI Chat surface only). |
operations | Operation[] | Structured plan - see Operation types. |
target | string | Where to apply - "selection-preview", "document", "insert", etc. |
autoRun | boolean | Auto-apply without user confirmation (use sparingly). |
You can also return a string or a Promise. A string becomes { result: ; a Promise is awaited.
Operation types
Operations are the structured plan your resolver can return. The editor executes them in order (or user-driven, one at a time, from the plan panel).
| type | Fields | What it does |
|---|---|---|
preview-suggestion | text, reason? | Shows an inline before/after preview. User accepts or rejects. |
replace-selection | text | Replaces the current selection with the given text. |
replace-document | text | Replaces the entire document body. |
insert-below | text | Inserts a new block after the caret or selection. |
add-comment | text, reason? | Drops a non-destructive AI comment marker on the current selection. |
open-chat-panel | message? | Opens the AI Chat panel; optionally seeds a message. |
open-review-panel | - | Opens the AI Review drawer. |
Custom operation types can be registered with editor.aiToolkit.registerOperationHandler(type, handler).
editor.aiToolkit.registerOperationHandler("insert-callout", function (op, ctx) {
ctx.editor.pasteHTML(
'<aside class="callout">' + ctx.escape(op.text) + '</aside>'
);
});
Programmatic API
All surfaces can be opened and driven from code via editor.aiToolkit.
// Open the Ask AI dialog
editor.aiToolkit.openDialog({ presetMode: "rewrite", autoRun: false });
// Quick actions (no dialog) - target: selection if present, else whole document
editor.aiToolkit.runQuickAction("proofread");
editor.aiToolkit.runQuickAction("translate", { language: "spanish" });
// Panels
editor.aiToolkit.openChatPanel({ focusComposer: true });
editor.aiToolkit.openReviewPanel({ focusPanel: true });
editor.aiToolkit.closeChatPanel();
editor.aiToolkit.closeReviewPanel();
editor.aiToolkit.toggleChatPanel();
editor.aiToolkit.toggleReviewPanel();
// Resolve without rendering - useful for tests + headless flows
editor.aiToolkit.resolveAction("rewrite", { source: "hello world" })
.then(function (resolved) { console.log(resolved.result); });
// Swap the resolver at runtime
editor.aiToolkit.setResolver(myResolver);
// Review queue
var pending = editor.aiToolkit.getSuggestions();
editor.aiToolkit.clearSuggestions();
editor.aiToolkit.saveSuggestionLedger();
editor.aiToolkit.loadSuggestionLedger();
Custom actions & modes
Register domain-specific actions (e.g. SEO rewrite, legal clause check, tone matcher). They plug into the same dialog, chat, and review UI.
// Add a new dialog mode (appears in the Action dropdown)
editor.aiToolkit.registerDialogMode({
id: "seo",
title: "SEO rewrite",
description: "Rewrite for stronger keyword coverage while preserving meaning."
});
// Register the action with a target (where output goes by default)
editor.aiToolkit.registerAction({
id: "seo",
title: "SEO rewrite",
target: "selection-preview",
resolverMode: "seo" // passed as request.mode to your resolver
});
// Your resolver handles it just like built-in modes
config.aiToolkitResolver = function (request) {
if (request.mode === "seo") { /* call your SEO service */ }
/* ... */
};
Persistence & remote ledger
Suggestions in the AI Review drawer persist to localStorage under a key you control. Set aiToolkitPersistenceKey to scope per editor instance (e.g. per document).
new RichTextEditor("#editor", {
aiToolkitPersistenceKey: "doc-42-review", // localStorage scope
aiToolkitSuggestionLedgerUrl: "/api/ai/ledger", // optional GET/POST for cross-session sync
aiToolkitReviewLogUrl: "/api/ai/review-log", // optional activity feed endpoint
aiToolkitReviewSyncInterval: 15000 // ms between sync polls (default 15s)
});
When the ledger URL is set, the plugin GETs on open and POSTs on accept/reject, letting the review queue survive across sessions and be shared between reviewers.
BYOK (bring your own key)
Important: the editor does not accept or store API keys. Keys belong in your server-side secrets manager.
The recommended BYOK pattern:
- Your admin UI lets a tenant pick a provider and paste a key. The form POSTs to your backend.
- Your backend writes the key into your secrets manager, scoped to the tenant.
- The editor's
aiToolkitResolverPOSTs to/api/aiwith the session cookie. No key in the browser. - Your backend looks up the tenant's key, calls the provider, and returns a trimmed response.
See the BYOK demo for a working admin form, a routed resolver, and an Express-style server sketch.
Config reference
| Option | Type | Default | Purpose |
|---|---|---|---|
aiToolkitEnabled | boolean | true | Set to false to disable the plugin entirely (no button injection, no API). |
aiToolkitResolver | function | null | Your hook - see Resolver. Falls back to the built-in demo resolver. |
aiToolkitActions | Action[] | built-ins | Full list of registered actions. Use registerAction to extend. |
aiToolkitDialogModes | Mode[] | 7 built-ins | Modes shown in the Ask AI dropdown. |
aiToolkitOperationHandlers | object | {} | Custom operation-type handlers, keyed by type. |
aiToolkitPersistenceKey | string | "" | localStorage scope for the review queue. Empty = no persistence. |
aiToolkitSuggestionLedgerUrl | string | "" | Server endpoint for shared ledger sync. |
aiToolkitReviewLogUrl | string | "" | Server endpoint for review activity feed. |
aiToolkitReviewSyncInterval | number | 15000 | Milliseconds between ledger polls. |
aiToolkitLabel | string | "Current editor" | Label shown on meta pills inside the Ask AI dialog. |
text_aiassist | string | "Ask AI" | Tooltip and dialog title (localize per UI language). |
Try it
Every surface above runs without an API key in the public demos:
- AI Toolkit demo - Ask AI, quick actions, Chat, Review drawer
- BYOK demo - admin settings form + routed resolver + server-side sketch
- AI Toolkit overview - marketing landing with a live editor mockup