@artooi/ag-ui-web-component 0.44.0 → 0.46.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.
Files changed (187) hide show
  1. package/CHANGELOG.md +182 -1
  2. package/README.md +245 -32
  3. package/dist/ag-ui-web-component.bundle.js +3696 -2798
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +4 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +28 -8
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/read_flag.d.ts +11 -0
  10. package/dist/core/read_flag.d.ts.map +1 -0
  11. package/dist/core/read_json_attribute.d.ts +15 -0
  12. package/dist/core/read_json_attribute.d.ts.map +1 -0
  13. package/dist/index.js +5084 -3481
  14. package/dist/index.js.map +4 -4
  15. package/dist/tools/tool_catalog.d.ts +5 -0
  16. package/dist/tools/tool_catalog.d.ts.map +1 -1
  17. package/dist/tools/tool_dispatch.d.ts.map +1 -1
  18. package/dist/ui/charts/chart_styles.d.ts +2 -0
  19. package/dist/ui/charts/chart_styles.d.ts.map +1 -0
  20. package/dist/ui/composer/attachment_styles.d.ts +2 -0
  21. package/dist/ui/composer/attachment_styles.d.ts.map +1 -0
  22. package/dist/ui/composer/build_composer.d.ts +30 -0
  23. package/dist/ui/composer/build_composer.d.ts.map +1 -0
  24. package/dist/ui/composer/composer_styles.d.ts +2 -0
  25. package/dist/ui/composer/composer_styles.d.ts.map +1 -0
  26. package/dist/ui/composer/skill_styles.d.ts +2 -0
  27. package/dist/ui/composer/skill_styles.d.ts.map +1 -0
  28. package/dist/ui/history/history_styles.d.ts +2 -0
  29. package/dist/ui/history/history_styles.d.ts.map +1 -0
  30. package/dist/ui/interrupts/approval_styles.d.ts +2 -0
  31. package/dist/ui/interrupts/approval_styles.d.ts.map +1 -0
  32. package/dist/ui/interrupts/confirmation_styles.d.ts +2 -0
  33. package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -0
  34. package/dist/ui/interrupts/question_styles.d.ts +2 -0
  35. package/dist/ui/interrupts/question_styles.d.ts.map +1 -0
  36. package/dist/ui/placement/collapse_styles.d.ts +2 -0
  37. package/dist/ui/placement/collapse_styles.d.ts.map +1 -0
  38. package/dist/ui/placement/embedded_styles.d.ts +2 -0
  39. package/dist/ui/placement/embedded_styles.d.ts.map +1 -0
  40. package/dist/ui/placement/launcher_styles.d.ts +2 -0
  41. package/dist/ui/placement/launcher_styles.d.ts.map +1 -0
  42. package/dist/ui/placement/page_styles.d.ts +2 -0
  43. package/dist/ui/placement/page_styles.d.ts.map +1 -0
  44. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  45. package/dist/ui/placement/preset_styles.d.ts +2 -0
  46. package/dist/ui/placement/preset_styles.d.ts.map +1 -0
  47. package/dist/ui/placement/resize_styles.d.ts +2 -0
  48. package/dist/ui/placement/resize_styles.d.ts.map +1 -0
  49. package/dist/ui/placement/sidebar_styles.d.ts +2 -0
  50. package/dist/ui/placement/sidebar_styles.d.ts.map +1 -0
  51. package/dist/ui/placement/small_viewport_styles.d.ts +2 -0
  52. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -0
  53. package/dist/ui/placement/surface_for.d.ts +74 -0
  54. package/dist/ui/placement/surface_for.d.ts.map +1 -0
  55. package/dist/ui/progress/run_notice_styles.d.ts +2 -0
  56. package/dist/ui/progress/run_notice_styles.d.ts.map +1 -0
  57. package/dist/ui/progress/thoughts_styles.d.ts +2 -0
  58. package/dist/ui/progress/thoughts_styles.d.ts.map +1 -0
  59. package/dist/ui/progress/tool_call_card.d.ts +8 -0
  60. package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
  61. package/dist/ui/progress/tool_group_styles.d.ts +2 -0
  62. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
  63. package/dist/ui/progress/tool_step_group.d.ts +47 -0
  64. package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
  65. package/dist/ui/progress/tool_step_styles.d.ts +2 -0
  66. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -0
  67. package/dist/ui/shell/adopt_styles.d.ts +9 -4
  68. package/dist/ui/shell/adopt_styles.d.ts.map +1 -1
  69. package/dist/ui/shell/build_header.d.ts +40 -0
  70. package/dist/ui/shell/build_header.d.ts.map +1 -0
  71. package/dist/ui/shell/build_launcher.d.ts +20 -0
  72. package/dist/ui/shell/build_launcher.d.ts.map +1 -0
  73. package/dist/ui/shell/frame_styles.d.ts +2 -0
  74. package/dist/ui/shell/frame_styles.d.ts.map +1 -0
  75. package/dist/ui/shell/theme_styles.d.ts +2 -0
  76. package/dist/ui/shell/theme_styles.d.ts.map +1 -0
  77. package/dist/ui/shell/token_styles.d.ts +2 -0
  78. package/dist/ui/shell/token_styles.d.ts.map +1 -0
  79. package/dist/ui/styles.d.ts +1 -1
  80. package/dist/ui/styles.d.ts.map +1 -1
  81. package/dist/ui/themes/classic_styles.d.ts +2 -0
  82. package/dist/ui/themes/classic_styles.d.ts.map +1 -0
  83. package/dist/ui/themes/classic_tokens.d.ts +2 -0
  84. package/dist/ui/themes/classic_tokens.d.ts.map +1 -0
  85. package/dist/ui/themes/code_tokens.d.ts +2 -0
  86. package/dist/ui/themes/code_tokens.d.ts.map +1 -0
  87. package/dist/ui/themes/ledger_styles.d.ts +2 -0
  88. package/dist/ui/themes/ledger_styles.d.ts.map +1 -0
  89. package/dist/ui/themes/ledger_tokens.d.ts +2 -0
  90. package/dist/ui/themes/ledger_tokens.d.ts.map +1 -0
  91. package/dist/ui/themes/modern_styles.d.ts +2 -0
  92. package/dist/ui/themes/modern_styles.d.ts.map +1 -0
  93. package/dist/ui/themes/quiet_styles.d.ts +2 -0
  94. package/dist/ui/themes/quiet_styles.d.ts.map +1 -0
  95. package/dist/ui/themes/scheme_styles.d.ts +2 -0
  96. package/dist/ui/themes/scheme_styles.d.ts.map +1 -0
  97. package/dist/ui/themes/soft_styles.d.ts +2 -0
  98. package/dist/ui/themes/soft_styles.d.ts.map +1 -0
  99. package/dist/ui/themes/soft_tokens.d.ts +2 -0
  100. package/dist/ui/themes/soft_tokens.d.ts.map +1 -0
  101. package/dist/ui/themes/theme_hosts.d.ts +15 -0
  102. package/dist/ui/themes/theme_hosts.d.ts.map +1 -0
  103. package/dist/ui/transcript/attachment_chip_styles.d.ts +2 -0
  104. package/dist/ui/transcript/attachment_chip_styles.d.ts.map +1 -0
  105. package/dist/ui/transcript/build_transcript.d.ts +21 -0
  106. package/dist/ui/transcript/build_transcript.d.ts.map +1 -0
  107. package/dist/ui/transcript/greeting_styles.d.ts +2 -0
  108. package/dist/ui/transcript/greeting_styles.d.ts.map +1 -0
  109. package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
  110. package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
  111. package/dist/ui/transcript/message_action_styles.d.ts +2 -0
  112. package/dist/ui/transcript/message_action_styles.d.ts.map +1 -0
  113. package/dist/ui/transcript/message_styles.d.ts +2 -0
  114. package/dist/ui/transcript/message_styles.d.ts.map +1 -0
  115. package/dist/ui/transcript/transcript.d.ts +16 -1
  116. package/dist/ui/transcript/transcript.d.ts.map +1 -1
  117. package/dist/ui/transcript/transcript_styles.d.ts +2 -0
  118. package/dist/ui/transcript/transcript_styles.d.ts.map +1 -0
  119. package/dist/ui/ui_strings.d.ts +18 -0
  120. package/dist/ui/ui_strings.d.ts.map +1 -1
  121. package/package.json +2 -2
  122. package/src/constants.ts +4 -1
  123. package/src/core/ag_ui_chat.ts +164 -299
  124. package/src/core/read_flag.ts +13 -0
  125. package/src/core/read_json_attribute.ts +30 -0
  126. package/src/tools/tool_catalog.ts +23 -10
  127. package/src/tools/tool_dispatch.ts +26 -11
  128. package/src/ui/charts/chart_block.ts +9 -9
  129. package/src/ui/charts/chart_styles.ts +63 -0
  130. package/src/ui/composer/attachment_styles.ts +71 -0
  131. package/src/ui/composer/build_composer.ts +112 -0
  132. package/src/ui/composer/composer_styles.ts +174 -0
  133. package/src/ui/composer/skill_styles.ts +120 -0
  134. package/src/ui/history/history_styles.ts +484 -0
  135. package/src/ui/interrupts/approval_styles.ts +52 -0
  136. package/src/ui/interrupts/confirmation_styles.ts +114 -0
  137. package/src/ui/interrupts/question_styles.ts +73 -0
  138. package/src/ui/placement/collapse_styles.ts +137 -0
  139. package/src/ui/placement/embedded_styles.ts +14 -0
  140. package/src/ui/placement/launcher_styles.ts +189 -0
  141. package/src/ui/placement/page_styles.ts +47 -0
  142. package/src/ui/placement/panel_placement.ts +3 -12
  143. package/src/ui/placement/preset_styles.ts +33 -0
  144. package/src/ui/placement/resize_styles.ts +186 -0
  145. package/src/ui/placement/sidebar_styles.ts +88 -0
  146. package/src/ui/placement/small_viewport_styles.ts +56 -0
  147. package/src/ui/placement/surface_for.ts +135 -0
  148. package/src/ui/progress/run_notice_styles.ts +52 -0
  149. package/src/ui/progress/thoughts_styles.ts +65 -0
  150. package/src/ui/progress/tool_call_card.ts +11 -0
  151. package/src/ui/progress/tool_group_styles.ts +232 -0
  152. package/src/ui/progress/tool_step_group.ts +269 -0
  153. package/src/ui/progress/tool_step_styles.ts +472 -0
  154. package/src/ui/shell/adopt_styles.ts +11 -5
  155. package/src/ui/shell/build_header.ts +108 -0
  156. package/src/ui/shell/build_launcher.ts +50 -0
  157. package/src/ui/shell/frame_styles.ts +119 -0
  158. package/src/ui/shell/theme_styles.ts +11 -0
  159. package/src/ui/shell/token_styles.ts +406 -0
  160. package/src/ui/styles.ts +104 -3774
  161. package/src/ui/themes/classic_styles.ts +32 -0
  162. package/src/ui/themes/classic_tokens.ts +50 -0
  163. package/src/ui/themes/code_tokens.ts +21 -0
  164. package/src/ui/themes/ledger_styles.ts +168 -0
  165. package/src/ui/themes/ledger_tokens.ts +32 -0
  166. package/src/ui/themes/modern_styles.ts +80 -0
  167. package/src/ui/themes/quiet_styles.ts +115 -0
  168. package/src/ui/themes/scheme_styles.ts +17 -0
  169. package/src/ui/themes/soft_styles.ts +148 -0
  170. package/src/ui/themes/soft_tokens.ts +28 -0
  171. package/src/ui/themes/theme_hosts.ts +14 -0
  172. package/src/ui/transcript/attachment_chip_styles.ts +116 -0
  173. package/src/ui/transcript/build_transcript.ts +67 -0
  174. package/src/ui/transcript/greeting_styles.ts +171 -0
  175. package/src/ui/transcript/groups_tool_steps.ts +16 -0
  176. package/src/ui/transcript/message_action_styles.ts +155 -0
  177. package/src/ui/transcript/message_styles.ts +262 -0
  178. package/src/ui/transcript/transcript.ts +109 -3
  179. package/src/ui/transcript/transcript_styles.ts +140 -0
  180. package/src/ui/ui_strings.ts +31 -0
  181. package/src/version.ts +1 -1
  182. package/dist/ui/placement/is_collapsible_placement.d.ts +0 -18
  183. package/dist/ui/placement/is_collapsible_placement.d.ts.map +0 -1
  184. package/dist/ui/placement/is_draggable_placement.d.ts +0 -9
  185. package/dist/ui/placement/is_draggable_placement.d.ts.map +0 -1
  186. package/src/ui/placement/is_collapsible_placement.ts +0 -19
  187. package/src/ui/placement/is_draggable_placement.ts +0 -23
