@artooi/ag-ui-web-component 0.22.0 → 0.23.1
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 +134 -36
- package/README.md +7 -7
- package/dist/ag-ui-web-component.bundle.js +145 -118
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/constants.d.ts +65 -78
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +93 -126
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +24 -30
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/attachment.d.ts +9 -14
- package/dist/core/attachment.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +20 -27
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +13 -15
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +8 -9
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +11 -20
- package/dist/core/run_index.d.ts.map +1 -1
- package/dist/core/transcribe_audio.d.ts +8 -8
- package/dist/core/transcribe_audio.d.ts.map +1 -1
- package/dist/core/upload_attachment.d.ts +15 -18
- package/dist/core/upload_attachment.d.ts.map +1 -1
- package/dist/core/utils.d.ts +4 -6
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +22 -30
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/dom_driver.d.ts +7 -7
- package/dist/dom/native_setter.d.ts +2 -2
- package/dist/dom/native_setter.d.ts.map +1 -1
- package/dist/index.js +347 -401
- package/dist/index.js.map +2 -2
- package/dist/skills/fill_template.d.ts +4 -5
- package/dist/skills/fill_template.d.ts.map +1 -1
- package/dist/skills/parse_skills.d.ts.map +1 -1
- package/dist/skills/skill.d.ts +7 -8
- package/dist/skills/skill.d.ts.map +1 -1
- package/dist/tools/client_tool_registry.d.ts +2 -2
- package/dist/tools/page_action_tools.d.ts +7 -10
- package/dist/tools/page_action_tools.d.ts.map +1 -1
- package/dist/tools/page_state.d.ts +5 -8
- package/dist/tools/page_state.d.ts.map +1 -1
- package/dist/tools/route_map.d.ts +7 -10
- package/dist/tools/route_map.d.ts.map +1 -1
- package/dist/ui/approval_card.d.ts +15 -20
- package/dist/ui/approval_card.d.ts.map +1 -1
- package/dist/ui/attach_copy_buttons.d.ts +4 -10
- package/dist/ui/attach_copy_buttons.d.ts.map +1 -1
- package/dist/ui/attachment_chips.d.ts +13 -5
- package/dist/ui/attachment_chips.d.ts.map +1 -1
- package/dist/ui/attachment_tray.d.ts +6 -6
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/checkpoint_menu.d.ts +7 -8
- package/dist/ui/checkpoint_menu.d.ts.map +1 -1
- package/dist/ui/confirmation_card.d.ts +10 -15
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- package/dist/ui/question_card.d.ts +12 -15
- package/dist/ui/question_card.d.ts.map +1 -1
- package/dist/ui/relative_time.d.ts +5 -7
- package/dist/ui/relative_time.d.ts.map +1 -1
- package/dist/ui/render_markdown.d.ts +8 -8
- package/dist/ui/render_markdown.d.ts.map +1 -1
- package/dist/ui/resize_handle.d.ts +21 -34
- package/dist/ui/resize_handle.d.ts.map +1 -1
- package/dist/ui/run_notice.d.ts +5 -7
- package/dist/ui/run_notice.d.ts.map +1 -1
- package/dist/ui/skills_menu.d.ts +4 -5
- package/dist/ui/skills_menu.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 +9 -11
- package/dist/ui/thoughts_block.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +6 -5
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts +17 -25
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +6 -12
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts +10 -11
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +71 -80
- package/src/core/ag_ui_chat.ts +242 -292
- package/src/core/agui_client.ts +60 -71
- package/src/core/attachment.ts +9 -14
- package/src/core/conversation_store.ts +25 -33
- package/src/core/create_http_agent.ts +18 -22
- package/src/core/remote_conversation_store.ts +14 -15
- package/src/core/run_index.ts +14 -23
- package/src/core/transcribe_audio.ts +9 -10
- package/src/core/upload_attachment.ts +18 -21
- package/src/core/utils.ts +4 -6
- package/src/dom/animations.ts +33 -43
- package/src/dom/dom_driver.ts +7 -7
- package/src/dom/native_setter.ts +11 -12
- package/src/skills/fill_template.ts +4 -5
- package/src/skills/parse_skills.ts +3 -4
- package/src/skills/skill.ts +7 -8
- package/src/tools/client_tool_registry.ts +2 -2
- package/src/tools/page_action_tools.ts +12 -15
- package/src/tools/page_state.ts +5 -8
- package/src/tools/route_map.ts +15 -19
- package/src/ui/approval_card.ts +15 -20
- package/src/ui/attach_copy_buttons.ts +9 -18
- package/src/ui/attachment_chips.ts +19 -10
- package/src/ui/attachment_tray.ts +8 -7
- package/src/ui/checkpoint_menu.ts +7 -8
- package/src/ui/confirmation_card.ts +10 -15
- package/src/ui/question_card.ts +12 -15
- package/src/ui/relative_time.ts +5 -7
- package/src/ui/render_markdown.ts +25 -51
- package/src/ui/resize_handle.ts +25 -38
- package/src/ui/run_notice.ts +9 -12
- package/src/ui/skills_menu.ts +4 -5
- package/src/ui/styles.ts +122 -93
- package/src/ui/thoughts_block.ts +11 -13
- package/src/ui/thread_drawer.ts +6 -5
- package/src/ui/tool_call_card.ts +22 -32
- package/src/ui/ui_strings.ts +6 -12
- package/src/ui/voice_input.ts +10 -11
- package/src/version.ts +1 -1
package/src/ui/styles.ts
CHANGED
|
@@ -8,12 +8,11 @@ export const STYLES = `
|
|
|
8
8
|
only the alias is used by the rules below.
|
|
9
9
|
|
|
10
10
|
The indirection is what makes ancestor theming work. Declaring the public
|
|
11
|
-
name on :host would set it
|
|
12
|
-
always beats one inherited from an ancestor
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
inherited normally while a value aimed at the element still wins over it.
|
|
11
|
+
name on :host would set it on the host element, and a value on an element
|
|
12
|
+
always beats one inherited from an ancestor, so tokens put on a wrapper
|
|
13
|
+
would have no effect. Reading the public name with the default as a var()
|
|
14
|
+
fallback leaves it undeclared on the element, so an ancestor's value
|
|
15
|
+
inherits normally while one aimed at the element still wins.
|
|
17
16
|
|
|
18
17
|
Two invariants hold this together:
|
|
19
18
|
1. No rule outside this file's :host blocks may reference a public name
|
|
@@ -40,11 +39,9 @@ export const STYLES = `
|
|
|
40
39
|
--_border: var(--ag-ui-border, #e2e2ec);
|
|
41
40
|
--_radius: var(--ag-ui-radius, 12px);
|
|
42
41
|
|
|
43
|
-
/* Body text and raised chrome
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
transparent box. The defaults below restate exactly that, so this is a
|
|
47
|
-
rename with no repaint — see the note on .code-copy. */
|
|
42
|
+
/* Body text and raised chrome, read only by the code-block copy button.
|
|
43
|
+
The defaults restate what it inherits, so a host that sets neither sees
|
|
44
|
+
no repaint — see the note on .code-copy. */
|
|
48
45
|
--_text: var(--ag-ui-text, var(--_fg));
|
|
49
46
|
--_surface: var(--ag-ui-surface, transparent);
|
|
50
47
|
|
|
@@ -97,9 +94,9 @@ export const STYLES = `
|
|
|
97
94
|
--_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);
|
|
98
95
|
|
|
99
96
|
/* Motion. One duration and two curves drive every collapse, expand and
|
|
100
|
-
slide-over, so the
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
slide-over, so the widget accelerates and settles as one thing. The
|
|
98
|
+
default curve decelerates into place; the pop curve overshoots slightly,
|
|
99
|
+
for something arriving. */
|
|
103
100
|
--_motion: var(--ag-ui-motion, 0.28s);
|
|
104
101
|
--_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));
|
|
105
102
|
--_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));
|
|
@@ -123,7 +120,7 @@ export const STYLES = `
|
|
|
123
120
|
/* Reading-column width for placement="page" (full-bleed, centred content). */
|
|
124
121
|
--_content-max-width: var(--ag-ui-content-max-width, 820px);
|
|
125
122
|
/* Slim rail the sidebar placement collapses to. Only that placement reads
|
|
126
|
-
it, but it is declared here so
|
|
123
|
+
it, but it is declared here so every alias has a default in one place. */
|
|
127
124
|
--_rail-width: var(--ag-ui-rail-width, 52px);
|
|
128
125
|
|
|
129
126
|
position: var(--_position);
|
|
@@ -215,11 +212,11 @@ export const STYLES = `
|
|
|
215
212
|
--_radius: var(--ag-ui-radius, 0);
|
|
216
213
|
}
|
|
217
214
|
|
|
218
|
-
/* Page: full-bleed background with a centred reading column
|
|
219
|
-
"full"
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
215
|
+
/* Page: full-bleed background with a centred reading column capped at
|
|
216
|
+
--ag-ui-content-max-width, where "full" is edge-to-edge and left-aligned.
|
|
217
|
+
The column comes from symmetric auto padding on the scroll area and
|
|
218
|
+
composer rather than a per-row wrapper, so user pills still right-align and
|
|
219
|
+
the assistant well spans the column. */
|
|
223
220
|
:host([placement="page"]) {
|
|
224
221
|
--_inset: var(--ag-ui-inset, 0);
|
|
225
222
|
--_width: var(--ag-ui-width, 100vw);
|
|
@@ -237,9 +234,8 @@ export const STYLES = `
|
|
|
237
234
|
padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));
|
|
238
235
|
}
|
|
239
236
|
|
|
240
|
-
/* The rows between the message list and the composer
|
|
241
|
-
|
|
242
|
-
with the column too — chips are padding-based, the palette/hint/tray are
|
|
237
|
+
/* The rows between the message list and the composer line up with the column
|
|
238
|
+
too. Chips and tray are padding-based while palette and hint are
|
|
243
239
|
margin-based, so each gets its own inline axis nudged by the same gutter. */
|
|
244
240
|
:host([placement="page"]) .skill-chips,
|
|
245
241
|
:host([placement="page"]) .attachment-tray {
|
|
@@ -348,13 +344,12 @@ export const STYLES = `
|
|
|
348
344
|
visibility var(--_motion) var(--_ease);
|
|
349
345
|
}
|
|
350
346
|
|
|
351
|
-
/* The launcher grows out of the corner the panel shrank into.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
must stay reachable. */
|
|
347
|
+
/* The launcher grows out of the corner the panel shrank into. It stays laid
|
|
348
|
+
out at rest rather than display:none, which is what lets it animate both in
|
|
349
|
+
and out: an unrendered element has no before-change style to transition
|
|
350
|
+
from, and one flipping display to none cannot transition at all. visibility
|
|
351
|
+
keeps it unpaintable, untabbable and unclickable in between, so the expanded
|
|
352
|
+
panel's own controls underneath stay reachable. */
|
|
358
353
|
:host([collapsed]) .launcher {
|
|
359
354
|
opacity: 1;
|
|
360
355
|
transform: none;
|
|
@@ -515,20 +510,18 @@ export const STYLES = `
|
|
|
515
510
|
|
|
516
511
|
/* ── Collapse ───────────────────────────────────────────────────────────────
|
|
517
512
|
Collapsing shrinks the widget to the round floating launcher: the panel
|
|
518
|
-
scales
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
The host box keeps its expanded size
|
|
523
|
-
a dragged --ag-ui-width would
|
|
524
|
-
paints there once the panel is gone, so the box only has to stop
|
|
525
|
-
clicks: pointer events go to none
|
|
526
|
-
|
|
527
|
-
Two placements collapse
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
floating circle would escape the layout, and page is a full-screen route
|
|
531
|
-
with no corner to float in. */
|
|
513
|
+
scales toward the launcher's corner and fades, the launcher pops in from the
|
|
514
|
+
same point. Both halves are transform and opacity only, so the morph runs on
|
|
515
|
+
the compositor and never reflows the host page.
|
|
516
|
+
|
|
517
|
+
The host box keeps its expanded size, since animating it would animate
|
|
518
|
+
layout and a dragged --ag-ui-width would fight the launcher's own size.
|
|
519
|
+
Nothing paints there once the panel is gone, so the box only has to stop
|
|
520
|
+
swallowing clicks: pointer events go to none and the launcher takes them.
|
|
521
|
+
|
|
522
|
+
Two placements collapse differently: "sidebar" slides to its rail (below),
|
|
523
|
+
while "embedded" and "page" keep the header bar, having no corner for a
|
|
524
|
+
floating circle that would escape the host's layout. */
|
|
532
525
|
:host([collapsed]) {
|
|
533
526
|
pointer-events: none;
|
|
534
527
|
}
|
|
@@ -539,11 +532,11 @@ export const STYLES = `
|
|
|
539
532
|
visibility: hidden;
|
|
540
533
|
}
|
|
541
534
|
|
|
542
|
-
/* visibility
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
535
|
+
/* visibility keeps the panel out of the tab order and the a11y tree at rest
|
|
536
|
+
without display:none killing the transition. It interpolates so any progress
|
|
537
|
+
below 1 still counts as visible: the panel stays on screen for the whole
|
|
538
|
+
collapse, flips hidden exactly at the end, and on expand is visible from the
|
|
539
|
+
first frame. */
|
|
547
540
|
.chat {
|
|
548
541
|
transform-origin: bottom right;
|
|
549
542
|
transition:
|
|
@@ -570,10 +563,9 @@ export const STYLES = `
|
|
|
570
563
|
visibility: visible;
|
|
571
564
|
}
|
|
572
565
|
|
|
573
|
-
/* These two keep the header bar, so the launcher must stay out of the way
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
whatever the page happens to position. */
|
|
566
|
+
/* These two keep the header bar, so the launcher must stay out of the way: an
|
|
567
|
+
embedded host is position: static, which would let an absolutely-positioned
|
|
568
|
+
circle escape the layout and land against whatever the page positions. */
|
|
577
569
|
:host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
|
|
578
570
|
visibility: hidden;
|
|
579
571
|
opacity: 0;
|
|
@@ -613,7 +605,7 @@ export const STYLES = `
|
|
|
613
605
|
and the pending indicator so a whole answer reads (and can be boxed) as one
|
|
614
606
|
unit. A flex column on the message-list gap, stretched to the list width so
|
|
615
607
|
its children keep their own left/right alignment. data-answer-well opts into
|
|
616
|
-
the bordered "well"; without it the
|
|
608
|
+
the bordered "well"; without it the turn renders as a flat stack. */
|
|
617
609
|
.answer {
|
|
618
610
|
display: flex;
|
|
619
611
|
flex-direction: column;
|
|
@@ -739,13 +731,9 @@ export const STYLES = `
|
|
|
739
731
|
position: relative;
|
|
740
732
|
}
|
|
741
733
|
|
|
742
|
-
/*
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
back to the initial transparent, and the hover/copied colour to the
|
|
746
|
-
inherited body colour. The defaults chosen upstream (transparent, and the
|
|
747
|
-
body foreground) reproduce that, so the rename repaints nothing. A raised
|
|
748
|
-
surface behind the button is a separate design question. */
|
|
734
|
+
/* The only reader of --ag-ui-surface and --ag-ui-text. Their defaults are
|
|
735
|
+
transparent and the body foreground, which is what this button rendered as
|
|
736
|
+
before either token existed; changing them repaints only this control. */
|
|
749
737
|
.code-copy {
|
|
750
738
|
position: absolute;
|
|
751
739
|
top: 4px;
|
|
@@ -791,12 +779,10 @@ export const STYLES = `
|
|
|
791
779
|
color: var(--_muted);
|
|
792
780
|
}
|
|
793
781
|
|
|
794
|
-
/* Markdown tables. table/thead/tbody/tr/th/td are all in
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
width-constrained, so without this the columns either crush or push the
|
|
799
|
-
layout sideways. */
|
|
782
|
+
/* Markdown tables. table/thead/tbody/tr/th/td are all in the sanitizer's
|
|
783
|
+
ALLOWED_TAGS, so an agent can emit one. A wide table must scroll inside its
|
|
784
|
+
own box rather than stretch the message: the bubble is width-constrained, so
|
|
785
|
+
without this the columns either crush or push the layout sideways. */
|
|
800
786
|
.message--assistant table {
|
|
801
787
|
display: block;
|
|
802
788
|
width: fit-content;
|
|
@@ -1053,8 +1039,6 @@ export const STYLES = `
|
|
|
1053
1039
|
color: var(--_muted);
|
|
1054
1040
|
}
|
|
1055
1041
|
|
|
1056
|
-
/* The record of a human decision on a gated call. An approved call used to
|
|
1057
|
-
look exactly like one that was never gated. */
|
|
1058
1042
|
.skill-item-token {
|
|
1059
1043
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
1060
1044
|
font-size: 0.92em;
|
|
@@ -1062,6 +1046,8 @@ export const STYLES = `
|
|
|
1062
1046
|
margin-right: 6px;
|
|
1063
1047
|
}
|
|
1064
1048
|
|
|
1049
|
+
/* The lasting record of a human decision on a gated call — without it an
|
|
1050
|
+
approved call looks exactly like one that was never gated. */
|
|
1065
1051
|
.tool-call-decision {
|
|
1066
1052
|
flex: none;
|
|
1067
1053
|
font-size: 11px;
|
|
@@ -1084,10 +1070,8 @@ export const STYLES = `
|
|
|
1084
1070
|
}
|
|
1085
1071
|
|
|
1086
1072
|
/* Display modes are pure visibility over one DOM shape, selected from the host
|
|
1087
|
-
attribute rather than a value stamped on the card
|
|
1088
|
-
|
|
1089
|
-
re-read it, the way data-answer-well behaves. Baking the structure per mode
|
|
1090
|
-
meant the setting only reached cards created afterwards.
|
|
1073
|
+
attribute rather than a value stamped on the card at build time, so flipping
|
|
1074
|
+
data-tool-display re-styles cards already on screen. See ToolCallCard.
|
|
1091
1075
|
|
|
1092
1076
|
Default (no attribute) is the full mode: arguments always visible, result
|
|
1093
1077
|
behind the toggle. */
|
|
@@ -1184,20 +1168,19 @@ export const STYLES = `
|
|
|
1184
1168
|
}
|
|
1185
1169
|
|
|
1186
1170
|
/* Then the measurement corrects it. Which edges are held still belongs to the
|
|
1187
|
-
host's layout
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
embedded panel its host right-aligns. */
|
|
1171
|
+
host's layout, not to placement -- a floating panel a host right-aligns is
|
|
1172
|
+
anchored bottom-left -- so the element measures them and stamps a single
|
|
1173
|
+
hyphenated "<y>-<x>" token here. Equal specificity to the rules above, so
|
|
1174
|
+
source order is what lets the measured value win.
|
|
1175
|
+
|
|
1176
|
+
Two traps, both of which silently draw the grip on the corner that moves:
|
|
1177
|
+
|
|
1178
|
+
Never write these as [data-resize-anchor~="left"]. The stamped value is one
|
|
1179
|
+
hyphenated token and ~= matches whitespace-separated words, so it can never
|
|
1180
|
+
match.
|
|
1181
|
+
|
|
1182
|
+
Each rule must set both sides of its axis, not only the side it moves, or it
|
|
1183
|
+
cannot undo a placement guess that flipped the other way. */
|
|
1201
1184
|
:host([data-resize-anchor$="-left"]) .resize-handle {
|
|
1202
1185
|
left: auto;
|
|
1203
1186
|
right: 0;
|
|
@@ -1258,10 +1241,9 @@ export const STYLES = `
|
|
|
1258
1241
|
}
|
|
1259
1242
|
|
|
1260
1243
|
/* ── Composer ───────────────────────────────────────────────────────────────
|
|
1261
|
-
One surface owns the border, the background and the focus ring; the field
|
|
1262
|
-
its tool row sit inside it
|
|
1263
|
-
textarea's height
|
|
1264
|
-
and turned Send into a full-height slab. */
|
|
1244
|
+
One surface owns the border, the background and the focus ring; the field
|
|
1245
|
+
and its tool row sit inside it, rather than being siblings stretched to the
|
|
1246
|
+
textarea's height. */
|
|
1265
1247
|
.input-row {
|
|
1266
1248
|
display: flex;
|
|
1267
1249
|
padding: 12px;
|
|
@@ -1454,11 +1436,25 @@ export const STYLES = `
|
|
|
1454
1436
|
display: contents;
|
|
1455
1437
|
}
|
|
1456
1438
|
|
|
1439
|
+
/* The padding is the only separation the tray gets: its slot is display:
|
|
1440
|
+
contents, so the tray is a direct child of the .chat column, and that column
|
|
1441
|
+
sets no gap. The bottom value keeps a chip off the composer's top edge.
|
|
1442
|
+
The inline value here is only the default: the page placement overrides
|
|
1443
|
+
padding-inline further down to compute its reading-column gutter, and wins
|
|
1444
|
+
on specificity whichever way this declaration is written. Read the two
|
|
1445
|
+
together before changing either. */
|
|
1457
1446
|
.attachment-tray {
|
|
1458
1447
|
display: flex;
|
|
1459
1448
|
flex-wrap: wrap;
|
|
1460
1449
|
gap: 6px;
|
|
1461
|
-
padding: 8px 12px
|
|
1450
|
+
padding: 8px 12px;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/* The tray sets the hidden property while empty, and an author display beats
|
|
1454
|
+
the UA stylesheet's rule for it, so without this the empty tray keeps
|
|
1455
|
+
laying out and its padding is permanent dead space above the composer. */
|
|
1456
|
+
.attachment-tray[hidden] {
|
|
1457
|
+
display: none;
|
|
1462
1458
|
}
|
|
1463
1459
|
|
|
1464
1460
|
.attachment-chips {
|
|
@@ -1494,15 +1490,28 @@ export const STYLES = `
|
|
|
1494
1490
|
overflow-wrap: anywhere;
|
|
1495
1491
|
}
|
|
1496
1492
|
|
|
1493
|
+
/* A chip carries its own text colour because it carries its own background.
|
|
1494
|
+
The same chip renders in two places with opposite inherited colours: the
|
|
1495
|
+
composer tray gives it the panel's, a sent user bubble gives it the user
|
|
1496
|
+
foreground, which is white on the stock light theme and near-invisible
|
|
1497
|
+
against the chip. Pairing the colour with the background it belongs to makes
|
|
1498
|
+
both placements read alike. Overridden below for an errored chip, which
|
|
1499
|
+
keeps its red. */
|
|
1497
1500
|
.attachment-chip {
|
|
1498
1501
|
display: inline-flex;
|
|
1499
1502
|
align-items: center;
|
|
1500
1503
|
gap: 6px;
|
|
1504
|
+
/* Without this the 100% below caps the content box, so the chip still
|
|
1505
|
+
overflows its container by its own padding and border. Invisible while a
|
|
1506
|
+
character cap kept names short; routine once the container is what bounds
|
|
1507
|
+
the name. */
|
|
1508
|
+
box-sizing: border-box;
|
|
1501
1509
|
max-width: 100%;
|
|
1502
1510
|
padding: 4px 8px;
|
|
1503
1511
|
border: 1px solid var(--_border);
|
|
1504
1512
|
border-radius: 999px;
|
|
1505
1513
|
background: var(--_assistant-bg);
|
|
1514
|
+
color: var(--_text);
|
|
1506
1515
|
font-size: 0.85em;
|
|
1507
1516
|
position: relative;
|
|
1508
1517
|
}
|
|
@@ -1512,11 +1521,31 @@ export const STYLES = `
|
|
|
1512
1521
|
color: var(--_danger);
|
|
1513
1522
|
}
|
|
1514
1523
|
|
|
1524
|
+
/* The type mark. Painted from currentColor, so it carries the chip's state
|
|
1525
|
+
with it and turns red along with an errored one; muted by opacity rather
|
|
1526
|
+
than a colour, which is what keeps that true. Sized from the chip's own text
|
|
1527
|
+
rather than --ag-ui-glyph-size, the way an icon holder's glyph takes the
|
|
1528
|
+
holder's size: the composer's 18px buttons would make a chip button-height. */
|
|
1529
|
+
.attachment-chip-icon {
|
|
1530
|
+
display: inline-flex;
|
|
1531
|
+
flex: none;
|
|
1532
|
+
opacity: 0.75;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
.attachment-chip-icon .glyph {
|
|
1536
|
+
width: 1.25em;
|
|
1537
|
+
height: 1.25em;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/* No character cap: the chip is already max-width 100%, so its container is
|
|
1541
|
+
what bounds the name, and a fixed cap only truncated names the chip had room
|
|
1542
|
+
for. min-width: 0 is what lets the flex item shrink past its content, so the
|
|
1543
|
+
ellipsis appears at the container edge instead of the chip overflowing. */
|
|
1515
1544
|
.attachment-chip-name {
|
|
1516
1545
|
overflow: hidden;
|
|
1517
1546
|
text-overflow: ellipsis;
|
|
1518
1547
|
white-space: nowrap;
|
|
1519
|
-
|
|
1548
|
+
min-width: 0;
|
|
1520
1549
|
}
|
|
1521
1550
|
|
|
1522
1551
|
.attachment-chip-size {
|
package/src/ui/thoughts_block.ts
CHANGED
|
@@ -4,15 +4,13 @@ import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
|
|
|
4
4
|
* A muted, collapsible "thinking" region for a reasoning model's streamed
|
|
5
5
|
* chain-of-thought.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* The header toggle lets the reader reopen it.
|
|
7
|
+
* Sits at the top of the current answer group. It opens expanded while the
|
|
8
|
+
* model reasons, {@link stream} replacing its body with the running buffer, and
|
|
9
|
+
* {@link collapse} folds it away once the answer's first text token arrives.
|
|
10
|
+
* The header toggle reopens it.
|
|
12
11
|
*
|
|
13
|
-
* Pure DOM
|
|
14
|
-
*
|
|
15
|
-
* chrome text is sourced from {@link UiStrings}.
|
|
12
|
+
* Pure DOM. The host inserts {@link element}; all visible chrome text comes
|
|
13
|
+
* from {@link UiStrings}.
|
|
16
14
|
*/
|
|
17
15
|
export class ThoughtsBlock {
|
|
18
16
|
/** The block's root element; insert this at the top of the answer group. */
|
|
@@ -30,8 +28,8 @@ export class ThoughtsBlock {
|
|
|
30
28
|
this.element = document.createElement("div");
|
|
31
29
|
this.element.className = "thoughts";
|
|
32
30
|
this.element.setAttribute("part", "thoughts");
|
|
33
|
-
//
|
|
34
|
-
//
|
|
31
|
+
// Lets CSS animate the header while the model is still reasoning; dropped
|
|
32
|
+
// on collapse.
|
|
35
33
|
this.element.setAttribute("data-streaming", "");
|
|
36
34
|
|
|
37
35
|
this.#toggle = document.createElement("button");
|
|
@@ -63,9 +61,9 @@ export class ThoughtsBlock {
|
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
/**
|
|
66
|
-
* Fold the region away
|
|
67
|
-
*
|
|
68
|
-
*
|
|
64
|
+
* Fold the region away when the answer's first text token arrives, flipping
|
|
65
|
+
* the header label to its settled form. Idempotent, since the per-token text
|
|
66
|
+
* handler calls it repeatedly.
|
|
69
67
|
*/
|
|
70
68
|
collapse(): void {
|
|
71
69
|
if (this.#collapsed) {
|
package/src/ui/thread_drawer.ts
CHANGED
|
@@ -17,13 +17,14 @@ export interface ThreadDrawerCallbacks {
|
|
|
17
17
|
/**
|
|
18
18
|
* The chat-history drawer: a slide-over listing the user's threads (title,
|
|
19
19
|
* relative time, preview), with select / new / rename / delete actions and an
|
|
20
|
-
* empty state. Pure DOM
|
|
20
|
+
* empty state. Pure DOM, like {@link SkillsMenu}: the host appends
|
|
21
21
|
* {@link element}, toggles it, feeds rows via {@link setThreads}, and acts on
|
|
22
|
-
* the callbacks.
|
|
23
|
-
* callback the host updates the store and calls {@link setThreads} to refresh.
|
|
22
|
+
* the callbacks.
|
|
24
23
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* A view only — it never mutates the store. After a callback the host updates
|
|
25
|
+
* the store and calls {@link setThreads} to refresh. All visible text comes
|
|
26
|
+
* from {@link UiStrings}; {@link setStrings} re-localizes a drawer built before
|
|
27
|
+
* its strings resolved.
|
|
27
28
|
*/
|
|
28
29
|
export class ThreadDrawer {
|
|
29
30
|
/** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
|
package/src/ui/tool_call_card.ts
CHANGED
|
@@ -41,29 +41,24 @@ function formatPayload(text: string): string {
|
|
|
41
41
|
/**
|
|
42
42
|
* A live tool-call card for the chat transcript.
|
|
43
43
|
*
|
|
44
|
-
* Construction renders a status icon, the tool name, a
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* the previous compact layout ran `args: {...}` and the result together in one
|
|
49
|
-
* `<pre>`, leaving no way to see where the call ended and the answer began.
|
|
44
|
+
* Construction renders a status icon, the tool name, a status pill, and a body
|
|
45
|
+
* of two separately-headed regions — arguments and result — each with its own
|
|
46
|
+
* `part`. {@link settle} fills in the result and flips the pill. Both payloads
|
|
47
|
+
* are pretty-printed and never concatenated into one block.
|
|
50
48
|
*
|
|
51
|
-
*
|
|
52
|
-
* shows
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* Visibility is selected from the host attribute rather than a value copied onto
|
|
57
|
-
* the card at construction, for the same reason.
|
|
49
|
+
* The card renders one DOM shape in every display mode and lets CSS decide what
|
|
50
|
+
* shows, selecting visibility from the host attribute rather than a value
|
|
51
|
+
* copied onto the card at construction. That is what lets `data-tool-display`
|
|
52
|
+
* be flipped on the host and re-read by every card already on screen; building
|
|
53
|
+
* a different structure per mode would leave existing cards stale.
|
|
58
54
|
*
|
|
59
|
-
* The leading icon carries no text
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
55
|
+
* The leading icon carries no text: the shadow CSS draws its glyph or spinner
|
|
56
|
+
* from the card's `data-status`, so a host themes it through the
|
|
57
|
+
* `--ag-ui-tool-icon-*` custom properties or the `tool-card-icon` part without
|
|
58
|
+
* the card reaching into the host stylesheet.
|
|
63
59
|
*
|
|
64
|
-
* Pure DOM
|
|
65
|
-
*
|
|
66
|
-
* `tool-card*` `part`s. All visible text is sourced from {@link UiStrings}.
|
|
60
|
+
* Pure DOM. The host appends {@link element} into its shadow root; all visible
|
|
61
|
+
* text comes from {@link UiStrings}.
|
|
67
62
|
*/
|
|
68
63
|
export class ToolCallCard {
|
|
69
64
|
/** The card's root element; append this into the message list. */
|
|
@@ -97,9 +92,8 @@ export class ToolCallCard {
|
|
|
97
92
|
head.className = "tool-call-head";
|
|
98
93
|
head.setAttribute("part", "tool-card-head");
|
|
99
94
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// the glyphs stay themeable (`--ag-ui-tool-icon-*`) and the spin is real.
|
|
95
|
+
// Left empty in the DOM on purpose: the shadow CSS draws the spinner or
|
|
96
|
+
// settled mark from `data-status`, keeping the glyphs themeable.
|
|
103
97
|
const icon = document.createElement("span");
|
|
104
98
|
icon.className = "tool-call-icon";
|
|
105
99
|
icon.setAttribute("part", "tool-card-icon");
|
|
@@ -125,16 +119,15 @@ export class ToolCallCard {
|
|
|
125
119
|
|
|
126
120
|
const argsSection = this.#section("args", strings.argumentsLabel);
|
|
127
121
|
argsSection.body.textContent = JSON.stringify(args, null, 2);
|
|
128
|
-
//
|
|
129
|
-
// which is a frame around nothing. Drop the region instead.
|
|
122
|
+
// Drop the region rather than frame an empty object.
|
|
130
123
|
argsSection.root.hidden = Object.keys(args).length === 0;
|
|
131
124
|
|
|
132
125
|
const resultSection = this.#section("result", strings.resultLabel);
|
|
133
126
|
this.#resultSection = resultSection.root;
|
|
134
127
|
this.#resultLabel = resultSection.label;
|
|
135
128
|
this.#resultBody = resultSection.body;
|
|
136
|
-
// Nothing to show until `settle` supplies it
|
|
137
|
-
//
|
|
129
|
+
// Nothing to show until `settle` supplies it, or a pending card expands
|
|
130
|
+
// onto an empty region.
|
|
138
131
|
resultSection.root.hidden = true;
|
|
139
132
|
|
|
140
133
|
this.#toggle = document.createElement("button");
|
|
@@ -155,11 +148,8 @@ export class ToolCallCard {
|
|
|
155
148
|
|
|
156
149
|
/**
|
|
157
150
|
* Record that a human approved or declined this call, as a line in the card.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* result saying so, while an approved one simply ran, making the transcript
|
|
161
|
-
* of a gated call byte-identical to one that was never gated. The prompt is
|
|
162
|
-
* gone once answered, so this is where the decision lives.
|
|
151
|
+
* The prompt disappears once answered, so this is the only lasting trace that
|
|
152
|
+
* the call was gated at all.
|
|
163
153
|
*/
|
|
164
154
|
recordDecision(kind: "approved" | "declined"): void {
|
|
165
155
|
this.element.setAttribute("data-decision", kind);
|
package/src/ui/ui_strings.ts
CHANGED
|
@@ -5,14 +5,9 @@
|
|
|
5
5
|
* or its `data-strings` JSON attribute) and the rest fall back to the English
|
|
6
6
|
* defaults.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* This module is one cohesive unit — the `UiStrings` shape, its `DEFAULT_UI_STRINGS`
|
|
13
|
-
* constant-like backing, and the {@link mergeUiStrings} merge over those defaults —
|
|
14
|
-
* so it bends the one-symbol-per-file rule the way `tool_call_card.ts` (class +
|
|
15
|
-
* its types) and `route_map.ts` (factory + its types) already do.
|
|
8
|
+
* Some values are templates carrying `{token}` placeholders the call site fills
|
|
9
|
+
* in; the token names are noted on each key, and a translation must keep them
|
|
10
|
+
* verbatim.
|
|
16
11
|
*/
|
|
17
12
|
export interface UiStrings {
|
|
18
13
|
// ── Header ────────────────────────────────────────────────────────────────
|
|
@@ -27,10 +22,9 @@ export interface UiStrings {
|
|
|
27
22
|
/** Expand affordance (the launcher, and the sidebar rail). */
|
|
28
23
|
expand: string;
|
|
29
24
|
/**
|
|
30
|
-
* The launcher's label while unread answers
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* needs.
|
|
25
|
+
* The launcher's label while unread answers wait behind it. Token: `{count}`.
|
|
26
|
+
* Replaces {@link expand} rather than appending to it, so a translation can
|
|
27
|
+
* order the two parts as its language needs.
|
|
34
28
|
*/
|
|
35
29
|
expandUnread: string;
|
|
36
30
|
/** Built-in header theme toggle (light ⇄ dark). */
|
package/src/ui/voice_input.ts
CHANGED
|
@@ -20,13 +20,13 @@ export interface VoiceInputOptions {
|
|
|
20
20
|
* `MediaRecorder`, then POSTs the clip through a {@link TranscribeHandler} and
|
|
21
21
|
* hands the transcript back via `onText`.
|
|
22
22
|
*
|
|
23
|
-
* Click to start recording
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* Click to start recording, which prompts for mic permission, and again to stop
|
|
24
|
+
* and transcribe. The button reflects its `idle` / `recording` / `transcribing`
|
|
25
|
+
* state on `data-state` for theming and is exposed as `part="voice-button"`; a
|
|
26
|
+
* capture or transcription failure returns it to idle and surfaces the message
|
|
27
|
+
* on its tooltip.
|
|
28
28
|
*
|
|
29
|
-
* Pure DOM
|
|
29
|
+
* Pure DOM. The host mounts {@link element} in the input row.
|
|
30
30
|
*/
|
|
31
31
|
export class VoiceInput {
|
|
32
32
|
/** The mic button; mount this in the composer. */
|
|
@@ -102,11 +102,10 @@ export class VoiceInput {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* Tear the control down
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* detached element.
|
|
105
|
+
* Tear the control down, for a host element removed mid-recording. Stops any
|
|
106
|
+
* live `MediaRecorder`, releases the mic tracks so the browser's recording
|
|
107
|
+
* indicator clears, and suppresses the pending transcription — a
|
|
108
|
+
* disconnected control must not fire `onText` into a detached element.
|
|
110
109
|
*/
|
|
111
110
|
dispose(): void {
|
|
112
111
|
this.#disposed = true;
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.23.1";
|