AI Writing Assistant

Pro

4 variants: toolbar (actions + readability stats + live editor), tone picker with preview, suggestions (accept/reject rewrite cards) and history (revision timeline with diff).

</>sv-ai-writing-assistant aiwritingeditor
import { SvAiWritingAssistantComponent } from '@styloviz-pro/ai-writing-assistant';

Demo & usage

Basic usage

Pro

Usage example — open the gallery for the full interactive demo.

Pro

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Wire it to a real model

Pro

The default already: handle actionRun, then write the answer back through text.

Inline — anchored to the selection

Pro

Select text and the toolbar appears over it. Portaled to <body> and clamped on both axes.

Tone picker & suggestions

Pro

When to use

  • Augmenting a writing surface with AI: rewrites, tone changes, readability stats.
  • Wiring a real model needs no flag: `simulate` is off by default, so handle `actionRun` / `toneChange` and write the result back through `text` — the component renders, it does not call anything.
  • Editors where the assistant should follow the selection rather than sit in a sidebar — the `inline` variant.
  • Editors where users want inline suggestions they can accept or reject.
  • For plain rich-text editing without AI, use the Rich Text Editor.

Async states

  • loading
  • error
  • retry

Inputs

NameTypeDefaultDescription
loadingbooleanfalseTrue while the data is being fetched.
errorSvDataError | string | nullnullLoad failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`.
variantWritingAssistantVariant'toolbar'View: toolbar editor, tone picker, suggestions or history.
textstring (two-way)''The text being edited. Two-way: write a rewrite back through it.
actionsreadonly WritingAction[][]Quick actions offered in the rail and the inline toolbar.
tonesreadonly ToneOption[][]Tones offered by the tone picker.
suggestionsreadonly WritingSuggestion[][]Proposed rewrites awaiting a decision.
historyreadonly RevisionEntry[][]Past revisions.
busybooleanfalseThe host has a request in flight — shows the busy state without owning a timer.
simulatebooleanfalseApply the bundled sample rewrites when an action runs. **Off by default.** The component emits `actionRun` / `toneChange` and waits for you to set `text` — reporting a rewrite the document never received is the worst thing this component can do, so it cannot happen by omission. Set it explicitly to `true` in a showcase.
protectedRangesreadonly SvProtectedRange[][]Stretches of the original that must survive a rewrite untouched. Offsets are half-open `[start, end)` into the CURRENT `text()`; when the text changes, send ranges for the new text. ENFORCED WHERE THIS COMPONENT WRITES `text`, NOT BY ASKING THE MODEL NICELY. A protected range in a prompt is a request; a model asked to "make this clearer" will reword a quotation, tidy a licence clause or correct somebody's surname while doing exactly what it was told. So every write the component makes — a simulated action or tone, `applyInlineResult`, restoring a revision — is checked first and REFUSED if a protected stretch would not survive: the text stays as it was, a `role="alert"` says why, and `protectedRefused` fires. Accepting a suggestion is refused the same way when its `originalText` is found in `text()`. What the component cannot check is a rewrite the host writes into `text` directly — that write never passes through here.

Outputs

EventPayload typeDescription
retryvoidThe host should re-run the failed load.
actionRunstringEmitted with the id of the writing action the user runs (rewrite, summarise …).
toneChangeToneTypeEmitted with the tone the user applies.
suggestionAcceptedstringEmitted with the id of a suggestion the user accepts.
suggestionRejectedstringEmitted with the id of a suggestion the user rejects.
protectedRefusedProtectedRefusalEmitted when a write was refused because it would alter a protected range.
textChangestringEmits the new `text` whenever it changes — the output half of `[(text)]`.

Wire it to your API

This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.

Template

Pro

Bound to the names this component actually exposes.

Host class

Pro

Drive the three async inputs from your own fetch.

  • This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `tones`, `loading` and `error`.
  • Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.

Full walkthrough: Connecting to a backend.

Customization & theming

  • Toolbar, tone, suggestions, history and inline views.
  • Every content set is an input with the bundled sample as its default — `text` (two-way), `actions`, `tones`, `suggestions` and `history` — so the component demonstrates itself out of the box and takes your data when you have it.
  • `inline` anchors a floating toolbar to the current text selection: three quick actions plus a free-text prompt. It portals to `<body>` so an overflow, transform or backdrop-filter ancestor cannot clip it, prefers to sit above the selection, and flips or slides when the viewport edge is close.
  • Readability stats and before/after diffs.
  • Accept/reject rewrite cards.
  • Emits `actionRun`, `toneChange` and `suggestionAccepted` for host integration.
  • Responsive: the diff stats strip stacks two-up on phones (`grid-cols-2 sm:grid-cols-4`) so the numbers stay readable.
  • `[protectedRanges]` marks stretches of the original — quotations, legal clauses, code, names — that must survive a rewrite untouched, and the component **checks the result** rather than asking the model nicely. A protected range in a prompt is a request; a model asked to 'make this clearer' will reword a quotation, tidy a licence clause or correct somebody’s surname while doing exactly what it was told, and the only enforcement a UI can actually offer is on what came back. Every write the component makes itself — a simulated action or tone, an inline rewrite, a history restore — is **refused** when it would change a protected range: the text stays as it was, a dismissible alert names the range, and `protectedRefused` fires. A suggestion whose `originalText` overlaps a range cannot be accepted (`acceptAll` skips it). Offsets are into the current `text()`; a history entry can carry its own `protectedRanges`, because one set of offsets cannot describe every revision; an entry without them is checked against `protectedRanges`, as before.
  • With `simulate` off, pressing a tone or an inline action says **requested**, not applied, until the host writes the rewrite back through `text` — the component never reports a rewrite it did not see.
  • Protection is about **containment, not position**: a rewrite that shortens the paragraph above a quotation has not touched the quotation, and reporting it as a violation because its offsets moved would make the check useless on exactly the rewrites it exists for. A breach renders as a `role='alert'` above the diff, naming what was changed.
  • Past `SV_PROSE_DIFF_MAX_TOKENS` (2,000 words) the word-by-word comparison is abandoned, and the panel now says so in a `role='status'` notice. One enormous red block beside one enormous green block is what the reader gets when a model rewrites a document from scratch — a real and alarming outcome — and it is also what they get when the comparison simply gave up. The colours cannot tell those apart, and only one of them is a finding.

Best practices

  • Do: Let users accept/reject each suggestion rather than auto-applying rewrites.
  • Do: Convey diff changes with more than color so they're accessible.
  • Tip: Keep a revision history so users can undo an AI edit they dislike.

Accessibility

  • Suggestions are reachable and dismissible.
  • The inline toolbar is `role="toolbar"` with a label naming what it acts on, and is dismissible by Escape, an outside click or a scroll — never a mouse-only affordance.
  • Category/tone/history/diff toggles report aria-pressed; action feedback uses polite live regions.
  • Diff changes conveyed beyond color.

Feedback on AI Writing Assistant

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.