@@ -0,0 +1,28 @@
1
+ // The soft theme's tokens: rounded, with surfaces tinted from the accent.
2
+ export const SOFT_TOKENS: string = `
3
+ /* ── Soft ─────────────────────────────────────────────────────────────────
4
+ Seeds, plus two surfaces mixed from the accent: a faint wash for the panel
5
+ chrome and a stronger tint for the user's turns. */
6
+ :host([theme~="soft"]) {
7
+ --_bg: var(--ag-ui-bg, light-dark(#fdfcff, #17161d));
8
+ --_fg: var(--ag-ui-fg, light-dark(#1f1d2b, #ebe9f5));
9
+ --_accent: var(--ag-ui-accent, light-dark(#6a4fd8, #a593ff));
10
+ --_tint: var(--ag-ui-tint, color-mix(in oklch, var(--_accent) 5%, var(--_bg)));
11
+ --_tint-2: var(--ag-ui-tint-2, color-mix(in oklch, var(--_accent) 13%, var(--_bg)));
12
+ --_header-bg: var(--ag-ui-header-bg, var(--_tint));
13
+ --_user-bg: var(--ag-ui-user-bg, var(--_tint-2));
14
+ --_border: var(--ag-ui-border, color-mix(in oklch, var(--_accent) 14%, var(--_bg)));
15
+ --_radius: var(--ag-ui-radius, 16px);
16
+ --_frame-radius: var(--ag-ui-frame-radius, var(--ag-ui-radius, 24px));
17
+ --_msg-radius: var(--ag-ui-msg-radius, 18px);
18
+ --_composer-radius: var(--ag-ui-composer-radius, 22px);
19
+ --_frame-shadow: var(--ag-ui-frame-shadow, 0 2px 6px light-dark(rgb(30 20 60 / 0.05), rgb(0 0 0 / 0.3)), 0 30px 60px -28px light-dark(color-mix(in oklch, var(--_accent) 45%, transparent), rgb(0 0 0 / 0.75)));
20
+ --_launcher-size: var(--ag-ui-launcher-size, 56px);
21
+ --_launcher-bg: var(--ag-ui-launcher-bg, var(--_accent));
22
+ --_launcher-fg: var(--ag-ui-launcher-fg, var(--_on-accent));
23
+ --_launcher-radius: var(--ag-ui-launcher-radius, 20px);
24
+ --_launcher-icon-size: var(--ag-ui-launcher-icon-size, 24px);
25
+ --_chart-1: var(--ag-ui-chart-1, var(--_accent));
26
+ --_chart-2: var(--ag-ui-chart-2, color-mix(in oklch, var(--_accent) 38%, var(--_bg)));
27
+ }
28
+ `;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The host selectors theme rules hang off, as one record so every theme
3
+ * module spells them the same way.
4
+ *
5
+ * Classic and code keep today's structure, so the structural rules the newer
6
+ * themes share apply to every element that wears neither. Quiet is the
7
+ * default, so its own structure applies to every element that wears no other
8
+ * built-in theme -- including one whose theme is a word the library does not
9
+ * know, which is how a host's own named theme starts from Quiet.
10
+ */
11
+ export const THEME_HOST = {
12
+ modern: ':host(:not([theme~="classic"], [theme~="code"]))',
13
+ quiet: ':host(:not([theme~="classic"], [theme~="code"], [theme~="soft"], [theme~="ledger"]))',
14
+ } as const;
@@ -0,0 +1,116 @@
1
+ // An attachment chip, in the tray and in a sent message alike, the drop
2
+ // outline, and the note a cancelled run leaves, which sits here only
3
+ // because no rule moved in the split.
4
+ export const ATTACHMENT_CHIP_STYLES: string = `
5
+ /* A chip carries its own text colour because it carries its own background.
6
+ The same chip renders in two places with opposite inherited colours: the
7
+ composer tray gives it the panel's, a sent user bubble gives it the user
8
+ foreground, which is white on the stock light theme and near-invisible
9
+ against the chip. Pairing the colour with the background it belongs to makes
10
+ both placements read alike. Overridden below for an errored chip, which
11
+ keeps its red. */
12
+ .attachment-chip {
13
+ display: inline-flex;
14
+ align-items: center;
15
+ gap: 6px;
16
+ /* Without this the 100% below caps the content box, so the chip still
17
+ overflows its container by its own padding and border. Invisible while a
18
+ character cap kept names short; routine once the container is what bounds
19
+ the name. */
20
+ box-sizing: border-box;
21
+ max-width: 100%;
22
+ padding: 4px 8px;
23
+ border: 1px solid var(--_border);
24
+ border-radius: 999px;
25
+ background: var(--_assistant-bg);
26
+ color: var(--_text);
27
+ font-size: 0.85em;
28
+ position: relative;
29
+ }
30
+
31
+ .attachment-chip--error {
32
+ border-color: var(--_danger);
33
+ color: var(--_danger);
34
+ }
35
+
36
+ /* The type mark. Painted from currentColor, so it carries the chip's state
37
+ with it and turns red along with an errored one; muted by opacity rather
38
+ than a colour, which is what keeps that true. Sized from the chip's own text
39
+ rather than --ag-ui-glyph-size, the way an icon holder's glyph takes the
40
+ holder's size: the composer's 18px buttons would make a chip button-height. */
41
+ .attachment-chip-icon {
42
+ display: inline-flex;
43
+ flex: none;
44
+ opacity: 0.75;
45
+ }
46
+
47
+ .attachment-chip-icon .glyph {
48
+ width: 1.25em;
49
+ height: 1.25em;
50
+ }
51
+
52
+ /* No character cap: the chip is already max-width 100%, so its container is
53
+ what bounds the name, and a fixed cap only truncated names the chip had room
54
+ for. min-width: 0 is what lets the flex item shrink past its content, so the
55
+ ellipsis appears at the container edge instead of the chip overflowing. */
56
+ .attachment-chip-name {
57
+ overflow: hidden;
58
+ text-overflow: ellipsis;
59
+ white-space: nowrap;
60
+ min-width: 0;
61
+ }
62
+
63
+ .attachment-chip-size {
64
+ color: var(--_muted);
65
+ white-space: nowrap;
66
+ }
67
+
68
+ .attachment-chip--error .attachment-chip-size {
69
+ color: var(--_danger);
70
+ }
71
+
72
+ /* The progress bar fills as the file uploads. */
73
+ .attachment-chip-bar {
74
+ flex-basis: 100%;
75
+ height: 3px;
76
+ border-radius: 2px;
77
+ background: var(--_border);
78
+ overflow: hidden;
79
+ }
80
+
81
+ .attachment-chip-bar-fill {
82
+ height: 100%;
83
+ background: var(--_accent);
84
+ transition: width 0.15s ease;
85
+ }
86
+
87
+ .attachment-chip-remove,
88
+ .attachment-chip-retry {
89
+ border: none;
90
+ background: none;
91
+ color: inherit;
92
+ cursor: pointer;
93
+ padding: 0;
94
+ line-height: 1;
95
+ opacity: 0.7;
96
+ }
97
+
98
+ .attachment-chip-remove:hover,
99
+ .attachment-chip-retry:hover {
100
+ opacity: 1;
101
+ }
102
+
103
+ /* A subtle outline while a file is dragged over the shell. */
104
+ .chat--dragover {
105
+ outline: 2px dashed var(--_accent);
106
+ outline-offset: -4px;
107
+ }
108
+
109
+ /* Muted "⏹ Stopped" line after a cancelled run — a note, not an error bubble. */
110
+ .stopped-note {
111
+ align-self: flex-start;
112
+ color: var(--_muted);
113
+ font-size: 12px;
114
+ padding: 2px 4px;
115
+ }
116
+ `;
@@ -0,0 +1,67 @@
1
+ import type { UiStrings } from "../ui_strings.js";
2
+
3
+ /** The transcript's parts, owned by the element, and what fills them. */
4
+ export interface TranscriptOptions {
5
+ readonly messages: HTMLElement;
6
+ readonly jumpButton: HTMLButtonElement;
7
+ readonly emptyWrap: HTMLElement;
8
+ /** The greeting's text node, kept by the element so a new user name updates it. */
9
+ readonly greetingText: HTMLElement;
10
+ /** The starter chips, or `null` when the host configured none. */
11
+ readonly starters: HTMLElement | null;
12
+ readonly strings: UiStrings;
13
+ }
14
+
15
+ /**
16
+ * Ready the message list, its jump-to-latest control and the empty state.
17
+ *
18
+ * The empty state is built but not put into the list: the element mounts the
19
+ * scroller, the quote offer and the announcer first and appends it after, so
20
+ * the scroller's observers see the list fill exactly as they always have.
21
+ */
22
+ export function buildTranscript(options: TranscriptOptions): void {
23
+ const { messages, jumpButton, emptyWrap, strings } = options;
24
+ messages.className = "messages";
25
+ messages.setAttribute("part", "messages");
26
+ messages.setAttribute("role", "log");
27
+ // NOT a live region. The streaming bubble's innerHTML is replaced inside
28
+ // this element on every animation frame, and `role="log"` already implies
29
+ // polite announcement whose default `aria-relevant` includes text
30
+ // additions -- so a screen reader was asked to re-announce the whole answer
31
+ // tens of times as it streamed. `aria-live="off"` is an explicit override
32
+ // of the role's implicit value, which is why the role can stay: the log
33
+ // semantics are what let the transcript be navigated as one, and only the
34
+ // announcing is the defect. Status goes to the announcer instead.
35
+ messages.setAttribute("aria-live", "off");
36
+ messages.setAttribute("aria-label", strings.conversation);
37
+
38
+ jumpButton.className = "jump-latest";
39
+ jumpButton.type = "button";
40
+ jumpButton.setAttribute("part", "jump-latest");
41
+ jumpButton.textContent = strings.jumpToLatest;
42
+
43
+ // Empty-state region: a host slot at the top of the list, hidden as soon as
44
+ // anything renders.
45
+ emptyWrap.className = "empty";
46
+ emptyWrap.setAttribute("part", "empty");
47
+ // The greeting heads the empty state, in a slot of its own: a host that
48
+ // replaces the starters keeps the greeting, and a host that replaces the
49
+ // greeting keeps the starters. A div rather than a heading, because this
50
+ // sits inside somebody else's page, which owns the document outline.
51
+ const greeting = document.createElement("div");
52
+ greeting.className = "greeting";
53
+ greeting.setAttribute("part", "greeting");
54
+ const greetingSlot = document.createElement("slot");
55
+ greetingSlot.name = "greeting";
56
+ greetingSlot.append(options.greetingText);
57
+ greeting.append(greetingSlot);
58
+ const emptySlot = document.createElement("slot");
59
+ emptySlot.name = "empty";
60
+ // Fallback content, so a host that slots its own gets exactly that and
61
+ // nothing of ours: the starters live *inside* the slot rather than beside
62
+ // it, which is the difference between an offer and an imposition.
63
+ if (options.starters !== null) {
64
+ emptySlot.append(options.starters);
65
+ }
66
+ emptyWrap.replaceChildren(greeting, emptySlot);
67
+ }
@@ -0,0 +1,171 @@
1
+ // The empty state: the greeting over an empty conversation, and the
2
+ // composer's travel from the centre to the foot on the first send.
3
+ export const GREETING_STYLES: string = `
4
+ /* Empty-state region (slot): centred while it's the only thing in the
5
+ list, hidden as soon as a message, card, or pending indicator renders. */
6
+ .empty {
7
+ margin: auto;
8
+ text-align: center;
9
+ color: var(--_muted);
10
+ }
11
+
12
+ .empty[hidden] {
13
+ display: none;
14
+ }
15
+
16
+ /* The greeting layout: a greeting over an empty conversation, and the
17
+ composer centred beneath it until the conversation has something in it.
18
+
19
+ On by default for the page placement, where a conversation is the whole
20
+ route; data-greeting="off" turns it off there. An embedded panel opts in
21
+ with any other data-greeting value. No other placement has it: a panel
22
+ opened from a launcher is already mid-task, and a greeting in a corner
23
+ panel costs space and says little.
24
+
25
+ The greeting itself renders under every placement and is hidden here, so a
26
+ placement switch at runtime needs nothing from script. */
27
+ .greeting {
28
+ display: none;
29
+ }
30
+
31
+ :host([placement="page"]:not([data-greeting="off"])) .greeting,
32
+ :host([placement="embedded"][data-greeting]:not([data-greeting="off"])) .greeting {
33
+ display: block;
34
+ margin-block-end: calc(var(--_space) * 2);
35
+ font-family: var(--_greeting-font);
36
+ font-size: var(--_greeting-size);
37
+ font-weight: 500;
38
+ line-height: 1.2;
39
+ color: var(--_fg);
40
+ overflow-wrap: anywhere;
41
+ }
42
+
43
+ /* Centring is two equal flexible boxes either side of the composer rows: the
44
+ transcript above, which already grows, and this one below. Equal zero bases
45
+ make the two halves the same height whatever they hold, so the rows sit in
46
+ the middle of the panel below its header, and stay there as a draft, an
47
+ attachment tray or a hint grows them, because growth splits into both
48
+ halves at once.
49
+
50
+ A pseudo element rather than a node, so no host layout and nothing that
51
+ counts children can see it. It exists whether or not the conversation is
52
+ empty and only its growth changes, because a box that appears and
53
+ disappears is a box nothing can ever transition.
54
+
55
+ Only the rule that generates it names the placements. The one that grows it
56
+ needs nothing but the state: where the first rule does not apply there is no
57
+ box to grow. The box is empty, so its automatic basis is already zero, the
58
+ same as the transcript's; nothing here writes the flex shorthand, because
59
+ this selector is the more specific of the two and a shorthand would win
60
+ over the growth. */
61
+ :host([placement="page"]:not([data-greeting="off"])) .chat::after,
62
+ :host([placement="embedded"][data-greeting]:not([data-greeting="off"])) .chat::after {
63
+ content: "";
64
+ }
65
+
66
+ /* Not while a restore is in flight: a conversation a remote store is still
67
+ fetching is more likely to have messages than not, and centring it first
68
+ would drop the composer the moment they land. */
69
+ :host([data-empty]:not([data-restoring])) .chat::after {
70
+ flex-grow: 1;
71
+ }
72
+
73
+ /* Only the send that leaves the empty state travels. The element arms this for
74
+ that one change and disarms it whenever the transcript is cleared, so a
75
+ restored conversation, a thread switch and a new chat all snap. Growth rather
76
+ than a transform, because the rows between the transcript and the composer
77
+ move with it, and growth is continuous where a reorder is not. Reduced motion
78
+ needs no rule of its own: the motion token already collapses to a frame
79
+ there.
80
+
81
+ The rule over the composer fades in over the same stretch. Appearing at once,
82
+ it was drawn across the page at the composer's destination while the
83
+ composer was still on its way there. */
84
+ :host([data-composer-settling]) .chat::after {
85
+ transition: flex-grow var(--_motion) var(--_ease);
86
+ }
87
+
88
+ :host([data-composer-settling]) .input-row {
89
+ transition: border-color var(--_motion) var(--_ease);
90
+ }
91
+
92
+ /* The greeting and whatever the host slotted sit at the foot of the upper
93
+ half, directly over the composer, rather than floating in its middle. The
94
+ region is hidden once the conversation has content, so this needs no state
95
+ of its own. */
96
+ :host([placement="page"]:not([data-greeting="off"]):not([data-restoring])) .empty,
97
+ :host([placement="embedded"][data-greeting]:not([data-greeting="off"]):not([data-restoring])) .empty {
98
+ margin: auto auto 0;
99
+ }
100
+
101
+ /* The rule over the composer separates it from a transcript. Over nothing it
102
+ is a line drawn across the middle of the page. */
103
+ :host([placement="page"][data-empty]:not([data-greeting="off"]):not([data-restoring])) .input-row,
104
+ :host([placement="embedded"][data-greeting][data-empty]:not([data-greeting="off"]):not([data-restoring])) .input-row {
105
+ border-block-start-color: transparent;
106
+ }
107
+
108
+ /* On a phone the composer stays at the foot and the greeting takes the room
109
+ above it, which is the other way round from everything above.
110
+
111
+ Centring is a shape for a screen with room to spare: the composer reads as
112
+ the one thing on the page, and whichever way the rows grow there is space
113
+ left on both sides of them. A phone has no space to spare, and the keyboard
114
+ is what takes it -- opening one leaves a 426px-tall visible area with the
115
+ composer halfway up it and an empty band underneath, which is the half of
116
+ the screen a thumb is already resting on. Docked, the field sits directly
117
+ over the keyboard, where every chat on the platform puts it, and the
118
+ greeting keeps the rest.
119
+
120
+ Both rules restate the selector they override, with the breakpoint's own
121
+ opt-out added: this is part of the shape a host keeps its desktop layout
122
+ instead of, so it goes the same way as the rest of that shape. Restating
123
+ rather than adding conditions to the originals keeps each of those rules
124
+ readable as one decision, and source order settles the pair. */
125
+ @media (max-width: 600px) {
126
+ :host([data-empty]:not([data-restoring]):not([data-small-viewport="off"])) .chat::after {
127
+ flex-grow: 0;
128
+ }
129
+
130
+ /* The empty region fills the transcript instead of being centred in it as a
131
+ block, so the two things in it can go to different places: the prompts the
132
+ host offers to the foot, against the composer, and the greeting to the
133
+ middle of what they leave.
134
+
135
+ A prompt chip is a way into the conversation. Against the field it starts,
136
+ it reads as one; under the greeting halfway up the panel, with the field
137
+ at the foot, it reads as decoration next to something else. Nothing here
138
+ moves them on a screen where the composer is still centred: there the
139
+ whole region hangs at the foot of the upper half, directly over the
140
+ composer, and the prompts are already against it.
141
+
142
+ Auto block margins on the greeting are what centre it, and they take the
143
+ free space whether or not there are prompts below to leave any, so a host
144
+ that offers none gets a greeting in the middle of the transcript rather
145
+ than one clinging to the composer.
146
+ The region declares a display here, which outranks the rule collapsing a
147
+ hidden one, so it says it is not hidden itself. Nothing sets the two
148
+ apart today -- the transcript writes the hidden property and the host's
149
+ data-empty from one expression -- and that is exactly why: a rule holding
150
+ because of how a method in another file happens to be written is a rule
151
+ holding by luck. */
152
+ :host([placement="page"][data-empty]:not([data-greeting="off"]):not([data-restoring]):not([data-small-viewport="off"])) .empty:not([hidden]),
153
+ :host([placement="embedded"][data-greeting][data-empty]:not([data-greeting="off"]):not([data-restoring]):not([data-small-viewport="off"])) .empty:not([hidden]) {
154
+ margin: 0;
155
+ flex: 1;
156
+ display: flex;
157
+ flex-direction: column;
158
+ }
159
+
160
+ :host([placement="page"][data-empty]:not([data-greeting="off"]):not([data-restoring]):not([data-small-viewport="off"])) .greeting,
161
+ :host([placement="embedded"][data-greeting][data-empty]:not([data-greeting="off"]):not([data-restoring]):not([data-small-viewport="off"])) .greeting {
162
+ margin-block: auto;
163
+ }
164
+ }
165
+
166
+ /* Nor does the greeting paint while a restore is in flight. Hidden rather than
167
+ removed, so it keeps its place and nothing around it moves when it shows. */
168
+ :host([data-restoring]) .greeting {
169
+ visibility: hidden;
170
+ }
171
+ `;
@@ -0,0 +1,16 @@
1
+ import { TOOL_DISPLAY } from "../../constants.js";
2
+ import type { ToolDisplayMode } from "../progress/tool_call_card.js";
3
+
4
+ /**
5
+ * Whether a new tool call folds into a step group, given the element's
6
+ * `toolDisplay`.
7
+ *
8
+ * The display mode alone decides, whatever the theme: `grouped` (the default,
9
+ * and what an unknown value reads as) folds, and the four card modes keep
10
+ * per-call cards. Asked as each card is built, so switching to `grouped`
11
+ * folds only the calls drawn afterwards; switching away flattens the groups
12
+ * already drawn, which the shadow CSS does from the host attribute.
13
+ */
14
+ export function groupsToolSteps(mode: ToolDisplayMode): boolean {
15
+ return mode === TOOL_DISPLAY.GROUPED;
16
+ }
@@ -0,0 +1,155 @@
1
+ // The action row under a message: copy, retry, feedback and the rest.
2
+ // Guest: the whole coarse-pointer block near the end (the resize handle hidden,
3
+ // the 44px touch-target tokens on :host, the 16px composer font floor).
4
+ export const MESSAGE_ACTION_STYLES: string = `
5
+ The wrap is insurance rather than a fix: these buttons are glyph-only, so at
6
+ every width tested they fit on one line and removing the wrap changes
7
+ nothing. It is here because the confirmation row one release earlier did
8
+ overflow when it gained a third button, off the left edge and outside its own
9
+ card, and the difference between the two rows is only that this one's labels
10
+ are icons today. */
11
+ .message-actions {
12
+ display: flex;
13
+ flex-wrap: wrap;
14
+ gap: 4px;
15
+ /* Negative, and that is the point. The answer group is a flex column with its
16
+ own gap, so a positive margin here pushes the row further from the message
17
+ it acts on than the next card is below it -- the buttons then read as
18
+ belonging to whatever follows. Pulling back inside the gap is what makes
19
+ them the message's own. */
20
+ margin-top: -6px;
21
+ }
22
+
23
+ /* The row's controls are icon-only, so the box is the whole target. Sized from
24
+ a variable a host can raise, with a floor rather than a fixed height: the
25
+ compact density shrinks the font, and a target that shrinks with it lands
26
+ under the 24px minimum that makes a control reliably tappable. It used to,
27
+ at roughly 20px square. */
28
+ .message-action {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ box-sizing: border-box;
33
+ min-width: var(--_action-size);
34
+ min-height: var(--_action-size);
35
+ padding: 4px;
36
+ border: none;
37
+ border-radius: 6px;
38
+ font: inherit;
39
+ line-height: 1.2;
40
+ cursor: pointer;
41
+ background: transparent;
42
+ color: var(--_muted);
43
+ opacity: 0.75;
44
+ /* The tooltip below is positioned against this box. */
45
+ position: relative;
46
+ }
47
+
48
+ .message-action:hover,
49
+ .message-action:focus-visible {
50
+ opacity: 1;
51
+ background: var(--_border);
52
+ }
53
+
54
+ .message-action-icon {
55
+ display: inline-flex;
56
+ width: var(--_action-icon-size);
57
+ height: var(--_action-icon-size);
58
+ }
59
+
60
+ .message-action-icon .glyph {
61
+ width: 100%;
62
+ height: 100%;
63
+ }
64
+
65
+ /* The label, drawn rather than left to the browser.
66
+
67
+ A title attribute is the usual answer and covers only half the readers: it
68
+ never appears on keyboard focus, so tabbing onto an icon-only control shows
69
+ nothing at all. The attribute stays for the pointer users who expect it, and
70
+ this shows the same words on hover and on focus alike.
71
+
72
+ Left-aligned rather than centred because the row sits at the left edge of an
73
+ answer inside a scrolling column, and a centred tooltip on the first control
74
+ is clipped by that column. Growing rightward keeps it inside.
75
+
76
+ Not exposed to assistive technology: the button already carries the same
77
+ string as its accessible name, and this would be a second copy of it. */
78
+ .message-action::after {
79
+ content: attr(data-tooltip);
80
+ position: absolute;
81
+ bottom: calc(100% + 4px);
82
+ left: 0;
83
+ z-index: 3;
84
+ padding: 3px 6px;
85
+ border-radius: 4px;
86
+ background: var(--_tooltip-bg);
87
+ color: var(--_tooltip-fg);
88
+ font-size: 11px;
89
+ line-height: 1.4;
90
+ white-space: nowrap;
91
+ pointer-events: none;
92
+ opacity: 0;
93
+ transition: opacity var(--_motion) var(--_ease);
94
+ }
95
+
96
+ .message-action:hover::after,
97
+ .message-action:focus-visible::after {
98
+ opacity: 1;
99
+ }
100
+
101
+ /* Touch has no hover to reveal it, and a tooltip that latches open under a
102
+ finger covers the answer it belongs to. */
103
+ @media (hover: none) {
104
+ .message-action::after {
105
+ content: none;
106
+ }
107
+ }
108
+
109
+ /* Touch. Separate from the width breakpoint in the small-viewport placement
110
+ rules on purpose: width decides the layout, the pointer decides which
111
+ controls make sense, and a touch laptop is coarse-pointered and wide while a
112
+ narrow desktop window is fine-pointered and small. */
113
+ @media (pointer: coarse) {
114
+ /* A 6px edge strip is not a control, it is a trap that eats a scroll. The
115
+ corners are 14px, still under the 24px floor a target is meant to clear,
116
+ and the panel has other ways to be resized on a device that has a pointer
117
+ precise enough to grab one. */
118
+ .resize-handle {
119
+ display: none;
120
+ }
121
+
122
+ /* Platform guidance is 44pt on iOS and 48dp on Android. These sit at 28-30px,
123
+ which clears the WCAG minimum and misses both. */
124
+ :host {
125
+ --_action-size: var(--ag-ui-action-size, 44px);
126
+ --_tool-btn-size: var(--ag-ui-tool-btn-size, 44px);
127
+ --_send-size: var(--ag-ui-send-size, 44px);
128
+ /* Missed the first time these were raised, which left the header -- the
129
+ widget's primary controls, and the only way to reach history, a new chat
130
+ or the collapse -- at half the size of everything beside it. */
131
+ --_header-btn-size: var(--ag-ui-header-btn-size, 44px);
132
+ --_header-gap: var(--ag-ui-header-gap, 6px);
133
+ }
134
+
135
+ /* iOS Safari zooms the page when a control under 16px takes focus, which
136
+ drags the whole fixed panel with it and leaves the user pinching back out
137
+ of a chat they only wanted to type into. The composer inherits the widget
138
+ font, which is 14px by default and 13px at compact density, so it has to
139
+ say 16 outright -- and only ever upward, so a host that has deliberately
140
+ set something larger keeps it. */
141
+ .input {
142
+ font-size: max(16px, var(--_font-size));
143
+ }
144
+ }
145
+
146
+ .message-action[aria-pressed="true"] {
147
+ opacity: 1;
148
+ color: var(--_accent);
149
+ }
150
+
151
+ .message-action--confirmed {
152
+ opacity: 1;
153
+ color: var(--_accent);
154
+ }
155
+ `;