@artooi/ag-ui-web-component 0.20.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +192 -1
- package/README.md +404 -28
- package/dist/ag-ui-web-component.bundle.js +781 -451
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +29 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +93 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +9 -0
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +8 -1
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +8 -1
- package/dist/core/run_index.d.ts.map +1 -1
- package/dist/core/transcribe_audio.d.ts +7 -0
- package/dist/core/transcribe_audio.d.ts.map +1 -1
- package/dist/core/upload_attachment.d.ts +9 -0
- package/dist/core/upload_attachment.d.ts.map +1 -1
- package/dist/core/utils.d.ts +12 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/dom/animations.d.ts +51 -11
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/dom_driver.d.ts +12 -7
- package/dist/dom/dom_driver.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1275 -520
- package/dist/index.js.map +3 -3
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +8 -1
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +35 -0
- package/src/core/ag_ui_chat.ts +452 -55
- package/src/core/create_http_agent.ts +14 -3
- package/src/core/remote_conversation_store.ts +25 -6
- package/src/core/run_index.ts +27 -5
- package/src/core/transcribe_audio.ts +20 -5
- package/src/core/upload_attachment.ts +13 -0
- package/src/core/utils.ts +18 -0
- package/src/dom/animations.ts +175 -31
- package/src/dom/dom_driver.ts +18 -12
- package/src/index.ts +4 -0
- package/src/ui/styles.ts +751 -421
- package/src/ui/ui_strings.ts +9 -1
- package/src/ui/voice_input.ts +7 -1
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
|
|
|
38
38
|
|
|
39
39
|
- [Install](#install)
|
|
40
40
|
- [Quickstart](#quickstart)
|
|
41
|
+
- [Authenticating requests](#authenticating-requests)
|
|
41
42
|
- [Core concepts](#core-concepts)
|
|
42
43
|
- [The run loop and the AG-UI client](#the-run-loop-and-the-ag-ui-client)
|
|
43
44
|
- [Stopping a run](#stopping-a-run)
|
|
@@ -46,6 +47,7 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
|
|
|
46
47
|
- [DOM-driver and animation primitives](#dom-driver-and-animation-primitives)
|
|
47
48
|
- [Page-action tools](#page-action-tools)
|
|
48
49
|
- [New chat and collapse](#new-chat-and-collapse)
|
|
50
|
+
- [Collapsing to the launcher](#collapsing-to-the-launcher)
|
|
49
51
|
- [Tool-call display modes](#tool-call-display-modes)
|
|
50
52
|
- [Markdown rendering](#markdown-rendering)
|
|
51
53
|
- [Run notices: compaction and agent skills](#run-notices-compaction-and-agent-skills)
|
|
@@ -103,7 +105,9 @@ Drop the element into your page and register the tools the agent may call:
|
|
|
103
105
|
|
|
104
106
|
const chat = document.querySelector("ag-ui-chat");
|
|
105
107
|
|
|
106
|
-
// Extra request headers (e.g. CSRF) sent
|
|
108
|
+
// Extra request headers (e.g. CSRF), sent with every request the element makes.
|
|
109
|
+
// For a credential that rotates, set chat.getHeaders instead - it is consulted
|
|
110
|
+
// per request. See "Authenticating requests".
|
|
107
111
|
chat.headers = { "X-CSRFToken": getCsrfToken() };
|
|
108
112
|
|
|
109
113
|
// A non-destructive tool: fills a text field with a typing animation.
|
|
@@ -138,7 +142,9 @@ Drop the element into your page and register the tools the agent may call:
|
|
|
138
142
|
```
|
|
139
143
|
|
|
140
144
|
That's the whole integration: an `endpoint` attribute pointing at your AG-UI server, optional
|
|
141
|
-
`headers`, and the tools you want the agent to be able to invoke in the browser.
|
|
145
|
+
`headers`, and the tools you want the agent to be able to invoke in the browser. If your API is on
|
|
146
|
+
another origin, add `credentials="include"` too; see
|
|
147
|
+
[Authenticating requests](#authenticating-requests).
|
|
142
148
|
|
|
143
149
|
### Attributes and properties
|
|
144
150
|
|
|
@@ -147,27 +153,31 @@ That's the whole integration: an `endpoint` attribute pointing at your AG-UI ser
|
|
|
147
153
|
| Attribute | Property | Notes |
|
|
148
154
|
| --- | --- | --- |
|
|
149
155
|
| `endpoint` | `endpoint` | The AG-UI endpoint URL. Required to send. Reflecting getter + setter. |
|
|
156
|
+
| `credentials` | `credentials` | Cookie policy for every request the element makes: `omit` / `same-origin` / `include`. Unset means the browser default (`same-origin`), which sends no cookies cross-origin. See [Authenticating requests](#authenticating-requests). |
|
|
150
157
|
| `title-text` | — | Header label; defaults to `"Assistant"`. The only **observed** attribute (live-updates the header). |
|
|
151
158
|
| `data-tool-display` | `toolDisplay` | Tool-call card detail: `inline` / `minimal` / `compact` / `full` (default `full`). |
|
|
152
159
|
| `data-text-animation` | — | Incoming-text reveal: `none` (default) / `fade` / `word`. |
|
|
153
160
|
| `data-prompt-chips` | — | Present (bare, or any value but `"false"`) to surface skills as chips. |
|
|
154
161
|
| `data-slash-commands` | — | Present (bare, or any value but `"false"`) to enable the `/`-command palette. |
|
|
155
162
|
| `data-skills` | — | Inline JSON skill catalog. |
|
|
156
|
-
| `data-skills-url` | — | URL of a JSON skill catalog (fetched with
|
|
157
|
-
| `data-tools-url` | — | URL of a server tool-label catalog (`[{ name, summary, description? }]`), fetched with
|
|
163
|
+
| `data-skills-url` | — | URL of a JSON skill catalog (fetched with the element's headers and cookie policy). |
|
|
164
|
+
| `data-tools-url` | — | URL of a server tool-label catalog (`[{ name, summary, description? }]`), fetched with the element's headers and cookie policy; labels tool-call cards for server-side tools. |
|
|
158
165
|
| `data-threads-url` | — | URL of a server thread index (django-ag-ui's `ThreadsView`); enables durable, cross-device chat history. |
|
|
159
166
|
| `data-runs-url` | — | URL of a server run index (django-ag-ui's `RunsView`); reveals the header's ⭯ *Continue a run* panel. See [Resuming a run](#resuming-a-run). |
|
|
160
|
-
| `data-attachments-url` | — | URL of the file-upload endpoint (django-ag-ui's `AttachmentsView`); reveals the composer's
|
|
167
|
+
| `data-attachments-url` | — | URL of the file-upload endpoint (django-ag-ui's `AttachmentsView`); reveals the composer's paperclip picker + drag-and-drop. |
|
|
161
168
|
| `data-attachment-accept` | — | `<input accept>` list for client-side type filtering (e.g. `image/*,.pdf`). The server stays authoritative. |
|
|
162
169
|
| `data-attachment-max-bytes` | — | Client-side upload size cap in bytes (default 10 MiB; `0` disables). The server stays authoritative. |
|
|
163
|
-
| `data-transcribe-url` | — | URL of the voice-transcription endpoint (django-ag-ui's `TranscribeView`); reveals the composer's
|
|
170
|
+
| `data-transcribe-url` | — | URL of the voice-transcription endpoint (django-ag-ui's `TranscribeView`); reveals the composer's mic button. See [Voice input](#voice-input). |
|
|
164
171
|
| `data-theme-toggle` | — | Boolean: show a built-in header light⇄dark toggle (persists per tab). Off by default. See [Theme toggle](#theme-toggle). |
|
|
165
172
|
| `data-strings` | `strings` | Partial JSON override of the UI string table (localization). The property wins key-by-key over the attribute; see [Internationalization](#internationalization-i18n). |
|
|
166
|
-
| `data-icon-url` | — | Header (and
|
|
173
|
+
| `data-icon-url` | — | Header (and launcher) icon image URL. A slotted `slot="icon"` wins; see [Header & launcher icon](#header-and-launcher-icon). |
|
|
174
|
+
| `data-launcher-icon-url` | — | Icon image URL for the collapsed launcher only, when it should differ from the header's. Falls back to `data-icon-url`; a slotted `slot="launcher"` wins over both. |
|
|
175
|
+
| `data-unread-badge` | — | **On by default.** `="false"` hides the launcher's unread badge; the count and the `ag-ui-unread` event keep running. See [Collapsing to the launcher](#collapsing-to-the-launcher). |
|
|
167
176
|
|
|
168
177
|
Each header control also takes its own icon slot — `icon-history`, `icon-checkpoints`,
|
|
169
178
|
`icon-new`, `icon-collapse` — with the built-in glyph as the fallback, so a host can project a
|
|
170
|
-
brand `<img>` or `<svg>` rather than only restyling the character
|
|
179
|
+
brand `<img>` or `<svg>` rather than only restyling the character. The composer's glyphs work the
|
|
180
|
+
same way: `icon-send`, `icon-stop`, `icon-attach`, `icon-voice`.
|
|
171
181
|
|
|
172
182
|
```html
|
|
173
183
|
<ag-ui-chat endpoint="/agent/">
|
|
@@ -177,16 +187,20 @@ brand `<img>` or `<svg>` rather than only restyling the character:
|
|
|
177
187
|
| `data-page-actions` | — | Opt-in built-in page-action tools: a comma list of `scroll` / `drag` (e.g. `"scroll,drag"`). See [Page-action tools](#page-action-tools). |
|
|
178
188
|
| `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
|
|
179
189
|
| `data-answer-well` | — | CSS-only boolean: box each assistant turn (its text, tool cards, and thinking) in one bordered "well". Off by default. See [The answer well](#the-answer-well). |
|
|
180
|
-
| `collapsed` | `collapsed` | Reflected boolean; collapses the widget (to a rail under `placement="sidebar"`). Persisted per-tab in `sessionStorage`. |
|
|
190
|
+
| `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded` / `page`). Persisted per-tab in `sessionStorage`. |
|
|
181
191
|
| `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
|
|
182
192
|
| `density` | — | CSS-only: `comfortable` (default) / `compact`. |
|
|
183
193
|
| `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
|
|
184
194
|
|
|
185
|
-
**Properties** (JS only, not attributes): `headers`, `allowImages`, `autoConfirm`,
|
|
195
|
+
**Properties** (JS only, not attributes): `headers`, `getHeaders`, `allowImages`, `autoConfirm`,
|
|
186
196
|
`confirmPredicate`, `askUser`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`,
|
|
187
197
|
`getPageMap`, `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`,
|
|
188
198
|
`navigationResult`, `skillContext`, `toolSummaries`, `strings`, `resolvePageTarget`, plus the
|
|
189
|
-
mirrors `endpoint` / `toolDisplay` / `collapsed`.
|
|
199
|
+
mirrors `endpoint` / `toolDisplay` / `collapsed` / `credentials`.
|
|
200
|
+
|
|
201
|
+
`headers` and `getHeaders` authenticate **every** request the element makes, not only the agent
|
|
202
|
+
run; `getHeaders` is the one to use for a credential that rotates. See
|
|
203
|
+
[Authenticating requests](#authenticating-requests).
|
|
190
204
|
|
|
191
205
|
`allowImages` (default `false`) re-enables `<img>` in rendered assistant markdown.
|
|
192
206
|
It is off by default because a model-controlled image URL is fetched by the browser
|
|
@@ -209,7 +223,8 @@ keyboard focus and styleable via the `code-copy` part. Override its labels with
|
|
|
209
223
|
the `copyCode` / `copied` / `copyFailed` strings.
|
|
210
224
|
|
|
211
225
|
**Methods**: `registerTool`, `registerPageState`, `setSkills`, `sendMessage`, `attachFile`,
|
|
212
|
-
`appendMessage`, `newChat`, `setCollapsed`, `toggleCollapsed
|
|
226
|
+
`appendMessage`, `newChat`, `setCollapsed`, `toggleCollapsed`, `toggleTheme`, `openThreads`,
|
|
227
|
+
`openCheckpoints`, `reload`.
|
|
213
228
|
|
|
214
229
|
### Sending from your own UI
|
|
215
230
|
|
|
@@ -243,6 +258,137 @@ mock AG-UI server.
|
|
|
243
258
|
|
|
244
259
|
---
|
|
245
260
|
|
|
261
|
+
## Authenticating requests
|
|
262
|
+
|
|
263
|
+
The element talks to more than one endpoint. Beyond the AG-UI run itself, it may fetch the thread
|
|
264
|
+
index and a thread's messages, the tool-label and skill catalogs, the run index, and it may POST an
|
|
265
|
+
upload or a voice clip. **Every one of them is authenticated the same way**, by the element rather
|
|
266
|
+
than by the agent — so configuring authentication on a custom `agentFactory` authenticates the run
|
|
267
|
+
and nothing else, and the history drawer comes back empty because its request was anonymous.
|
|
268
|
+
|
|
269
|
+
| Request | Endpoint | Transport |
|
|
270
|
+
| --- | --- | --- |
|
|
271
|
+
| The agent run | `endpoint` | `fetch` (SSE), via `agentFactory` |
|
|
272
|
+
| Thread index / a thread's messages / rename / delete | `data-threads-url` | `fetch` |
|
|
273
|
+
| Tool-label catalog | `data-tools-url` | `fetch` |
|
|
274
|
+
| Skill catalog | `data-skills-url` | `fetch` |
|
|
275
|
+
| Run index | `data-runs-url` | `fetch` |
|
|
276
|
+
| Voice transcription | `data-transcribe-url` | `fetch` |
|
|
277
|
+
| File upload | `data-attachments-url` | `XMLHttpRequest` (for progress events) |
|
|
278
|
+
|
|
279
|
+
### `headers` and `getHeaders`
|
|
280
|
+
|
|
281
|
+
`headers` is a plain record sent with every request above:
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
chat.headers = { "X-CSRFToken": getCsrfToken() };
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
It is read at request time, but only an assignment changes it — so a token captured there is pinned
|
|
288
|
+
until you remember to assign again. For anything that rotates (a short-lived JWT, a re-issued CSRF
|
|
289
|
+
token) set **`getHeaders`** instead, a function consulted immediately before every request:
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
chat.getHeaders = () => ({ Authorization: `Bearer ${auth.accessToken()}` });
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Because it is called per request, a token refreshed between two requests reaches the second one —
|
|
296
|
+
including mid-conversation, on the cached agent's own stream.
|
|
297
|
+
|
|
298
|
+
The two **compose**: they are merged per key, `getHeaders` winning, so a fixed header and a rotating
|
|
299
|
+
one can be configured independently and neither silently drops the other.
|
|
300
|
+
|
|
301
|
+
```js
|
|
302
|
+
chat.headers = { "X-Client": "admin" };
|
|
303
|
+
chat.getHeaders = () => ({ Authorization: `Bearer ${auth.accessToken()}` });
|
|
304
|
+
// every request: X-Client: admin + a freshly-read Authorization
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Cross-origin cookies (`credentials`)
|
|
308
|
+
|
|
309
|
+
If your API is on a different origin from the page — `app.example.com` calling `api.example.com`
|
|
310
|
+
counts, subdomains are cross-origin — the browser's default of `same-origin` sends **no cookies at
|
|
311
|
+
all**. The requests still go out; they arrive unauthenticated, and the server answers `401` while
|
|
312
|
+
looking perfectly configured. Set the cookie policy explicitly:
|
|
313
|
+
|
|
314
|
+
```html
|
|
315
|
+
<ag-ui-chat endpoint="https://api.example.com/agent/" credentials="include"></ag-ui-chat>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
chat.credentials = "include"; // mirrors the attribute
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
It takes `fetch`'s own three modes — `omit`, `same-origin`, `include` — applies to every request in
|
|
323
|
+
the table above, and is read per request, so a late assignment applies to everything after it.
|
|
324
|
+
Anything else is rejected where you wrote it: an unknown value assigned as a property throws, and an
|
|
325
|
+
unknown value in the attribute is reported to the console and ignored, rather than becoming a `401`
|
|
326
|
+
later on.
|
|
327
|
+
|
|
328
|
+
The server has to agree: `Access-Control-Allow-Credentials: true` and a concrete
|
|
329
|
+
`Access-Control-Allow-Origin` (the wildcard is invalid with credentials).
|
|
330
|
+
|
|
331
|
+
One asymmetry: uploads use `XMLHttpRequest` for real progress events, and its cookie switch is
|
|
332
|
+
two-state. `include` turns it on; every other value leaves it off. `omit` therefore cannot suppress
|
|
333
|
+
cookies on a *same-origin* upload — supply your own `uploadHandler` if that matters.
|
|
334
|
+
|
|
335
|
+
### Framework hosts: configure before you insert
|
|
336
|
+
|
|
337
|
+
`headers`, `getHeaders` and `credentials` are read when a request is made, so they can be set at any
|
|
338
|
+
time. Several other things are read once, while the element **connects**: `strings`, `uploadHandler`
|
|
339
|
+
and `transcribeHandler` (they decide whether the attach and voice affordances exist at all) and
|
|
340
|
+
every chrome-building `data-*` attribute — and the catalogs and thread history are requested at, or
|
|
341
|
+
just after, that same moment.
|
|
342
|
+
|
|
343
|
+
React attaches `ref`s *after* it inserts the node, which puts the canonical integration on the wrong
|
|
344
|
+
side of that boundary. Create the element, configure it, then append:
|
|
345
|
+
|
|
346
|
+
```jsx
|
|
347
|
+
function Assistant() {
|
|
348
|
+
const host = useRef(null);
|
|
349
|
+
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
defineAgUiChat();
|
|
352
|
+
const chat = document.createElement("ag-ui-chat");
|
|
353
|
+
|
|
354
|
+
// Configure first - every one of these is read as the element connects.
|
|
355
|
+
chat.setAttribute("endpoint", "/agent/");
|
|
356
|
+
chat.setAttribute("data-threads-url", "/agent/threads/");
|
|
357
|
+
chat.credentials = "include";
|
|
358
|
+
chat.getHeaders = () => ({ Authorization: `Bearer ${auth.accessToken()}` });
|
|
359
|
+
chat.registerTool(myTool);
|
|
360
|
+
|
|
361
|
+
// ...then insert it.
|
|
362
|
+
host.current.appendChild(chat);
|
|
363
|
+
return () => chat.remove();
|
|
364
|
+
}, []);
|
|
365
|
+
|
|
366
|
+
return <div ref={host} />;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Writing it as `<ag-ui-chat ref={...} />` in JSX and configuring in the ref callback mostly works —
|
|
371
|
+
the catalog requests are held back one microtask precisely so a ref assigned in the same commit is
|
|
372
|
+
honoured — but the thread-history request is **not** deferred (a deferred replay could land after a
|
|
373
|
+
`sendMessage()` and duplicate the transcript), so that one goes out with whatever was configured at
|
|
374
|
+
insertion.
|
|
375
|
+
|
|
376
|
+
If your credentials can only arrive later still — an awaited token, a passive effect — call
|
|
377
|
+
**`reload()`** once they land:
|
|
378
|
+
|
|
379
|
+
```js
|
|
380
|
+
const token = await auth.login();
|
|
381
|
+
chat.getHeaders = () => ({ Authorization: `Bearer ${token}` });
|
|
382
|
+
await chat.reload();
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`reload()` re-runs everything the element loads on startup (tool catalog, skills, thread history)
|
|
386
|
+
with the configuration as it then stands. It is a reload, not a merge: the in-flight run is
|
|
387
|
+
cancelled and the transcript is rebuilt from the persisted history, so call it when configuration
|
|
388
|
+
lands rather than between turns.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
246
392
|
## Core concepts
|
|
247
393
|
|
|
248
394
|
### The run loop and the AG-UI client
|
|
@@ -432,19 +578,58 @@ human-readable speed (configurable; pass small/zero durations in tests):
|
|
|
432
578
|
- `highlightThenClick(el, { highlightMs })` / `pressThenClick(el, options)` — outline/press an
|
|
433
579
|
element, pause, then click.
|
|
434
580
|
- `selectOption(el, value)` / `toggleControl(el, checked)` — animate a `<select>` / checkbox.
|
|
435
|
-
- `scrollIntoCenterView(el)`
|
|
436
|
-
|
|
437
|
-
|
|
581
|
+
- `scrollIntoCenterView(el, { settleMs })` — scrolls the element to the vertical centre and
|
|
582
|
+
resolves once the scroll has **settled**, so the ring that follows is drawn where the user is
|
|
583
|
+
looking rather than mid-glide. Awaiting is optional; the scroll is requested synchronously
|
|
584
|
+
either way. Settlement is `scrollend` where the browser has it, a short probe when nothing
|
|
585
|
+
actually moved (the element was already in view), and a 600 ms cap otherwise.
|
|
586
|
+
- `flash(el, { flashMs, color })` / `focusWithFlash(el, { flashMs, color, focus })` — ring the
|
|
587
|
+
element so the user can find it. The ring is an `outline`, not a `box-shadow`, because a shadow
|
|
588
|
+
paints outside the border box and any `overflow: hidden` ancestor sharing the element's box (a
|
|
589
|
+
card, a table cell) clips it away entirely. It holds for **1200 ms** by default and fades out
|
|
590
|
+
over the last third — a 200 ms blink is not long enough to be *found* by someone who does not
|
|
591
|
+
yet know where to look. The colour comes from the target's own `--ag-ui-accent` (so a themed
|
|
592
|
+
page is flashed in its own colour), or from `color`.
|
|
593
|
+
`flash` leaves focus alone; `focusWithFlash` moves it — see "Flash versus focus" below.
|
|
594
|
+
- `prefersReducedMotion()` — honoured by every primitive that *moves* something: the hold delays
|
|
595
|
+
in `pressThenClick` / `selectOption` / `toggleControl` collapse to instant, `scrollIntoCenterView`
|
|
596
|
+
jumps instead of gliding and settles immediately, and the flash drops its fade while still
|
|
597
|
+
holding the ring for its full duration. Reduced motion asks for no animation, not for no
|
|
598
|
+
feedback. `typeInto` and `highlightThenClick` are the exceptions: they keep their
|
|
599
|
+
explicit-duration contract, so pass `charDelayMs: 0` / `highlightMs: 0` yourself if you want
|
|
600
|
+
them instant.
|
|
438
601
|
|
|
439
602
|
The **DOM-driver** primitives ([`dom_driver.ts`](src/dom/dom_driver.ts)) compose those into the
|
|
440
603
|
operations a tool handler typically wants:
|
|
441
604
|
|
|
442
|
-
- `fillField(el, value, options)` — scroll to, focus-flash, and type into a text field.
|
|
605
|
+
- `fillField(el, value, options)` — scroll to, focus-flash, and type into a text field. The
|
|
606
|
+
flash defaults to `flashMs: 0` here: the field is about to be typed into, which is its own
|
|
607
|
+
highlight. Pass `flashMs` (and optionally `color`) to ring it first.
|
|
443
608
|
- `clickElement(el, options)` / `pressButton(el, options)` — scroll to, highlight/press, and click.
|
|
444
609
|
- `selectControl(el, value)` / `toggleCheckbox(el, checked)` — animate a `<select>` / checkbox.
|
|
445
610
|
- `setControlValue(el, value)` — set a `<select>` or checkbox without animation, dispatching
|
|
446
611
|
`input`/`change`.
|
|
447
612
|
|
|
613
|
+
Every driver primitive **awaits the scroll** before it animates. A smooth scroll is not awaitable
|
|
614
|
+
on its own, so a highlight fired straight after `scrollIntoView` could be applied and removed
|
|
615
|
+
while the element was still travelling — visible to nobody. Budget up to ~600 ms of settle time
|
|
616
|
+
per action in a browser without `scrollend`; an element already in view costs ~100 ms.
|
|
617
|
+
|
|
618
|
+
**Flash versus focus.** `focusWithFlash` does what its name says: it moves keyboard focus. That is
|
|
619
|
+
rarely what you want just to *point at* something — it takes focus off the composer, can fire blur
|
|
620
|
+
validation on whatever the user was mid-edit in, and can close an open menu. Reach for `flash(el)`
|
|
621
|
+
to highlight, and keep `focusWithFlash(el)` for the case where the agent is about to type. Either
|
|
622
|
+
way you can be explicit with `focus`:
|
|
623
|
+
|
|
624
|
+
```js
|
|
625
|
+
await flash(el); // highlight, focus untouched
|
|
626
|
+
await focusWithFlash(el); // highlight and take focus
|
|
627
|
+
await focusWithFlash(el, { focus: false }); // same as flash(el)
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
`focusWithFlash` focuses with `preventScroll: true`, so it cannot fight a smooth scroll that is
|
|
631
|
+
still in flight.
|
|
632
|
+
|
|
448
633
|
The native-setter helpers ([`native_setter.ts`](src/dom/native_setter.ts)) — `setNativeValue` /
|
|
449
634
|
`setNativeChecked` — set a control through its native prototype setter so React-controlled inputs
|
|
450
635
|
register the change.
|
|
@@ -505,6 +690,71 @@ chat.toggleCollapsed();
|
|
|
505
690
|
chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
|
|
506
691
|
```
|
|
507
692
|
|
|
693
|
+
### Collapsing to the launcher
|
|
694
|
+
|
|
695
|
+
A collapsed widget shrinks to a round **floating launcher** in the corner it already occupies: the
|
|
696
|
+
panel scales down into it and fades, the launcher grows out of the same point, and clicking the
|
|
697
|
+
launcher reverses it. Only `transform` and `opacity` animate, so the motion is compositor-only and
|
|
698
|
+
never reflows your page. Two placements collapse to something else instead — `sidebar` slides out
|
|
699
|
+
to its [edge rail](#sidebar-placement), and `embedded` / `page` keep the header bar, since one is
|
|
700
|
+
laid out by your page and the other is a full-screen route.
|
|
701
|
+
|
|
702
|
+
The launcher's mark comes from the same seam as the header icon, most specific first: a slotted
|
|
703
|
+
`slot="launcher"` child, then `data-launcher-icon-url`, then `data-icon-url`, then the built-in
|
|
704
|
+
speech bubble.
|
|
705
|
+
|
|
706
|
+
```html
|
|
707
|
+
<ag-ui-chat endpoint="/agent/" data-launcher-icon-url="/mark.svg"></ag-ui-chat>
|
|
708
|
+
|
|
709
|
+
<!-- or any markup at all -->
|
|
710
|
+
<ag-ui-chat endpoint="/agent/">
|
|
711
|
+
<svg slot="launcher" width="26" height="26"><!-- ... --></svg>
|
|
712
|
+
</ag-ui-chat>
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
```css
|
|
716
|
+
ag-ui-chat {
|
|
717
|
+
--ag-ui-launcher-size: 56px;
|
|
718
|
+
--ag-ui-launcher-radius: 50%; /* 12px for a squircle */
|
|
719
|
+
--ag-ui-launcher-bg: #14532d; /* defaults to the header background */
|
|
720
|
+
--ag-ui-launcher-fg: #ffffff;
|
|
721
|
+
--ag-ui-launcher-icon-size: 26px;
|
|
722
|
+
--ag-ui-launcher-inset: auto 0 0 auto; /* which corner of the widget's box */
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
> **The collapsed host keeps its box.** Animating the element's own width and height would animate
|
|
727
|
+
> layout; instead the box stays put with `pointer-events: none`, and the launcher takes the clicks.
|
|
728
|
+
> A host measuring `getBoundingClientRect()` on a collapsed widget still sees the panel's
|
|
729
|
+
> footprint — nothing there paints or takes input.
|
|
730
|
+
|
|
731
|
+
#### The unread badge
|
|
732
|
+
|
|
733
|
+
A collapsed widget is the one state where an answer can arrive with nothing on screen to say so,
|
|
734
|
+
so the launcher carries a count of the answers that finished while it was closed (capped at `9+`).
|
|
735
|
+
Expanding — or `newChat()` — marks them read. It is the only affordance here that is **on by
|
|
736
|
+
default**; `data-unread-badge="false"` turns the badge off.
|
|
737
|
+
|
|
738
|
+
The count is also the launcher's accessible name (`Expand — 2 unread`, from the `expandUnread`
|
|
739
|
+
string), because a coloured dot says nothing to a screen reader.
|
|
740
|
+
|
|
741
|
+
```js
|
|
742
|
+
chat.unread; // 2
|
|
743
|
+
|
|
744
|
+
// Every change, whether or not the badge renders it — so a host that hides the
|
|
745
|
+
// badge can put the count in its own chrome.
|
|
746
|
+
chat.addEventListener("ag-ui-unread", (e) => setDockBadge(e.detail.unread));
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
```css
|
|
750
|
+
ag-ui-chat {
|
|
751
|
+
--ag-ui-badge-bg: #b91c1c; /* defaults to --ag-ui-danger */
|
|
752
|
+
--ag-ui-badge-fg: #ffffff;
|
|
753
|
+
--ag-ui-badge-size: 18px;
|
|
754
|
+
--ag-ui-badge-font-size: 11px;
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
508
758
|
---
|
|
509
759
|
|
|
510
760
|
## Tool-call display modes
|
|
@@ -919,6 +1169,11 @@ Client-side `accept` / size checks are an instant-feedback nicety — **the serv
|
|
|
919
1169
|
authoritative**. Refs persist on the message, so a restored conversation re-renders its chips.
|
|
920
1170
|
Without the attribute the affordance stays hidden and the chat is text-only.
|
|
921
1171
|
|
|
1172
|
+
The built-in handler sends the element's `headers` / `getHeaders` with every upload, and honours
|
|
1173
|
+
`credentials="include"` — with the caveat that it is an `XMLHttpRequest` (for real progress
|
|
1174
|
+
events), whose cookie switch is two-state: `include` turns it on, every other value leaves it off.
|
|
1175
|
+
See [Authenticating requests](#authenticating-requests).
|
|
1176
|
+
|
|
922
1177
|
**Swapping the upload transport.** The built-in multipart `POST` is just the default
|
|
923
1178
|
`uploadHandler`. Set your own to use a different transport — a resumable
|
|
924
1179
|
[`tus-js-client`](https://github.com/tus/tus-js-client) adapter, direct-to-S3 multipart, etc.
|
|
@@ -963,6 +1218,7 @@ re-export point. Internal modules import from leaf paths.
|
|
|
963
1218
|
| `MessageRole` | type | Role of a rendered chat message. |
|
|
964
1219
|
| `SubmitDetail` | type | `detail` shape of the submit event. |
|
|
965
1220
|
| `ToggleDetail` | type | `detail` shape of the `ag-ui-toggle` event (`{ collapsed }`). |
|
|
1221
|
+
| `UnreadDetail` | type | `detail` shape of the `ag-ui-unread` event (`{ unread }`). |
|
|
966
1222
|
|
|
967
1223
|
### AG-UI client & agent
|
|
968
1224
|
|
|
@@ -1038,10 +1294,10 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1038
1294
|
| `DEFAULT_UI_STRINGS` | const | The English defaults (the override floor). |
|
|
1039
1295
|
| `mergeUiStrings(overrides)` | function | Merge a partial override over the defaults. |
|
|
1040
1296
|
| `renderMarkdown(text)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
|
|
1041
|
-
| `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
|
|
1297
|
+
| `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `flash` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
|
|
1042
1298
|
| `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
|
|
1043
1299
|
| `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
|
|
1044
|
-
| `TypeOptions` / `HighlightClickOptions` / `PressOptions` / `SelectOptions` / `ToggleOptions` / `FlashOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
|
|
1300
|
+
| `TypeOptions` / `HighlightClickOptions` / `PressOptions` / `SelectOptions` / `ToggleOptions` / `FlashOptions` / `ScrollOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
|
|
1045
1301
|
|
|
1046
1302
|
### Constants
|
|
1047
1303
|
|
|
@@ -1050,6 +1306,7 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1050
1306
|
| `ELEMENT_TAG` | The registered tag name (`ag-ui-chat`). |
|
|
1051
1307
|
| `SUBMIT_EVENT` | The submit CustomEvent name. |
|
|
1052
1308
|
| `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
|
|
1309
|
+
| `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
|
|
1053
1310
|
| `MESSAGE_ROLE` | Message role constants. |
|
|
1054
1311
|
| `TOOL_CALL_STATUS` | Tool-call card status constants. |
|
|
1055
1312
|
| `TOOL_DISPLAY` | Tool-call display-mode constants (`minimal` / `compact` / `full`). |
|
|
@@ -1063,8 +1320,24 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1063
1320
|
## Theming, density, and placement
|
|
1064
1321
|
|
|
1065
1322
|
The chat shell is styled inside its Shadow DOM and exposes a large set of `--ag-ui-*` CSS custom
|
|
1066
|
-
properties
|
|
1067
|
-
|
|
1323
|
+
properties (colors, status, surface, spacing, layout), so you theme it from outside without
|
|
1324
|
+
piercing the shadow boundary. Set them anywhere above the element and they inherit in — on the
|
|
1325
|
+
element itself, on a wrapper, or on `:root` for a whole page. The closest declaration wins, the
|
|
1326
|
+
way any inherited CSS property behaves:
|
|
1327
|
+
|
|
1328
|
+
```css
|
|
1329
|
+
/* All three work. The most specific one that applies wins. */
|
|
1330
|
+
:root { --ag-ui-accent: #4f46e5; } /* whole page */
|
|
1331
|
+
.chat-dock { --ag-ui-accent: #0f766e; } /* one region */
|
|
1332
|
+
ag-ui-chat { --ag-ui-accent: #b91c1c; } /* one widget */
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
> Until 0.20.x the defaults were declared on `:host`, which set them *on the element* — and an
|
|
1336
|
+
> element's own value beats anything inherited from an ancestor, so only the `ag-ui-chat { … }`
|
|
1337
|
+
> form did anything and the wrapper form silently did nothing. The defaults now sit behind an
|
|
1338
|
+
> internal alias, so all three forms work.
|
|
1339
|
+
|
|
1340
|
+
A few of the knobs:
|
|
1068
1341
|
|
|
1069
1342
|
```css
|
|
1070
1343
|
ag-ui-chat {
|
|
@@ -1081,6 +1354,45 @@ ag-ui-chat {
|
|
|
1081
1354
|
}
|
|
1082
1355
|
```
|
|
1083
1356
|
|
|
1357
|
+
### Where to put the variables
|
|
1358
|
+
|
|
1359
|
+
There is one vocabulary — the `--ag-ui-*` names above — and it works from any ancestor. What
|
|
1360
|
+
differs is only *which* declaration wins, and that is ordinary CSS inheritance:
|
|
1361
|
+
|
|
1362
|
+
```css
|
|
1363
|
+
/* A whole page or design-system scope. */
|
|
1364
|
+
:root { --ag-ui-accent: var(--brand-600); --ag-ui-radius: 4px; }
|
|
1365
|
+
|
|
1366
|
+
/* One region — the widget picks this up through the wrapper. */
|
|
1367
|
+
aside.support-dock { --ag-ui-accent: #0f766e; }
|
|
1368
|
+
|
|
1369
|
+
/* One widget. Beats both of the above, because it targets the element. */
|
|
1370
|
+
ag-ui-chat#support { --ag-ui-accent: #b91c1c; }
|
|
1371
|
+
|
|
1372
|
+
/* Set at runtime with el.style.setProperty(...) — an inline style beats all of the above. */
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
Two that do not work:
|
|
1376
|
+
|
|
1377
|
+
```css
|
|
1378
|
+
/* ::part() reaches structural elements, not variables — a custom property set
|
|
1379
|
+
here applies to that part's own subtree, not to the whole shell. */
|
|
1380
|
+
ag-ui-chat::part(panel) { --ag-ui-accent: #b91c1c; }
|
|
1381
|
+
|
|
1382
|
+
/* The internal --_* aliases are private and unversioned; they are renamed
|
|
1383
|
+
without notice. Always set the public --ag-ui-* name. */
|
|
1384
|
+
ag-ui-chat { --_accent: #b91c1c; }
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
The preset attributes below sit *underneath* anything you declare: a `theme="dark"` widget still
|
|
1388
|
+
honours an explicit `--ag-ui-bg` from your CSS, so you can adopt a preset and correct one token
|
|
1389
|
+
rather than re-declaring the whole palette.
|
|
1390
|
+
|
|
1391
|
+
`--ag-ui-accent` reaches further than the widget: it also colours the rings the DOM driver draws
|
|
1392
|
+
on **your** page. Each primitive reads it from the computed style of the element it is about to
|
|
1393
|
+
touch, so setting it on `:root` (or on any ancestor of the elements the agent drives) themes the
|
|
1394
|
+
highlights too. Without it they fall back to the package indigo.
|
|
1395
|
+
|
|
1084
1396
|
For the common cases there are three CSS-reactive **preset attributes** (no JS API), so you don't
|
|
1085
1397
|
have to hand-tune the variables:
|
|
1086
1398
|
|
|
@@ -1123,20 +1435,40 @@ Available parts: `panel`, `header`, `title`, `icon`, `header-controls`, `header-
|
|
|
1123
1435
|
`-args` / `-actions` / `-button` / `-cancel` / `-confirm`),
|
|
1124
1436
|
`approval` (plus `approval-body` / `-actions` / `-button` / `-approve` / `-deny`),
|
|
1125
1437
|
`question` (plus `question-body` / `-options` / `-choice` / `-choice-text` / `-radio` / `-input` /
|
|
1126
|
-
`-actions` / `-button`), `composer
|
|
1438
|
+
`-actions` / `-button`), `composer` (plus `composer-surface` / `composer-tools`), `input`, `send`,
|
|
1127
1439
|
`attach-button`, `voice-button`,
|
|
1128
1440
|
the attachment chips — `attachment-tray` and `attachment-chips` (the read-only chips on sent
|
|
1129
1441
|
bubbles) with the shared chip parts `attachment-chip` (plus `-icon` / `-name` / `-size` / `-bar` /
|
|
1130
1442
|
`-bar-fill` / `-retry` / `-remove`),
|
|
1131
1443
|
the skills UI (`skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`,
|
|
1132
1444
|
`skill-item-desc`, and the missing-placeholder `skill-hint`),
|
|
1133
|
-
`launcher`, `launcher-icon`, and the drawer parts
|
|
1445
|
+
`launcher`, `launcher-icon`, `launcher-badge`, and the drawer parts
|
|
1134
1446
|
(`drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`,
|
|
1135
1447
|
`drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`,
|
|
1136
1448
|
`drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`,
|
|
1137
1449
|
`drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`,
|
|
1138
1450
|
`drawer-confirm-yes`, `drawer-confirm-no`).
|
|
1139
1451
|
|
|
1452
|
+
> **Hiding `::part(header)` hides the controls inside it.** The history, checkpoints, new-chat,
|
|
1453
|
+
> theme and collapse buttons are all children of the header, so a host that renders its own title
|
|
1454
|
+
> bar and does `ag-ui-chat::part(header) { display: none }` loses thread switching entirely. Every
|
|
1455
|
+
> one of them has an imperative equivalent, so your own chrome can drive them:
|
|
1456
|
+
>
|
|
1457
|
+
> | Control | Method |
|
|
1458
|
+
> | --- | --- |
|
|
1459
|
+
> | History drawer | `chat.openThreads()` |
|
|
1460
|
+
> | Checkpoints panel | `chat.openCheckpoints()` |
|
|
1461
|
+
> | New chat | `chat.newChat()` |
|
|
1462
|
+
> | Collapse | `chat.toggleCollapsed()` / `chat.setCollapsed(bool)` |
|
|
1463
|
+
> | Theme toggle | `chat.toggleTheme()` |
|
|
1464
|
+
>
|
|
1465
|
+
> ```js
|
|
1466
|
+
> myHeaderButton.onclick = () => chat.openThreads();
|
|
1467
|
+
> ```
|
|
1468
|
+
>
|
|
1469
|
+
> The built-in buttons call exactly these methods, so the two routes cannot drift. If you only want
|
|
1470
|
+
> to restyle the header, prefer `::part(header)` styling or the `header-actions` slot over hiding it.
|
|
1471
|
+
|
|
1140
1472
|
Coarse **slots** let you replace whole regions with your own markup (project light-DOM children
|
|
1141
1473
|
with a matching `slot=`):
|
|
1142
1474
|
|
|
@@ -1146,7 +1478,10 @@ with a matching `slot=`):
|
|
|
1146
1478
|
| `header-actions` | Extra controls between the title and the built-in buttons. |
|
|
1147
1479
|
| `empty` | The empty-state shown before any message. |
|
|
1148
1480
|
| `footer` | Below the composer. |
|
|
1149
|
-
| `launcher` | The collapsed sidebar rail
|
|
1481
|
+
| `launcher` | The collapsed widget's mark — the floating launcher, or the sidebar rail. |
|
|
1482
|
+
| `icon-send` / `icon-stop` | The composer button's two glyphs (idle and mid-run). |
|
|
1483
|
+
| `icon-attach` / `icon-voice` | The paperclip and mic glyphs. |
|
|
1484
|
+
| `icon-history` / `icon-checkpoints` / `icon-new` / `icon-collapse` | The header controls' glyphs. |
|
|
1150
1485
|
|
|
1151
1486
|
```html
|
|
1152
1487
|
<ag-ui-chat endpoint="/agent/">
|
|
@@ -1160,7 +1495,7 @@ with a matching `slot=`):
|
|
|
1160
1495
|
Give the header a brand icon with either the `icon` slot (any markup) or the `data-icon-url`
|
|
1161
1496
|
convenience attribute (an `<img>`); the slot wins when both are set, and with neither the header
|
|
1162
1497
|
stays icon-less. The same icon seam feeds the collapsed sidebar rail. Size it via
|
|
1163
|
-
`--ag-ui-icon-size` (default `22px`).
|
|
1498
|
+
`--ag-ui-icon-size` (default `22px`) and round it with `--ag-ui-icon-radius` (default `4px`).
|
|
1164
1499
|
|
|
1165
1500
|
```html
|
|
1166
1501
|
<ag-ui-chat endpoint="/agent/" data-icon-url="/logo.png"></ag-ui-chat>
|
|
@@ -1169,9 +1504,11 @@ stays icon-less. The same icon seam feeds the collapsed sidebar rail. Size it vi
|
|
|
1169
1504
|
### Sidebar placement
|
|
1170
1505
|
|
|
1171
1506
|
`placement="sidebar"` is a full-height **docked** panel that slides open/closed and collapses to a
|
|
1172
|
-
slim **icon rail**
|
|
1173
|
-
|
|
1174
|
-
|
|
1507
|
+
slim **icon rail** rather than the [floating launcher](#collapsing-to-the-launcher) — the same
|
|
1508
|
+
element, shaped by the placement. It docks right by default; `data-side="left"` docks it left. The
|
|
1509
|
+
panel slides out through the edge it docks against. Collapse state reuses the `collapsed` attribute
|
|
1510
|
+
(persisted per-tab), and the rail carries `aria-expanded`. The slide honours
|
|
1511
|
+
`prefers-reduced-motion`.
|
|
1175
1512
|
|
|
1176
1513
|
```html
|
|
1177
1514
|
<ag-ui-chat endpoint="/agent/" placement="sidebar" data-side="left"></ag-ui-chat>
|
|
@@ -1215,9 +1552,48 @@ web component handles the `REASONING_*` event family (and the deprecated `THINKI
|
|
|
1215
1552
|
`@ag-ui/client` maps onto it), so no client config is needed — the thoughts appear whenever the
|
|
1216
1553
|
server forwards reasoning.
|
|
1217
1554
|
|
|
1555
|
+
### The composer
|
|
1556
|
+
|
|
1557
|
+
The composer is one bordered surface (part `composer-surface`) that owns the border and the focus
|
|
1558
|
+
ring: the field sits on top and grows with what is typed until it hits its ceiling and scrolls,
|
|
1559
|
+
and a tool row (part `composer-tools`) sits underneath with the paperclip and mic as quiet icon
|
|
1560
|
+
buttons on the left and a circular **Send** on the right. Send is icon-only — its accessible name
|
|
1561
|
+
still comes from the `send` / `stop` [strings](#internationalization-i18n) — and it becomes the
|
|
1562
|
+
Stop control mid-run by swapping its glyph, so nothing moves when a run starts.
|
|
1563
|
+
|
|
1564
|
+
```css
|
|
1565
|
+
ag-ui-chat {
|
|
1566
|
+
--ag-ui-composer-radius: 14px;
|
|
1567
|
+
--ag-ui-composer-max-height: 40vh; /* where the growing field starts scrolling */
|
|
1568
|
+
--ag-ui-tool-btn-size: 30px; /* the paperclip / mic hit targets */
|
|
1569
|
+
--ag-ui-send-size: 30px; /* the send circle */
|
|
1570
|
+
--ag-ui-glyph-size: 18px;
|
|
1571
|
+
--ag-ui-glyph-stroke: 1.75;
|
|
1572
|
+
}
|
|
1573
|
+
```
|
|
1574
|
+
|
|
1575
|
+
Every glyph is a slot with the built-in mark as its fallback (`icon-send`, `icon-stop`,
|
|
1576
|
+
`icon-attach`, `icon-voice`), so projecting your own icon set never means restyling a character.
|
|
1577
|
+
|
|
1578
|
+
### Motion
|
|
1579
|
+
|
|
1580
|
+
One duration and two curves drive every collapse, expand and slide-over, so the whole widget
|
|
1581
|
+
settles as one thing:
|
|
1582
|
+
|
|
1583
|
+
```css
|
|
1584
|
+
ag-ui-chat {
|
|
1585
|
+
--ag-ui-motion: 0.28s;
|
|
1586
|
+
--ag-ui-ease: cubic-bezier(0.32, 0.72, 0, 1); /* the settle */
|
|
1587
|
+
--ag-ui-ease-pop: cubic-bezier(0.34, 1.36, 0.64, 1); /* the arrival, with overshoot */
|
|
1588
|
+
}
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
Under `prefers-reduced-motion: reduce` the duration collapses to a single frame: states still
|
|
1592
|
+
change, nothing travels. Set `--ag-ui-motion: 0s` to switch the animation off outright.
|
|
1593
|
+
|
|
1218
1594
|
### Voice input
|
|
1219
1595
|
|
|
1220
|
-
Set `data-transcribe-url` (django-ag-ui's `TranscribeView`) to reveal a
|
|
1596
|
+
Set `data-transcribe-url` (django-ag-ui's `TranscribeView`) to reveal a mic button in the
|
|
1221
1597
|
composer (part `voice-button`). Click it to record via `MediaRecorder`, click again to stop — the
|
|
1222
1598
|
clip is POSTed to the endpoint and the returned transcript is dropped into the textarea. Swap the
|
|
1223
1599
|
transport with a custom `transcribeHandler` — `(audio: Blob) => Promise<string>` — to use a
|