Skip to content
EspañolEnglish

AI interfaces and text editor

The pieces of an assistant interface come from the chat of the AMSET assistant, which uses them every day: the input box, the messages, the reasoning, the tool calls, the log of what an agent does, the files it generates and the model picker. There are 12, with no dependencies, one CSS file per component, and everything conveyed with text as well as colour.

The components

Activity log

ActivityLog

The log of what an agent is doing.

Artifact card

ArtifactCard

A file an assistant has generated, with its preview one click away.

Attachment

Attachment

A file attached to a message.

Chat message

ChatMessage

A message in a chat with an assistant.

Engine selector

ModelSelector

What serves what comes next.

Image viewer

ImageViewer

The viewer for an image at almost full screen.

Prompt input

PromptInput

The box where you write to an assistant.

Reasoning

Reasoning

A model's reasoning, folded.
The search bar INSIDE something -a long conversation, a document-.

Suggestions

Suggestions

The quick replies an assistant proposes.

Tool call

ToolCall

A call to a tool.

Usage and cost

UsageBadge

What a response cost.
  • PromptInput grows with the text up to a limit, sends with Enter and adds a new line with Shift+Enter (without sending while composing with an IME), recalls the last message with the up arrow and accepts files that are pasted, dragged or picked. With busyModes you can keep typing while it answers: add to the turn, stop and correct, or queue.
  • ChatMessage includes copy, regenerate and edit in the same bubble, plus custom actions.
  • ActivityLog follows the end only if you were already at the bottom and groups repeated events; with group, a history of several tasks.
  • FindBar searches within a long conversation, where the browser's find cannot see what has not been loaded.
  • ArtifactFrame isolates the HTML an assistant generates with sandbox="allow-scripts", never with allow-same-origin.

Rich text editor

@amset/ui does not include a text editor and does not depend on one. It includes what has to look and behave like the rest of the library (the toolbar, its link and image panels and the styles of the writing area) and leaves the engine to your application. There are two complete recipes, one for Tiptap and one for Lexical, compiled and checked in the library's repository.

DirectionPropWhat it carries
editor → toolbarstateWhat is applied where the cursor is (active), what cannot be used right now (unavailable) and the link at the cursor (link).
toolbar → editoronCommandThe command: 'bold', 'heading2', 'bulletList', 'undo', 'clearFormatting'...
toolbar → editoronLinkChange{ href, newTab } already checked, or null to remove it.
toolbar → editoronImageInsert{ src, alt } already checked; alt is only empty if the image is marked as decorative.
application → toolbarshortcutsThe editor's shortcuts (Mod+B), which the toolbar announces and shows in its help. The editor listens for them, not the toolbar.
  • A single tab stop for the whole toolbar, arrow keys between buttons and focus memory when you come back.
  • Each format is a ToggleButton with aria-pressed; clicking it with the mouse does not take focus away from the editor, so the selection stays where it was.
  • The link and image panels are only mounted when opened and are not a <form>: the toolbar works inside your application's form.
  • Addresses are checked with normalizeLinkHref and normalizeImageSrc (an allowlist of schemes: a javascript: or a data: does not get through). The toolbar does not clean what the editor stores: that HTML is sanitised on the server before storing or rendering it.

The Tiptap and Lexical recipes

You install the editor of your choice in your application; the library needs nothing new. The recipes are built on each editor's core, without @tiptap/react or @lexical/react: those packages pull in floating menu code that this toolbar does not need.

Install the editor
# Tiptapnpm install @tiptap/core @tiptap/pm @tiptap/starter-kit @tiptap/extension-image # Lexicalnpm install lexical @lexical/rich-text @lexical/list @lexical/link @lexical/history @lexical/selection @lexical/utils
The stylesheets, if you import styles per component
import '@amset/ui/styles/rich-text.css'import '@amset/ui/styles/toggle-button.css'
The frame and the writing area
<span id={labelId}>Product description</span><div className="amset-richtext__frame">  <RichTextToolbar label="Description formatting" aria-controls={editorId} variant="flat" {...toolbarProps} />  <div ref={hostRef} /></div>

Code from the library's guide (docs/rich-text-editor.md, revision 3bb3996d), checked there by the compiler.

Each recipe is a React hook that mounts the editor in a <div>, keeps state up to date on every change and translates each command into the editor's API. That is all the glue there is. With another editor it is enough to answer the same questions: what is active at the cursor, what cannot be used right now, and how each command is executed.