@artooi/ag-ui-web-component 0.9.0 → 0.10.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 +61 -7
- package/dist/ag-ui-web-component.bundle.js +56 -56
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/core/ag_ui_chat.d.ts +10 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/attachment.d.ts +5 -0
- package/dist/core/attachment.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +8 -0
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/upload_attachment.d.ts +8 -2
- package/dist/core/upload_attachment.d.ts.map +1 -1
- package/dist/index.js +300 -58
- package/dist/index.js.map +2 -2
- package/dist/ui/attachment_tray.d.ts +7 -1
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/relative_time.d.ts +5 -3
- package/dist/ui/relative_time.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thoughts_block.d.ts +2 -2
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts +9 -1
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/ag_ui_chat.ts +79 -11
- package/src/core/agui_client.ts +14 -3
- package/src/core/attachment.ts +21 -1
- package/src/core/conversation_store.ts +84 -18
- package/src/core/remote_conversation_store.ts +24 -3
- package/src/core/upload_attachment.ts +8 -1
- package/src/ui/attachment_tray.ts +42 -5
- package/src/ui/relative_time.ts +8 -3
- package/src/ui/styles.ts +9 -9
- package/src/ui/thoughts_block.ts +2 -2
- package/src/ui/thread_drawer.ts +83 -9
- package/src/ui/tool_call_card.ts +6 -0
- package/src/ui/voice_input.ts +21 -1
- package/src/version.ts +1 -1
|
@@ -39,7 +39,13 @@ export declare class AttachmentTray {
|
|
|
39
39
|
isEmpty(): boolean;
|
|
40
40
|
/** Drop the settled (ready / error) chips, leaving any still uploading. */
|
|
41
41
|
clearReady(): void;
|
|
42
|
-
/** Drop every chip (a reset / new-chat). */
|
|
42
|
+
/** Drop every chip (a reset / new-chat), aborting any in-flight upload. */
|
|
43
43
|
clear(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Abort every in-flight upload without touching the rendered chips — the
|
|
46
|
+
* teardown path when the host element is removed mid-upload, so a cancelled
|
|
47
|
+
* transfer doesn't orphan a server-side file.
|
|
48
|
+
*/
|
|
49
|
+
dispose(): void;
|
|
44
50
|
}
|
|
45
51
|
//# sourceMappingURL=attachment_tray.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment_tray.d.ts","sourceRoot":"","sources":["../../src/ui/attachment_tray.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKrE,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"attachment_tray.d.ts","sourceRoot":"","sources":["../../src/ui/attachment_tray.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKrE,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;CAC9B;AAcD;;;;;;;;;GASG;AACH,qBAAa,cAAc;;IACzB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;gBAMrB,MAAM,EAAE,oBAAoB;IASxC,gFAAgF;IAChF,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAwBrB,8DAA8D;IAC9D,SAAS,IAAI,SAAS,aAAa,EAAE;IAUrC,gFAAgF;IAChF,UAAU,IAAI,OAAO;IAIrB,uCAAuC;IACvC,OAAO,IAAI,OAAO;IAIlB,2EAA2E;IAC3E,UAAU,IAAI,IAAI;IAKlB,2EAA2E;IAC3E,KAAK,IAAI,IAAI;IAQb;;;;OAIG;IACH,OAAO,IAAI,IAAI;CAgIhB"}
|
|
@@ -5,9 +5,11 @@ import { type UiStrings } from "./ui_strings.js";
|
|
|
5
5
|
*
|
|
6
6
|
* `now` is injectable so callers (and tests) can pin the reference point; it
|
|
7
7
|
* defaults to the current time. A timestamp in the future (clock skew) reads as
|
|
8
|
-
* `"just now"`.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* `"just now"`. A non-finite timestamp — an unparseable or missing `updated_at`
|
|
9
|
+
* that arrived as `NaN` — has no meaningful age, so it falls back to `justNow`
|
|
10
|
+
* rather than rendering `"NaNw ago"` or `"~2950w ago"`. The unit words come from
|
|
11
|
+
* {@link UiStrings} (the `{n}` token is filled in here) so a localized host
|
|
12
|
+
* translates them; the bucketing stays integer-rounded and locale-neutral.
|
|
11
13
|
*/
|
|
12
14
|
export declare function relativeTime(timestamp: number, now?: number, strings?: UiStrings): string;
|
|
13
15
|
//# sourceMappingURL=relative_time.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relative_time.d.ts","sourceRoot":"","sources":["../../src/ui/relative_time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE
|
|
1
|
+
{"version":3,"file":"relative_time.d.ts","sourceRoot":"","sources":["../../src/ui/relative_time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,MAAmB,EACxB,OAAO,GAAE,SAA8B,GACtC,MAAM,CAqBR"}
|
package/dist/ui/styles.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const STYLES = "\n:host {\n /* Colors */\n --ag-ui-bg: #ffffff;\n --ag-ui-fg: #1a1a2e;\n --ag-ui-accent: #4f46e5;\n --ag-ui-user-bg: #4f46e5;\n --ag-ui-user-fg: #ffffff;\n --ag-ui-assistant-bg: #f1f1f6;\n --ag-ui-input-bg: var(--ag-ui-bg);\n --ag-ui-tool-bg: var(--ag-ui-assistant-bg);\n --ag-ui-tool-fg: var(--ag-ui-accent);\n --ag-ui-header-bg: var(--ag-ui-accent);\n --ag-ui-header-fg: #ffffff;\n --ag-ui-border: #e2e2ec;\n --ag-ui-radius: 12px;\n\n /* Status accents for tool-call cards. */\n --ag-ui-success: #15803d;\n --ag-ui-danger: #b91c1c;\n --ag-ui-muted: #6b7280;\n\n /* Tool-call status icon glyphs (override to re-theme) + spinner speed.\n The pending state is the animated ring; the settled states use these. */\n --ag-ui-tool-icon-done: \"\u2713\";\n --ag-ui-tool-icon-error: \"\u2715\";\n --ag-ui-tool-icon-declined: \"\u2298\";\n --ag-ui-tool-spin-duration: 0.7s;\n\n /* Answer well (opt-in via data-answer-well) \u2014 boxes a whole assistant turn. */\n --ag-ui-well-bg: transparent;\n --ag-ui-well-border: var(--ag-ui-border);\n\n /* Surface \u2014 set --ag-ui-shadow: none for a flush, embedded panel. */\n --ag-ui-shadow: 0 12px 32px rgba(20, 20, 50, 0.18);\n --ag-ui-font: inherit;\n --ag-ui-font-size: 14px;\n --ag-ui-code-font: ui-monospace, \"SF Mono\", Menlo, monospace;\n\n /* Spacing \u2014 the density preset overrides these. */\n --ag-ui-space: 10px;\n --ag-ui-pad: 16px;\n --ag-ui-msg-pad: 8px 12px;\n --ag-ui-msg-radius: 14px;\n\n /* Layout \u2014 override from outside to dock the widget anywhere.\n Set --ag-ui-position: static (and place this element in your own\n grid/flex layout) to embed it in the page flow instead of floating. */\n --ag-ui-position: fixed;\n --ag-ui-z-index: 2147483000;\n --ag-ui-width: 380px;\n --ag-ui-height: 560px;\n --ag-ui-inset: auto 24px 24px auto;\n --ag-ui-max-width: calc(100vw - 48px);\n --ag-ui-max-height: calc(100vh - 48px);\n /* Reading-column width for placement=\"page\" (full-bleed, centred content). */\n --ag-ui-content-max-width: 820px;\n\n position: var(--ag-ui-position);\n inset: var(--ag-ui-inset);\n z-index: var(--ag-ui-z-index);\n width: var(--ag-ui-width);\n max-width: var(--ag-ui-max-width);\n height: var(--ag-ui-height);\n max-height: var(--ag-ui-max-height);\n display: flex;\n font-family: var(--ag-ui-font);\n font-size: var(--ag-ui-font-size);\n color: var(--ag-ui-fg);\n}\n\n/* \u2500\u2500 Themes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Themes only re-set the colour variables; layout/spacing are unaffected.\n theme=\"auto\" follows the OS via prefers-color-scheme. */\n:host([theme=\"dark\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n}\n\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n }\n}\n\n/* A terminal-flavoured \"code\" theme: dark, monospace, green accent. */\n:host([theme=\"code\"]) {\n --ag-ui-bg: #0d1117;\n --ag-ui-fg: #c9d1d9;\n --ag-ui-accent: #3fb950;\n --ag-ui-user-bg: #238636;\n --ag-ui-assistant-bg: #161b22;\n --ag-ui-header-bg: #010409;\n --ag-ui-header-fg: #c9d1d9;\n --ag-ui-border: #30363d;\n --ag-ui-muted: #8b949e;\n --ag-ui-font: var(--ag-ui-code-font);\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);\n}\n\n/* \u2500\u2500 Density \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([density=\"compact\"]) {\n --ag-ui-font-size: 13px;\n --ag-ui-space: 6px;\n --ag-ui-pad: 10px;\n --ag-ui-msg-pad: 5px 9px;\n --ag-ui-msg-radius: 10px;\n}\n\n/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([placement=\"bottom-left\"]) {\n --ag-ui-inset: auto auto 24px 24px;\n}\n\n:host([placement=\"side\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"full\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n/* Page (PAGE-1): full-bleed background with a centred reading column. Unlike\n \"full\" (edge-to-edge, left-aligned messages) the content sits in a column\n capped at --ag-ui-content-max-width. The column is produced by symmetric auto\n padding on the scroll area + composer (no per-row wrapper), so user pills\n still right-align and the assistant well spans the column. */\n:host([placement=\"page\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"page\"]) .messages {\n padding-inline: max(var(--ag-ui-pad), calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .input-row {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* The rows between the message list and the composer (skill chips, the\n /-command palette, the missing-placeholder hint, the upload tray) line up\n with the column too \u2014 chips are padding-based, the palette/hint/tray are\n margin-based, so each gets its own inline axis nudged by the same gutter. */\n:host([placement=\"page\"]) .skill-chips,\n:host([placement=\"page\"]) .attachment-tray {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .skill-palette,\n:host([placement=\"page\"]) .skill-hint {\n margin-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* In the reading column the assistant well uses the full width; the user\n message stays a right-aligned pill (its default align-self + max-width). */\n:host([placement=\"page\"]) .message--assistant {\n max-width: 100%;\n}\n\n/* Sidebar (CUST-3): a full-height docked panel that slides open/closed and\n collapses to a slim icon rail (not the floating launcher). Docked right by\n default; data-side=\"left\" docks it left. Overlay by default \u2014 set\n --ag-ui-position: static (and place this element in your own layout) for a\n host-managed push instead. */\n:host([placement=\"sidebar\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n --ag-ui-rail-width: 52px;\n transition: width 0.28s ease;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) {\n --ag-ui-inset: 0 auto 0 0;\n}\n\n:host([placement=\"sidebar\"]) .chat {\n transition: transform 0.28s ease;\n}\n\n/* Collapsed sidebar: shrink the host to the rail width, hide the panel, and\n reveal the rail. Higher specificity than the generic collapse rules, so it\n wins regardless of source order. */\n:host([placement=\"sidebar\"][collapsed]) {\n width: var(--ag-ui-rail-width);\n height: 100vh;\n max-height: 100vh;\n bottom: 0;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .chat {\n display: none;\n}\n\n/* The rail is a sibling of the panel (so it survives the panel being hidden);\n shown only for a collapsed sidebar. */\n.rail {\n display: none;\n border: none;\n font: inherit;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .rail {\n display: flex;\n position: absolute;\n inset: 0;\n align-items: flex-start;\n justify-content: center;\n padding-top: 16px;\n border: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n cursor: pointer;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([placement=\"sidebar\"]),\n :host([placement=\"sidebar\"]) .chat {\n transition: none;\n }\n}\n\n/* Embedded: drop the floating chrome and the high z-index stacking context so\n the widget lives in the host's own layout (fixes overlay/z-index clashes). */\n:host([placement=\"embedded\"]) {\n --ag-ui-position: static;\n --ag-ui-width: 100%;\n --ag-ui-height: 100%;\n --ag-ui-max-width: 100%;\n --ag-ui-max-height: 100%;\n --ag-ui-shadow: none;\n --ag-ui-z-index: auto;\n}\n\n.chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n}\n\n.header-title {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Header / launcher icon holder (CUST-2): a slot, with a data-icon-url <img>\n fallback, sized via --ag-ui-icon-size. */\n.icon-holder {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--ag-ui-icon-size, 22px);\n height: var(--ag-ui-icon-size, 22px);\n line-height: 1;\n}\n\n.icon-img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n border-radius: var(--ag-ui-icon-radius, 4px);\n}\n\n.header-controls {\n display: flex;\n gap: 2px;\n flex: none;\n}\n\n.header-btn {\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1;\n padding: 4px 7px;\n border-radius: 6px;\n cursor: pointer;\n opacity: 0.85;\n}\n\n.header-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.18);\n}\n\n/* Collapsed: keep just the header bar \u2014 hide the whole body, and let the host\n shrink to the header. (A host can also fully hide the element itself.) */\n:host([collapsed]) {\n height: auto;\n max-height: none;\n}\n\n:host([collapsed]) .messages,\n:host([collapsed]) .input-row,\n:host([collapsed]) .skill-chips,\n:host([collapsed]) .skill-palette,\n:host([collapsed]) .skill-hint {\n display: none;\n}\n\n/* Edge-docked placements pin top *and* bottom, which would otherwise keep the\n panel full-height when collapsed; unpin the bottom so it shrinks to the\n header. (Floating/bottom-left pin only the bottom, so they already shrink.) */\n:host([collapsed][placement=\"side\"]),\n:host([collapsed][placement=\"full\"]) {\n bottom: auto;\n}\n\n.messages {\n flex: 1;\n overflow-y: auto;\n padding: var(--ag-ui-pad);\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n}\n\n/* Empty-state region (CUST-1 slot): centred while it's the only thing in the\n list, hidden as soon as a message, card, or pending indicator renders. */\n.empty {\n margin: auto;\n text-align: center;\n color: var(--ag-ui-muted);\n}\n\n.empty[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Answer group / well (WELL-1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One .answer per assistant turn wraps the streamed text, its tool cards,\n and the pending indicator so a whole answer reads (and can be boxed) as one\n unit. A flex column on the message-list gap, stretched to the list width so\n its children keep their own left/right alignment. data-answer-well opts into\n the bordered \"well\"; without it the layout is today's flat stack. */\n.answer {\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n align-self: stretch;\n min-width: 0;\n}\n\n:host([data-answer-well]) .answer {\n padding: var(--ag-ui-pad);\n background: var(--ag-ui-well-bg);\n border: 1px solid var(--ag-ui-well-border);\n border-radius: var(--ag-ui-radius);\n}\n\n.message {\n max-width: 80%;\n padding: var(--ag-ui-msg-pad);\n border-radius: var(--ag-ui-msg-radius);\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n/* \u2500\u2500 Incoming-text animations (data-text-animation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* .message--restored (rehydrated history) is excluded: entrance animations are\n for freshly-arriving messages, not the whole transcript replaying on reload.\n Word mode is excluded implicitly \u2014 restored bubbles aren't wrapped. */\n:host([data-text-animation=\"fade\"]) .message--assistant:not(.message--restored) {\n animation: ag-ui-msg-fade 0.25s ease both;\n}\n\n@keyframes ag-ui-msg-fade {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n.message--assistant .word {\n animation: ag-ui-word-in 0.3s ease both;\n animation-delay: calc(var(--ag-ui-word-index, 0) * 35ms);\n}\n\n@keyframes ag-ui-word-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([data-text-animation=\"fade\"]) .message--assistant,\n .message--assistant .word {\n animation: none;\n }\n}\n\n.message--user {\n align-self: flex-end;\n background: var(--ag-ui-user-bg);\n color: var(--ag-ui-user-fg);\n border-bottom-right-radius: 4px;\n}\n\n.message--assistant {\n align-self: flex-start;\n background: var(--ag-ui-assistant-bg);\n border-bottom-left-radius: 4px;\n /* Assistant bubbles hold rendered markdown/HTML, so collapse the source\n whitespace the renderer leaves between block tags. */\n white-space: normal;\n}\n\n/* Rendered-markdown elements inside an assistant bubble. */\n.message--assistant > :first-child {\n margin-top: 0;\n}\n\n.message--assistant > :last-child {\n margin-bottom: 0;\n}\n\n.message--assistant p,\n.message--assistant ul,\n.message--assistant ol,\n.message--assistant blockquote,\n.message--assistant pre {\n margin: 0.5em 0;\n}\n\n.message--assistant ul,\n.message--assistant ol {\n padding-left: 1.3em;\n}\n\n.message--assistant a {\n color: var(--ag-ui-accent);\n text-decoration: underline;\n}\n\n.message--assistant code {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n background: rgba(127, 127, 127, 0.16);\n padding: 1px 4px;\n border-radius: 4px;\n}\n\n.message--assistant pre {\n padding: 8px 10px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n}\n\n.message--assistant pre code {\n background: none;\n padding: 0;\n}\n\n.message--assistant blockquote {\n padding-left: 10px;\n border-left: 3px solid var(--ag-ui-border);\n color: var(--ag-ui-muted);\n}\n\n.pending {\n align-self: flex-start;\n display: inline-flex;\n gap: 4px;\n align-items: center;\n padding: 12px 14px;\n background: var(--ag-ui-assistant-bg);\n border-radius: 14px;\n border-bottom-left-radius: 4px;\n}\n\n.pending-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--ag-ui-muted);\n animation: ag-ui-pending 1.2s infinite ease-in-out both;\n}\n\n.pending-dot:nth-child(2) {\n animation-delay: 0.16s;\n}\n\n.pending-dot:nth-child(3) {\n animation-delay: 0.32s;\n}\n\n@keyframes ag-ui-pending {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pending-dot {\n animation: none;\n opacity: 0.6;\n }\n}\n\n/* \u2500\u2500 Thoughts region (THINK-1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A muted, collapsible chain-of-thought at the top of the answer group: open\n while the model reasons, folded once the answer text starts. */\n.thoughts {\n align-self: stretch;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n color: var(--ag-ui-muted);\n}\n\n.thoughts-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--ag-ui-muted);\n cursor: pointer;\n}\n\n.thoughts-toggle::before {\n content: \"\u25BE \";\n}\n\n.thoughts-toggle[aria-expanded=\"false\"]::before {\n content: \"\u25B8 \";\n}\n\n/* A gentle pulse on the label while reasoning is still streaming. */\n.thoughts[data-streaming] .thoughts-label {\n animation: ag-ui-thoughts-pulse 1.4s ease-in-out infinite;\n}\n\n@keyframes ag-ui-thoughts-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n\n.thoughts-body {\n margin: 0;\n padding: 4px 0 4px 10px;\n border-left: 2px solid var(--ag-ui-border);\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n}\n\n.thoughts-body[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .thoughts[data-streaming] .thoughts-label {\n animation: none;\n }\n}\n\n.tool-call {\n align-self: flex-start;\n max-width: 80%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--ag-ui-tool-bg);\n border: 1px solid var(--ag-ui-border);\n color: var(--ag-ui-tool-fg);\n}\n\n.tool-call-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.tool-call-name {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n word-break: break-word;\n}\n\n/* Leading status icon (CARD-1). Empty in the DOM \u2014 the glyph/spinner is drawn\n here from the card's data-status, so it stays themeable. */\n.tool-call-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n font-size: 12px;\n line-height: 1;\n}\n\n/* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */\n.tool-call[data-status=\"pending\"] .tool-call-icon {\n border: 2px solid var(--ag-ui-muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--ag-ui-tool-spin-duration) linear infinite;\n}\n\n@keyframes ag-ui-tool-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Settled: a themeable glyph coloured by outcome. */\n.tool-call[data-status=\"done\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-done);\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-error);\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-declined);\n color: var(--ag-ui-muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tool-call[data-status=\"pending\"] .tool-call-icon {\n animation: none;\n }\n}\n\n/* Inline display mode (CARD-1): the lightest card \u2014 drop the box chrome so the\n status row reads as one line of the answer; the result toggle still expands\n below it. */\n.tool-call[data-display=\"inline\"] {\n max-width: 100%;\n background: transparent;\n border: none;\n padding: 2px 0;\n gap: 2px;\n}\n\n.tool-call-status {\n flex: none;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n background: rgba(127, 127, 127, 0.16);\n color: var(--ag-ui-muted);\n}\n\n.tool-call[data-status=\"done\"] .tool-call-status {\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-status {\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-status {\n color: var(--ag-ui-muted);\n}\n\n.tool-call-args,\n.tool-call-result {\n margin: 0;\n padding: 6px 8px;\n max-height: 160px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--ag-ui-fg);\n}\n\n.tool-call-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-weight: 600;\n color: var(--ag-ui-accent);\n cursor: pointer;\n}\n\n.tool-call-toggle::before {\n content: \"\u25B8 \";\n}\n\n.tool-call-toggle[aria-expanded=\"true\"]::before {\n content: \"\u25BE \";\n}\n\n.input-row {\n display: flex;\n gap: 8px;\n padding: 12px;\n border-top: 1px solid var(--ag-ui-border);\n}\n\n.input {\n flex: 1;\n resize: none;\n background: var(--ag-ui-input-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 10px;\n font: inherit;\n color: inherit;\n outline: none;\n}\n\n.input:focus {\n border-color: var(--ag-ui-accent);\n}\n\n.send {\n border: none;\n border-radius: 8px;\n padding: 0 16px;\n background: var(--ag-ui-accent);\n color: #ffffff;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n\n.send:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n/* The composer button doubles as the Stop control while a run is in flight. */\n.send[data-state=\"running\"] {\n background: var(--ag-ui-muted);\n}\n\n/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* The \uD83D\uDCCE picker button sits left of the input; hidden until data-attachments-url. */\n.attach-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.attach-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.attach-input {\n display: none;\n}\n\n/* The \uD83C\uDFA4 mic button (VOICE-1); shown only once #wireVoice mounts it. */\n.voice-slot {\n display: contents;\n}\n\n.voice-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.voice-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.voice-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Recording: a red tint + a gentle pulse so it's clearly \"live\". */\n.voice-btn[data-state=\"recording\"] {\n border-color: var(--ag-ui-danger);\n background: var(--ag-ui-danger);\n color: #ffffff;\n animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;\n}\n\n@keyframes ag-ui-voice-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .voice-btn[data-state=\"recording\"] {\n animation: none;\n }\n}\n\n/* Pending-attachments tray, above the input row; collapses (hidden) when empty. */\n.attachment-slot {\n display: contents;\n}\n\n.attachment-tray {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 12px 0;\n}\n\n.attachment-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.attachment-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n background: var(--ag-ui-assistant-bg);\n font-size: 0.85em;\n position: relative;\n}\n\n.attachment-chip--error {\n border-color: var(--ag-ui-danger);\n color: var(--ag-ui-danger);\n}\n\n.attachment-chip-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 14ch;\n}\n\n.attachment-chip-size {\n color: var(--ag-ui-muted);\n white-space: nowrap;\n}\n\n.attachment-chip--error .attachment-chip-size {\n color: var(--ag-ui-danger);\n}\n\n/* The progress bar fills as the file uploads. */\n.attachment-chip-bar {\n flex-basis: 100%;\n height: 3px;\n border-radius: 2px;\n background: var(--ag-ui-border);\n overflow: hidden;\n}\n\n.attachment-chip-bar-fill {\n height: 100%;\n background: var(--ag-ui-accent);\n transition: width 0.15s ease;\n}\n\n.attachment-chip-remove,\n.attachment-chip-retry {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n opacity: 0.7;\n}\n\n.attachment-chip-remove:hover,\n.attachment-chip-retry:hover {\n opacity: 1;\n}\n\n/* A subtle outline while a file is dragged over the shell. */\n.chat--dragover {\n outline: 2px dashed var(--ag-ui-accent);\n outline-offset: -4px;\n}\n\n/* Muted \"\u23F9 Stopped\" line after a cancelled run \u2014 a note, not an error bubble. */\n.stopped-note {\n align-self: flex-start;\n color: var(--ag-ui-muted);\n font-size: 12px;\n padding: 2px 4px;\n}\n\n/* Inline confirmation card \u2014 lives in the transcript, no focus-stealing overlay. */\n.confirm {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.confirm[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.confirm-body {\n font-weight: 600;\n}\n\n.confirm-args {\n margin: 0;\n padding: 8px 10px;\n max-height: 140px;\n overflow: auto;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n background: var(--ag-ui-assistant-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.confirm-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-bg);\n color: var(--ag-ui-fg);\n}\n\n.confirm-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.confirm-btn--confirm {\n border-color: var(--ag-ui-accent);\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n/* Skills \u2014 chips row + the /-command palette, above the input. */\n.skill-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 12px;\n}\n\n.skill-chip {\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n cursor: pointer;\n background: var(--ag-ui-assistant-bg);\n color: var(--ag-ui-fg);\n}\n\n.skill-chip:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.skill-palette {\n margin: 8px 12px 0;\n display: flex;\n flex-direction: column;\n max-height: 220px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(20, 20, 50, 0.16);\n}\n\n.skill-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: flex-start;\n padding: 8px 12px;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n cursor: pointer;\n color: var(--ag-ui-fg);\n}\n\n.skill-item[aria-selected=\"true\"] {\n background: var(--ag-ui-assistant-bg);\n}\n\n.skill-item-title {\n font-weight: 600;\n}\n\n.skill-item-desc {\n font-size: 0.85em;\n color: var(--ag-ui-muted);\n}\n\n.skill-hint {\n margin: 8px 12px 0;\n font-size: 0.85em;\n color: var(--ag-ui-danger);\n}\n\n/* Chat-history drawer \u2014 a slide-over within the chat panel. */\n.drawer {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n}\n\n.drawer[hidden] {\n display: none;\n}\n\n.drawer-backdrop {\n position: absolute;\n inset: 0;\n background: rgba(20, 20, 50, 0.32);\n}\n\n.drawer-panel {\n position: relative;\n display: flex;\n flex-direction: column;\n width: min(300px, 85%);\n height: 100%;\n background: var(--ag-ui-bg);\n border-right: 1px solid var(--ag-ui-border);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ag-ui-space);\n padding: var(--ag-ui-pad);\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-title {\n font-weight: 600;\n}\n\n.drawer-new {\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n background: var(--ag-ui-bg);\n color: var(--ag-ui-accent);\n padding: 4px 10px;\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n}\n\n.drawer-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n\n.drawer-empty {\n padding: var(--ag-ui-pad);\n font-size: 0.9em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row {\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-row--active {\n background: var(--ag-ui-assistant-bg);\n}\n\n.drawer-row-select {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.drawer-row-title {\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-time {\n font-size: 0.72em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row-preview {\n font-size: 0.8em;\n color: var(--ag-ui-muted);\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 6px;\n}\n\n.drawer-row-rename,\n.drawer-row-delete {\n border: none;\n background: none;\n color: var(--ag-ui-muted);\n font-size: 0.9em;\n padding: 4px;\n cursor: pointer;\n}\n\n.drawer-rename-input {\n flex: 1;\n min-width: 0;\n margin: 6px 10px;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-accent);\n border-radius: 6px;\n background: var(--ag-ui-input-bg);\n color: var(--ag-ui-fg);\n font: inherit;\n}\n\n.drawer-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 0.85em;\n}\n\n.drawer-confirm-label {\n color: var(--ag-ui-danger);\n}\n\n.drawer-confirm-yes {\n border: none;\n border-radius: 6px;\n background: var(--ag-ui-danger);\n color: #ffffff;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n.drawer-confirm-no {\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n background: none;\n color: inherit;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n/* Embedded placement: an inline, flush side panel rather than a dimmed,\n floating slide-over. */\n:host([placement=\"embedded\"]) .drawer-backdrop {\n background: none;\n}\n\n:host([placement=\"embedded\"]) .drawer-panel {\n width: 100%;\n border-right: none;\n box-shadow: none;\n}\n";
|
|
1
|
+
export declare const STYLES = "\n:host {\n /* Colors */\n --ag-ui-bg: #ffffff;\n --ag-ui-fg: #1a1a2e;\n --ag-ui-accent: #4f46e5;\n --ag-ui-user-bg: #4f46e5;\n --ag-ui-user-fg: #ffffff;\n --ag-ui-assistant-bg: #f1f1f6;\n --ag-ui-input-bg: var(--ag-ui-bg);\n --ag-ui-tool-bg: var(--ag-ui-assistant-bg);\n --ag-ui-tool-fg: var(--ag-ui-accent);\n --ag-ui-header-bg: var(--ag-ui-accent);\n --ag-ui-header-fg: #ffffff;\n --ag-ui-border: #e2e2ec;\n --ag-ui-radius: 12px;\n\n /* Status accents for tool-call cards. */\n --ag-ui-success: #15803d;\n --ag-ui-danger: #b91c1c;\n --ag-ui-muted: #6b7280;\n\n /* Tool-call status icon glyphs (override to re-theme) + spinner speed.\n The pending state is the animated ring; the settled states use these. */\n --ag-ui-tool-icon-done: \"\u2713\";\n --ag-ui-tool-icon-error: \"\u2715\";\n --ag-ui-tool-icon-declined: \"\u2298\";\n --ag-ui-tool-spin-duration: 0.7s;\n\n /* Answer well (opt-in via data-answer-well) \u2014 boxes a whole assistant turn. */\n --ag-ui-well-bg: transparent;\n --ag-ui-well-border: var(--ag-ui-border);\n\n /* Surface \u2014 set --ag-ui-shadow: none for a flush, embedded panel. */\n --ag-ui-shadow: 0 12px 32px rgba(20, 20, 50, 0.18);\n --ag-ui-font: inherit;\n --ag-ui-font-size: 14px;\n --ag-ui-code-font: ui-monospace, \"SF Mono\", Menlo, monospace;\n\n /* Spacing \u2014 the density preset overrides these. */\n --ag-ui-space: 10px;\n --ag-ui-pad: 16px;\n --ag-ui-msg-pad: 8px 12px;\n --ag-ui-msg-radius: 14px;\n\n /* Layout \u2014 override from outside to dock the widget anywhere.\n Set --ag-ui-position: static (and place this element in your own\n grid/flex layout) to embed it in the page flow instead of floating. */\n --ag-ui-position: fixed;\n --ag-ui-z-index: 2147483000;\n --ag-ui-width: 380px;\n --ag-ui-height: 560px;\n --ag-ui-inset: auto 24px 24px auto;\n --ag-ui-max-width: calc(100vw - 48px);\n --ag-ui-max-height: calc(100vh - 48px);\n /* Reading-column width for placement=\"page\" (full-bleed, centred content). */\n --ag-ui-content-max-width: 820px;\n\n position: var(--ag-ui-position);\n inset: var(--ag-ui-inset);\n z-index: var(--ag-ui-z-index);\n width: var(--ag-ui-width);\n max-width: var(--ag-ui-max-width);\n height: var(--ag-ui-height);\n max-height: var(--ag-ui-max-height);\n display: flex;\n font-family: var(--ag-ui-font);\n font-size: var(--ag-ui-font-size);\n color: var(--ag-ui-fg);\n}\n\n/* \u2500\u2500 Themes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Themes only re-set the colour variables; layout/spacing are unaffected.\n theme=\"auto\" follows the OS via prefers-color-scheme. */\n:host([theme=\"dark\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n}\n\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]) {\n --ag-ui-bg: #15151f;\n --ag-ui-fg: #e8e8f2;\n --ag-ui-assistant-bg: #25253a;\n --ag-ui-header-bg: #1f1f30;\n --ag-ui-header-fg: #e8e8f2;\n --ag-ui-border: #33334a;\n --ag-ui-muted: #9aa0b4;\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);\n }\n}\n\n/* A terminal-flavoured \"code\" theme: dark, monospace, green accent. */\n:host([theme=\"code\"]) {\n --ag-ui-bg: #0d1117;\n --ag-ui-fg: #c9d1d9;\n --ag-ui-accent: #3fb950;\n --ag-ui-user-bg: #238636;\n --ag-ui-assistant-bg: #161b22;\n --ag-ui-header-bg: #010409;\n --ag-ui-header-fg: #c9d1d9;\n --ag-ui-border: #30363d;\n --ag-ui-muted: #8b949e;\n --ag-ui-font: var(--ag-ui-code-font);\n --ag-ui-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);\n}\n\n/* \u2500\u2500 Density \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([density=\"compact\"]) {\n --ag-ui-font-size: 13px;\n --ag-ui-space: 6px;\n --ag-ui-pad: 10px;\n --ag-ui-msg-pad: 5px 9px;\n --ag-ui-msg-radius: 10px;\n}\n\n/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([placement=\"bottom-left\"]) {\n --ag-ui-inset: auto auto 24px 24px;\n}\n\n:host([placement=\"side\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"full\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n/* Page: full-bleed background with a centred reading column. Unlike\n \"full\" (edge-to-edge, left-aligned messages) the content sits in a column\n capped at --ag-ui-content-max-width. The column is produced by symmetric auto\n padding on the scroll area + composer (no per-row wrapper), so user pills\n still right-align and the assistant well spans the column. */\n:host([placement=\"page\"]) {\n --ag-ui-inset: 0;\n --ag-ui-width: 100vw;\n --ag-ui-height: 100vh;\n --ag-ui-max-width: 100vw;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n}\n\n:host([placement=\"page\"]) .messages {\n padding-inline: max(var(--ag-ui-pad), calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .input-row {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* The rows between the message list and the composer (skill chips, the\n /-command palette, the missing-placeholder hint, the upload tray) line up\n with the column too \u2014 chips are padding-based, the palette/hint/tray are\n margin-based, so each gets its own inline axis nudged by the same gutter. */\n:host([placement=\"page\"]) .skill-chips,\n:host([placement=\"page\"]) .attachment-tray {\n padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .skill-palette,\n:host([placement=\"page\"]) .skill-hint {\n margin-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));\n}\n\n/* In the reading column the assistant well uses the full width; the user\n message stays a right-aligned pill (its default align-self + max-width). */\n:host([placement=\"page\"]) .message--assistant {\n max-width: 100%;\n}\n\n/* Sidebar: a full-height docked panel that slides open/closed and\n collapses to a slim icon rail (not the floating launcher). Docked right by\n default; data-side=\"left\" docks it left. Overlay by default \u2014 set\n --ag-ui-position: static (and place this element in your own layout) for a\n host-managed push instead. */\n:host([placement=\"sidebar\"]) {\n --ag-ui-inset: 0 0 0 auto;\n --ag-ui-width: 420px;\n --ag-ui-height: 100vh;\n --ag-ui-max-height: 100vh;\n --ag-ui-radius: 0;\n --ag-ui-rail-width: 52px;\n transition: width 0.28s ease;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) {\n --ag-ui-inset: 0 auto 0 0;\n}\n\n:host([placement=\"sidebar\"]) .chat {\n transition: transform 0.28s ease;\n}\n\n/* Collapsed sidebar: shrink the host to the rail width, hide the panel, and\n reveal the rail. Higher specificity than the generic collapse rules, so it\n wins regardless of source order. */\n:host([placement=\"sidebar\"][collapsed]) {\n width: var(--ag-ui-rail-width);\n height: 100vh;\n max-height: 100vh;\n bottom: 0;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .chat {\n display: none;\n}\n\n/* The rail is a sibling of the panel (so it survives the panel being hidden);\n shown only for a collapsed sidebar. */\n.rail {\n display: none;\n border: none;\n font: inherit;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .rail {\n display: flex;\n position: absolute;\n inset: 0;\n align-items: flex-start;\n justify-content: center;\n padding-top: 16px;\n border: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n cursor: pointer;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([placement=\"sidebar\"]),\n :host([placement=\"sidebar\"]) .chat {\n transition: none;\n }\n}\n\n/* Embedded: drop the floating chrome and the high z-index stacking context so\n the widget lives in the host's own layout (fixes overlay/z-index clashes). */\n:host([placement=\"embedded\"]) {\n --ag-ui-position: static;\n --ag-ui-width: 100%;\n --ag-ui-height: 100%;\n --ag-ui-max-width: 100%;\n --ag-ui-max-height: 100%;\n --ag-ui-shadow: none;\n --ag-ui-z-index: auto;\n}\n\n.chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--ag-ui-border);\n background: var(--ag-ui-header-bg);\n color: var(--ag-ui-header-fg);\n}\n\n.header-title {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Header / launcher icon holder: a slot, with a data-icon-url <img>\n fallback, sized via --ag-ui-icon-size. */\n.icon-holder {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--ag-ui-icon-size, 22px);\n height: var(--ag-ui-icon-size, 22px);\n line-height: 1;\n}\n\n.icon-img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n border-radius: var(--ag-ui-icon-radius, 4px);\n}\n\n.header-controls {\n display: flex;\n gap: 2px;\n flex: none;\n}\n\n.header-btn {\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1;\n padding: 4px 7px;\n border-radius: 6px;\n cursor: pointer;\n opacity: 0.85;\n}\n\n.header-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.18);\n}\n\n/* Collapsed: keep just the header bar \u2014 hide the whole body, and let the host\n shrink to the header. (A host can also fully hide the element itself.) */\n:host([collapsed]) {\n height: auto;\n max-height: none;\n}\n\n:host([collapsed]) .messages,\n:host([collapsed]) .input-row,\n:host([collapsed]) .skill-chips,\n:host([collapsed]) .skill-palette,\n:host([collapsed]) .skill-hint {\n display: none;\n}\n\n/* Edge-docked placements pin top *and* bottom, which would otherwise keep the\n panel full-height when collapsed; unpin the bottom so it shrinks to the\n header. (Floating/bottom-left pin only the bottom, so they already shrink.) */\n:host([collapsed][placement=\"side\"]),\n:host([collapsed][placement=\"full\"]) {\n bottom: auto;\n}\n\n.messages {\n flex: 1;\n overflow-y: auto;\n padding: var(--ag-ui-pad);\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n}\n\n/* Empty-state region (slot): centred while it's the only thing in the\n list, hidden as soon as a message, card, or pending indicator renders. */\n.empty {\n margin: auto;\n text-align: center;\n color: var(--ag-ui-muted);\n}\n\n.empty[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Answer group / well \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One .answer per assistant turn wraps the streamed text, its tool cards,\n and the pending indicator so a whole answer reads (and can be boxed) as one\n unit. A flex column on the message-list gap, stretched to the list width so\n its children keep their own left/right alignment. data-answer-well opts into\n the bordered \"well\"; without it the layout is today's flat stack. */\n.answer {\n display: flex;\n flex-direction: column;\n gap: var(--ag-ui-space);\n align-self: stretch;\n min-width: 0;\n}\n\n:host([data-answer-well]) .answer {\n padding: var(--ag-ui-pad);\n background: var(--ag-ui-well-bg);\n border: 1px solid var(--ag-ui-well-border);\n border-radius: var(--ag-ui-radius);\n}\n\n.message {\n max-width: 80%;\n padding: var(--ag-ui-msg-pad);\n border-radius: var(--ag-ui-msg-radius);\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n/* \u2500\u2500 Incoming-text animations (data-text-animation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* .message--restored (rehydrated history) is excluded: entrance animations are\n for freshly-arriving messages, not the whole transcript replaying on reload.\n Word mode is excluded implicitly \u2014 restored bubbles aren't wrapped. */\n:host([data-text-animation=\"fade\"]) .message--assistant:not(.message--restored) {\n animation: ag-ui-msg-fade 0.25s ease both;\n}\n\n@keyframes ag-ui-msg-fade {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n.message--assistant .word {\n animation: ag-ui-word-in 0.3s ease both;\n animation-delay: calc(var(--ag-ui-word-index, 0) * 35ms);\n}\n\n@keyframes ag-ui-word-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([data-text-animation=\"fade\"]) .message--assistant,\n .message--assistant .word {\n animation: none;\n }\n}\n\n.message--user {\n align-self: flex-end;\n background: var(--ag-ui-user-bg);\n color: var(--ag-ui-user-fg);\n border-bottom-right-radius: 4px;\n}\n\n.message--assistant {\n align-self: flex-start;\n background: var(--ag-ui-assistant-bg);\n border-bottom-left-radius: 4px;\n /* Assistant bubbles hold rendered markdown/HTML, so collapse the source\n whitespace the renderer leaves between block tags. */\n white-space: normal;\n}\n\n/* Rendered-markdown elements inside an assistant bubble. */\n.message--assistant > :first-child {\n margin-top: 0;\n}\n\n.message--assistant > :last-child {\n margin-bottom: 0;\n}\n\n.message--assistant p,\n.message--assistant ul,\n.message--assistant ol,\n.message--assistant blockquote,\n.message--assistant pre {\n margin: 0.5em 0;\n}\n\n.message--assistant ul,\n.message--assistant ol {\n padding-left: 1.3em;\n}\n\n.message--assistant a {\n color: var(--ag-ui-accent);\n text-decoration: underline;\n}\n\n.message--assistant code {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n background: rgba(127, 127, 127, 0.16);\n padding: 1px 4px;\n border-radius: 4px;\n}\n\n.message--assistant pre {\n padding: 8px 10px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n}\n\n.message--assistant pre code {\n background: none;\n padding: 0;\n}\n\n.message--assistant blockquote {\n padding-left: 10px;\n border-left: 3px solid var(--ag-ui-border);\n color: var(--ag-ui-muted);\n}\n\n.pending {\n align-self: flex-start;\n display: inline-flex;\n gap: 4px;\n align-items: center;\n padding: 12px 14px;\n background: var(--ag-ui-assistant-bg);\n border-radius: 14px;\n border-bottom-left-radius: 4px;\n}\n\n.pending-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--ag-ui-muted);\n animation: ag-ui-pending 1.2s infinite ease-in-out both;\n}\n\n.pending-dot:nth-child(2) {\n animation-delay: 0.16s;\n}\n\n.pending-dot:nth-child(3) {\n animation-delay: 0.32s;\n}\n\n@keyframes ag-ui-pending {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pending-dot {\n animation: none;\n opacity: 0.6;\n }\n}\n\n/* \u2500\u2500 Thoughts region \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A muted, collapsible chain-of-thought at the top of the answer group: open\n while the model reasons, folded once the answer text starts. */\n.thoughts {\n align-self: stretch;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n color: var(--ag-ui-muted);\n}\n\n.thoughts-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--ag-ui-muted);\n cursor: pointer;\n}\n\n.thoughts-toggle::before {\n content: \"\u25BE \";\n}\n\n.thoughts-toggle[aria-expanded=\"false\"]::before {\n content: \"\u25B8 \";\n}\n\n/* A gentle pulse on the label while reasoning is still streaming. */\n.thoughts[data-streaming] .thoughts-label {\n animation: ag-ui-thoughts-pulse 1.4s ease-in-out infinite;\n}\n\n@keyframes ag-ui-thoughts-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n\n.thoughts-body {\n margin: 0;\n padding: 4px 0 4px 10px;\n border-left: 2px solid var(--ag-ui-border);\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n}\n\n.thoughts-body[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .thoughts[data-streaming] .thoughts-label {\n animation: none;\n }\n}\n\n.tool-call {\n align-self: flex-start;\n max-width: 80%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--ag-ui-tool-bg);\n border: 1px solid var(--ag-ui-border);\n color: var(--ag-ui-tool-fg);\n}\n\n.tool-call-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.tool-call-name {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n word-break: break-word;\n}\n\n/* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn\n here from the card's data-status, so it stays themeable. */\n.tool-call-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n font-size: 12px;\n line-height: 1;\n}\n\n/* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */\n.tool-call[data-status=\"pending\"] .tool-call-icon {\n border: 2px solid var(--ag-ui-muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--ag-ui-tool-spin-duration) linear infinite;\n}\n\n@keyframes ag-ui-tool-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Settled: a themeable glyph coloured by outcome. */\n.tool-call[data-status=\"done\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-done);\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-error);\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-icon::before {\n content: var(--ag-ui-tool-icon-declined);\n color: var(--ag-ui-muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tool-call[data-status=\"pending\"] .tool-call-icon {\n animation: none;\n }\n}\n\n/* Inline display mode: the lightest card \u2014 drop the box chrome so the\n status row reads as one line of the answer; the result toggle still expands\n below it. */\n.tool-call[data-display=\"inline\"] {\n max-width: 100%;\n background: transparent;\n border: none;\n padding: 2px 0;\n gap: 2px;\n}\n\n.tool-call-status {\n flex: none;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n background: rgba(127, 127, 127, 0.16);\n color: var(--ag-ui-muted);\n}\n\n.tool-call[data-status=\"done\"] .tool-call-status {\n color: var(--ag-ui-success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-status {\n color: var(--ag-ui-danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-status {\n color: var(--ag-ui-muted);\n}\n\n.tool-call-args,\n.tool-call-result {\n margin: 0;\n padding: 6px 8px;\n max-height: 160px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--ag-ui-fg);\n}\n\n.tool-call-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-weight: 600;\n color: var(--ag-ui-accent);\n cursor: pointer;\n}\n\n.tool-call-toggle::before {\n content: \"\u25B8 \";\n}\n\n.tool-call-toggle[aria-expanded=\"true\"]::before {\n content: \"\u25BE \";\n}\n\n.input-row {\n display: flex;\n gap: 8px;\n padding: 12px;\n border-top: 1px solid var(--ag-ui-border);\n}\n\n.input {\n flex: 1;\n resize: none;\n background: var(--ag-ui-input-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 10px;\n font: inherit;\n color: inherit;\n outline: none;\n}\n\n.input:focus {\n border-color: var(--ag-ui-accent);\n}\n\n.send {\n border: none;\n border-radius: 8px;\n padding: 0 16px;\n background: var(--ag-ui-accent);\n color: #ffffff;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n\n.send:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n/* The composer button doubles as the Stop control while a run is in flight. */\n.send[data-state=\"running\"] {\n background: var(--ag-ui-muted);\n}\n\n/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* The \uD83D\uDCCE picker button sits left of the input; hidden until data-attachments-url. */\n.attach-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.attach-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.attach-input {\n display: none;\n}\n\n/* The \uD83C\uDFA4 mic button; shown only once #wireVoice mounts it. */\n.voice-slot {\n display: contents;\n}\n\n.voice-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 0 10px;\n background: var(--ag-ui-input-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.voice-btn:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.voice-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Recording: a red tint + a gentle pulse so it's clearly \"live\". */\n.voice-btn[data-state=\"recording\"] {\n border-color: var(--ag-ui-danger);\n background: var(--ag-ui-danger);\n color: #ffffff;\n animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;\n}\n\n@keyframes ag-ui-voice-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .voice-btn[data-state=\"recording\"] {\n animation: none;\n }\n}\n\n/* Pending-attachments tray, above the input row; collapses (hidden) when empty. */\n.attachment-slot {\n display: contents;\n}\n\n.attachment-tray {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 12px 0;\n}\n\n.attachment-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.attachment-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n background: var(--ag-ui-assistant-bg);\n font-size: 0.85em;\n position: relative;\n}\n\n.attachment-chip--error {\n border-color: var(--ag-ui-danger);\n color: var(--ag-ui-danger);\n}\n\n.attachment-chip-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 14ch;\n}\n\n.attachment-chip-size {\n color: var(--ag-ui-muted);\n white-space: nowrap;\n}\n\n.attachment-chip--error .attachment-chip-size {\n color: var(--ag-ui-danger);\n}\n\n/* The progress bar fills as the file uploads. */\n.attachment-chip-bar {\n flex-basis: 100%;\n height: 3px;\n border-radius: 2px;\n background: var(--ag-ui-border);\n overflow: hidden;\n}\n\n.attachment-chip-bar-fill {\n height: 100%;\n background: var(--ag-ui-accent);\n transition: width 0.15s ease;\n}\n\n.attachment-chip-remove,\n.attachment-chip-retry {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n opacity: 0.7;\n}\n\n.attachment-chip-remove:hover,\n.attachment-chip-retry:hover {\n opacity: 1;\n}\n\n/* A subtle outline while a file is dragged over the shell. */\n.chat--dragover {\n outline: 2px dashed var(--ag-ui-accent);\n outline-offset: -4px;\n}\n\n/* Muted \"\u23F9 Stopped\" line after a cancelled run \u2014 a note, not an error bubble. */\n.stopped-note {\n align-self: flex-start;\n color: var(--ag-ui-muted);\n font-size: 12px;\n padding: 2px 4px;\n}\n\n/* Inline confirmation card \u2014 lives in the transcript, no focus-stealing overlay. */\n.confirm {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-accent);\n border-radius: 10px;\n}\n\n.confirm[data-resolved] {\n opacity: 0.7;\n border-color: var(--ag-ui-border);\n}\n\n.confirm-body {\n font-weight: 600;\n}\n\n.confirm-args {\n margin: 0;\n padding: 8px 10px;\n max-height: 140px;\n overflow: auto;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n background: var(--ag-ui-assistant-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.confirm-btn {\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--ag-ui-bg);\n color: var(--ag-ui-fg);\n}\n\n.confirm-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.confirm-btn--confirm {\n border-color: var(--ag-ui-accent);\n background: var(--ag-ui-accent);\n color: #ffffff;\n}\n\n/* Skills \u2014 chips row + the /-command palette, above the input. */\n.skill-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 12px;\n}\n\n.skill-chip {\n border: 1px solid var(--ag-ui-border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n cursor: pointer;\n background: var(--ag-ui-assistant-bg);\n color: var(--ag-ui-fg);\n}\n\n.skill-chip:hover {\n border-color: var(--ag-ui-accent);\n}\n\n.skill-palette {\n margin: 8px 12px 0;\n display: flex;\n flex-direction: column;\n max-height: 220px;\n overflow: auto;\n background: var(--ag-ui-bg);\n border: 1px solid var(--ag-ui-border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(20, 20, 50, 0.16);\n}\n\n.skill-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: flex-start;\n padding: 8px 12px;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n cursor: pointer;\n color: var(--ag-ui-fg);\n}\n\n.skill-item[aria-selected=\"true\"] {\n background: var(--ag-ui-assistant-bg);\n}\n\n.skill-item-title {\n font-weight: 600;\n}\n\n.skill-item-desc {\n font-size: 0.85em;\n color: var(--ag-ui-muted);\n}\n\n.skill-hint {\n margin: 8px 12px 0;\n font-size: 0.85em;\n color: var(--ag-ui-danger);\n}\n\n/* Chat-history drawer \u2014 a slide-over within the chat panel. */\n.drawer {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n}\n\n.drawer[hidden] {\n display: none;\n}\n\n.drawer-backdrop {\n position: absolute;\n inset: 0;\n background: rgba(20, 20, 50, 0.32);\n}\n\n.drawer-panel {\n position: relative;\n display: flex;\n flex-direction: column;\n width: min(300px, 85%);\n height: 100%;\n background: var(--ag-ui-bg);\n border-right: 1px solid var(--ag-ui-border);\n box-shadow: var(--ag-ui-shadow);\n overflow: hidden;\n}\n\n.drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ag-ui-space);\n padding: var(--ag-ui-pad);\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-title {\n font-weight: 600;\n}\n\n.drawer-new {\n border: 1px solid var(--ag-ui-border);\n border-radius: var(--ag-ui-radius);\n background: var(--ag-ui-bg);\n color: var(--ag-ui-accent);\n padding: 4px 10px;\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n}\n\n.drawer-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n\n.drawer-empty {\n padding: var(--ag-ui-pad);\n font-size: 0.9em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row {\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--ag-ui-border);\n}\n\n.drawer-row--active {\n background: var(--ag-ui-assistant-bg);\n}\n\n.drawer-row-select {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.drawer-row-title {\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-time {\n font-size: 0.72em;\n color: var(--ag-ui-muted);\n}\n\n.drawer-row-preview {\n font-size: 0.8em;\n color: var(--ag-ui-muted);\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 6px;\n}\n\n.drawer-row-rename,\n.drawer-row-delete {\n border: none;\n background: none;\n color: var(--ag-ui-muted);\n font-size: 0.9em;\n padding: 4px;\n cursor: pointer;\n}\n\n.drawer-rename-input {\n flex: 1;\n min-width: 0;\n margin: 6px 10px;\n padding: 4px 8px;\n border: 1px solid var(--ag-ui-accent);\n border-radius: 6px;\n background: var(--ag-ui-input-bg);\n color: var(--ag-ui-fg);\n font: inherit;\n}\n\n.drawer-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 0.85em;\n}\n\n.drawer-confirm-label {\n color: var(--ag-ui-danger);\n}\n\n.drawer-confirm-yes {\n border: none;\n border-radius: 6px;\n background: var(--ag-ui-danger);\n color: #ffffff;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n.drawer-confirm-no {\n border: 1px solid var(--ag-ui-border);\n border-radius: 6px;\n background: none;\n color: inherit;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n/* Embedded placement: an inline, flush side panel rather than a dimmed,\n floating slide-over. */\n:host([placement=\"embedded\"]) .drawer-backdrop {\n background: none;\n}\n\n:host([placement=\"embedded\"]) .drawer-panel {\n width: 100%;\n border-right: none;\n box-shadow: none;\n}\n";
|
|
2
2
|
//# sourceMappingURL=styles.d.ts.map
|
package/dist/ui/styles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM,qy+BA6vClB,CAAC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type UiStrings } from "./ui_strings.js";
|
|
2
2
|
/**
|
|
3
3
|
* A muted, collapsible "thinking" region for a reasoning model's streamed
|
|
4
|
-
* chain-of-thought
|
|
4
|
+
* chain-of-thought.
|
|
5
5
|
*
|
|
6
|
-
* Lives at the top of the current answer group (the
|
|
6
|
+
* Lives at the top of the current answer group (the turn container): it
|
|
7
7
|
* opens expanded while the model reasons — {@link stream} replaces its body with
|
|
8
8
|
* the running reasoning buffer — and {@link collapse} folds it away once the
|
|
9
9
|
* answer's first text token arrives, so the thoughts don't crowd the answer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread_drawer.d.ts","sourceRoot":"","sources":["../../src/ui/thread_drawer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;;IACvB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"thread_drawer.d.ts","sourceRoot":"","sources":["../../src/ui/thread_drawer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;;IACvB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;gBAarB,SAAS,EAAE,qBAAqB,EAAE,OAAO,GAAE,SAA8B;IAiDrF,iFAAiF;IACjF,UAAU,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAQpC,MAAM,IAAI,OAAO;IAIjB,IAAI,IAAI,IAAI;IAWZ,KAAK,IAAI,IAAI;IASb,MAAM,IAAI,IAAI;IAsCd,4EAA4E;IAC5E,UAAU,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CA0InE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool_call_card.d.ts","sourceRoot":"","sources":["../../src/ui/tool_call_card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAEtF,sCAAsC;AACtC,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE/E,0EAA0E;AAC1E,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,cAAc,EAAE,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAqBrF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,YAAY;;IACvB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;gBAS/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,GAAE,eAAmC,EACzC,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,SAA8B;IAgDzC,gFAAgF;IAChF,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"tool_call_card.d.ts","sourceRoot":"","sources":["../../src/ui/tool_call_card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAEtF,sCAAsC;AACtC,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE/E,0EAA0E;AAC1E,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,cAAc,EAAE,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAqBrF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,YAAY;;IACvB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;gBAS/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,GAAE,eAAmC,EACzC,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,SAA8B;IAgDzC,gFAAgF;IAChF,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CA0ClD"}
|
package/dist/ui/voice_input.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface VoiceInputOptions {
|
|
|
10
10
|
readonly strings?: UiStrings;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* The composer's voice-input control
|
|
13
|
+
* The composer's voice-input control: a mic button that records via
|
|
14
14
|
* `MediaRecorder`, then POSTs the clip through a {@link TranscribeHandler} and
|
|
15
15
|
* hands the transcript back via `onText`.
|
|
16
16
|
*
|
|
@@ -29,5 +29,13 @@ export declare class VoiceInput {
|
|
|
29
29
|
constructor(options: VoiceInputOptions);
|
|
30
30
|
/** Start recording when idle, stop (and transcribe) when recording. */
|
|
31
31
|
toggle(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Tear the control down — the teardown path when the host element is removed
|
|
34
|
+
* mid-recording. Stops any live `MediaRecorder`, releases the mic tracks (so
|
|
35
|
+
* the browser's recording indicator clears), and suppresses the pending
|
|
36
|
+
* transcription: a disconnected control must not fire `onText` back into a
|
|
37
|
+
* detached element.
|
|
38
|
+
*/
|
|
39
|
+
dispose(): void;
|
|
32
40
|
}
|
|
33
41
|
//# sourceMappingURL=voice_input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice_input.d.ts","sourceRoot":"","sources":["../../src/ui/voice_input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKrE,mDAAmD;AACnD,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,yCAAyC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAU;;IACrB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"voice_input.d.ts","sourceRoot":"","sources":["../../src/ui/voice_input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKrE,mDAAmD;AACnD,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,yCAAyC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAU;;IACrB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;gBAWxB,OAAO,EAAE,iBAAiB;IAgBtC,uEAAuE;IACjE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC7B;;;;;;OAMG;IACH,OAAO,IAAI,IAAI;CAgEhB"}
|
package/dist/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,EAAE,MAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artooi/ag-ui-web-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Framework-free <ag-ui-chat> Web Component over the AG-UI protocol. Drop-in chat sidebar with a pluggable client-side tool registry, DOM driver primitives, animations, and destructive-action confirmation modal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/src/core/ag_ui_chat.ts
CHANGED
|
@@ -68,7 +68,7 @@ export interface ToggleDetail {
|
|
|
68
68
|
/** Per-tab persistence key for the collapsed state (survives MPA reloads). */
|
|
69
69
|
const COLLAPSED_KEY = "ag-ui-chat:collapsed";
|
|
70
70
|
|
|
71
|
-
/** Per-tab persistence key for the built-in theme toggle
|
|
71
|
+
/** Per-tab persistence key for the built-in theme toggle. */
|
|
72
72
|
const THEME_KEY = "ag-ui-chat:theme";
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -259,7 +259,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
259
259
|
readonly #attachButton: HTMLButtonElement;
|
|
260
260
|
readonly #fileInput: HTMLInputElement;
|
|
261
261
|
readonly #attachSlot: HTMLDivElement;
|
|
262
|
-
/** Optional built-in header theme toggle
|
|
262
|
+
/** Optional built-in header theme toggle; shown only with `data-theme-toggle`. */
|
|
263
263
|
readonly #themeToggle: HTMLButtonElement;
|
|
264
264
|
/** The collapsed-sidebar rail (an expand affordance; shown only for `placement="sidebar"`). */
|
|
265
265
|
readonly #rail: HTMLButtonElement;
|
|
@@ -288,18 +288,25 @@ export class AgUiChat extends HTMLElement {
|
|
|
288
288
|
// it; ≤1 ⇒ it arrived at once and the word reveal is appropriate.
|
|
289
289
|
#streamDeltas = 0;
|
|
290
290
|
#pending: HTMLDivElement | null = null;
|
|
291
|
-
// The current assistant turn's grouping container
|
|
291
|
+
// The current assistant turn's grouping container. One `.answer`
|
|
292
292
|
// wraps everything a single answer produces — streamed text, tool cards, the
|
|
293
293
|
// pending indicator — so it can be boxed as one "well" by CSS. Opened on the
|
|
294
294
|
// turn's first run start, closed at settle, so it spans the whole multi-round
|
|
295
295
|
// frontend-tool loop (which is several AG-UI runs), not one run. `null`
|
|
296
296
|
// between turns; user bubbles never enter it.
|
|
297
297
|
#currentGroup: HTMLDivElement | null = null;
|
|
298
|
-
// The current turn's streamed-reasoning region
|
|
298
|
+
// The current turn's streamed-reasoning region, shown at the top of
|
|
299
299
|
// the answer group while a reasoning model thinks and collapsed once the
|
|
300
300
|
// answer's first text token arrives. `null` outside a reasoning turn.
|
|
301
301
|
#thoughts: ThoughtsBlock | null = null;
|
|
302
302
|
#threadId = "";
|
|
303
|
+
// Per-instance suffix for the origin-scoped storage keys (collapsed / theme /
|
|
304
|
+
// active thread), so two instances on one origin don't clobber each other.
|
|
305
|
+
// Empty ⇒ the pre-namespacing global keys (back-compat). Resolved on connect.
|
|
306
|
+
#storageNs = "";
|
|
307
|
+
// Bumped on every #rehydrate; a replay whose generation is stale (a newer
|
|
308
|
+
// thread switch started while it awaited a slow store) drops its result.
|
|
309
|
+
#rehydrateGeneration = 0;
|
|
303
310
|
#initialMessages: readonly Message[] = [];
|
|
304
311
|
// Skill catalog by source; merged backend → embed → client (later wins).
|
|
305
312
|
#backendSkills: readonly Skill[] = [];
|
|
@@ -470,25 +477,34 @@ export class AgUiChat extends HTMLElement {
|
|
|
470
477
|
}
|
|
471
478
|
|
|
472
479
|
connectedCallback(): void {
|
|
480
|
+
// Resolve the per-instance storage namespace (id, else endpoint) before any
|
|
481
|
+
// key read/write, so this instance doesn't share collapsed/theme/thread
|
|
482
|
+
// state with another on the same origin.
|
|
483
|
+
this.#storageNs = this.id !== "" ? this.id : this.endpoint;
|
|
473
484
|
// Resolve the string table before rendering any chrome (defaults are the
|
|
474
485
|
// floor; `data-strings` then the `strings` property layer over them).
|
|
475
486
|
this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
|
|
476
487
|
// Restore a theme the built-in toggle persisted last visit (opt-in only, so
|
|
477
488
|
// it never overrides a host that drives `theme` itself).
|
|
478
489
|
if (this.getAttribute("data-theme-toggle") !== null) {
|
|
479
|
-
const saved =
|
|
490
|
+
const saved = this.#readScopedItem(THEME_KEY);
|
|
480
491
|
if (saved !== null) {
|
|
481
492
|
this.setAttribute("theme", saved);
|
|
482
493
|
}
|
|
483
494
|
}
|
|
484
495
|
this.#render();
|
|
485
496
|
this.#drawer.setStrings(this.#strings);
|
|
486
|
-
if (
|
|
497
|
+
if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
|
|
487
498
|
this.setAttribute("collapsed", "");
|
|
488
499
|
}
|
|
489
500
|
this.#syncRail();
|
|
490
501
|
this.#initSkills();
|
|
491
502
|
void this.#fetchToolCatalog();
|
|
503
|
+
// Namespace the built-in default store too (a host-injected store is used
|
|
504
|
+
// verbatim). Must precede #wireThreadStore, which wraps the current store.
|
|
505
|
+
if (this.#storageNs !== "" && this.conversationStore instanceof SessionStorageStore) {
|
|
506
|
+
this.conversationStore = new SessionStorageStore(this.#storageNs);
|
|
507
|
+
}
|
|
492
508
|
this.#wireThreadStore();
|
|
493
509
|
this.#wireAttachments();
|
|
494
510
|
this.#wireVoice();
|
|
@@ -496,6 +512,20 @@ export class AgUiChat extends HTMLElement {
|
|
|
496
512
|
void this.#rehydrate();
|
|
497
513
|
}
|
|
498
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Tear down live resources when the element leaves the DOM (a removed node, a
|
|
517
|
+
* client-side route swap): cancel the in-flight run so its SSE stream closes,
|
|
518
|
+
* abort any in-flight uploads so they don't orphan server-side files, and
|
|
519
|
+
* release the mic so the browser's recording indicator clears. Without this a
|
|
520
|
+
* removed `<ag-ui-chat>` leaks a streaming request, uploads, and a live
|
|
521
|
+
* `MediaRecorder`.
|
|
522
|
+
*/
|
|
523
|
+
disconnectedCallback(): void {
|
|
524
|
+
this.#cancelRun();
|
|
525
|
+
this.#attachTray?.dispose();
|
|
526
|
+
this.#voice?.dispose();
|
|
527
|
+
}
|
|
528
|
+
|
|
499
529
|
/** Parse the inline `data-strings` JSON overrides (empty when absent/malformed). */
|
|
500
530
|
#readStringOverrides(): Partial<UiStrings> {
|
|
501
531
|
const raw = this.getAttribute("data-strings");
|
|
@@ -544,7 +574,10 @@ export class AgUiChat extends HTMLElement {
|
|
|
544
574
|
if (url === null) {
|
|
545
575
|
return null;
|
|
546
576
|
}
|
|
547
|
-
|
|
577
|
+
// Forward the tray's abort signal so removing a chip (or tearing the
|
|
578
|
+
// element down) cancels the XHR.
|
|
579
|
+
return (file, onProgress, signal) =>
|
|
580
|
+
uploadAttachment(file, { url, headers: this.headers, onProgress, signal });
|
|
548
581
|
}
|
|
549
582
|
|
|
550
583
|
/**
|
|
@@ -770,7 +803,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
770
803
|
} else {
|
|
771
804
|
this.removeAttribute("collapsed");
|
|
772
805
|
}
|
|
773
|
-
sessionStorage.setItem(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
806
|
+
sessionStorage.setItem(this.#storageKey(COLLAPSED_KEY), collapsed ? "1" : "0");
|
|
774
807
|
this.#syncRail();
|
|
775
808
|
this.dispatchEvent(
|
|
776
809
|
new CustomEvent<ToggleDetail>(TOGGLE_EVENT, {
|
|
@@ -795,10 +828,28 @@ export class AgUiChat extends HTMLElement {
|
|
|
795
828
|
toggleTheme(): void {
|
|
796
829
|
const next = this.getAttribute("theme") === "dark" ? "light" : "dark";
|
|
797
830
|
this.setAttribute("theme", next);
|
|
798
|
-
sessionStorage.setItem(THEME_KEY, next);
|
|
831
|
+
sessionStorage.setItem(this.#storageKey(THEME_KEY), next);
|
|
799
832
|
this.#syncThemeGlyph();
|
|
800
833
|
}
|
|
801
834
|
|
|
835
|
+
/** This instance's namespaced form of an origin-scoped storage key. */
|
|
836
|
+
#storageKey(base: string): string {
|
|
837
|
+
return this.#storageNs === "" ? base : `${base}:${this.#storageNs}`;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Read a namespaced origin-scoped value, falling back once to the legacy
|
|
842
|
+
* pre-namespacing global key (left in place) so an existing collapsed/theme
|
|
843
|
+
* preference survives the upgrade.
|
|
844
|
+
*/
|
|
845
|
+
#readScopedItem(base: string): string | null {
|
|
846
|
+
const scoped = sessionStorage.getItem(this.#storageKey(base));
|
|
847
|
+
if (scoped !== null || this.#storageNs === "") {
|
|
848
|
+
return scoped;
|
|
849
|
+
}
|
|
850
|
+
return sessionStorage.getItem(base);
|
|
851
|
+
}
|
|
852
|
+
|
|
802
853
|
/** Reflect the current theme on the toggle: show the destination's glyph. */
|
|
803
854
|
#syncThemeGlyph(): void {
|
|
804
855
|
const dark = this.getAttribute("theme") === "dark";
|
|
@@ -875,7 +926,16 @@ export class AgUiChat extends HTMLElement {
|
|
|
875
926
|
* result from the page we landed on.
|
|
876
927
|
*/
|
|
877
928
|
async #rehydrate(): Promise<void> {
|
|
929
|
+
// Guard against a thread-switch race: with a slow remote store, picking
|
|
930
|
+
// thread B then C would interleave both replays into one transcript. Each
|
|
931
|
+
// rehydrate claims a generation before awaiting and bails if a newer one
|
|
932
|
+
// started meanwhile (its `#resetState` already cleared the transcript).
|
|
933
|
+
this.#rehydrateGeneration += 1;
|
|
934
|
+
const generation = this.#rehydrateGeneration;
|
|
878
935
|
const messages = await this.conversationStore.loadMessages(this.#threadId);
|
|
936
|
+
if (generation !== this.#rehydrateGeneration) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
879
939
|
if (messages !== null) {
|
|
880
940
|
this.#initialMessages = messages;
|
|
881
941
|
for (const message of messages) {
|
|
@@ -973,7 +1033,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
973
1033
|
* stays literal text (no need to parse what the user typed, and it avoids
|
|
974
1034
|
* rendering user-authored markup).
|
|
975
1035
|
*
|
|
976
|
-
* Assistant bubbles land in the current answer group
|
|
1036
|
+
* Assistant bubbles land in the current answer group, opening one if
|
|
977
1037
|
* needed; a user bubble closes the prior group and sits directly in the list
|
|
978
1038
|
* (the well wraps the *assistant* turn, the user message precedes it).
|
|
979
1039
|
*/
|
|
@@ -1060,7 +1120,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
1060
1120
|
collapse.addEventListener("click", () => this.toggleCollapsed());
|
|
1061
1121
|
|
|
1062
1122
|
controls.append(history, newChat);
|
|
1063
|
-
// Optional built-in theme toggle
|
|
1123
|
+
// Optional built-in theme toggle: off unless the host opts in, so
|
|
1064
1124
|
// it never competes with a host-supplied switch in `slot="header-actions"`.
|
|
1065
1125
|
if (this.getAttribute("data-theme-toggle") !== null) {
|
|
1066
1126
|
this.#themeToggle.type = "button";
|
|
@@ -1269,6 +1329,14 @@ export class AgUiChat extends HTMLElement {
|
|
|
1269
1329
|
}
|
|
1270
1330
|
|
|
1271
1331
|
async #submit(): Promise<void> {
|
|
1332
|
+
// Ignore a submit while a run is in flight — the single choke point for
|
|
1333
|
+
// both Enter and the Send button. The button already turns into Stop, but
|
|
1334
|
+
// Enter has no such guard; without this it would start a second concurrent
|
|
1335
|
+
// SSE run that orphans the first (unabortable) and lets the second run's
|
|
1336
|
+
// settle sweep corrupt the first's still-pending tool cards.
|
|
1337
|
+
if (this.#running) {
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1272
1340
|
const content = this.#input.value.trim();
|
|
1273
1341
|
const attachments = this.#attachTray?.readyRefs() ?? [];
|
|
1274
1342
|
// Allow an attachments-only message (no typed text), but nothing empty.
|
package/src/core/agui_client.ts
CHANGED
|
@@ -248,7 +248,7 @@ export class AgUiClient {
|
|
|
248
248
|
return;
|
|
249
249
|
}
|
|
250
250
|
const pending: AgUiToolCall[] = [];
|
|
251
|
-
const runState = { terminal: false };
|
|
251
|
+
const runState = { terminal: false, errored: false };
|
|
252
252
|
await this.#agent.runAgent(
|
|
253
253
|
{ tools: this.#getTools(), context: this.#getContext() },
|
|
254
254
|
this.#buildSubscriber(pending, runState),
|
|
@@ -265,6 +265,13 @@ export class AgUiClient {
|
|
|
265
265
|
if (!runState.terminal) {
|
|
266
266
|
throw new ConnectionLostError(this.#connectionLostMessage);
|
|
267
267
|
}
|
|
268
|
+
// RUN_ERROR is terminal: the agent already reported the failure via
|
|
269
|
+
// onError. Don't execute the tool calls collected before it or start
|
|
270
|
+
// another round — that would run into a broken context and surface a
|
|
271
|
+
// confusing second error. Any pending tool card is swept at onSettled.
|
|
272
|
+
if (runState.errored) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
268
275
|
if (this.#executeTool === null || pending.length === 0) {
|
|
269
276
|
return;
|
|
270
277
|
}
|
|
@@ -294,7 +301,10 @@ export class AgUiClient {
|
|
|
294
301
|
}
|
|
295
302
|
}
|
|
296
303
|
|
|
297
|
-
#buildSubscriber(
|
|
304
|
+
#buildSubscriber(
|
|
305
|
+
pending: AgUiToolCall[],
|
|
306
|
+
runState: { terminal: boolean; errored: boolean },
|
|
307
|
+
): AgentSubscriber {
|
|
298
308
|
const h = this.#handlers;
|
|
299
309
|
return {
|
|
300
310
|
onRunInitialized() {
|
|
@@ -318,7 +328,7 @@ export class AgUiClient {
|
|
|
318
328
|
onToolCallResultEvent({ event }) {
|
|
319
329
|
h.onToolResult(event.toolCallId, event.content);
|
|
320
330
|
},
|
|
321
|
-
// Reasoning
|
|
331
|
+
// Reasoning. `@ag-ui/client` already maps the deprecated
|
|
322
332
|
// THINKING_* events onto these REASONING_* callbacks, so handling the
|
|
323
333
|
// reasoning family alone covers both protocol versions.
|
|
324
334
|
onReasoningStartEvent() {
|
|
@@ -332,6 +342,7 @@ export class AgUiClient {
|
|
|
332
342
|
},
|
|
333
343
|
onRunErrorEvent({ event }) {
|
|
334
344
|
runState.terminal = true;
|
|
345
|
+
runState.errored = true;
|
|
335
346
|
h.onError(event.message);
|
|
336
347
|
},
|
|
337
348
|
onRunFinalized() {
|
package/src/core/attachment.ts
CHANGED
|
@@ -32,8 +32,28 @@ export interface AttachmentRef {
|
|
|
32
32
|
* restored conversation re-renders its attachment chips. The server's strict
|
|
33
33
|
* `RunAgentInput` validation ignores the unknown field — the model learns the
|
|
34
34
|
* ids from the run context manifest instead.
|
|
35
|
+
*
|
|
36
|
+
* The persisted array is untrusted (it can be hand-edited, truncated, or
|
|
37
|
+
* corrupted in storage), so every entry is validated and malformed ones are
|
|
38
|
+
* dropped — a `null` or shapeless entry would otherwise throw in `iconFor` and
|
|
39
|
+
* abort the whole history replay.
|
|
35
40
|
*/
|
|
36
41
|
export function messageAttachments(message: Message): readonly AttachmentRef[] {
|
|
37
42
|
const refs = (message as { attachments?: unknown }).attachments;
|
|
38
|
-
return Array.isArray(refs) ? (
|
|
43
|
+
return Array.isArray(refs) ? refs.filter(isAttachmentRef) : [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Whether an unknown value is a structurally valid {@link AttachmentRef}. */
|
|
47
|
+
function isAttachmentRef(value: unknown): value is AttachmentRef {
|
|
48
|
+
if (typeof value !== "object" || value === null) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const ref = value as Record<string, unknown>;
|
|
52
|
+
return (
|
|
53
|
+
typeof ref["id"] === "string" &&
|
|
54
|
+
typeof ref["name"] === "string" &&
|
|
55
|
+
typeof ref["mime"] === "string" &&
|
|
56
|
+
typeof ref["size"] === "number" &&
|
|
57
|
+
(ref["url"] === undefined || typeof ref["url"] === "string")
|
|
58
|
+
);
|
|
39
59
|
}
|