@artooi/ag-ui-web-component 0.38.0 → 0.40.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 +515 -1
- package/README.md +232 -56
- package/dist/ag-ui-web-component.bundle.js +370 -139
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +17 -14
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/activity_registry.d.ts +46 -0
- package/dist/core/activity_registry.d.ts.map +1 -0
- package/dist/core/ag_ui_chat.d.ts +23 -10
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +28 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/client_seed.d.ts +27 -0
- package/dist/core/client_seed.d.ts.map +1 -0
- package/dist/core/read_max_tool_rounds.d.ts +9 -0
- package/dist/core/read_max_tool_rounds.d.ts.map +1 -0
- package/dist/core/run_handlers.d.ts +80 -0
- package/dist/core/run_handlers.d.ts.map +1 -0
- package/dist/core/storage_scope.d.ts +110 -0
- package/dist/core/storage_scope.d.ts.map +1 -0
- package/dist/core/tool_outcome.d.ts +2 -3
- package/dist/core/tool_outcome.d.ts.map +1 -1
- package/dist/core/utils.d.ts +46 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.js +16268 -14747
- package/dist/index.js.map +4 -4
- package/dist/skills/skill_catalog.d.ts +67 -0
- package/dist/skills/skill_catalog.d.ts.map +1 -0
- package/dist/skills/skill_name_from.d.ts +12 -0
- package/dist/skills/skill_name_from.d.ts.map +1 -0
- package/dist/tools/tool_catalog.d.ts +79 -0
- package/dist/tools/tool_catalog.d.ts.map +1 -0
- package/dist/tools/tool_dispatch.d.ts +104 -0
- package/dist/tools/tool_dispatch.d.ts.map +1 -0
- package/dist/ui/composer/attachment_tray.d.ts.map +1 -1
- package/dist/ui/composer/auto_grow.d.ts +10 -0
- package/dist/ui/composer/auto_grow.d.ts.map +1 -0
- package/dist/ui/composer/composer_attachments.d.ts +57 -0
- package/dist/ui/composer/composer_attachments.d.ts.map +1 -0
- package/dist/ui/composer/composer_voice.d.ts +48 -0
- package/dist/ui/composer/composer_voice.d.ts.map +1 -0
- package/dist/ui/composer/voice_input.d.ts.map +1 -1
- package/dist/ui/excerpts/transcript_quote_offer.d.ts +57 -0
- package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -0
- package/dist/ui/fill_ui_string.d.ts +32 -0
- package/dist/ui/fill_ui_string.d.ts.map +1 -0
- package/dist/ui/history/conversation_history.d.ts +203 -0
- package/dist/ui/history/conversation_history.d.ts.map +1 -0
- package/dist/ui/history/relative_time.d.ts.map +1 -1
- package/dist/ui/history/thread_drawer.d.ts +0 -14
- package/dist/ui/history/thread_drawer.d.ts.map +1 -1
- package/dist/ui/interrupts/confirmation_card.d.ts.map +1 -1
- package/dist/ui/interrupts/pending_decision.d.ts +23 -0
- package/dist/ui/interrupts/pending_decision.d.ts.map +1 -0
- package/dist/ui/placement/is_collapsible_placement.d.ts +18 -0
- package/dist/ui/placement/is_collapsible_placement.d.ts.map +1 -0
- package/dist/ui/placement/is_draggable_placement.d.ts +9 -0
- package/dist/ui/placement/is_draggable_placement.d.ts.map +1 -0
- package/dist/ui/placement/launcher_drag.d.ts +6 -0
- package/dist/ui/placement/launcher_drag.d.ts.map +1 -1
- package/dist/ui/placement/panel_placement.d.ts +119 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -0
- package/dist/ui/progress/run_announcer.d.ts +41 -0
- package/dist/ui/progress/run_announcer.d.ts.map +1 -0
- package/dist/ui/progress/subagent_progress.d.ts +58 -0
- package/dist/ui/progress/subagent_progress.d.ts.map +1 -0
- package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
- package/dist/ui/shell/adopt_styles.d.ts +23 -0
- package/dist/ui/shell/adopt_styles.d.ts.map +1 -0
- package/dist/ui/shell/glyph_slot.d.ts +7 -0
- package/dist/ui/shell/glyph_slot.d.ts.map +1 -0
- package/dist/ui/shell/header_button.d.ts +10 -0
- package/dist/ui/shell/header_button.d.ts.map +1 -0
- package/dist/ui/shell/icon_element.d.ts +12 -0
- package/dist/ui/shell/icon_element.d.ts.map +1 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts +10 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts.map +1 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts +8 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/transcript/answer_actions.d.ts +39 -0
- package/dist/ui/transcript/answer_actions.d.ts.map +1 -0
- package/dist/ui/transcript/answer_stream.d.ts +56 -0
- package/dist/ui/transcript/answer_stream.d.ts.map +1 -0
- package/dist/ui/transcript/render_or_warn.d.ts +16 -0
- package/dist/ui/transcript/render_or_warn.d.ts.map +1 -0
- package/dist/ui/transcript/starter_chips.d.ts +21 -0
- package/dist/ui/transcript/starter_chips.d.ts.map +1 -0
- package/dist/ui/transcript/transcript.d.ts +171 -0
- package/dist/ui/transcript/transcript.d.ts.map +1 -0
- package/dist/ui/ui_strings.d.ts +29 -1
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +17 -15
- package/src/core/activity_registry.ts +133 -0
- package/src/core/ag_ui_chat.ts +945 -4419
- package/src/core/agui_client.ts +172 -16
- package/src/core/client_seed.ts +27 -0
- package/src/core/read_max_tool_rounds.ts +13 -0
- package/src/core/remote_conversation_store.ts +1 -1
- package/src/core/run_handlers.ts +501 -0
- package/src/core/storage_scope.ts +279 -0
- package/src/core/tool_outcome.ts +5 -3
- package/src/core/utils.ts +109 -0
- package/src/skills/skill_catalog.ts +166 -0
- package/src/skills/skill_name_from.ts +19 -0
- package/src/tools/tool_catalog.ts +354 -0
- package/src/tools/tool_dispatch.ts +521 -0
- package/src/ui/composer/attachment_tray.ts +6 -5
- package/src/ui/composer/auto_grow.ts +12 -0
- package/src/ui/composer/composer_attachments.ts +324 -0
- package/src/ui/composer/composer_voice.ts +93 -0
- package/src/ui/composer/voice_input.ts +4 -4
- package/src/ui/excerpts/transcript_quote_offer.ts +198 -0
- package/src/ui/fill_ui_string.ts +41 -0
- package/src/ui/history/conversation_history.ts +699 -0
- package/src/ui/history/relative_time.ts +5 -4
- package/src/ui/history/thread_drawer.ts +3 -50
- package/src/ui/interrupts/confirmation_card.ts +4 -2
- package/src/ui/interrupts/pending_decision.ts +34 -0
- package/src/ui/placement/is_collapsible_placement.ts +19 -0
- package/src/ui/placement/is_draggable_placement.ts +23 -0
- package/src/ui/placement/launcher_drag.ts +104 -89
- package/src/ui/placement/panel_placement.ts +1130 -0
- package/src/ui/progress/run_announcer.ts +67 -0
- package/src/ui/progress/subagent_progress.ts +201 -0
- package/src/ui/progress/tool_call_card.ts +2 -0
- package/src/ui/shell/adopt_styles.ts +28 -0
- package/src/ui/shell/glyph_slot.ts +12 -0
- package/src/ui/shell/header_button.ts +21 -0
- package/src/ui/shell/icon_element.ts +33 -0
- package/src/ui/shell/is_unread_badge_enabled.ts +11 -0
- package/src/ui/shell/read_launcher_icon_url.ts +9 -0
- package/src/ui/styles.ts +301 -70
- package/src/ui/transcript/answer_actions.ts +139 -0
- package/src/ui/transcript/answer_stream.ts +123 -0
- package/src/ui/transcript/render_or_warn.ts +22 -0
- package/src/ui/transcript/starter_chips.ts +42 -0
- package/src/ui/transcript/transcript.ts +429 -0
- package/src/ui/ui_strings.ts +38 -1
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -184,6 +184,7 @@ another origin, add `credentials="include"` too; see
|
|
|
184
184
|
| `data-skills-url` | — | URL of a JSON skill catalog (fetched with the element's headers and cookie policy). |
|
|
185
185
|
| `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. |
|
|
186
186
|
| `user-key` | `userKey` | Who the stored conversation belongs to — any string identifying the signed-in principal. Joins the storage namespace, and **changing it purges what the previous principal left behind**. Live (not connect-time): a logout is the host's to announce. See [Who the stored conversation belongs to](#who-the-stored-conversation-belongs-to-user-key). |
|
|
187
|
+
| `user-name` | `userName` | The signed-in user's display name, for the [greeting](#the-greeting-on-an-empty-page) an empty conversation shows under `placement="page"`. Presentation only: unlike `user-key` it scopes nothing and is never sent. Absent or blank gives the nameless greeting. Live. |
|
|
187
188
|
| `data-threads-url` | — | URL of a server thread index (django-ag-ui's `ThreadsView`); enables durable, cross-device chat history. |
|
|
188
189
|
| `data-threads-cache` | — | **On by default.** `="false"` stops mirroring message bodies into `sessionStorage` when `data-threads-url` is set, for a deployment that put history on the server so transcripts stay off the client. Only meaningful alongside `data-threads-url`. |
|
|
189
190
|
| `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). |
|
|
@@ -206,6 +207,8 @@ another origin, add `credentials="include"` too; see
|
|
|
206
207
|
| `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded`). Persisted per tab. `placement="page"` has no collapsed state and ignores it. |
|
|
207
208
|
| `data-dragging` | — | **Written by the element, not by you.** Stamped on whichever handle a gesture is currently using, so the styles can react and so the element knows not to re-place the widget under a drag in progress. Cleared on `pointerup` and on `pointercancel`. |
|
|
208
209
|
| `data-expand-corner` | — | **Written by the element, not by you.** It stamps the corner a dragged or agent-moved panel opens from, so the collapse animation starts where the panel actually is. Listed because the element reads its own stamp back; setting it yourself is overwritten on the next move. |
|
|
210
|
+
| `data-greeting` | — | CSS-only: `off` turns the [greeting layout](#the-greeting-on-an-empty-page) off under `placement="page"`, where it is on by default. Any other value turns it on under `placement="embedded"`. No other placement has it. |
|
|
211
|
+
| `data-empty` | — | **Written by the element, not by you.** Present while the conversation has nothing in it, which is when the greeting shows. Public as a styling hook, so the chrome around a full-page chat can react to the same state, e.g. `ag-ui-chat[data-empty] ~ .page-footer { display: none }`. |
|
|
209
212
|
| `data-small-viewport` | — | CSS-only: `off` keeps the desktop layout at every width, opting out of the [small-viewport override](#small-viewports). Everything that override sets is a token you can re-state; its trigger is a media query, which is the one thing you cannot. |
|
|
210
213
|
| `data-paste-attach` | — | When to turn a long text paste into an attachment instead of composer text: absent for the 5000-character default, `off` to never, or a positive number of characters. Only acts where `data-attachments-url` (or a custom `uploadHandler`) gives it somewhere to go. |
|
|
211
214
|
| `data-starters` | — | JSON array of prompts offered on an empty transcript, e.g. `'["Summarise this page"]'`. Fallback content for `slot="empty"`, so slotting your own replaces them. Shares the four-prompt and 120-character limits with the suggestion chips a run pushes. Read once at connect. |
|
|
@@ -237,7 +240,7 @@ neither camp is `data-launcher-icon-url`: it is read while the element connects,
|
|
|
237
240
|
below, but is not observed, so a late write is inert and says nothing.
|
|
238
241
|
|
|
239
242
|
**Live attributes.** Written at any time, before or after the element connects, and acted on
|
|
240
|
-
either way: `title-text`, `placement`, `credentials`, `user-key`.
|
|
243
|
+
either way: `title-text`, `placement`, `credentials`, `user-key`, `user-name`.
|
|
241
244
|
|
|
242
245
|
**Connect-time attributes.** Read once, while the element connects, to decide what chrome exists at
|
|
243
246
|
all — the tray, the mic, the skills menu, the header mark. Writing one afterwards has **no effect**;
|
|
@@ -250,13 +253,19 @@ The list: `data-attachments-url`, `data-attachment-accept`, `data-attachment-max
|
|
|
250
253
|
`data-skills-url`, `data-skills`, `data-prompt-chips`, `data-slash-commands`, `data-theme-toggle`,
|
|
251
254
|
`data-strings`, `data-icon-url`.
|
|
252
255
|
|
|
256
|
+
What re-inserting costs: the chrome is built again from the attributes as they stand, a run in
|
|
257
|
+
progress is cancelled, and the conversation is redrawn from its stored history — the same rebuild
|
|
258
|
+
`reload()` performs, and the reason that method exists for a host that can only configure the
|
|
259
|
+
element after the fact.
|
|
260
|
+
|
|
253
261
|
**Properties** (JS only, not attributes): `headers`, `getHeaders`, `trustedOrigins`, `allowImages`,
|
|
254
262
|
`autoConfirm`, `confirmPredicate`, `askUser`, `askUserRenderer`, `approvalRenderer`,
|
|
255
263
|
`approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
|
|
256
264
|
`autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`, `navigationResult`,
|
|
257
265
|
`skillContext`, `toolSummaries`, `formatToolPayload`, `formatRelativeTime`, `strings`,
|
|
258
266
|
`resolvePageTarget`, `sharedState`, plus the read-only `unread` and `unhandledActivityTypes`, and
|
|
259
|
-
the attribute mirrors `endpoint` / `userKey` / `toolDisplay` / `collapsed` /
|
|
267
|
+
the attribute mirrors `endpoint` / `userKey` / `userName` / `toolDisplay` / `collapsed` /
|
|
268
|
+
`credentials`.
|
|
260
269
|
|
|
261
270
|
`headers` and `getHeaders` authenticate **every** request the element makes, not only the agent
|
|
262
271
|
run; `getHeaders` is the one to use for a credential that rotates. See
|
|
@@ -296,10 +305,13 @@ the `copyCode` / `copied` / `copyFailed` strings.
|
|
|
296
305
|
|
|
297
306
|
`sendMessage(content, attachments?)` sends as if the user had typed it — user bubble,
|
|
298
307
|
`ag-ui-submit` event, run started. Use it for an "Ask about this order" button, a command
|
|
299
|
-
palette, or a composer of your own replacing the built-in one. It no-ops
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
308
|
+
palette, or a composer of your own replacing the built-in one. It no-ops for an entirely
|
|
309
|
+
empty message, and while a run — or a checkpoint continuation picked from the panel — is in
|
|
310
|
+
flight; a continuation counts from the pick, not from its first event, so the gap where two
|
|
311
|
+
runs could start against one conversation is closed. Unlike the built-in Send it does **not**
|
|
312
|
+
queue, so your composer keeps what it tried to send, and it does **not** consult the
|
|
313
|
+
attachment tray: what you pass is what is sent, so your composer stays in charge of its own
|
|
314
|
+
state.
|
|
303
315
|
|
|
304
316
|
`attachFile(file)` queues a file into the tray exactly as the picker and drag-and-drop do, with
|
|
305
317
|
the same validation and progress chip. It returns `false` when uploads are not configured
|
|
@@ -566,9 +578,10 @@ pydantic-ai's own `ToolReturnPart` vocabulary — read the values off `TOOL_OUTC
|
|
|
566
578
|
| `success` | done | The same thing, stated. |
|
|
567
579
|
| `failed` | error | The call ran and failed. |
|
|
568
580
|
| `denied` | declined | A person or a guard refused it, so it never ran. |
|
|
581
|
+
| `interrupted` | not finished | It produced no result because the run ended first, and nobody refused it. |
|
|
569
582
|
|
|
570
583
|
**Absent is a success, and anything unrecognised is too** — including a value from a later protocol
|
|
571
|
-
version
|
|
584
|
+
version. A card is a claim about what happened, and refusing to
|
|
572
585
|
recognise a word is not grounds for claiming failure. So no server has to change to keep the
|
|
573
586
|
rendering it has today, and a server that adds the field gets the truth on screen instead of a
|
|
574
587
|
green card with a refusal folded inside it.
|
|
@@ -587,10 +600,23 @@ Either way the outcome is **persisted beside the tool message**, so a reload rep
|
|
|
587
600
|
settled rather than as a plain result. See
|
|
588
601
|
[MPA durability](#mpa-durability-surviving-full-page-reloads).
|
|
589
602
|
|
|
603
|
+
**Every request carries a result for every tool call in it.** Several model providers reject a
|
|
604
|
+
turn holding a tool call with no result, and a run can leave one open in more ways than one: Stop
|
|
605
|
+
while the stream is still arriving, a round that ends on `RUN_ERROR`, a call naming a tool nothing
|
|
606
|
+
here owns, a server that never streamed the result. So before each request the client answers
|
|
607
|
+
every call still open with the `callNotFinished` string (by default `Not finished: the run ended
|
|
608
|
+
or moved on before this tool call returned a result.`) and the outcome `interrupted`, placed at the
|
|
609
|
+
end of the round that made the call, and the card settles to **not finished** in the same words.
|
|
610
|
+
That result blames nobody, because nobody declined: `declinedAction` is kept for a call a person
|
|
611
|
+
was asked about and turned down — a confirmation card or an approval they declined, or one Stop
|
|
612
|
+
closed while it was open. The calls a resumed approval is answering are left alone, because the
|
|
613
|
+
resume is their answer.
|
|
614
|
+
|
|
590
615
|
The catalog a run advertises is also the set that run can execute. Override `getTools` to scope
|
|
591
616
|
what a page offers — say, exposing `delete_record` only where deleting makes sense — and a call
|
|
592
617
|
naming a tool you withheld is treated exactly as a call naming a tool you never registered: no
|
|
593
|
-
handler runs,
|
|
618
|
+
handler runs, the card settles as not finished, and the next request answers the call in those
|
|
619
|
+
words. Withholding is per run, so the
|
|
594
620
|
mount-wide registry can stay complete. Hosts that leave `getTools` alone advertise the built-ins
|
|
595
621
|
plus everything registered, which is precisely what dispatch could reach anyway.
|
|
596
622
|
|
|
@@ -606,16 +632,29 @@ AG-UI has no server-side cancel route: cancelling **aborts the streaming request
|
|
|
606
632
|
`onPersist`, so a reload shows the truncated exchange. A muted **"⏹ Stopped"** note is appended
|
|
607
633
|
(`.stopped-note`) — a deliberate stop is not an error, so no bubble.
|
|
608
634
|
- The run loop stops: tool calls collected before the abort are **not executed**, and no further
|
|
609
|
-
round starts. A frontend tool handler already running completes
|
|
610
|
-
a re-run.
|
|
635
|
+
round starts. A frontend tool handler already running completes and its result is kept, but it
|
|
636
|
+
doesn't trigger a re-run, and the calls after it in the same round do not start. Each call that
|
|
637
|
+
did not run settles as **not finished**, and the next request answers
|
|
638
|
+
it in those words rather than sending it without a result.
|
|
611
639
|
- An **open confirmation card is declined** (`data-resolved="declined"`) — cancelling the run
|
|
612
|
-
answers the pending question. Likewise an open **approval card** is denied and
|
|
613
|
-
|
|
640
|
+
answers the pending question. Likewise an open **approval card** is denied, and the next request
|
|
641
|
+
carries that decline as the call's result; a call already approved when Stop lands was declined
|
|
642
|
+
by nobody, so it is answered as not finished instead. An open **question card** (`ask_user`)
|
|
643
|
+
resolves with an empty answer. Reloading the page while a card is open does not decline it,
|
|
644
|
+
because nobody answered: the call comes back not finished; see
|
|
645
|
+
[MPA durability](#mpa-durability-surviving-full-page-reloads).
|
|
614
646
|
- The new `onCancelled()` handler fires instead of `onError()`; `onSettled()` still follows
|
|
615
647
|
(the terminal-rest guarantee), returning the button to **Send**.
|
|
616
648
|
|
|
617
649
|
`cancel()` with no run in flight is a safe no-op. `newChat()` cancels any in-flight run before
|
|
618
|
-
discarding the client.
|
|
650
|
+
discarding the client, and so do switching conversations, `reload()` and removing the element. A
|
|
651
|
+
[checkpoint continuation](#resuming-a-run) is the run in flight while it lasts, so Stop and all of
|
|
652
|
+
these end it too.
|
|
653
|
+
|
|
654
|
+
A cancelled run ends a moment later, once its request has closed or a running tool handler has
|
|
655
|
+
returned. What it does then stays with the conversation it belonged to: its truncated exchange is
|
|
656
|
+
saved to that thread, `ag-ui-run-finished` still reports the tools it ran, and nothing reaches the
|
|
657
|
+
transcript or the Stop button of the conversation that replaced it.
|
|
619
658
|
|
|
620
659
|
### Registering tools
|
|
621
660
|
|
|
@@ -639,8 +678,9 @@ chat.registerTool({
|
|
|
639
678
|
Registering a name twice replaces the earlier handler rather than throwing, so a re-fired
|
|
640
679
|
host ref or React StrictMode's double-invoke is harmless -- but two different tools sharing
|
|
641
680
|
a name means the second silently wins. Each `<ag-ui-chat>` element owns its own
|
|
642
|
-
registry, AG-UI client, and Shadow DOM, so **multiple instances on one page never interfere
|
|
643
|
-
|
|
681
|
+
registry, AG-UI client, and Shadow DOM, so **multiple instances on one page never interfere**.
|
|
682
|
+
The one record they share is which storage namespace each has claimed, and it exists to keep two
|
|
683
|
+
of them out of each other's conversation.
|
|
644
684
|
|
|
645
685
|
**A handler's thrown message leaves the browser.** If a handler rejects, its `Error.message` is
|
|
646
686
|
posted back as that call's tool result: into the conversation, on to the AG-UI endpoint, persisted
|
|
@@ -668,7 +708,10 @@ Whether a call is gated is decided in this order:
|
|
|
668
708
|
1. If `chat.autoConfirm === true`, the call **never** prompts (an "autopilot" toggle).
|
|
669
709
|
2. Else if `chat.confirmPredicate` is set, its boolean return is authoritative — given the tool
|
|
670
710
|
name + parsed args it decides per-call (so one tool can be instant for some args and confirmed
|
|
671
|
-
for others, which a static flag can't express).
|
|
711
|
+
for others, which a static flag can't express). **A predicate that throws or rejects refuses
|
|
712
|
+
the call**: no card, the handler does not run, the tool card settles as declined, and the agent
|
|
713
|
+
gets the `confirmCheckFailed` string as the result and carries on, as after a decline. The
|
|
714
|
+
error goes to the console, never to the endpoint.
|
|
672
715
|
3. Else if the user has waived this tool name for the session, the call runs.
|
|
673
716
|
4. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
|
|
674
717
|
which reads the `x-destructive` JSON-Schema flag.
|
|
@@ -688,7 +731,10 @@ The offer and the allowlist sit on the same path, so there is no dead button eit
|
|
|
688
731
|
|
|
689
732
|
The waiver is **per tool name and per element**, held in memory and never persisted. A session
|
|
690
733
|
decision that outlived the tab would be a permanent grant made by one click — which is what
|
|
691
|
-
`autoConfirm` already exists to say deliberately. It is cleared when the element goes away
|
|
734
|
+
`autoConfirm` already exists to say deliberately. It is cleared when the element goes away, and
|
|
735
|
+
when [`user-key`](#who-the-stored-conversation-belongs-to-user-key) changes hands: one person's
|
|
736
|
+
*Always allow* is not the next person's, and a sign-out in the same tab does not remount anything.
|
|
737
|
+
The first key to arrive keeps it, for the same reason it keeps the conversation.
|
|
692
738
|
|
|
693
739
|
AG-UI has no built-in risk flag, so destructiveness is carried as a JSON-Schema extension at the
|
|
694
740
|
**schema root**: `parameters["x-destructive"] = true` (use the exported `X_DESTRUCTIVE_KEY`
|
|
@@ -789,6 +835,12 @@ chat.approvalRenderer = (request, { signal }) =>
|
|
|
789
835
|
myConfirmDialog(request.message ?? `Run ${request.toolName}?`, { signal });
|
|
790
836
|
```
|
|
791
837
|
|
|
838
|
+
**A renderer that throws or rejects falls back to the built-in card** for that interrupt, with a
|
|
839
|
+
`console.warn` naming it. The renderer decides how the question looks, not whether it is asked, so
|
|
840
|
+
nothing runs without a click and the run carries on as if no renderer were set. The one exception
|
|
841
|
+
is a rejection after the signal fired, which is how a renderer is expected to honour Stop: that
|
|
842
|
+
wait resolves as denied, as the built-in card does on the same signal, with no card and no warning.
|
|
843
|
+
|
|
792
844
|
### Asking the user a question (`ask_user`)
|
|
793
845
|
|
|
794
846
|
Set `chat.askUser = true` to offer the agent a built-in `ask_user` frontend tool. When the agent
|
|
@@ -825,6 +877,13 @@ chat.askUserRenderer = (request, { signal }) =>
|
|
|
825
877
|
myModal.ask(request.question, request.options, { allowCustom: request.allowCustom, signal });
|
|
826
878
|
```
|
|
827
879
|
|
|
880
|
+
**A renderer that throws or rejects falls back to the built-in card** for that call, with a
|
|
881
|
+
`console.warn` naming the tool call. As with `approvalRenderer`, the renderer decides how the
|
|
882
|
+
question looks, not whether it is asked, so the run carries on as if no renderer were set. The one
|
|
883
|
+
exception is a rejection after the signal fired, which is how a renderer is expected to honour Stop:
|
|
884
|
+
that wait resolves with an empty answer, as the built-in card does on the same signal, with no card
|
|
885
|
+
and no warning.
|
|
886
|
+
|
|
828
887
|
### DOM-driver and animation primitives
|
|
829
888
|
|
|
830
889
|
So the agent can visibly drive the host page, the package ships generic, framework-free
|
|
@@ -1012,7 +1071,8 @@ matching JS API:
|
|
|
1012
1071
|
The conversation it leaves is **kept**: it stays in the history drawer to return to, and on a
|
|
1013
1072
|
server-backed store it stays on the server. Deleting one is the drawer row's own action. A chat
|
|
1014
1073
|
nothing was ever sent in is the exception — it was never listed, so it is dropped rather than
|
|
1015
|
-
left behind.
|
|
1074
|
+
left behind. Focus moves to the composer, whichever control started the new chat, unless the
|
|
1075
|
+
widget is collapsed, and the page is not scrolled to it.
|
|
1016
1076
|
- `describeSurface()` — where the panel is and what can be done to it: placement, collapsed,
|
|
1017
1077
|
whether it can be moved, whether it fills the screen, its box and the viewport. `movable` folds
|
|
1018
1078
|
the two reasons a move can fail into the one answer a caller needs.
|
|
@@ -1037,7 +1097,10 @@ chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
|
|
|
1037
1097
|
### The composer's own keys
|
|
1038
1098
|
|
|
1039
1099
|
**Enter during a run queues.** A second run cannot start while one is in flight —
|
|
1040
|
-
it would orphan the first — so that key used to do nothing at all, silently.
|
|
1100
|
+
it would orphan the first — so that key used to do nothing at all, silently. A
|
|
1101
|
+
checkpoint continuation picked from the panel counts as in flight from the pick
|
|
1102
|
+
rather than from its first event, which is a request later, so a turn typed in
|
|
1103
|
+
that gap is parked too rather than racing it. What
|
|
1041
1104
|
is waiting shows above the composer as chips, each of which takes its message
|
|
1042
1105
|
back when pressed, and the next one is sent when the run settles. Stopping the
|
|
1043
1106
|
run discards them: sending into a conversation someone has just stopped is the
|
|
@@ -1414,9 +1477,10 @@ If a tool's schema carries an `x-summary` string (use `X_SUMMARY_KEY`), the card
|
|
|
1414
1477
|
label instead of the raw tool name.
|
|
1415
1478
|
|
|
1416
1479
|
Every card leads with a **status icon** drawn entirely in CSS — a spinning ring while the call
|
|
1417
|
-
runs, then a check / cross / slash on success / error / decline
|
|
1418
|
-
properties (or the `tool-card-icon` part): `--ag-ui-tool-icon-done`,
|
|
1419
|
-
`--ag-ui-tool-icon-
|
|
1480
|
+
runs, then a check / cross / slash / dotted ring on success / error / decline / not finished.
|
|
1481
|
+
Re-theme it via custom properties (or the `tool-card-icon` part): `--ag-ui-tool-icon-done`,
|
|
1482
|
+
`--ag-ui-tool-icon-error`, `--ag-ui-tool-icon-declined`, `--ag-ui-tool-icon-interrupted`
|
|
1483
|
+
(quoted-string glyphs) and `--ag-ui-tool-spin-duration` (spinner
|
|
1420
1484
|
speed; the spin respects `prefers-reduced-motion`).
|
|
1421
1485
|
|
|
1422
1486
|
```html
|
|
@@ -1962,7 +2026,9 @@ does not declare the field, exactly as it does not declare the `attachments` an
|
|
|
1962
2026
|
user message; the default store round-trips both through `JSON.stringify`. **A store that drops
|
|
1963
2027
|
unknown fields loses only the distinction** — the card falls back to *done*, which is what it did
|
|
1964
2028
|
before. It is written onto the copy handed to the store and never onto `agent.messages`, so it is
|
|
1965
|
-
not sent back to the server on the next run.
|
|
2029
|
+
not sent back to the server on the next run. That holds after a restore too: the client seeded from
|
|
2030
|
+
a stored conversation takes the field off every message it is seeded with and writes it back on the
|
|
2031
|
+
next save.
|
|
1966
2032
|
|
|
1967
2033
|
**3. Resumable loop (`x-navigates` + `navigationResult`).** A tool whose schema carries
|
|
1968
2034
|
`x-navigates: true` (use `X_NAVIGATES_KEY`; read back by [`isNavigates`](src/tools/is_navigates.ts))
|
|
@@ -1978,6 +2044,20 @@ triggers a full reload. Before the handler navigates, the element writes a check
|
|
|
1978
2044
|
|
|
1979
2045
|
The MPA round-trip becomes a clean observation point instead of a dropped conversation.
|
|
1980
2046
|
|
|
2047
|
+
**A reload the run did not expect settles every open call as not finished.** A round's history is
|
|
2048
|
+
saved when its stream ends, before the element asks about a gated call, runs a frontend tool or
|
|
2049
|
+
collects a server-side approval, so a reload in that window finds the round's calls with no result
|
|
2050
|
+
and no run left to produce one. On restore, every call without a result (other than a checkpointed
|
|
2051
|
+
navigating call) is answered the way the client answers an open call before a request: its card
|
|
2052
|
+
settles as `interrupted` ("not finished"), and the restored conversation gains the `callNotFinished`
|
|
2053
|
+
result with the outcome `interrupted`, so the next request carries a result for every call and a
|
|
2054
|
+
later reload shows the same.
|
|
2055
|
+
|
|
2056
|
+
It is not declined, although Stop declines an open card. Pressing Stop is a person answering the
|
|
2057
|
+
question; a reload answers nothing. The saved history is the same whether the round was waiting on a
|
|
2058
|
+
person or on a frontend tool the reload killed, so a decline would be unproven for one and false for
|
|
2059
|
+
the other, and "not finished" is true of both. The same goes for a server-side approval left open.
|
|
2060
|
+
|
|
1981
2061
|
### Who the stored conversation belongs to (`user-key`)
|
|
1982
2062
|
|
|
1983
2063
|
`sessionStorage` is scoped to a tab, not to a session. It survives every same-tab navigation,
|
|
@@ -1992,7 +2072,11 @@ data, so treat that as the default and turn it off:
|
|
|
1992
2072
|
The value is any string that identifies the principal — a user id, an account id, a hash of one.
|
|
1993
2073
|
It joins the storage namespace, so two principals in the same tab cannot reach each other's
|
|
1994
2074
|
conversation, and **changing it purges everything the previous principal stored**: transcript,
|
|
1995
|
-
history drawer index and navigation checkpoints, for this element's namespace only.
|
|
2075
|
+
history drawer index and navigation checkpoints, for this element's namespace only. The tools they
|
|
2076
|
+
waived with *Always allow* are forgotten with it, so the next principal is asked again, and so is
|
|
2077
|
+
[`sharedState`](#host-seams-the-spa-story): whatever the agent last wrote into it for them is cleared rather than
|
|
2078
|
+
sent on the next principal's first run, so a host that seeds shared state assigns it again for the
|
|
2079
|
+
principal who arrived.
|
|
1996
2080
|
|
|
1997
2081
|
Set it live, from script, as part of signing out or in:
|
|
1998
2082
|
|
|
@@ -2158,7 +2242,9 @@ async def write_document(ctx: RunContext[AgentDeps], body: str) -> ToolReturn:
|
|
|
2158
2242
|
```
|
|
2159
2243
|
|
|
2160
2244
|
Use this when the agent and the page are editing **the same object** (a document, a form, a
|
|
2161
|
-
canvas).
|
|
2245
|
+
canvas). Because it is the page's object rather than the conversation's, **New chat keeps it**; a
|
|
2246
|
+
change of [`user-key`](#who-the-stored-conversation-belongs-to-user-key) clears it. Use
|
|
2247
|
+
`registerPageState` when the agent should *ask* for a value or *request* a change —
|
|
2162
2248
|
the tool call is visible in the transcript and can be gated by a confirmation card, which state
|
|
2163
2249
|
events cannot.
|
|
2164
2250
|
|
|
@@ -2316,6 +2402,12 @@ Picking a row with an empty composer says so above the input and puts the caret
|
|
|
2316
2402
|
there, rather than closing the panel over nothing: a continuation sends **only**
|
|
2317
2403
|
the new turn, so with nothing typed there is nothing to send.
|
|
2318
2404
|
|
|
2405
|
+
Picking a row while an answer is still streaming — the conversation's own, or
|
|
2406
|
+
another continuation's — is refused the same way: the hint asks you to wait or
|
|
2407
|
+
stop it, and what you typed stays in the composer for the pick after. The run in
|
|
2408
|
+
flight is not cancelled for you, since a continuation starting over it would
|
|
2409
|
+
leave two answers streaming into one transcript with Stop reaching only one.
|
|
2410
|
+
|
|
2319
2411
|
### One URL, three endpoints
|
|
2320
2412
|
|
|
2321
2413
|
`data-runs-url` is the only thing to configure. `resume/<id>/` and `fork/<id>/`
|
|
@@ -2333,11 +2425,24 @@ The component satisfies that structurally rather than by remembering a rule. A
|
|
|
2333
2425
|
continuation runs on its own short-lived agent, built pointing at the resume
|
|
2334
2426
|
endpoint and seeded with **no** history — so "only the new turn" is the only
|
|
2335
2427
|
thing it *can* send, and the fresh run id comes free because a new agent mints
|
|
2336
|
-
one.
|
|
2428
|
+
one. Nothing of the conversation's history goes out on its request.
|
|
2337
2429
|
|
|
2338
2430
|
A resumed run is a normal run in every other respect: frontend tools execute,
|
|
2339
|
-
approval interrupts render their card,
|
|
2340
|
-
a rotated CSRF token or JWT still reaches the endpoint
|
|
2431
|
+
approval interrupts render their card, `headers` are re-read per request so
|
|
2432
|
+
a rotated CSRF token or JWT still reaches the endpoint, it carries and updates
|
|
2433
|
+
[shared state](#host-seams-the-spa-story) like any other run, it is bounded by
|
|
2434
|
+
`data-max-tool-rounds`, and Stop ends it. Its client is built by the same
|
|
2435
|
+
construction as the conversation's own, differing only in the endpoint and the
|
|
2436
|
+
empty seed.
|
|
2437
|
+
|
|
2438
|
+
The continued exchange joins the conversation. Its agent holds only the new turn
|
|
2439
|
+
and the answer, and a store keeps one message list per thread, so each save
|
|
2440
|
+
writes the conversation as it stood when you picked the row with the exchange
|
|
2441
|
+
after it — through the same `saveMessages` as any other run, and as far as it
|
|
2442
|
+
got if it was stopped. A reload then replays the exchange, and the next message
|
|
2443
|
+
you send, now to `endpoint` again, carries it together with the shared state the
|
|
2444
|
+
continuation left. What is saved is what the transcript shows: a fork's exchange
|
|
2445
|
+
follows the conversation it was picked from, in that same thread.
|
|
2341
2446
|
|
|
2342
2447
|
If the index can't be reached, the panel shows its empty state rather than an
|
|
2343
2448
|
error — a history affordance that fails is empty, not broken.
|
|
@@ -2650,6 +2755,13 @@ ag-ui-chat {
|
|
|
2650
2755
|
}
|
|
2651
2756
|
```
|
|
2652
2757
|
|
|
2758
|
+
`--ag-ui-radius` is the theme's corner radius. It rounds the panel's frame and the corners inside
|
|
2759
|
+
the panel that have no radius of their own: the answer well, the conversation list's *New chat*
|
|
2760
|
+
button and filter field, and the *Undo* on a run notice. The frame is the exception where it meets
|
|
2761
|
+
the edge of the window. `page` and `sidebar` draw it square, and so does every placement but
|
|
2762
|
+
`embedded` on a small viewport, while the corners inside keep the theme's radius, since none of them
|
|
2763
|
+
meets that edge. `--ag-ui-panel-radius` sets the frame on its own, in any placement.
|
|
2764
|
+
|
|
2653
2765
|
### Small viewports
|
|
2654
2766
|
|
|
2655
2767
|
At **600px wide and below** every placement but `embedded` becomes one full-bleed
|
|
@@ -2662,25 +2774,34 @@ of the screen with a frame drawn round it.
|
|
|
2662
2774
|
`embedded` is left alone deliberately: it sits in a box you sized and placed, and
|
|
2663
2775
|
only you know whether that column should become the whole screen.
|
|
2664
2776
|
|
|
2777
|
+
The [greeting layout](#the-greeting-on-an-empty-page) changes shape here too, and
|
|
2778
|
+
it is the one part of this that `embedded` gets as well, because it moves nothing
|
|
2779
|
+
about the box you placed: an empty conversation keeps its composer at the foot of
|
|
2780
|
+
the panel, with the starters against it and the greeting over the space above
|
|
2781
|
+
them, instead of centring all three together.
|
|
2782
|
+
|
|
2665
2783
|
The corner placements still rest at their launcher, so a full-bleed panel is
|
|
2666
2784
|
something the user opens rather than something they are given.
|
|
2667
2785
|
|
|
2668
|
-
|
|
2786
|
+
**To keep your desktop layout at every width**, set `data-small-viewport="off"`.
|
|
2787
|
+
That exists because the *trigger* is the one part of this you cannot reach: every
|
|
2788
|
+
value the override sets is a `--ag-ui-*` token you can re-state, but a media
|
|
2789
|
+
query cannot read a custom property, so the breakpoint itself is a literal.
|
|
2669
2790
|
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
get back to, and a dedicated route is the one surface with width to spare.
|
|
2791
|
+
The breakpoint is a width rather than a pointer test, and that is on purpose: a
|
|
2792
|
+
touch laptop is coarse-pointered and wide, a narrow desktop window is
|
|
2793
|
+
fine-pointered and small. Width decides the layout; the pointer decides which
|
|
2794
|
+
controls make sense.
|
|
2675
2795
|
|
|
2676
|
-
|
|
2677
|
-
a few hundred pixels of panel with a list docked into it leaves a column of
|
|
2678
|
-
transcript narrower than the messages in it. Width alone is not the test — an app
|
|
2679
|
-
shell can hand `embedded` a page-sized box, and that box is still a column of
|
|
2680
|
-
somebody's layout.
|
|
2796
|
+
### The conversation list
|
|
2681
2797
|
|
|
2682
|
-
|
|
2683
|
-
|
|
2798
|
+
The chat-history list slides over the transcript at every width and under every
|
|
2799
|
+
placement, a full page included. Opening it moves nothing underneath: the
|
|
2800
|
+
conversation, the centred greeting and the composer are exactly where they were.
|
|
2801
|
+
While it is open it is a modal dialog: focus moves into it, Tab stays inside it,
|
|
2802
|
+
and Escape, its close control or a click on the backdrop beside it closes it and
|
|
2803
|
+
returns focus to where it was. `openThreads()` and `closeThreads()` do the same
|
|
2804
|
+
from your own chrome.
|
|
2684
2805
|
|
|
2685
2806
|
The list also grows a filter once there are eight or more conversations in it
|
|
2686
2807
|
— above that a search box is worth having, below it it is a control asking to be
|
|
@@ -2690,16 +2811,6 @@ phrase you remember is as likely to be inside the conversation as on it, and it
|
|
|
2690
2811
|
filters what the drawer already holds rather than going back to the server for a
|
|
2691
2812
|
list that is already in memory.
|
|
2692
2813
|
|
|
2693
|
-
**To keep your desktop layout at every width**, set `data-small-viewport="off"`.
|
|
2694
|
-
That exists because the *trigger* is the one part of this you cannot reach: every
|
|
2695
|
-
value the override sets is a `--ag-ui-*` token you can re-state, but a media
|
|
2696
|
-
query cannot read a custom property, so the breakpoint itself is a literal.
|
|
2697
|
-
|
|
2698
|
-
The breakpoint is a width rather than a pointer test, and that is on purpose: a
|
|
2699
|
-
touch laptop is coarse-pointered and wide, a narrow desktop window is
|
|
2700
|
-
fine-pointered and small. Width decides the layout; the pointer decides which
|
|
2701
|
-
controls make sense.
|
|
2702
|
-
|
|
2703
2814
|
### Reserving the space your own chrome occupies
|
|
2704
2815
|
|
|
2705
2816
|
A fixed placement covers the viewport it is given, and it does not know about
|
|
@@ -2755,11 +2866,25 @@ widget measures the hidden band and publishes it as
|
|
|
2755
2866
|
`--ag-ui-visual-viewport-inset-bottom`; state this one instead to outrank that
|
|
2756
2867
|
measurement, or set it to `0px` to opt out of the lift entirely.
|
|
2757
2868
|
|
|
2869
|
+
`--ag-ui-keyboard-inset-top` does the same at the top. To show a field a
|
|
2870
|
+
keyboard would cover, a mobile browser pans the visible area down the page, and
|
|
2871
|
+
a panel anchored to the screen (`page`, `full`, `side`, `sidebar`, and at phone
|
|
2872
|
+
width the corner placements too) moves down with it. An `embedded` panel renders
|
|
2873
|
+
in your own box rather than against the screen, so it does not move at any width.
|
|
2874
|
+
The widget publishes the pan as `--ag-ui-visual-viewport-inset-top`, and the
|
|
2875
|
+
panel moves by as much of it as goes past your `--ag-ui-viewport-inset-top`,
|
|
2876
|
+
since the pan scrolls a reserved bar away with the page. State
|
|
2877
|
+
`--ag-ui-keyboard-inset-top` to outrank that distance, or `0px` to keep the
|
|
2878
|
+
panel below your reserved top.
|
|
2879
|
+
|
|
2758
2880
|
`--ag-ui-viewport-height` and `--ag-ui-viewport-width` state the usable box
|
|
2759
2881
|
outright, for the case where no viewport-percentage length describes it. An
|
|
2760
2882
|
on-screen keyboard is the one that matters: it changes neither `vh` nor `dvh` nor
|
|
2761
2883
|
`svh` on any current mobile browser, so a full-bleed panel has to be told the
|
|
2762
|
-
visual viewport's height rather than deriving it.
|
|
2884
|
+
visual viewport's height rather than deriving it. The widget does that itself;
|
|
2885
|
+
where it has nothing to report, the panel falls back to `100dvh` rather than
|
|
2886
|
+
`100vh`, because iOS Safari resolves `vh` to the screen with its bars collapsed
|
|
2887
|
+
and a panel sized from it runs under them.
|
|
2763
2888
|
|
|
2764
2889
|
Marks are variables too, so one vocabulary covers a re-theme rather than
|
|
2765
2890
|
leaving half the transcript in the built-in set: `--ag-ui-tool-icon-done` /
|
|
@@ -2869,7 +2994,7 @@ component sets, so a new one cannot ship undocumented.
|
|
|
2869
2994
|
|
|
2870
2995
|
| Feature | Parts |
|
|
2871
2996
|
| --- | --- |
|
|
2872
|
-
| Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `jump-latest`, and one per resize grip: `resize-handle` plus `resize-handle-top`, `resize-handle-bottom`, `resize-handle-left`, `resize-handle-right`, `resize-handle-top-left`, `resize-handle-top-right`, `resize-handle-bottom-left`, `resize-handle-bottom-right` |
|
|
2997
|
+
| Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `greeting`, `pending`, `stopped`, `jump-latest`, and one per resize grip: `resize-handle` plus `resize-handle-top`, `resize-handle-bottom`, `resize-handle-left`, `resize-handle-right`, `resize-handle-top-left`, `resize-handle-top-right`, `resize-handle-bottom-left`, `resize-handle-bottom-right` |
|
|
2873
2998
|
| Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
|
|
2874
2999
|
| Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge`, `rail-label` |
|
|
2875
3000
|
| Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
|
|
@@ -2885,7 +3010,7 @@ component sets, so a new one cannot ship undocumented.
|
|
|
2885
3010
|
| Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
|
|
2886
3011
|
| Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
|
|
2887
3012
|
| Attachments | `attachment-tray`, `attachment-chips` (the read-only chips on sent bubbles), and the shared chip parts `attachment-chip`, `attachment-chip-icon`, `attachment-chip-name`, `attachment-chip-size`, `attachment-chip-bar`, `attachment-chip-bar-fill`, `attachment-chip-retry`, `attachment-chip-remove` |
|
|
2888
|
-
| Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the composer hint: a skill’s missing placeholders, and a run continuation with nothing typed) |
|
|
3013
|
+
| Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the composer hint: a skill’s missing placeholders, and a run continuation picked with nothing typed or while a run is in flight) |
|
|
2889
3014
|
| Thread drawer | `drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`, `drawer-close`, `drawer-filter`, `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`, `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`, `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`, `drawer-confirm-yes`, `drawer-confirm-no` |
|
|
2890
3015
|
| Charts | `chart-block`, `chart-title`, `chart-legend` |
|
|
2891
3016
|
| Checkpoints panel | `checkpoints`, `checkpoints-header`, `checkpoints-title`, `checkpoints-list`, `checkpoints-empty`, `checkpoint-row`, `checkpoint-label`, `checkpoint-time`, `checkpoint-id`, `checkpoint-branch`, `checkpoint-action` (plus `checkpoint-resume`, `checkpoint-fork`) |
|
|
@@ -2917,6 +3042,7 @@ with a matching `slot=`):
|
|
|
2917
3042
|
| --- | --- |
|
|
2918
3043
|
| `icon` | A header brand icon, before the title. |
|
|
2919
3044
|
| `header-actions` | Extra controls between the title and the built-in buttons. |
|
|
3045
|
+
| `greeting` | The greeting's text, above the empty state, where the [greeting layout](#the-greeting-on-an-empty-page) is on. Replaces the text only; `empty` and the starters still render beneath it. |
|
|
2920
3046
|
| `empty` | The empty-state shown before any message. |
|
|
2921
3047
|
| `footer` | Below the composer. |
|
|
2922
3048
|
| `launcher` | The collapsed widget's mark — the floating launcher, or the sidebar rail. |
|
|
@@ -2970,14 +3096,64 @@ naturally with the [answer well](#the-answer-well).
|
|
|
2970
3096
|
<ag-ui-chat endpoint="/agent/" placement="page" data-answer-well></ag-ui-chat>
|
|
2971
3097
|
```
|
|
2972
3098
|
|
|
3099
|
+
#### The greeting on an empty page
|
|
3100
|
+
|
|
3101
|
+
Until the first message is sent, a page shows a greeting with the composer centred beneath it,
|
|
3102
|
+
and the composer moves to the foot of the page once the conversation has something in it. On a
|
|
3103
|
+
phone it starts at the foot, for the reason below.
|
|
3104
|
+
|
|
3105
|
+
```html
|
|
3106
|
+
<ag-ui-chat endpoint="/agent/" placement="page" user-name="Ada"></ag-ui-chat>
|
|
3107
|
+
```
|
|
3108
|
+
|
|
3109
|
+
- **The text** is `Hello, {name}`, filled from `user-name`, or `Hello there` when there is no
|
|
3110
|
+
name. Both are string-table keys, `greeting` and `greetingNoName`, so `strings` and
|
|
3111
|
+
`data-strings` translate them like everything else. `user-name` is live: a name that arrives
|
|
3112
|
+
after an auth handshake replaces the greeting on screen.
|
|
3113
|
+
- **Your own greeting** goes in `slot="greeting"`, which replaces the text and nothing else. A
|
|
3114
|
+
brand mark belongs there too. The `empty` slot and `data-starters` still render beneath it.
|
|
3115
|
+
- **Styling**: the `greeting` part, plus `--ag-ui-greeting-font` (default: the widget's font) and
|
|
3116
|
+
`--ag-ui-greeting-size` (default `clamp(1.5rem, 4vw, 2.25rem)`, scaling with the viewport rather
|
|
3117
|
+
than stepping at a breakpoint).
|
|
3118
|
+
- **`data-empty`** is present on the element while the conversation is empty, for chrome of your
|
|
3119
|
+
own that should react to the same state.
|
|
3120
|
+
- **Which placements**: on by default for `page`, which is a route of its own. `data-greeting="off"`
|
|
3121
|
+
restores the plain layout there. `embedded` opts in with `data-greeting` (any value but `off`),
|
|
3122
|
+
for an app shell that gives the panel a page-sized box. The corner placements and the sidebar
|
|
3123
|
+
never show it: a panel opened from a launcher is already mid-task.
|
|
3124
|
+
- **On a phone** the composer stays at the foot, the starters (or your own `empty` content) sit
|
|
3125
|
+
against it, and the greeting takes the middle of what they leave — the middle of the
|
|
3126
|
+
transcript when there are none. Centring is a shape for a screen with room to spare; at
|
|
3127
|
+
[600px wide and below](#small-viewports) the room is what the on-screen keyboard takes, and a
|
|
3128
|
+
composer centred over an empty band sits halfway up what is left of the screen, with the
|
|
3129
|
+
prompts that start a conversation nowhere near the field that sends one.
|
|
3130
|
+
`data-small-viewport="off"` keeps the centred shape at every width, as it keeps the rest of
|
|
3131
|
+
the desktop layout.
|
|
3132
|
+
|
|
3133
|
+
The composer's rows stay centred while you type, while a draft grows and while attachments are
|
|
3134
|
+
added, because the centring is two equal flexible halves either side of them and growth splits
|
|
3135
|
+
into both. Nothing in the DOM moves, so focus, the caret and IME composition are unaffected.
|
|
3136
|
+
|
|
3137
|
+
**Sending is what moves it**, not typing, and the composer slides to the foot of the page over
|
|
3138
|
+
`--ag-ui-motion` (a single frame under reduced motion). Everything else that changes the
|
|
3139
|
+
conversation snaps: a restored conversation mounts docked, picking a thread from the list or
|
|
3140
|
+
continuing a run docks at once, and a new chat returns to the centre at once. While a slow
|
|
3141
|
+
conversation store is still fetching, the page stays docked with the greeting hidden, so a
|
|
3142
|
+
restored conversation never paints the empty layout first.
|
|
3143
|
+
|
|
2973
3144
|
### The answer well
|
|
2974
3145
|
|
|
2975
3146
|
Each assistant turn renders inside one `.answer` group (part `answer`) that holds its streamed
|
|
2976
3147
|
text, tool cards, and pending indicator — so a turn that calls tools reads as a single answer
|
|
2977
3148
|
rather than a string of loose siblings. Add the boolean `data-answer-well` attribute to box that
|
|
2978
3149
|
group in a bordered, padded "well"; without it the layout is the flat stack as before. The well is
|
|
2979
|
-
pure CSS and turn-scoped — no JS API — and themeable via `--ag-ui-well-bg
|
|
2980
|
-
(and `::part(answer)`).
|
|
3150
|
+
pure CSS and turn-scoped — no JS API — and themeable via `--ag-ui-well-bg`, `--ag-ui-well-border`
|
|
3151
|
+
and `--ag-ui-well-radius` (and `::part(answer)`).
|
|
3152
|
+
|
|
3153
|
+
The well's corners default to `--ag-ui-radius`, 12px unless you set it, in every placement. The
|
|
3154
|
+
panel's own frame is different: `page` and `sidebar` draw it square because it meets the edge of
|
|
3155
|
+
the window, but nothing inside the panel does, so the well stays round there too. Set
|
|
3156
|
+
`--ag-ui-well-radius` to round the well differently from the rest of the panel.
|
|
2981
3157
|
|
|
2982
3158
|
```html
|
|
2983
3159
|
<ag-ui-chat endpoint="/agent/" data-answer-well></ag-ui-chat>
|