@speles7172/ai-console
v0.2.1
Published
React UI for the AI module — a Bedrock model picker per AI flow, and the grammar and translation assistants, over a transport you supply.
Downloads
405
Readme
@speles7172/ai-console
The two faces of the AI module, in React.
<AiModelsConsole> is what an administrator sees: one row per AI flow, each
pointed at a model chosen from the whole live Bedrock catalog, changeable
without a deploy. <AiAssistPanel> is what everybody else sees: ask, read,
accept or discard — for correcting a draft and for translating one.
npm install @speles7172/ai-consoleReact 18 or 19. Browser-only, and it calls nothing itself: the consuming
application exposes endpoints backed by @speles7172/ai-client and supplies a
transport.
The settings page
import { AiModelsConsole } from '@speles7172/ai-console';
import '@speles7172/ai-console/styles.css';
// Module scope, not inline in JSX — see below.
const transport = {
settings: () => fetch('/api/ai/settings').then((r) => r.json()),
models: () => fetch('/api/ai/models').then((r) => r.json()),
save: (body) =>
fetch('/api/ai/settings', { method: 'PUT', body: JSON.stringify(body) }).then(() => undefined),
saveTone: (body) =>
fetch('/api/ai/tone', { method: 'PUT', body: JSON.stringify(body) }).then(() => undefined),
};
<AiModelsConsole transport={transport} />;Every capability is optional and inferred from the transport: no save, and
the page is a read-only view of what is configured; no saveTone, and there is
no tone row. A button that always fails is worse than no button.
The assistant
import { AiAssistPanel } from '@speles7172/ai-console';
const [draft, setDraft] = useState('');
<textarea value={draft} onChange={(event) => setDraft(event.target.value)} />
<AiAssistPanel transport={transport} text={draft} onAccept={setDraft} context={thread} />It never edits the draft. The host owns the text, the panel asks for a suggestion, and applying one is an explicit click — which is what lets the same component sit behind a message composer, a description field and a document editor without knowing anything about any of them.
The endpoints behind it
Four, and each one is a seam worth thinking about:
| Method | Backed by |
|---|---|
| settings() | the feature registry plus the stored model per flow |
| models() | listBedrockModels() |
| save({ feature, modelId }) | writing AI_MODEL_<FEATURE> |
| correctGrammar / translate | ai.correctGrammar / ai.translate |
Put the first three behind whatever your application means by "administrator",
and the assistants behind whatever it means by "may use AI". This package
applies no access control of its own, deliberately — the same stance
audit-client and file-client take. Note what crosses the seam: a feature key
and a model id, never a prompt template and never a model invoked directly, so a
page cannot be talked into spending a Bedrock budget on something else.
Without the markup
useAiModels and useAiAssist are the state without any of it — the two-speed
load, the reload-after-write, the race guards, the error handling — for an
application with its own design system.
Styling
The components render aic- class names and no styling dependency. The
stylesheet is optional, driven entirely by custom properties on .aic, and
includes @speles7172/controls' own because every picker here is one of its
controls. theme is light (the default), dark or auto.
Traps
The transport must be identity-stable. Both components key their effects on
it, so an object literal written inline in JSX is a new transport on every
render and therefore an endless reload. Module constant, or useMemo. Same rule
every console in this repository follows.
A flow following its default is not pre-filled with that default. Showing the effective value in the box looks identical to a value somebody set, and saving it turns "follows the default" into "pinned to today's default" — silently detaching the setting from a default the next release was going to change. The row says which it is instead.
The model picker takes a typed id. The catalog is a convenience, not the permitted set: a model id copied from the AWS console, or an application inference profile ARN — which no list call ever returns — must be enterable, and a configured value the catalog does not know is kept selected rather than rendered as an empty box.
The catalog loads separately from the settings, on purpose. The settings are one row per flow and arrive immediately; the catalog is two Bedrock control-plane calls and does not. Waiting for both would leave the page blank for a second to populate a dropdown nobody has opened yet — and a catalog that fails costs the suggestions, not the page.
A suggestion is dropped the moment the draft changes under it. A suggestion for a draft that has since been edited is a suggestion for a different draft, and accepting it would silently discard whatever was typed in between.
Licence
MIT.
