@artooi/ag-ui-web-component 0.2.0 → 0.2.2
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 +55 -1
- package/README.md +15 -5
- package/dist/ag-ui-web-component.bundle.js +28 -25
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/core/ag_ui_chat.d.ts +9 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +13 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/index.js +118 -23
- package/dist/index.js.map +2 -2
- package/dist/tools/route_map.d.ts.map +1 -1
- package/dist/tools/state_hook.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/ag_ui_chat.ts +116 -18
- package/src/core/agui_client.ts +18 -0
- package/src/tools/route_map.ts +3 -2
- package/src/tools/state_hook.ts +12 -3
- package/src/ui/styles.ts +4 -1
- package/src/version.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route_map.d.ts","sourceRoot":"","sources":["../../src/tools/route_map.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,gEAAgE;AAChE,MAAM,WAAW,KAAK;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qDAAqD;AACrD,MAAM,MAAM,QAAQ,GAAG,SAAS,KAAK,EAAE,CAAC;AAExC,yEAAyE;AACzE,MAAM,WAAW,eAAgB,SAAQ,KAAK;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAmDD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,QAAQ,EAC3B,WAAW,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,GACjD,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"route_map.d.ts","sourceRoot":"","sources":["../../src/tools/route_map.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,gEAAgE;AAChE,MAAM,WAAW,KAAK;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qDAAqD;AACrD,MAAM,MAAM,QAAQ,GAAG,SAAS,KAAK,EAAE,CAAC;AAExC,yEAAyE;AACzE,MAAM,WAAW,eAAgB,SAAQ,KAAK;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAmDD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,QAAQ,EAC3B,WAAW,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,GACjD,UAAU,EAAE,CA6Cd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state_hook.d.ts","sourceRoot":"","sources":["../../src/tools/state_hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAC5D,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"state_hook.d.ts","sourceRoot":"","sources":["../../src/tools/state_hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAC5D,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,EAAE,CA4BlE"}
|
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 /* 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\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/* 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 font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\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.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:host([data-text-animation=\"fade\"]) .message--assistant {\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.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 font-weight: 600;\n word-break: break-word;\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/* 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";
|
|
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 /* 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\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/* 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 font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\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.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.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 font-weight: 600;\n word-break: break-word;\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/* 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";
|
|
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,4veAqnBlB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artooi/ag-ui-web-component",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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
|
@@ -149,9 +149,25 @@ export class AgUiChat extends HTMLElement {
|
|
|
149
149
|
*/
|
|
150
150
|
skillContext: () => Record<string, unknown> = () => ({});
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Friendly display labels for tool-call cards, keyed by tool name (e.g.
|
|
154
|
+
* `{ list_projects: "Search projects" }`). Used as a fallback when a tool has
|
|
155
|
+
* no `x-summary` in its own schema — chiefly **server-side tools** (drf-mcp,
|
|
156
|
+
* `@tool` registry), whose schema never reaches the browser (AG-UI streams
|
|
157
|
+
* only the tool-call name). Client tools should prefer `x-summary` on their
|
|
158
|
+
* schema; this map is the seam for everything else.
|
|
159
|
+
*/
|
|
160
|
+
toolSummaries: Record<string, string> = {};
|
|
161
|
+
|
|
152
162
|
readonly #toolRegistry = new ClientToolRegistry();
|
|
153
163
|
/** Tool-call cards awaiting execution, keyed by call id. */
|
|
154
164
|
readonly #toolCards = new Map<string, ToolCallCard>();
|
|
165
|
+
/**
|
|
166
|
+
* Call ids whose card was already settled from a streamed server-side result
|
|
167
|
+
* (`TOOL_CALL_RESULT`), so the post-run executeTool sweep doesn't overwrite
|
|
168
|
+
* the real output with the generic "executed on the server" fallback.
|
|
169
|
+
*/
|
|
170
|
+
readonly #serverSettled = new Set<string>();
|
|
155
171
|
readonly #root: ShadowRoot;
|
|
156
172
|
readonly #chat: HTMLDivElement;
|
|
157
173
|
readonly #messages: HTMLDivElement;
|
|
@@ -163,6 +179,10 @@ export class AgUiChat extends HTMLElement {
|
|
|
163
179
|
|
|
164
180
|
#client: AgUiClient | null = null;
|
|
165
181
|
#streamingBubble: HTMLDivElement | null = null;
|
|
182
|
+
// Text deltas applied to the current streaming bubble. >1 ⇒ the message
|
|
183
|
+
// revealed progressively as it streamed, so the word reveal must not re-animate
|
|
184
|
+
// it; ≤1 ⇒ it arrived at once and the word reveal is appropriate.
|
|
185
|
+
#streamDeltas = 0;
|
|
166
186
|
#pending: HTMLDivElement | null = null;
|
|
167
187
|
#threadId = "";
|
|
168
188
|
#initialMessages: readonly Message[] = [];
|
|
@@ -233,7 +253,12 @@ export class AgUiChat extends HTMLElement {
|
|
|
233
253
|
description:
|
|
234
254
|
"Read the current page's structure (fields, buttons, route). Call after " +
|
|
235
255
|
"acting to observe the result within the same turn.",
|
|
236
|
-
parameters: {
|
|
256
|
+
parameters: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {},
|
|
259
|
+
required: [],
|
|
260
|
+
[X_SUMMARY_KEY]: "Read the page",
|
|
261
|
+
},
|
|
237
262
|
handler: () => getPageMap(),
|
|
238
263
|
},
|
|
239
264
|
];
|
|
@@ -410,6 +435,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
410
435
|
this.#streamingBubble = null;
|
|
411
436
|
this.#hidePending();
|
|
412
437
|
this.#toolCards.clear();
|
|
438
|
+
this.#serverSettled.clear();
|
|
413
439
|
this.#initialMessages = [];
|
|
414
440
|
this.#messages.replaceChildren();
|
|
415
441
|
this.#threadId = this.conversationStore.threadId();
|
|
@@ -435,18 +461,62 @@ export class AgUiChat extends HTMLElement {
|
|
|
435
461
|
}
|
|
436
462
|
}
|
|
437
463
|
|
|
438
|
-
/**
|
|
464
|
+
/**
|
|
465
|
+
* Replay a restored message: text bubbles *and* tool activity. An assistant
|
|
466
|
+
* turn may carry `toolCalls` (rendered as cards) and/or text; a `tool` turn
|
|
467
|
+
* carries a result that settles the matching card. So a refreshed page shows
|
|
468
|
+
* the full transcript — tool calls and their results — not just the prose.
|
|
469
|
+
*/
|
|
439
470
|
#renderHistoricMessage(message: Message): void {
|
|
440
|
-
|
|
471
|
+
const text = typeof message.content === "string" ? message.content : "";
|
|
472
|
+
if (message.role === MESSAGE_ROLE.USER) {
|
|
473
|
+
if (text !== "") {
|
|
474
|
+
this.appendMessage(MESSAGE_ROLE.USER, text);
|
|
475
|
+
}
|
|
441
476
|
return;
|
|
442
477
|
}
|
|
443
|
-
if (message.role === MESSAGE_ROLE.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
478
|
+
if (message.role === MESSAGE_ROLE.ASSISTANT) {
|
|
479
|
+
if (text !== "") {
|
|
480
|
+
// Restored history must appear statically — entrance animations
|
|
481
|
+
// (fade / word) are for freshly-arriving messages. On reload the whole
|
|
482
|
+
// transcript mounts at once, so animating every bubble's text in
|
|
483
|
+
// parallel looks wrong. Mark it so the fade CSS skips it, and don't
|
|
484
|
+
// wrap words.
|
|
485
|
+
this.appendMessage(MESSAGE_ROLE.ASSISTANT, text).classList.add("message--restored");
|
|
486
|
+
}
|
|
487
|
+
const toolCalls = message.toolCalls;
|
|
488
|
+
if (toolCalls !== undefined) {
|
|
489
|
+
for (const call of toolCalls) {
|
|
490
|
+
this.#cardFor({
|
|
491
|
+
id: call.id,
|
|
492
|
+
name: call.function.name,
|
|
493
|
+
args: this.#parseArgs(call.function.arguments),
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (message.role === "tool") {
|
|
500
|
+
const card = this.#toolCards.get(message.toolCallId);
|
|
501
|
+
if (card !== undefined) {
|
|
502
|
+
card.settle(TOOL_CALL_STATUS.DONE, message.content);
|
|
503
|
+
}
|
|
447
504
|
}
|
|
448
505
|
}
|
|
449
506
|
|
|
507
|
+
/** Parse a tool call's JSON `arguments` string from history into an object. */
|
|
508
|
+
#parseArgs(raw: string): Record<string, unknown> {
|
|
509
|
+
try {
|
|
510
|
+
const parsed: unknown = JSON.parse(raw);
|
|
511
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
512
|
+
return parsed as Record<string, unknown>;
|
|
513
|
+
}
|
|
514
|
+
} catch {
|
|
515
|
+
// Malformed history — fall back to empty args rather than failing replay.
|
|
516
|
+
}
|
|
517
|
+
return {};
|
|
518
|
+
}
|
|
519
|
+
|
|
450
520
|
/**
|
|
451
521
|
* Word-by-word reveal for the `word` text-animation mode, applied to a
|
|
452
522
|
* completed assistant bubble. `fade` is pure CSS (no JS); `none` is a no-op.
|
|
@@ -638,9 +708,16 @@ export class AgUiChat extends HTMLElement {
|
|
|
638
708
|
this.#toolCards.delete(call.id);
|
|
639
709
|
const tool = this.#resolveTool(call.name);
|
|
640
710
|
if (tool === null) {
|
|
641
|
-
// A server-side tool
|
|
642
|
-
|
|
643
|
-
|
|
711
|
+
// Not a client tool. A server-side tool's real output arrives via
|
|
712
|
+
// `onToolResult` (TOOL_CALL_RESULT) and already settled the card — only
|
|
713
|
+
// fall back when it didn't. When no result ever arrived, the call wasn't
|
|
714
|
+
// executed by either side (no handler, no server result), so say so
|
|
715
|
+
// honestly rather than claiming server execution. We do NOT show the
|
|
716
|
+
// pending indicator: nothing here triggers another client round, so it
|
|
717
|
+
// would hang after the run ended.
|
|
718
|
+
if (!this.#serverSettled.has(call.id)) {
|
|
719
|
+
card.settle(TOOL_CALL_STATUS.DONE, "No result returned.");
|
|
720
|
+
}
|
|
644
721
|
return null;
|
|
645
722
|
}
|
|
646
723
|
if (await this.#needsConfirmation(call, tool)) {
|
|
@@ -699,15 +776,31 @@ export class AgUiChat extends HTMLElement {
|
|
|
699
776
|
onTextDelta: (buffer) => {
|
|
700
777
|
this.#hidePending();
|
|
701
778
|
this.#streamInto(buffer);
|
|
779
|
+
this.#streamDeltas += 1;
|
|
702
780
|
},
|
|
703
781
|
onTextEnd: (buffer) => {
|
|
704
|
-
this.#
|
|
782
|
+
const bubble = this.#streamInto(buffer);
|
|
783
|
+
// Only reveal word-by-word when the message arrived at once. If it
|
|
784
|
+
// streamed across multiple deltas it already revealed progressively, so
|
|
785
|
+
// wrapping it now would re-animate the whole message — the awkward
|
|
786
|
+
// "finished response replays one word at a time" bug.
|
|
787
|
+
if (this.#streamDeltas <= 1) {
|
|
788
|
+
this.#revealWords(bubble);
|
|
789
|
+
}
|
|
705
790
|
this.#streamingBubble = null;
|
|
706
791
|
},
|
|
707
792
|
onToolCall: (call) => {
|
|
708
793
|
this.#hidePending();
|
|
709
794
|
this.#cardFor(call);
|
|
710
795
|
},
|
|
796
|
+
onToolResult: (toolCallId, content) => {
|
|
797
|
+
const card = this.#toolCards.get(toolCallId);
|
|
798
|
+
if (card === undefined) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
card.settle(TOOL_CALL_STATUS.DONE, content);
|
|
802
|
+
this.#serverSettled.add(toolCallId);
|
|
803
|
+
},
|
|
711
804
|
onRunEnd: () => {
|
|
712
805
|
this.#hidePending();
|
|
713
806
|
this.#send.disabled = false;
|
|
@@ -719,6 +812,12 @@ export class AgUiChat extends HTMLElement {
|
|
|
719
812
|
this.#send.disabled = false;
|
|
720
813
|
this.#streamingBubble = null;
|
|
721
814
|
},
|
|
815
|
+
onSettled: () => {
|
|
816
|
+
// Terminal guarantee: whatever path ended the run, return to rest.
|
|
817
|
+
this.#hidePending();
|
|
818
|
+
this.#send.disabled = false;
|
|
819
|
+
this.#streamingBubble = null;
|
|
820
|
+
},
|
|
722
821
|
};
|
|
723
822
|
}
|
|
724
823
|
|
|
@@ -754,6 +853,7 @@ export class AgUiChat extends HTMLElement {
|
|
|
754
853
|
#streamInto(buffer: string): HTMLDivElement {
|
|
755
854
|
if (this.#streamingBubble === null) {
|
|
756
855
|
this.#streamingBubble = this.appendMessage(MESSAGE_ROLE.ASSISTANT, "");
|
|
856
|
+
this.#streamDeltas = 0;
|
|
757
857
|
}
|
|
758
858
|
this.#streamingBubble.innerHTML = renderMarkdown(buffer);
|
|
759
859
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
@@ -771,13 +871,11 @@ export class AgUiChat extends HTMLElement {
|
|
|
771
871
|
if (existing !== undefined) {
|
|
772
872
|
return existing;
|
|
773
873
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
typeof summary === "string" ? summary : undefined,
|
|
780
|
-
);
|
|
874
|
+
// Prefer the tool's own `x-summary`; fall back to the `toolSummaries` map
|
|
875
|
+
// for server-side tools whose schema never reached the browser.
|
|
876
|
+
const labelled = this.#resolveTool(call.name)?.parameters[X_SUMMARY_KEY];
|
|
877
|
+
const summary = typeof labelled === "string" ? labelled : this.toolSummaries[call.name];
|
|
878
|
+
const card = new ToolCallCard(call.name, call.args, this.toolDisplay, summary);
|
|
781
879
|
this.#toolCards.set(call.id, card);
|
|
782
880
|
this.#messages.appendChild(card.element);
|
|
783
881
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
package/src/core/agui_client.ts
CHANGED
|
@@ -45,8 +45,21 @@ export interface AgUiClientHandlers {
|
|
|
45
45
|
onTextEnd(buffer: string): void;
|
|
46
46
|
/** Fired when the agent finishes calling a tool (server- or frontend-side). */
|
|
47
47
|
onToolCall(call: AgUiToolCall): void;
|
|
48
|
+
/**
|
|
49
|
+
* Fired when a server-side tool's result streams back (AG-UI's
|
|
50
|
+
* `TOOL_CALL_RESULT`). Frontend tools don't emit this — the client supplies
|
|
51
|
+
* their result itself — so this is the channel for server-executed output.
|
|
52
|
+
*/
|
|
53
|
+
onToolResult(toolCallId: string, content: string): void;
|
|
48
54
|
onRunEnd(): void;
|
|
49
55
|
onError(message: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Fired exactly once when the whole interaction settles — after the run loop
|
|
58
|
+
* ends for any reason (a server-only round, frontend-tool rounds exhausted,
|
|
59
|
+
* or an error). The terminal guarantee that the UI returns to rest (pending
|
|
60
|
+
* indicator cleared, input re-enabled) no matter how the run finished.
|
|
61
|
+
*/
|
|
62
|
+
onSettled(): void;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
/**
|
|
@@ -140,6 +153,8 @@ export class AgUiClient {
|
|
|
140
153
|
await this.#runLoop();
|
|
141
154
|
} catch (error) {
|
|
142
155
|
this.#handlers.onError(error instanceof Error ? error.message : String(error));
|
|
156
|
+
} finally {
|
|
157
|
+
this.#handlers.onSettled();
|
|
143
158
|
}
|
|
144
159
|
}
|
|
145
160
|
|
|
@@ -201,6 +216,9 @@ export class AgUiClient {
|
|
|
201
216
|
pending.push(call);
|
|
202
217
|
h.onToolCall(call);
|
|
203
218
|
},
|
|
219
|
+
onToolCallResultEvent({ event }) {
|
|
220
|
+
h.onToolResult(event.toolCallId, event.content);
|
|
221
|
+
},
|
|
204
222
|
onRunErrorEvent({ event }) {
|
|
205
223
|
h.onError(event.message);
|
|
206
224
|
},
|
package/src/tools/route_map.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X_NAVIGATES_KEY } from "../constants.js";
|
|
1
|
+
import { X_NAVIGATES_KEY, X_SUMMARY_KEY } from "../constants.js";
|
|
2
2
|
import type { ClientTool } from "./client_tool_registry.js";
|
|
3
3
|
|
|
4
4
|
/** A single navigable route the host declares for the agent. */
|
|
@@ -100,7 +100,7 @@ export function createRouteTools(
|
|
|
100
100
|
description:
|
|
101
101
|
"List the routes the app can navigate to. Each route's `pathParams` " +
|
|
102
102
|
"names the dynamic segments to pass as `params` to `navigate_to_route`.",
|
|
103
|
-
parameters: { type: "object", properties: {}, required: [] },
|
|
103
|
+
parameters: { type: "object", properties: {}, required: [], [X_SUMMARY_KEY]: "List pages" },
|
|
104
104
|
handler: (): RouteWithParams[] =>
|
|
105
105
|
getRouteMap().map((route) => ({ ...route, pathParams: pathParamNames(route.path) })),
|
|
106
106
|
},
|
|
@@ -117,6 +117,7 @@ export function createRouteTools(
|
|
|
117
117
|
},
|
|
118
118
|
required: ["route_id"],
|
|
119
119
|
[X_NAVIGATES_KEY]: true,
|
|
120
|
+
[X_SUMMARY_KEY]: "Navigate",
|
|
120
121
|
},
|
|
121
122
|
handler: (args) => {
|
|
122
123
|
const routeId = args["route_id"];
|
package/src/tools/state_hook.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X_DESTRUCTIVE_KEY } from "../constants.js";
|
|
1
|
+
import { X_DESTRUCTIVE_KEY, X_SUMMARY_KEY } from "../constants.js";
|
|
2
2
|
import type { ClientTool } from "./client_tool_registry.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -27,7 +27,12 @@ export function createStateHookTools(hook: StateHook): ClientTool[] {
|
|
|
27
27
|
{
|
|
28
28
|
name: `read_${hook.name}`,
|
|
29
29
|
description: `Read the "${hook.name}" state.`,
|
|
30
|
-
parameters: {
|
|
30
|
+
parameters: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {},
|
|
33
|
+
required: [],
|
|
34
|
+
[X_SUMMARY_KEY]: `Read ${hook.name}`,
|
|
35
|
+
},
|
|
31
36
|
handler: () => hook.read(),
|
|
32
37
|
},
|
|
33
38
|
];
|
|
@@ -36,7 +41,11 @@ export function createStateHookTools(hook: StateHook): ClientTool[] {
|
|
|
36
41
|
tools.push({
|
|
37
42
|
name: `set_${hook.name}`,
|
|
38
43
|
description: `Update the "${hook.name}" state.`,
|
|
39
|
-
parameters: {
|
|
44
|
+
parameters: {
|
|
45
|
+
...(hook.schema ?? { type: "object" }),
|
|
46
|
+
[X_DESTRUCTIVE_KEY]: true,
|
|
47
|
+
[X_SUMMARY_KEY]: `Update ${hook.name}`,
|
|
48
|
+
},
|
|
40
49
|
handler: (args) => write(args),
|
|
41
50
|
});
|
|
42
51
|
}
|
package/src/ui/styles.ts
CHANGED
|
@@ -241,7 +241,10 @@ export const STYLES = `
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/* ── Incoming-text animations (data-text-animation) ─────────────────────── */
|
|
244
|
-
|
|
244
|
+
/* .message--restored (rehydrated history) is excluded: entrance animations are
|
|
245
|
+
for freshly-arriving messages, not the whole transcript replaying on reload.
|
|
246
|
+
Word mode is excluded implicitly — restored bubbles aren't wrapped. */
|
|
247
|
+
:host([data-text-animation="fade"]) .message--assistant:not(.message--restored) {
|
|
245
248
|
animation: ag-ui-msg-fade 0.25s ease both;
|
|
246
249
|
}
|
|
247
250
|
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.2.
|
|
1
|
+
export const VERSION: string = "0.2.2";
|