@artooi/ag-ui-web-component 0.20.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +192 -1
- package/README.md +404 -28
- package/dist/ag-ui-web-component.bundle.js +781 -451
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +29 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +93 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +9 -0
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +8 -1
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +8 -1
- package/dist/core/run_index.d.ts.map +1 -1
- package/dist/core/transcribe_audio.d.ts +7 -0
- package/dist/core/transcribe_audio.d.ts.map +1 -1
- package/dist/core/upload_attachment.d.ts +9 -0
- package/dist/core/upload_attachment.d.ts.map +1 -1
- package/dist/core/utils.d.ts +12 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/dom/animations.d.ts +51 -11
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/dom_driver.d.ts +12 -7
- package/dist/dom/dom_driver.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1275 -520
- package/dist/index.js.map +3 -3
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +8 -1
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +35 -0
- package/src/core/ag_ui_chat.ts +452 -55
- package/src/core/create_http_agent.ts +14 -3
- package/src/core/remote_conversation_store.ts +25 -6
- package/src/core/run_index.ts +27 -5
- package/src/core/transcribe_audio.ts +20 -5
- package/src/core/upload_attachment.ts +13 -0
- package/src/core/utils.ts +18 -0
- package/src/dom/animations.ts +175 -31
- package/src/dom/dom_driver.ts +18 -12
- package/src/index.ts +4 -0
- package/src/ui/styles.ts +751 -421
- package/src/ui/ui_strings.ts +9 -1
- package/src/ui/voice_input.ts +7 -1
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var ELEMENT_TAG = "ag-ui-chat";
|
|
3
3
|
var SUBMIT_EVENT = "ag-ui-submit";
|
|
4
4
|
var TOGGLE_EVENT = "ag-ui-toggle";
|
|
5
|
+
var UNREAD_EVENT = "ag-ui-unread";
|
|
5
6
|
var STATE_EVENT = "ag-ui-state";
|
|
6
7
|
var ATTACHMENT_EVENT = "ag-ui-attachments";
|
|
7
8
|
var MESSAGE_ROLE = {
|
|
@@ -34,6 +35,11 @@ var TOOL_DISPLAY = {
|
|
|
34
35
|
};
|
|
35
36
|
var COMPACTION_ACTIVITY_TYPE = "compaction";
|
|
36
37
|
var LOAD_CAPABILITY_TOOL = "load_capability";
|
|
38
|
+
var ICON_SEND = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19.5V5m-6.5 6.5L12 5l6.5 6.5"/></svg>`;
|
|
39
|
+
var ICON_STOP = `<svg class="glyph glyph--solid" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2.5"/></svg>`;
|
|
40
|
+
var ICON_ATTACH = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8.5V15a5 5 0 0 1-10 0V7a3 3 0 0 1 6 0v7.5a1 1 0 0 1-2 0V8.5"/></svg>`;
|
|
41
|
+
var ICON_VOICE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V7a3 3 0 0 1 3-3z"/><path d="M5 11v1a7 7 0 0 0 14 0v-1"/><path d="M12 19v3"/></svg>`;
|
|
42
|
+
var ICON_LAUNCHER = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-5 4z"/></svg>`;
|
|
37
43
|
|
|
38
44
|
// src/skills/fill_template.ts
|
|
39
45
|
var PLACEHOLDER_RE = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
@@ -134,11 +140,29 @@ function setNativeChecked(el, checked) {
|
|
|
134
140
|
|
|
135
141
|
// src/dom/animations.ts
|
|
136
142
|
var ACCENT = "#4f46e5";
|
|
143
|
+
var ACCENT_HALO = "rgba(79, 70, 229, 0.4)";
|
|
144
|
+
var ACCENT_PROPERTY = "--ag-ui-accent";
|
|
137
145
|
function delay(ms) {
|
|
138
146
|
return new Promise((resolve) => {
|
|
139
147
|
setTimeout(resolve, ms);
|
|
140
148
|
});
|
|
141
149
|
}
|
|
150
|
+
function prefersReducedMotion() {
|
|
151
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
152
|
+
}
|
|
153
|
+
function motionDelay(ms) {
|
|
154
|
+
if (ms <= 0 || prefersReducedMotion()) {
|
|
155
|
+
return Promise.resolve();
|
|
156
|
+
}
|
|
157
|
+
return delay(ms);
|
|
158
|
+
}
|
|
159
|
+
function accentColor(el, fallback) {
|
|
160
|
+
const themed = window.getComputedStyle(el).getPropertyValue(ACCENT_PROPERTY).trim();
|
|
161
|
+
return themed === "" ? fallback : themed;
|
|
162
|
+
}
|
|
163
|
+
function ringShadow(el) {
|
|
164
|
+
return `0 0 0 3px ${accentColor(el, ACCENT_HALO)}`;
|
|
165
|
+
}
|
|
142
166
|
async function typeInto(el, value, options = {}) {
|
|
143
167
|
const charDelayMs = options.charDelayMs ?? 35;
|
|
144
168
|
setNativeValue(el, "");
|
|
@@ -156,33 +180,75 @@ async function highlightThenClick(el, options = {}) {
|
|
|
156
180
|
const highlightMs = options.highlightMs ?? 280;
|
|
157
181
|
const previousOutline = el.style.outline;
|
|
158
182
|
const previousOffset = el.style.outlineOffset;
|
|
159
|
-
el.style.outline = `2px solid ${ACCENT}`;
|
|
183
|
+
el.style.outline = `2px solid ${accentColor(el, ACCENT)}`;
|
|
160
184
|
el.style.outlineOffset = "2px";
|
|
161
185
|
await delay(highlightMs);
|
|
162
186
|
el.style.outline = previousOutline;
|
|
163
187
|
el.style.outlineOffset = previousOffset;
|
|
164
188
|
el.click();
|
|
165
189
|
}
|
|
166
|
-
|
|
167
|
-
|
|
190
|
+
var SCROLL_SETTLE_MS = 600;
|
|
191
|
+
var SCROLL_START_MS = 100;
|
|
192
|
+
function scrollIntoCenterView(el, options = {}) {
|
|
193
|
+
const reduced = prefersReducedMotion();
|
|
194
|
+
el.scrollIntoView({
|
|
195
|
+
block: "center",
|
|
196
|
+
inline: "nearest",
|
|
197
|
+
behavior: reduced ? "auto" : "smooth"
|
|
198
|
+
});
|
|
199
|
+
if (reduced) {
|
|
200
|
+
return Promise.resolve();
|
|
201
|
+
}
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
let timer;
|
|
204
|
+
const settle = () => {
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
document.removeEventListener("scroll", started, true);
|
|
207
|
+
document.removeEventListener("scrollend", settle, true);
|
|
208
|
+
resolve();
|
|
209
|
+
};
|
|
210
|
+
const started = () => {
|
|
211
|
+
document.removeEventListener("scroll", started, true);
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
timer = setTimeout(settle, options.settleMs ?? SCROLL_SETTLE_MS);
|
|
214
|
+
};
|
|
215
|
+
timer = setTimeout(settle, SCROLL_START_MS);
|
|
216
|
+
document.addEventListener("scroll", started, true);
|
|
217
|
+
document.addEventListener("scrollend", settle, true);
|
|
218
|
+
});
|
|
168
219
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
220
|
+
var FLASH_MS = 1200;
|
|
221
|
+
var FLASH_FADE_RATIO = 1 / 3;
|
|
222
|
+
async function flashRing(el, options, focusByDefault) {
|
|
223
|
+
if (options.focus ?? focusByDefault) {
|
|
224
|
+
el.focus({ preventScroll: true });
|
|
225
|
+
}
|
|
226
|
+
const flashMs = options.flashMs ?? FLASH_MS;
|
|
227
|
+
if (flashMs <= 0) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const previousOutline = el.style.outline;
|
|
231
|
+
const previousOffset = el.style.outlineOffset;
|
|
232
|
+
const previousTransition = el.style.transition;
|
|
233
|
+
const color = options.color ?? accentColor(el, ACCENT);
|
|
234
|
+
el.style.outline = `3px solid ${color}`;
|
|
235
|
+
el.style.outlineOffset = "2px";
|
|
236
|
+
const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
|
|
237
|
+
await delay(flashMs - fadeMs);
|
|
238
|
+
if (fadeMs > 0) {
|
|
239
|
+
el.style.transition = `outline-color ${fadeMs}ms ease-out`;
|
|
240
|
+
el.style.outline = "3px solid transparent";
|
|
241
|
+
await delay(fadeMs);
|
|
242
|
+
}
|
|
243
|
+
el.style.outline = previousOutline;
|
|
244
|
+
el.style.outlineOffset = previousOffset;
|
|
245
|
+
el.style.transition = previousTransition;
|
|
176
246
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
247
|
+
function flash(el, options = {}) {
|
|
248
|
+
return flashRing(el, options, false);
|
|
180
249
|
}
|
|
181
|
-
function
|
|
182
|
-
|
|
183
|
-
return Promise.resolve();
|
|
184
|
-
}
|
|
185
|
-
return delay(ms);
|
|
250
|
+
function focusWithFlash(el, options = {}) {
|
|
251
|
+
return flashRing(el, options, true);
|
|
186
252
|
}
|
|
187
253
|
async function pressThenClick(el, options = {}) {
|
|
188
254
|
const pressMs = options.pressMs ?? 140;
|
|
@@ -191,7 +257,7 @@ async function pressThenClick(el, options = {}) {
|
|
|
191
257
|
const previousShadow = el.style.boxShadow;
|
|
192
258
|
el.style.transition = "transform 80ms ease";
|
|
193
259
|
el.style.transform = "scale(0.96)";
|
|
194
|
-
el.style.boxShadow =
|
|
260
|
+
el.style.boxShadow = ringShadow(el);
|
|
195
261
|
await motionDelay(pressMs);
|
|
196
262
|
el.style.transform = previousTransform;
|
|
197
263
|
el.style.transition = previousTransition;
|
|
@@ -214,7 +280,7 @@ async function selectOption(el, value, options = {}) {
|
|
|
214
280
|
const highlightMs = options.highlightMs ?? 220;
|
|
215
281
|
const previousOutline = el.style.outline;
|
|
216
282
|
const previousOffset = el.style.outlineOffset;
|
|
217
|
-
el.style.outline = `2px solid ${ACCENT}`;
|
|
283
|
+
el.style.outline = `2px solid ${accentColor(el, ACCENT)}`;
|
|
218
284
|
el.style.outlineOffset = "2px";
|
|
219
285
|
await motionDelay(highlightMs);
|
|
220
286
|
setNativeValue(el, option.value);
|
|
@@ -226,7 +292,7 @@ async function selectOption(el, value, options = {}) {
|
|
|
226
292
|
async function toggleControl(el, checked, options = {}) {
|
|
227
293
|
const flashMs = options.flashMs ?? 200;
|
|
228
294
|
const previousShadow = el.style.boxShadow;
|
|
229
|
-
el.style.boxShadow =
|
|
295
|
+
el.style.boxShadow = ringShadow(el);
|
|
230
296
|
await motionDelay(flashMs);
|
|
231
297
|
setNativeChecked(el, checked);
|
|
232
298
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -449,6 +515,7 @@ var DEFAULT_UI_STRINGS = {
|
|
|
449
515
|
newChat: "New chat",
|
|
450
516
|
collapse: "Collapse",
|
|
451
517
|
expand: "Expand",
|
|
518
|
+
expandUnread: "Expand \u2014 {count} unread",
|
|
452
519
|
toggleTheme: "Toggle theme",
|
|
453
520
|
copyCode: "Copy",
|
|
454
521
|
copied: "Copied",
|
|
@@ -4401,151 +4468,216 @@ var SkillsMenu = class {
|
|
|
4401
4468
|
|
|
4402
4469
|
// src/ui/styles.ts
|
|
4403
4470
|
var STYLES = `
|
|
4471
|
+
/* \u2500\u2500 Token defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4472
|
+
Every public --ag-ui-* token is read here into a private --_* alias, and
|
|
4473
|
+
only the alias is used by the rules below.
|
|
4474
|
+
|
|
4475
|
+
The indirection is what makes ancestor theming work. Declaring the public
|
|
4476
|
+
name on :host would set it *on the host element*, and a value on an element
|
|
4477
|
+
always beats one inherited from an ancestor \u2014 so a page that put the tokens
|
|
4478
|
+
on a wrapper would see no effect at all, which is the one thing the API is
|
|
4479
|
+
documented to support. Reading the public name with the default as a var()
|
|
4480
|
+
fallback leaves it undeclared on the element, so an ancestor's value is
|
|
4481
|
+
inherited normally while a value aimed at the element still wins over it.
|
|
4482
|
+
|
|
4483
|
+
Two invariants hold this together:
|
|
4484
|
+
1. No rule outside this file's :host blocks may reference a public name
|
|
4485
|
+
directly. A public name read outside these blocks resolves to nothing and
|
|
4486
|
+
silently drops its declaration rather than erroring, so the miss shows up
|
|
4487
|
+
as a missing colour, not a failure.
|
|
4488
|
+
2. Every --_* alias in use is declared here. Aliases inherit like any custom
|
|
4489
|
+
property, so an undeclared one would pick up a same-named property from
|
|
4490
|
+
the host page; declaring it on :host shields the shadow tree from that. */
|
|
4404
4491
|
:host {
|
|
4405
4492
|
/* Colors */
|
|
4406
|
-
--ag-ui-bg
|
|
4407
|
-
--ag-ui-fg
|
|
4408
|
-
--ag-ui-accent
|
|
4409
|
-
--ag-ui-user-bg
|
|
4410
|
-
--ag-ui-user-fg
|
|
4411
|
-
--ag-ui-assistant-bg
|
|
4412
|
-
--ag-ui-hover
|
|
4413
|
-
--
|
|
4414
|
-
--
|
|
4415
|
-
--ag-ui-tool-fg
|
|
4416
|
-
--ag-ui-header-bg
|
|
4417
|
-
--ag-ui-header-fg
|
|
4418
|
-
--ag-ui-border
|
|
4419
|
-
--ag-ui-radius
|
|
4493
|
+
--_bg: var(--ag-ui-bg, #ffffff);
|
|
4494
|
+
--_fg: var(--ag-ui-fg, #1a1a2e);
|
|
4495
|
+
--_accent: var(--ag-ui-accent, #4f46e5);
|
|
4496
|
+
--_user-bg: var(--ag-ui-user-bg, #4f46e5);
|
|
4497
|
+
--_user-fg: var(--ag-ui-user-fg, #ffffff);
|
|
4498
|
+
--_assistant-bg: var(--ag-ui-assistant-bg, #f1f1f6);
|
|
4499
|
+
--_hover: var(--ag-ui-hover, #e7e7ee);
|
|
4500
|
+
--_input-bg: var(--ag-ui-input-bg, var(--_bg));
|
|
4501
|
+
--_tool-bg: var(--ag-ui-tool-bg, var(--_assistant-bg));
|
|
4502
|
+
--_tool-fg: var(--ag-ui-tool-fg, var(--_accent));
|
|
4503
|
+
--_header-bg: var(--ag-ui-header-bg, var(--_accent));
|
|
4504
|
+
--_header-fg: var(--ag-ui-header-fg, #ffffff);
|
|
4505
|
+
--_border: var(--ag-ui-border, #e2e2ec);
|
|
4506
|
+
--_radius: var(--ag-ui-radius, 12px);
|
|
4507
|
+
|
|
4508
|
+
/* Body text and raised chrome. Both are referenced by the code-block copy
|
|
4509
|
+
button; neither had a default before, so the declarations reading them
|
|
4510
|
+
were dropped and the button fell back to the inherited colour over a
|
|
4511
|
+
transparent box. The defaults below restate exactly that, so this is a
|
|
4512
|
+
rename with no repaint \u2014 see the note on .code-copy. */
|
|
4513
|
+
--_text: var(--ag-ui-text, var(--_fg));
|
|
4514
|
+
--_surface: var(--ag-ui-surface, transparent);
|
|
4420
4515
|
|
|
4421
4516
|
/* Status accents for tool-call cards. */
|
|
4422
|
-
--ag-ui-success
|
|
4423
|
-
--ag-ui-danger
|
|
4424
|
-
--ag-ui-muted
|
|
4517
|
+
--_success: var(--ag-ui-success, #15803d);
|
|
4518
|
+
--_danger: var(--ag-ui-danger, #b91c1c);
|
|
4519
|
+
--_muted: var(--ag-ui-muted, #6b7280);
|
|
4425
4520
|
|
|
4426
4521
|
/* Tool-call status icon glyphs (override to re-theme) + spinner speed.
|
|
4427
4522
|
The pending state is the animated ring; the settled states use these. */
|
|
4428
|
-
--ag-ui-tool-icon-done
|
|
4429
|
-
--ag-ui-tool-icon-error
|
|
4430
|
-
--ag-ui-tool-icon-declined
|
|
4431
|
-
--ag-ui-tool-spin-duration
|
|
4523
|
+
--_tool-icon-done: var(--ag-ui-tool-icon-done, "\u2713");
|
|
4524
|
+
--_tool-icon-error: var(--ag-ui-tool-icon-error, "\u2715");
|
|
4525
|
+
--_tool-icon-declined: var(--ag-ui-tool-icon-declined, "\u2298");
|
|
4526
|
+
--_tool-spin-duration: var(--ag-ui-tool-spin-duration, 0.7s);
|
|
4432
4527
|
|
|
4433
4528
|
/* Answer well (opt-in via data-answer-well) \u2014 boxes a whole assistant turn. */
|
|
4434
|
-
--ag-ui-well-bg
|
|
4435
|
-
--
|
|
4529
|
+
--_well-bg: var(--ag-ui-well-bg, transparent);
|
|
4530
|
+
--_well-border: var(--ag-ui-well-border, var(--_border));
|
|
4436
4531
|
|
|
4437
4532
|
/* Surface \u2014 set --ag-ui-shadow: none for a flush, embedded panel. */
|
|
4438
|
-
--ag-ui-shadow
|
|
4439
|
-
--ag-ui-font
|
|
4440
|
-
--ag-ui-font-size
|
|
4441
|
-
--ag-ui-code-font
|
|
4533
|
+
--_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(20, 20, 50, 0.18));
|
|
4534
|
+
--_font: var(--ag-ui-font, inherit);
|
|
4535
|
+
--_font-size: var(--ag-ui-font-size, 14px);
|
|
4536
|
+
--_code-font: var(--ag-ui-code-font, ui-monospace, "SF Mono", Menlo, monospace);
|
|
4537
|
+
|
|
4538
|
+
/* Header / launcher icon box. */
|
|
4539
|
+
--_icon-size: var(--ag-ui-icon-size, 22px);
|
|
4540
|
+
--_icon-radius: var(--ag-ui-icon-radius, 4px);
|
|
4541
|
+
|
|
4542
|
+
/* Composer \u2014 one bordered surface holding the field and its tool row. */
|
|
4543
|
+
--_composer-radius: var(--ag-ui-composer-radius, 14px);
|
|
4544
|
+
--_composer-max-height: var(--ag-ui-composer-max-height, 40vh);
|
|
4545
|
+
--_tool-btn-size: var(--ag-ui-tool-btn-size, 30px);
|
|
4546
|
+
--_send-size: var(--ag-ui-send-size, 30px);
|
|
4547
|
+
--_glyph-size: var(--ag-ui-glyph-size, 18px);
|
|
4548
|
+
--_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);
|
|
4549
|
+
|
|
4550
|
+
/* Unread badge on the launcher. */
|
|
4551
|
+
--_badge-bg: var(--ag-ui-badge-bg, var(--_danger));
|
|
4552
|
+
--_badge-fg: var(--ag-ui-badge-fg, #ffffff);
|
|
4553
|
+
--_badge-size: var(--ag-ui-badge-size, 18px);
|
|
4554
|
+
--_badge-font-size: var(--ag-ui-badge-font-size, 11px);
|
|
4555
|
+
|
|
4556
|
+
/* The floating launcher a collapsed widget shrinks to. */
|
|
4557
|
+
--_launcher-size: var(--ag-ui-launcher-size, 56px);
|
|
4558
|
+
--_launcher-bg: var(--ag-ui-launcher-bg, var(--_header-bg));
|
|
4559
|
+
--_launcher-fg: var(--ag-ui-launcher-fg, var(--_header-fg));
|
|
4560
|
+
--_launcher-radius: var(--ag-ui-launcher-radius, 50%);
|
|
4561
|
+
--_launcher-icon-size: var(--ag-ui-launcher-icon-size, 26px);
|
|
4562
|
+
--_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);
|
|
4563
|
+
|
|
4564
|
+
/* Motion. One duration and two curves drive every collapse, expand and
|
|
4565
|
+
slide-over, so the whole widget accelerates and settles as one thing.
|
|
4566
|
+
The default curve is a long-tailed ease-out (a fast start that decelerates
|
|
4567
|
+
into place); the pop curve overshoots slightly, for something arriving. */
|
|
4568
|
+
--_motion: var(--ag-ui-motion, 0.28s);
|
|
4569
|
+
--_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));
|
|
4570
|
+
--_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));
|
|
4442
4571
|
|
|
4443
4572
|
/* Spacing \u2014 the density preset overrides these. */
|
|
4444
|
-
--ag-ui-space
|
|
4445
|
-
--ag-ui-pad
|
|
4446
|
-
--ag-ui-msg-pad
|
|
4447
|
-
--ag-ui-msg-radius
|
|
4573
|
+
--_space: var(--ag-ui-space, 10px);
|
|
4574
|
+
--_pad: var(--ag-ui-pad, 16px);
|
|
4575
|
+
--_msg-pad: var(--ag-ui-msg-pad, 8px 12px);
|
|
4576
|
+
--_msg-radius: var(--ag-ui-msg-radius, 14px);
|
|
4448
4577
|
|
|
4449
4578
|
/* Layout \u2014 override from outside to dock the widget anywhere.
|
|
4450
4579
|
Set --ag-ui-position: static (and place this element in your own
|
|
4451
4580
|
grid/flex layout) to embed it in the page flow instead of floating. */
|
|
4452
|
-
--ag-ui-position
|
|
4453
|
-
--ag-ui-z-index
|
|
4454
|
-
--ag-ui-width
|
|
4455
|
-
--ag-ui-height
|
|
4456
|
-
--ag-ui-inset
|
|
4457
|
-
--ag-ui-max-width
|
|
4458
|
-
--ag-ui-max-height
|
|
4581
|
+
--_position: var(--ag-ui-position, fixed);
|
|
4582
|
+
--_z-index: var(--ag-ui-z-index, 2147483000);
|
|
4583
|
+
--_width: var(--ag-ui-width, 380px);
|
|
4584
|
+
--_height: var(--ag-ui-height, 560px);
|
|
4585
|
+
--_inset: var(--ag-ui-inset, auto 24px 24px auto);
|
|
4586
|
+
--_max-width: var(--ag-ui-max-width, calc(100vw - 48px));
|
|
4587
|
+
--_max-height: var(--ag-ui-max-height, calc(100vh - 48px));
|
|
4459
4588
|
/* Reading-column width for placement="page" (full-bleed, centred content). */
|
|
4460
|
-
--ag-ui-content-max-width
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4589
|
+
--_content-max-width: var(--ag-ui-content-max-width, 820px);
|
|
4590
|
+
/* Slim rail the sidebar placement collapses to. Only that placement reads
|
|
4591
|
+
it, but it is declared here so invariant 2 holds for every alias. */
|
|
4592
|
+
--_rail-width: var(--ag-ui-rail-width, 52px);
|
|
4593
|
+
|
|
4594
|
+
position: var(--_position);
|
|
4595
|
+
inset: var(--_inset);
|
|
4596
|
+
z-index: var(--_z-index);
|
|
4597
|
+
width: var(--_width);
|
|
4598
|
+
max-width: var(--_max-width);
|
|
4599
|
+
height: var(--_height);
|
|
4600
|
+
max-height: var(--_max-height);
|
|
4469
4601
|
display: flex;
|
|
4470
|
-
font-family: var(--
|
|
4471
|
-
font-size: var(--
|
|
4472
|
-
color: var(--
|
|
4602
|
+
font-family: var(--_font);
|
|
4603
|
+
font-size: var(--_font-size);
|
|
4604
|
+
color: var(--_fg);
|
|
4473
4605
|
}
|
|
4474
4606
|
|
|
4475
4607
|
/* \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
|
|
4476
4608
|
Themes only re-set the colour variables; layout/spacing are unaffected.
|
|
4477
4609
|
theme="auto" follows the OS via prefers-color-scheme. */
|
|
4478
4610
|
:host([theme="dark"]) {
|
|
4479
|
-
--ag-ui-bg
|
|
4480
|
-
--ag-ui-fg
|
|
4481
|
-
--ag-ui-assistant-bg
|
|
4482
|
-
--ag-ui-hover
|
|
4483
|
-
--ag-ui-header-bg
|
|
4484
|
-
--ag-ui-header-fg
|
|
4485
|
-
--ag-ui-border
|
|
4486
|
-
--ag-ui-muted
|
|
4487
|
-
--ag-ui-shadow
|
|
4611
|
+
--_bg: var(--ag-ui-bg, #15151f);
|
|
4612
|
+
--_fg: var(--ag-ui-fg, #e8e8f2);
|
|
4613
|
+
--_assistant-bg: var(--ag-ui-assistant-bg, #25253a);
|
|
4614
|
+
--_hover: var(--ag-ui-hover, #303049);
|
|
4615
|
+
--_header-bg: var(--ag-ui-header-bg, #1f1f30);
|
|
4616
|
+
--_header-fg: var(--ag-ui-header-fg, #e8e8f2);
|
|
4617
|
+
--_border: var(--ag-ui-border, #33334a);
|
|
4618
|
+
--_muted: var(--ag-ui-muted, #9aa0b4);
|
|
4619
|
+
--_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.5));
|
|
4488
4620
|
}
|
|
4489
4621
|
|
|
4490
4622
|
@media (prefers-color-scheme: dark) {
|
|
4491
4623
|
:host([theme="auto"]) {
|
|
4492
|
-
--ag-ui-bg
|
|
4493
|
-
--ag-ui-fg
|
|
4494
|
-
--ag-ui-assistant-bg
|
|
4495
|
-
--ag-ui-hover
|
|
4496
|
-
--ag-ui-header-bg
|
|
4497
|
-
--ag-ui-header-fg
|
|
4498
|
-
--ag-ui-border
|
|
4499
|
-
--ag-ui-muted
|
|
4500
|
-
--ag-ui-shadow
|
|
4624
|
+
--_bg: var(--ag-ui-bg, #15151f);
|
|
4625
|
+
--_fg: var(--ag-ui-fg, #e8e8f2);
|
|
4626
|
+
--_assistant-bg: var(--ag-ui-assistant-bg, #25253a);
|
|
4627
|
+
--_hover: var(--ag-ui-hover, #303049);
|
|
4628
|
+
--_header-bg: var(--ag-ui-header-bg, #1f1f30);
|
|
4629
|
+
--_header-fg: var(--ag-ui-header-fg, #e8e8f2);
|
|
4630
|
+
--_border: var(--ag-ui-border, #33334a);
|
|
4631
|
+
--_muted: var(--ag-ui-muted, #9aa0b4);
|
|
4632
|
+
--_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.5));
|
|
4501
4633
|
}
|
|
4502
4634
|
}
|
|
4503
4635
|
|
|
4504
4636
|
/* A terminal-flavoured "code" theme: dark, monospace, green accent. */
|
|
4505
4637
|
:host([theme="code"]) {
|
|
4506
|
-
--ag-ui-bg
|
|
4507
|
-
--ag-ui-fg
|
|
4508
|
-
--ag-ui-accent
|
|
4509
|
-
--ag-ui-user-bg
|
|
4510
|
-
--ag-ui-assistant-bg
|
|
4511
|
-
--ag-ui-hover
|
|
4512
|
-
--ag-ui-header-bg
|
|
4513
|
-
--ag-ui-header-fg
|
|
4514
|
-
--ag-ui-border
|
|
4515
|
-
--ag-ui-muted
|
|
4516
|
-
--ag-ui-font
|
|
4517
|
-
--ag-ui-shadow
|
|
4638
|
+
--_bg: var(--ag-ui-bg, #0d1117);
|
|
4639
|
+
--_fg: var(--ag-ui-fg, #c9d1d9);
|
|
4640
|
+
--_accent: var(--ag-ui-accent, #3fb950);
|
|
4641
|
+
--_user-bg: var(--ag-ui-user-bg, #238636);
|
|
4642
|
+
--_assistant-bg: var(--ag-ui-assistant-bg, #161b22);
|
|
4643
|
+
--_hover: var(--ag-ui-hover, #21262d);
|
|
4644
|
+
--_header-bg: var(--ag-ui-header-bg, #010409);
|
|
4645
|
+
--_header-fg: var(--ag-ui-header-fg, #c9d1d9);
|
|
4646
|
+
--_border: var(--ag-ui-border, #30363d);
|
|
4647
|
+
--_muted: var(--ag-ui-muted, #8b949e);
|
|
4648
|
+
--_font: var(--ag-ui-font, var(--_code-font));
|
|
4649
|
+
--_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.6));
|
|
4518
4650
|
}
|
|
4519
4651
|
|
|
4520
4652
|
/* \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 */
|
|
4521
4653
|
:host([density="compact"]) {
|
|
4522
|
-
--ag-ui-font-size
|
|
4523
|
-
--ag-ui-space
|
|
4524
|
-
--ag-ui-pad
|
|
4525
|
-
--ag-ui-msg-pad
|
|
4526
|
-
--ag-ui-msg-radius
|
|
4654
|
+
--_font-size: var(--ag-ui-font-size, 13px);
|
|
4655
|
+
--_space: var(--ag-ui-space, 6px);
|
|
4656
|
+
--_pad: var(--ag-ui-pad, 10px);
|
|
4657
|
+
--_msg-pad: var(--ag-ui-msg-pad, 5px 9px);
|
|
4658
|
+
--_msg-radius: var(--ag-ui-msg-radius, 10px);
|
|
4527
4659
|
}
|
|
4528
4660
|
|
|
4529
4661
|
/* \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 */
|
|
4530
4662
|
:host([placement="bottom-left"]) {
|
|
4531
|
-
--ag-ui-inset
|
|
4663
|
+
--_inset: var(--ag-ui-inset, auto auto 24px 24px);
|
|
4532
4664
|
}
|
|
4533
4665
|
|
|
4534
4666
|
:host([placement="side"]) {
|
|
4535
|
-
--ag-ui-inset
|
|
4536
|
-
--ag-ui-width
|
|
4537
|
-
--ag-ui-height
|
|
4538
|
-
--ag-ui-max-height
|
|
4539
|
-
--ag-ui-radius
|
|
4667
|
+
--_inset: var(--ag-ui-inset, 0 0 0 auto);
|
|
4668
|
+
--_width: var(--ag-ui-width, 420px);
|
|
4669
|
+
--_height: var(--ag-ui-height, 100vh);
|
|
4670
|
+
--_max-height: var(--ag-ui-max-height, 100vh);
|
|
4671
|
+
--_radius: var(--ag-ui-radius, 0);
|
|
4540
4672
|
}
|
|
4541
4673
|
|
|
4542
4674
|
:host([placement="full"]) {
|
|
4543
|
-
--ag-ui-inset
|
|
4544
|
-
--ag-ui-width
|
|
4545
|
-
--ag-ui-height
|
|
4546
|
-
--ag-ui-max-width
|
|
4547
|
-
--ag-ui-max-height
|
|
4548
|
-
--ag-ui-radius
|
|
4675
|
+
--_inset: var(--ag-ui-inset, 0);
|
|
4676
|
+
--_width: var(--ag-ui-width, 100vw);
|
|
4677
|
+
--_height: var(--ag-ui-height, 100vh);
|
|
4678
|
+
--_max-width: var(--ag-ui-max-width, 100vw);
|
|
4679
|
+
--_max-height: var(--ag-ui-max-height, 100vh);
|
|
4680
|
+
--_radius: var(--ag-ui-radius, 0);
|
|
4549
4681
|
}
|
|
4550
4682
|
|
|
4551
4683
|
/* Page: full-bleed background with a centred reading column. Unlike
|
|
@@ -4554,85 +4686,20 @@ var STYLES = `
|
|
|
4554
4686
|
padding on the scroll area + composer (no per-row wrapper), so user pills
|
|
4555
4687
|
still right-align and the assistant well spans the column. */
|
|
4556
4688
|
:host([placement="page"]) {
|
|
4557
|
-
--ag-ui-inset
|
|
4558
|
-
--ag-ui-width
|
|
4559
|
-
--ag-ui-height
|
|
4560
|
-
--ag-ui-max-width
|
|
4561
|
-
--ag-ui-max-height
|
|
4562
|
-
--ag-ui-radius
|
|
4689
|
+
--_inset: var(--ag-ui-inset, 0);
|
|
4690
|
+
--_width: var(--ag-ui-width, 100vw);
|
|
4691
|
+
--_height: var(--ag-ui-height, 100vh);
|
|
4692
|
+
--_max-width: var(--ag-ui-max-width, 100vw);
|
|
4693
|
+
--_max-height: var(--ag-ui-max-height, 100vh);
|
|
4694
|
+
--_radius: var(--ag-ui-radius, 0);
|
|
4563
4695
|
}
|
|
4564
4696
|
|
|
4565
4697
|
:host([placement="page"]) .messages {
|
|
4566
|
-
padding-inline: max(var(--
|
|
4567
|
-
}
|
|
4568
|
-
|
|
4569
|
-
:host([placement="page"]) /* Resize handle: a corner grip on a floating panel, an edge grip on a docked
|
|
4570
|
-
one. Absent entirely where the placement is full-bleed, since there is
|
|
4571
|
-
nothing to drag. */
|
|
4572
|
-
.resize-handle {
|
|
4573
|
-
position: absolute;
|
|
4574
|
-
z-index: 2;
|
|
4575
|
-
background: transparent;
|
|
4576
|
-
touch-action: none;
|
|
4577
|
-
}
|
|
4578
|
-
|
|
4579
|
-
.resize-handle:focus-visible {
|
|
4580
|
-
outline: 2px solid var(--ag-ui-accent);
|
|
4581
|
-
outline-offset: -2px;
|
|
4698
|
+
padding-inline: max(var(--_pad), calc((100% - var(--_content-max-width)) / 2));
|
|
4582
4699
|
}
|
|
4583
4700
|
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
and stamps data-resize-anchor with the edges that stay put.
|
|
4587
|
-
|
|
4588
|
-
Default (bottom-right pinned, the floating case) puts the grip top-left. */
|
|
4589
|
-
.resize-handle {
|
|
4590
|
-
top: 0;
|
|
4591
|
-
left: 0;
|
|
4592
|
-
width: 14px;
|
|
4593
|
-
height: 14px;
|
|
4594
|
-
cursor: nwse-resize;
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
|
-
:host([data-resize-anchor~="left"]) .resize-handle {
|
|
4598
|
-
left: auto;
|
|
4599
|
-
right: 0;
|
|
4600
|
-
}
|
|
4601
|
-
|
|
4602
|
-
:host([data-resize-anchor~="top"]) .resize-handle {
|
|
4603
|
-
top: auto;
|
|
4604
|
-
bottom: 0;
|
|
4605
|
-
}
|
|
4606
|
-
|
|
4607
|
-
/* One axis flipped means the drag runs along the other diagonal. */
|
|
4608
|
-
:host([data-resize-anchor="top-right"]) .resize-handle,
|
|
4609
|
-
:host([data-resize-anchor="bottom-left"]) .resize-handle {
|
|
4610
|
-
cursor: nesw-resize;
|
|
4611
|
-
}
|
|
4612
|
-
|
|
4613
|
-
/* Docked: the placement owns the height, so only the inner edge is the user's. */
|
|
4614
|
-
:host([placement="sidebar"]) .resize-handle,
|
|
4615
|
-
:host([placement="side"]) .resize-handle {
|
|
4616
|
-
top: 0;
|
|
4617
|
-
bottom: auto;
|
|
4618
|
-
width: 8px;
|
|
4619
|
-
height: 100%;
|
|
4620
|
-
cursor: ew-resize;
|
|
4621
|
-
}
|
|
4622
|
-
|
|
4623
|
-
/* Full-bleed: nothing to drag. */
|
|
4624
|
-
:host([placement="full"]) .resize-handle,
|
|
4625
|
-
:host([placement="page"]) .resize-handle {
|
|
4626
|
-
display: none;
|
|
4627
|
-
}
|
|
4628
|
-
|
|
4629
|
-
.resize-handle[data-dragging] {
|
|
4630
|
-
background: var(--ag-ui-accent);
|
|
4631
|
-
opacity: 0.35;
|
|
4632
|
-
}
|
|
4633
|
-
|
|
4634
|
-
.input-row {
|
|
4635
|
-
padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));
|
|
4701
|
+
:host([placement="page"]) .input-row {
|
|
4702
|
+
padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));
|
|
4636
4703
|
}
|
|
4637
4704
|
|
|
4638
4705
|
/* The rows between the message list and the composer (skill chips, the
|
|
@@ -4641,12 +4708,12 @@ var STYLES = `
|
|
|
4641
4708
|
margin-based, so each gets its own inline axis nudged by the same gutter. */
|
|
4642
4709
|
:host([placement="page"]) .skill-chips,
|
|
4643
4710
|
:host([placement="page"]) .attachment-tray {
|
|
4644
|
-
padding-inline: max(12px, calc((100% - var(--
|
|
4711
|
+
padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));
|
|
4645
4712
|
}
|
|
4646
4713
|
|
|
4647
4714
|
:host([placement="page"]) .skill-palette,
|
|
4648
4715
|
:host([placement="page"]) .skill-hint {
|
|
4649
|
-
margin-inline: max(12px, calc((100% - var(--
|
|
4716
|
+
margin-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));
|
|
4650
4717
|
}
|
|
4651
4718
|
|
|
4652
4719
|
/* In the reading column the assistant well uses the full width; the user
|
|
@@ -4661,75 +4728,179 @@ var STYLES = `
|
|
|
4661
4728
|
--ag-ui-position: static (and place this element in your own layout) for a
|
|
4662
4729
|
host-managed push instead. */
|
|
4663
4730
|
:host([placement="sidebar"]) {
|
|
4664
|
-
--ag-ui-inset
|
|
4665
|
-
--ag-ui-width
|
|
4666
|
-
--ag-ui-height
|
|
4667
|
-
--ag-ui-max-height
|
|
4668
|
-
--ag-ui-radius
|
|
4669
|
-
|
|
4670
|
-
transition: width 0.28s ease;
|
|
4731
|
+
--_inset: var(--ag-ui-inset, 0 0 0 auto);
|
|
4732
|
+
--_width: var(--ag-ui-width, 420px);
|
|
4733
|
+
--_height: var(--ag-ui-height, 100vh);
|
|
4734
|
+
--_max-height: var(--ag-ui-max-height, 100vh);
|
|
4735
|
+
--_radius: var(--ag-ui-radius, 0);
|
|
4736
|
+
transition: width var(--_motion) var(--_ease);
|
|
4671
4737
|
}
|
|
4672
4738
|
|
|
4673
4739
|
:host([placement="sidebar"][data-side="left"]) {
|
|
4674
|
-
--ag-ui-inset
|
|
4740
|
+
--_inset: var(--ag-ui-inset, 0 auto 0 0);
|
|
4675
4741
|
}
|
|
4676
4742
|
|
|
4743
|
+
/* The docked panel is pinned to the edge it docks against rather than filling
|
|
4744
|
+
the host as a flex child. Collapsing shrinks the host to the rail width, and
|
|
4745
|
+
a flex child would be squashed to 52px on the way out instead of sliding out
|
|
4746
|
+
at full width. */
|
|
4677
4747
|
:host([placement="sidebar"]) .chat {
|
|
4678
|
-
|
|
4748
|
+
position: absolute;
|
|
4749
|
+
inset: 0 0 0 auto;
|
|
4750
|
+
width: var(--_width);
|
|
4751
|
+
transform-origin: right center;
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
:host([placement="sidebar"][data-side="left"]) .chat {
|
|
4755
|
+
inset: 0 auto 0 0;
|
|
4756
|
+
transform-origin: left center;
|
|
4679
4757
|
}
|
|
4680
4758
|
|
|
4681
|
-
/* Collapsed sidebar: shrink the host to the rail width
|
|
4682
|
-
|
|
4683
|
-
wins regardless of source order. */
|
|
4759
|
+
/* Collapsed sidebar: shrink the host to the rail width and slide the panel out
|
|
4760
|
+
through the edge it docks against. Higher specificity than the generic
|
|
4761
|
+
collapse rules, so it wins regardless of source order. */
|
|
4684
4762
|
:host([placement="sidebar"][collapsed]) {
|
|
4685
|
-
width: var(--
|
|
4763
|
+
width: var(--_rail-width);
|
|
4686
4764
|
height: 100vh;
|
|
4687
4765
|
max-height: 100vh;
|
|
4688
4766
|
bottom: 0;
|
|
4767
|
+
pointer-events: auto;
|
|
4689
4768
|
}
|
|
4690
4769
|
|
|
4691
4770
|
:host([placement="sidebar"][collapsed]) .chat {
|
|
4692
|
-
|
|
4771
|
+
transform: translateX(100%);
|
|
4693
4772
|
}
|
|
4694
4773
|
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4774
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .chat {
|
|
4775
|
+
transform: translateX(-100%);
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4779
|
+
:host([placement="sidebar"]),
|
|
4780
|
+
:host([placement="sidebar"]) .chat {
|
|
4781
|
+
transition: none;
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4785
|
+
/* \u2500\u2500 Launcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4786
|
+
What a collapsed widget shrinks to: a round floating button by default, the
|
|
4787
|
+
sidebar's slim edge rail under that placement. A sibling of the panel, so it
|
|
4788
|
+
survives the panel being hidden, and the only part of a collapsed widget
|
|
4789
|
+
that takes pointer events. */
|
|
4790
|
+
.launcher {
|
|
4791
|
+
display: flex;
|
|
4792
|
+
position: absolute;
|
|
4793
|
+
inset: var(--_launcher-inset);
|
|
4794
|
+
align-items: center;
|
|
4795
|
+
justify-content: center;
|
|
4796
|
+
width: var(--_launcher-size);
|
|
4797
|
+
height: var(--_launcher-size);
|
|
4798
|
+
padding: 0;
|
|
4699
4799
|
border: none;
|
|
4800
|
+
border-radius: var(--_launcher-radius);
|
|
4801
|
+
background: var(--_launcher-bg);
|
|
4802
|
+
color: var(--_launcher-fg);
|
|
4803
|
+
box-shadow: var(--_shadow);
|
|
4700
4804
|
font: inherit;
|
|
4805
|
+
cursor: pointer;
|
|
4806
|
+
pointer-events: auto;
|
|
4807
|
+
opacity: 0;
|
|
4808
|
+
transform: scale(0.4);
|
|
4809
|
+
visibility: hidden;
|
|
4810
|
+
transition:
|
|
4811
|
+
opacity var(--_motion) var(--_ease),
|
|
4812
|
+
transform var(--_motion) var(--_ease-pop),
|
|
4813
|
+
visibility var(--_motion) var(--_ease);
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4816
|
+
/* The launcher grows out of the corner the panel shrank into.
|
|
4817
|
+
It is laid out at rest rather than display:none, which is what lets it
|
|
4818
|
+
animate in *and* out: an element that was not rendered has no before-change
|
|
4819
|
+
style to transition from, and one whose display flips to none cannot
|
|
4820
|
+
transition at all. visibility keeps it unpaintable, untabbable and
|
|
4821
|
+
unclickable in between -- the expanded panel's own controls sit under it and
|
|
4822
|
+
must stay reachable. */
|
|
4823
|
+
:host([collapsed]) .launcher {
|
|
4824
|
+
opacity: 1;
|
|
4825
|
+
transform: none;
|
|
4826
|
+
visibility: visible;
|
|
4701
4827
|
}
|
|
4702
4828
|
|
|
4703
|
-
:host([
|
|
4704
|
-
|
|
4829
|
+
:host([collapsed]) .launcher:hover {
|
|
4830
|
+
transform: scale(1.06);
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4833
|
+
:host([collapsed]) .launcher:active {
|
|
4834
|
+
transform: scale(0.94);
|
|
4835
|
+
}
|
|
4836
|
+
|
|
4837
|
+
.launcher .icon-holder {
|
|
4838
|
+
width: var(--_launcher-icon-size);
|
|
4839
|
+
height: var(--_launcher-icon-size);
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
/* The unread badge rides the launcher's top-right corner. The ring in the
|
|
4843
|
+
widget's own background is what separates it from the launcher underneath,
|
|
4844
|
+
whatever colour the host themes either one. */
|
|
4845
|
+
.launcher-badge {
|
|
4705
4846
|
position: absolute;
|
|
4847
|
+
top: -2px;
|
|
4848
|
+
right: -2px;
|
|
4849
|
+
display: inline-flex;
|
|
4850
|
+
align-items: center;
|
|
4851
|
+
justify-content: center;
|
|
4852
|
+
box-sizing: border-box;
|
|
4853
|
+
min-width: var(--_badge-size);
|
|
4854
|
+
height: var(--_badge-size);
|
|
4855
|
+
padding: 0 5px;
|
|
4856
|
+
border-radius: 999px;
|
|
4857
|
+
background: var(--_badge-bg);
|
|
4858
|
+
color: var(--_badge-fg);
|
|
4859
|
+
font-size: var(--_badge-font-size);
|
|
4860
|
+
font-weight: 600;
|
|
4861
|
+
line-height: 1;
|
|
4862
|
+
box-shadow: 0 0 0 2px var(--_bg);
|
|
4863
|
+
}
|
|
4864
|
+
|
|
4865
|
+
.launcher-badge[hidden] {
|
|
4866
|
+
display: none;
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
/* The sidebar collapses to an edge rail instead: full height, square, flush
|
|
4870
|
+
against the dock. It slides in with the panel rather than popping. */
|
|
4871
|
+
:host([placement="sidebar"][collapsed]) .launcher {
|
|
4706
4872
|
inset: 0;
|
|
4873
|
+
width: auto;
|
|
4874
|
+
height: auto;
|
|
4707
4875
|
align-items: flex-start;
|
|
4708
|
-
justify-content: center;
|
|
4709
4876
|
padding-top: 16px;
|
|
4710
|
-
border: 1px solid var(--
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4877
|
+
border: 1px solid var(--_border);
|
|
4878
|
+
border-radius: 0;
|
|
4879
|
+
background: var(--_header-bg);
|
|
4880
|
+
color: var(--_header-fg);
|
|
4881
|
+
box-shadow: none;
|
|
4882
|
+
transform: none;
|
|
4714
4883
|
}
|
|
4715
4884
|
|
|
4885
|
+
/* Every transition in this file is timed by --_motion, so collapsing it to a
|
|
4886
|
+
frame is the whole reduced-motion story: states still change (and display
|
|
4887
|
+
still flips at the end of its discrete transition), nothing travels. */
|
|
4716
4888
|
@media (prefers-reduced-motion: reduce) {
|
|
4717
|
-
:host
|
|
4718
|
-
|
|
4719
|
-
transition: none;
|
|
4889
|
+
:host {
|
|
4890
|
+
--_motion: var(--ag-ui-motion, 0.001s);
|
|
4720
4891
|
}
|
|
4721
4892
|
}
|
|
4722
4893
|
|
|
4723
4894
|
/* Embedded: drop the floating chrome and the high z-index stacking context so
|
|
4724
4895
|
the widget lives in the host's own layout (fixes overlay/z-index clashes). */
|
|
4725
4896
|
:host([placement="embedded"]) {
|
|
4726
|
-
--ag-ui-position
|
|
4727
|
-
--ag-ui-width
|
|
4728
|
-
--ag-ui-height
|
|
4729
|
-
--ag-ui-max-width
|
|
4730
|
-
--ag-ui-max-height
|
|
4731
|
-
--ag-ui-shadow
|
|
4732
|
-
--ag-ui-z-index
|
|
4897
|
+
--_position: var(--ag-ui-position, static);
|
|
4898
|
+
--_width: var(--ag-ui-width, 100%);
|
|
4899
|
+
--_height: var(--ag-ui-height, 100%);
|
|
4900
|
+
--_max-width: var(--ag-ui-max-width, 100%);
|
|
4901
|
+
--_max-height: var(--ag-ui-max-height, 100%);
|
|
4902
|
+
--_shadow: var(--ag-ui-shadow, none);
|
|
4903
|
+
--_z-index: var(--ag-ui-z-index, auto);
|
|
4733
4904
|
}
|
|
4734
4905
|
|
|
4735
4906
|
.chat {
|
|
@@ -4738,10 +4909,10 @@ var STYLES = `
|
|
|
4738
4909
|
flex-direction: column;
|
|
4739
4910
|
flex: 1;
|
|
4740
4911
|
min-height: 0;
|
|
4741
|
-
background: var(--
|
|
4742
|
-
border: 1px solid var(--
|
|
4743
|
-
border-radius: var(--
|
|
4744
|
-
box-shadow: var(--
|
|
4912
|
+
background: var(--_bg);
|
|
4913
|
+
border: 1px solid var(--_border);
|
|
4914
|
+
border-radius: var(--_radius);
|
|
4915
|
+
box-shadow: var(--_shadow);
|
|
4745
4916
|
overflow: hidden;
|
|
4746
4917
|
}
|
|
4747
4918
|
|
|
@@ -4751,9 +4922,9 @@ var STYLES = `
|
|
|
4751
4922
|
justify-content: space-between;
|
|
4752
4923
|
gap: 8px;
|
|
4753
4924
|
padding: 10px 14px;
|
|
4754
|
-
border-bottom: 1px solid var(--
|
|
4755
|
-
background: var(--
|
|
4756
|
-
color: var(--
|
|
4925
|
+
border-bottom: 1px solid var(--_border);
|
|
4926
|
+
background: var(--_header-bg);
|
|
4927
|
+
color: var(--_header-fg);
|
|
4757
4928
|
}
|
|
4758
4929
|
|
|
4759
4930
|
.header-title {
|
|
@@ -4772,8 +4943,8 @@ var STYLES = `
|
|
|
4772
4943
|
align-items: center;
|
|
4773
4944
|
justify-content: center;
|
|
4774
4945
|
flex: none;
|
|
4775
|
-
width: var(--
|
|
4776
|
-
height: var(--
|
|
4946
|
+
width: var(--_icon-size);
|
|
4947
|
+
height: var(--_icon-size);
|
|
4777
4948
|
line-height: 1;
|
|
4778
4949
|
}
|
|
4779
4950
|
|
|
@@ -4781,7 +4952,7 @@ var STYLES = `
|
|
|
4781
4952
|
width: 100%;
|
|
4782
4953
|
height: 100%;
|
|
4783
4954
|
object-fit: contain;
|
|
4784
|
-
border-radius: var(--
|
|
4955
|
+
border-radius: var(--_icon-radius);
|
|
4785
4956
|
}
|
|
4786
4957
|
|
|
4787
4958
|
.header-controls {
|
|
@@ -4807,36 +4978,87 @@ var STYLES = `
|
|
|
4807
4978
|
background: rgba(255, 255, 255, 0.18);
|
|
4808
4979
|
}
|
|
4809
4980
|
|
|
4810
|
-
/*
|
|
4811
|
-
|
|
4981
|
+
/* \u2500\u2500 Collapse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4982
|
+
Collapsing shrinks the widget to the round floating launcher: the panel
|
|
4983
|
+
scales down toward the launcher's corner and fades out, the launcher pops in
|
|
4984
|
+
from that same point. Both halves are transform and opacity only, so the
|
|
4985
|
+
morph runs on the compositor and never reflows the host page.
|
|
4986
|
+
|
|
4987
|
+
The host box keeps its expanded size. Animating it would animate layout, and
|
|
4988
|
+
a dragged --ag-ui-width would then fight the launcher's own size. Nothing
|
|
4989
|
+
paints there once the panel is gone, so the box only has to stop swallowing
|
|
4990
|
+
clicks: pointer events go to none, and the launcher takes them back.
|
|
4991
|
+
|
|
4992
|
+
Two placements collapse to something else and restore what they need:
|
|
4993
|
+
"sidebar" slides to its rail (below), while "embedded" and "page" keep the
|
|
4994
|
+
original header bar \u2014 embedded is laid out by the host page, where a
|
|
4995
|
+
floating circle would escape the layout, and page is a full-screen route
|
|
4996
|
+
with no corner to float in. */
|
|
4812
4997
|
:host([collapsed]) {
|
|
4998
|
+
pointer-events: none;
|
|
4999
|
+
}
|
|
5000
|
+
|
|
5001
|
+
:host([collapsed]) .chat {
|
|
5002
|
+
opacity: 0;
|
|
5003
|
+
transform: scale(0.94);
|
|
5004
|
+
visibility: hidden;
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
/* visibility is what keeps the panel out of the tab order and the a11y tree at
|
|
5008
|
+
rest without display:none killing the transition. It interpolates so that any
|
|
5009
|
+
progress below 1 still counts as visible: the panel stays on screen for the
|
|
5010
|
+
whole collapse and flips hidden exactly at the end, and on expand it is
|
|
5011
|
+
visible from the first frame. */
|
|
5012
|
+
.chat {
|
|
5013
|
+
transform-origin: bottom right;
|
|
5014
|
+
transition:
|
|
5015
|
+
opacity var(--_motion) var(--_ease),
|
|
5016
|
+
transform var(--_motion) var(--_ease),
|
|
5017
|
+
visibility var(--_motion) var(--_ease);
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
:host([placement="bottom-left"]) .chat {
|
|
5021
|
+
transform-origin: bottom left;
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
/* The two in-flow placements keep the original collapse: hide the body, let
|
|
5025
|
+
the host shrink to the header bar. */
|
|
5026
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) {
|
|
4813
5027
|
height: auto;
|
|
4814
5028
|
max-height: none;
|
|
5029
|
+
pointer-events: auto;
|
|
4815
5030
|
}
|
|
4816
5031
|
|
|
4817
|
-
:host([collapsed]) .
|
|
4818
|
-
:
|
|
4819
|
-
:
|
|
4820
|
-
:
|
|
4821
|
-
:host([collapsed]) .skill-hint {
|
|
4822
|
-
display: none;
|
|
5032
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .chat {
|
|
5033
|
+
opacity: 1;
|
|
5034
|
+
transform: none;
|
|
5035
|
+
visibility: visible;
|
|
4823
5036
|
}
|
|
4824
5037
|
|
|
4825
|
-
/*
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
:host([collapsed][placement="
|
|
4830
|
-
|
|
5038
|
+
/* These two keep the header bar, so the launcher must stay out of the way \u2014
|
|
5039
|
+
an embedded host is position: static, which would otherwise leave an
|
|
5040
|
+
absolutely-positioned circle to escape the layout entirely and land against
|
|
5041
|
+
whatever the page happens to position. */
|
|
5042
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
|
|
5043
|
+
visibility: hidden;
|
|
5044
|
+
opacity: 0;
|
|
5045
|
+
}
|
|
5046
|
+
|
|
5047
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .messages,
|
|
5048
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .input-row,
|
|
5049
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-chips,
|
|
5050
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-palette,
|
|
5051
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-hint {
|
|
5052
|
+
display: none;
|
|
4831
5053
|
}
|
|
4832
5054
|
|
|
4833
5055
|
.messages {
|
|
4834
5056
|
flex: 1;
|
|
4835
5057
|
overflow-y: auto;
|
|
4836
|
-
padding: var(--
|
|
5058
|
+
padding: var(--_pad);
|
|
4837
5059
|
display: flex;
|
|
4838
5060
|
flex-direction: column;
|
|
4839
|
-
gap: var(--
|
|
5061
|
+
gap: var(--_space);
|
|
4840
5062
|
}
|
|
4841
5063
|
|
|
4842
5064
|
/* Empty-state region (slot): centred while it's the only thing in the
|
|
@@ -4844,7 +5066,7 @@ var STYLES = `
|
|
|
4844
5066
|
.empty {
|
|
4845
5067
|
margin: auto;
|
|
4846
5068
|
text-align: center;
|
|
4847
|
-
color: var(--
|
|
5069
|
+
color: var(--_muted);
|
|
4848
5070
|
}
|
|
4849
5071
|
|
|
4850
5072
|
.empty[hidden] {
|
|
@@ -4860,22 +5082,22 @@ var STYLES = `
|
|
|
4860
5082
|
.answer {
|
|
4861
5083
|
display: flex;
|
|
4862
5084
|
flex-direction: column;
|
|
4863
|
-
gap: var(--
|
|
5085
|
+
gap: var(--_space);
|
|
4864
5086
|
align-self: stretch;
|
|
4865
5087
|
min-width: 0;
|
|
4866
5088
|
}
|
|
4867
5089
|
|
|
4868
5090
|
:host([data-answer-well]) .answer {
|
|
4869
|
-
padding: var(--
|
|
4870
|
-
background: var(--
|
|
4871
|
-
border: 1px solid var(--
|
|
4872
|
-
border-radius: var(--
|
|
5091
|
+
padding: var(--_pad);
|
|
5092
|
+
background: var(--_well-bg);
|
|
5093
|
+
border: 1px solid var(--_well-border);
|
|
5094
|
+
border-radius: var(--_radius);
|
|
4873
5095
|
}
|
|
4874
5096
|
|
|
4875
5097
|
.message {
|
|
4876
5098
|
max-width: 80%;
|
|
4877
|
-
padding: var(--
|
|
4878
|
-
border-radius: var(--
|
|
5099
|
+
padding: var(--_msg-pad);
|
|
5100
|
+
border-radius: var(--_msg-radius);
|
|
4879
5101
|
line-height: 1.4;
|
|
4880
5102
|
white-space: pre-wrap;
|
|
4881
5103
|
word-break: break-word;
|
|
@@ -4913,14 +5135,14 @@ var STYLES = `
|
|
|
4913
5135
|
|
|
4914
5136
|
.message--user {
|
|
4915
5137
|
align-self: flex-end;
|
|
4916
|
-
background: var(--
|
|
4917
|
-
color: var(--
|
|
5138
|
+
background: var(--_user-bg);
|
|
5139
|
+
color: var(--_user-fg);
|
|
4918
5140
|
border-bottom-right-radius: 4px;
|
|
4919
5141
|
}
|
|
4920
5142
|
|
|
4921
5143
|
.message--assistant {
|
|
4922
5144
|
align-self: flex-start;
|
|
4923
|
-
background: var(--
|
|
5145
|
+
background: var(--_assistant-bg);
|
|
4924
5146
|
border-bottom-left-radius: 4px;
|
|
4925
5147
|
/* Assistant bubbles hold rendered markdown/HTML, so collapse the source
|
|
4926
5148
|
whitespace the renderer leaves between block tags. */
|
|
@@ -4950,7 +5172,7 @@ var STYLES = `
|
|
|
4950
5172
|
}
|
|
4951
5173
|
|
|
4952
5174
|
.message--assistant a {
|
|
4953
|
-
color: var(--
|
|
5175
|
+
color: var(--_accent);
|
|
4954
5176
|
text-decoration: underline;
|
|
4955
5177
|
}
|
|
4956
5178
|
|
|
@@ -4965,8 +5187,8 @@ var STYLES = `
|
|
|
4965
5187
|
.message--assistant pre {
|
|
4966
5188
|
padding: 8px 10px;
|
|
4967
5189
|
overflow: auto;
|
|
4968
|
-
background: var(--
|
|
4969
|
-
border: 1px solid var(--
|
|
5190
|
+
background: var(--_bg);
|
|
5191
|
+
border: 1px solid var(--_border);
|
|
4970
5192
|
border-radius: 6px;
|
|
4971
5193
|
}
|
|
4972
5194
|
|
|
@@ -4982,6 +5204,13 @@ var STYLES = `
|
|
|
4982
5204
|
position: relative;
|
|
4983
5205
|
}
|
|
4984
5206
|
|
|
5207
|
+
/* This button is the only reader of --ag-ui-surface and --ag-ui-text, and
|
|
5208
|
+
neither had a default until the alias layer gave them one. Both references
|
|
5209
|
+
used to resolve to nothing, dropping the declaration: the background fell
|
|
5210
|
+
back to the initial transparent, and the hover/copied colour to the
|
|
5211
|
+
inherited body colour. The defaults chosen upstream (transparent, and the
|
|
5212
|
+
body foreground) reproduce that, so the rename repaints nothing. A raised
|
|
5213
|
+
surface behind the button is a separate design question. */
|
|
4985
5214
|
.code-copy {
|
|
4986
5215
|
position: absolute;
|
|
4987
5216
|
top: 4px;
|
|
@@ -4990,9 +5219,9 @@ var STYLES = `
|
|
|
4990
5219
|
font: inherit;
|
|
4991
5220
|
font-size: 0.75em;
|
|
4992
5221
|
line-height: 1.6;
|
|
4993
|
-
color: var(--
|
|
4994
|
-
background: var(--
|
|
4995
|
-
border: 1px solid var(--
|
|
5222
|
+
color: var(--_muted);
|
|
5223
|
+
background: var(--_surface);
|
|
5224
|
+
border: 1px solid var(--_border);
|
|
4996
5225
|
border-radius: 4px;
|
|
4997
5226
|
cursor: pointer;
|
|
4998
5227
|
opacity: 0;
|
|
@@ -5007,24 +5236,24 @@ var STYLES = `
|
|
|
5007
5236
|
}
|
|
5008
5237
|
|
|
5009
5238
|
.code-copy:hover {
|
|
5010
|
-
color: var(--
|
|
5011
|
-
background: var(--
|
|
5239
|
+
color: var(--_text);
|
|
5240
|
+
background: var(--_hover);
|
|
5012
5241
|
}
|
|
5013
5242
|
|
|
5014
5243
|
.code-copy[data-state="copied"] {
|
|
5015
5244
|
opacity: 1;
|
|
5016
|
-
color: var(--
|
|
5245
|
+
color: var(--_text);
|
|
5017
5246
|
}
|
|
5018
5247
|
|
|
5019
5248
|
.code-copy[data-state="failed"] {
|
|
5020
5249
|
opacity: 1;
|
|
5021
|
-
color: var(--
|
|
5250
|
+
color: var(--_danger, var(--_text));
|
|
5022
5251
|
}
|
|
5023
5252
|
|
|
5024
5253
|
.message--assistant blockquote {
|
|
5025
5254
|
padding-left: 10px;
|
|
5026
|
-
border-left: 3px solid var(--
|
|
5027
|
-
color: var(--
|
|
5255
|
+
border-left: 3px solid var(--_border);
|
|
5256
|
+
color: var(--_muted);
|
|
5028
5257
|
}
|
|
5029
5258
|
|
|
5030
5259
|
/* Markdown tables. table/thead/tbody/tr/th/td are all in
|
|
@@ -5045,13 +5274,13 @@ var STYLES = `
|
|
|
5045
5274
|
.message--assistant th,
|
|
5046
5275
|
.message--assistant td {
|
|
5047
5276
|
padding: 6px 10px;
|
|
5048
|
-
border: 1px solid var(--
|
|
5277
|
+
border: 1px solid var(--_border);
|
|
5049
5278
|
text-align: left;
|
|
5050
5279
|
vertical-align: top;
|
|
5051
5280
|
}
|
|
5052
5281
|
|
|
5053
5282
|
.message--assistant th {
|
|
5054
|
-
background: var(--
|
|
5283
|
+
background: var(--_hover);
|
|
5055
5284
|
font-weight: 600;
|
|
5056
5285
|
}
|
|
5057
5286
|
|
|
@@ -5061,7 +5290,7 @@ var STYLES = `
|
|
|
5061
5290
|
gap: 4px;
|
|
5062
5291
|
align-items: center;
|
|
5063
5292
|
padding: 12px 14px;
|
|
5064
|
-
background: var(--
|
|
5293
|
+
background: var(--_assistant-bg);
|
|
5065
5294
|
border-radius: 14px;
|
|
5066
5295
|
border-bottom-left-radius: 4px;
|
|
5067
5296
|
}
|
|
@@ -5070,7 +5299,7 @@ var STYLES = `
|
|
|
5070
5299
|
width: 6px;
|
|
5071
5300
|
height: 6px;
|
|
5072
5301
|
border-radius: 50%;
|
|
5073
|
-
background: var(--
|
|
5302
|
+
background: var(--_muted);
|
|
5074
5303
|
animation: ag-ui-pending 1.2s infinite ease-in-out both;
|
|
5075
5304
|
}
|
|
5076
5305
|
|
|
@@ -5103,7 +5332,7 @@ var STYLES = `
|
|
|
5103
5332
|
flex-direction: column;
|
|
5104
5333
|
gap: 4px;
|
|
5105
5334
|
font-size: 12px;
|
|
5106
|
-
color: var(--
|
|
5335
|
+
color: var(--_muted);
|
|
5107
5336
|
}
|
|
5108
5337
|
|
|
5109
5338
|
.thoughts-toggle {
|
|
@@ -5114,7 +5343,7 @@ var STYLES = `
|
|
|
5114
5343
|
font: inherit;
|
|
5115
5344
|
font-size: 12px;
|
|
5116
5345
|
font-weight: 600;
|
|
5117
|
-
color: var(--
|
|
5346
|
+
color: var(--_muted);
|
|
5118
5347
|
cursor: pointer;
|
|
5119
5348
|
}
|
|
5120
5349
|
|
|
@@ -5139,7 +5368,7 @@ var STYLES = `
|
|
|
5139
5368
|
.thoughts-body {
|
|
5140
5369
|
margin: 0;
|
|
5141
5370
|
padding: 4px 0 4px 10px;
|
|
5142
|
-
border-left: 2px solid var(--
|
|
5371
|
+
border-left: 2px solid var(--_border);
|
|
5143
5372
|
max-height: 220px;
|
|
5144
5373
|
overflow: auto;
|
|
5145
5374
|
white-space: pre-wrap;
|
|
@@ -5168,9 +5397,9 @@ var STYLES = `
|
|
|
5168
5397
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
5169
5398
|
padding: 8px 10px;
|
|
5170
5399
|
border-radius: 8px;
|
|
5171
|
-
background: var(--
|
|
5172
|
-
border: 1px solid var(--
|
|
5173
|
-
color: var(--
|
|
5400
|
+
background: var(--_tool-bg);
|
|
5401
|
+
border: 1px solid var(--_border);
|
|
5402
|
+
color: var(--_tool-fg);
|
|
5174
5403
|
}
|
|
5175
5404
|
|
|
5176
5405
|
.tool-call-head {
|
|
@@ -5203,10 +5432,10 @@ var STYLES = `
|
|
|
5203
5432
|
|
|
5204
5433
|
/* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */
|
|
5205
5434
|
.tool-call[data-status="pending"] .tool-call-icon {
|
|
5206
|
-
border: 2px solid var(--
|
|
5435
|
+
border: 2px solid var(--_muted);
|
|
5207
5436
|
border-top-color: transparent;
|
|
5208
5437
|
border-radius: 50%;
|
|
5209
|
-
animation: ag-ui-tool-spin var(--
|
|
5438
|
+
animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;
|
|
5210
5439
|
}
|
|
5211
5440
|
|
|
5212
5441
|
@keyframes ag-ui-tool-spin {
|
|
@@ -5215,18 +5444,18 @@ var STYLES = `
|
|
|
5215
5444
|
|
|
5216
5445
|
/* Settled: a themeable glyph coloured by outcome. */
|
|
5217
5446
|
.tool-call[data-status="done"] .tool-call-icon::before {
|
|
5218
|
-
content: var(--
|
|
5219
|
-
color: var(--
|
|
5447
|
+
content: var(--_tool-icon-done);
|
|
5448
|
+
color: var(--_success);
|
|
5220
5449
|
}
|
|
5221
5450
|
|
|
5222
5451
|
.tool-call[data-status="error"] .tool-call-icon::before {
|
|
5223
|
-
content: var(--
|
|
5224
|
-
color: var(--
|
|
5452
|
+
content: var(--_tool-icon-error);
|
|
5453
|
+
color: var(--_danger);
|
|
5225
5454
|
}
|
|
5226
5455
|
|
|
5227
5456
|
.tool-call[data-status="declined"] .tool-call-icon::before {
|
|
5228
|
-
content: var(--
|
|
5229
|
-
color: var(--
|
|
5457
|
+
content: var(--_tool-icon-declined);
|
|
5458
|
+
color: var(--_muted);
|
|
5230
5459
|
}
|
|
5231
5460
|
|
|
5232
5461
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -5252,19 +5481,19 @@ var STYLES = `
|
|
|
5252
5481
|
font-size: 11px;
|
|
5253
5482
|
font-weight: 600;
|
|
5254
5483
|
background: rgba(127, 127, 127, 0.16);
|
|
5255
|
-
color: var(--
|
|
5484
|
+
color: var(--_muted);
|
|
5256
5485
|
}
|
|
5257
5486
|
|
|
5258
5487
|
.tool-call[data-status="done"] .tool-call-status {
|
|
5259
|
-
color: var(--
|
|
5488
|
+
color: var(--_success);
|
|
5260
5489
|
}
|
|
5261
5490
|
|
|
5262
5491
|
.tool-call[data-status="error"] .tool-call-status {
|
|
5263
|
-
color: var(--
|
|
5492
|
+
color: var(--_danger);
|
|
5264
5493
|
}
|
|
5265
5494
|
|
|
5266
5495
|
.tool-call[data-status="declined"] .tool-call-status {
|
|
5267
|
-
color: var(--
|
|
5496
|
+
color: var(--_muted);
|
|
5268
5497
|
}
|
|
5269
5498
|
|
|
5270
5499
|
.tool-call-body {
|
|
@@ -5286,7 +5515,7 @@ var STYLES = `
|
|
|
5286
5515
|
font-weight: 700;
|
|
5287
5516
|
letter-spacing: 0.06em;
|
|
5288
5517
|
text-transform: uppercase;
|
|
5289
|
-
color: var(--
|
|
5518
|
+
color: var(--_muted);
|
|
5290
5519
|
}
|
|
5291
5520
|
|
|
5292
5521
|
/* The record of a human decision on a gated call. An approved call used to
|
|
@@ -5294,7 +5523,7 @@ var STYLES = `
|
|
|
5294
5523
|
.skill-item-token {
|
|
5295
5524
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
5296
5525
|
font-size: 0.92em;
|
|
5297
|
-
color: var(--
|
|
5526
|
+
color: var(--_accent);
|
|
5298
5527
|
margin-right: 6px;
|
|
5299
5528
|
}
|
|
5300
5529
|
|
|
@@ -5302,7 +5531,7 @@ var STYLES = `
|
|
|
5302
5531
|
flex: none;
|
|
5303
5532
|
font-size: 11px;
|
|
5304
5533
|
font-style: italic;
|
|
5305
|
-
color: var(--
|
|
5534
|
+
color: var(--_muted);
|
|
5306
5535
|
}
|
|
5307
5536
|
|
|
5308
5537
|
.tool-call-args,
|
|
@@ -5311,12 +5540,12 @@ var STYLES = `
|
|
|
5311
5540
|
padding: 6px 8px;
|
|
5312
5541
|
max-height: 160px;
|
|
5313
5542
|
overflow: auto;
|
|
5314
|
-
background: var(--
|
|
5315
|
-
border: 1px solid var(--
|
|
5543
|
+
background: var(--_bg);
|
|
5544
|
+
border: 1px solid var(--_border);
|
|
5316
5545
|
border-radius: 6px;
|
|
5317
5546
|
white-space: pre-wrap;
|
|
5318
5547
|
word-break: break-word;
|
|
5319
|
-
color: var(--
|
|
5548
|
+
color: var(--_fg);
|
|
5320
5549
|
}
|
|
5321
5550
|
|
|
5322
5551
|
/* Display modes are pure visibility over one DOM shape, selected from the host
|
|
@@ -5363,7 +5592,7 @@ var STYLES = `
|
|
|
5363
5592
|
background: none;
|
|
5364
5593
|
font: inherit;
|
|
5365
5594
|
font-weight: 600;
|
|
5366
|
-
color: var(--
|
|
5595
|
+
color: var(--_accent);
|
|
5367
5596
|
cursor: pointer;
|
|
5368
5597
|
}
|
|
5369
5598
|
|
|
@@ -5386,13 +5615,12 @@ var STYLES = `
|
|
|
5386
5615
|
}
|
|
5387
5616
|
|
|
5388
5617
|
.resize-handle:focus-visible {
|
|
5389
|
-
outline: 2px solid var(--
|
|
5618
|
+
outline: 2px solid var(--_accent);
|
|
5390
5619
|
outline-offset: -2px;
|
|
5391
5620
|
}
|
|
5392
5621
|
|
|
5393
5622
|
/* The grip sits on the corner opposite the panel's anchor, because a resize
|
|
5394
|
-
measures from whichever edge is not moving.
|
|
5395
|
-
so switching placement moves the grip immediately.
|
|
5623
|
+
measures from whichever edge is not moving.
|
|
5396
5624
|
|
|
5397
5625
|
Default is floating: pinned bottom-right, so the grip is top-left. */
|
|
5398
5626
|
.resize-handle {
|
|
@@ -5403,7 +5631,9 @@ var STYLES = `
|
|
|
5403
5631
|
cursor: nwse-resize;
|
|
5404
5632
|
}
|
|
5405
5633
|
|
|
5406
|
-
/*
|
|
5634
|
+
/* Placement is only the opening guess, and these two rules are all it is good
|
|
5635
|
+
for. Embedded sits in normal flow, pinned top-left, so it grows bottom-right;
|
|
5636
|
+
bottom-left is pinned there, so its free corner is top-right. */
|
|
5407
5637
|
:host([placement="embedded"]) .resize-handle {
|
|
5408
5638
|
top: auto;
|
|
5409
5639
|
left: auto;
|
|
@@ -5412,16 +5642,64 @@ var STYLES = `
|
|
|
5412
5642
|
cursor: nwse-resize;
|
|
5413
5643
|
}
|
|
5414
5644
|
|
|
5415
|
-
/* Pinned bottom-left, so the free corner is top-right. */
|
|
5416
5645
|
:host([placement="bottom-left"]) .resize-handle {
|
|
5417
5646
|
left: auto;
|
|
5418
5647
|
right: 0;
|
|
5419
5648
|
cursor: nesw-resize;
|
|
5420
5649
|
}
|
|
5421
5650
|
|
|
5422
|
-
/*
|
|
5651
|
+
/* Then the measurement corrects it. Which edges are held still belongs to the
|
|
5652
|
+
host's layout rather than to placement -- a floating panel a host right-aligns
|
|
5653
|
+
is anchored bottom-left, not bottom-right -- so the element measures the edges
|
|
5654
|
+
that stay put and stamps them here as "<y>-<x>". Equal specificity to the
|
|
5655
|
+
rules above, so source order is what lets the measured value win.
|
|
5656
|
+
|
|
5657
|
+
These must not be written as [data-resize-anchor~="left"]: the stamped value
|
|
5658
|
+
is one hyphenated token, and ~= matches whitespace-separated words, so such a
|
|
5659
|
+
selector cannot ever match. It did not, which left the grip drawn at the
|
|
5660
|
+
corner that moves while the cursor pointed along the right diagonal.
|
|
5661
|
+
|
|
5662
|
+
Each rule also sets both sides of its axis rather than only the side it
|
|
5663
|
+
moves. One that flipped a single way could not undo a placement guess that
|
|
5664
|
+
had flipped the other, which put the grip on the anchored corner for an
|
|
5665
|
+
embedded panel its host right-aligns. */
|
|
5666
|
+
:host([data-resize-anchor$="-left"]) .resize-handle {
|
|
5667
|
+
left: auto;
|
|
5668
|
+
right: 0;
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5671
|
+
:host([data-resize-anchor$="-right"]) .resize-handle {
|
|
5672
|
+
left: 0;
|
|
5673
|
+
right: auto;
|
|
5674
|
+
}
|
|
5675
|
+
|
|
5676
|
+
:host([data-resize-anchor^="top-"]) .resize-handle {
|
|
5677
|
+
top: auto;
|
|
5678
|
+
bottom: 0;
|
|
5679
|
+
}
|
|
5680
|
+
|
|
5681
|
+
:host([data-resize-anchor^="bottom-"]) .resize-handle {
|
|
5682
|
+
top: 0;
|
|
5683
|
+
bottom: auto;
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5686
|
+
/* One axis flipped means the drag runs along the other diagonal. */
|
|
5687
|
+
:host([data-resize-anchor="top-left"]) .resize-handle,
|
|
5688
|
+
:host([data-resize-anchor="bottom-right"]) .resize-handle {
|
|
5689
|
+
cursor: nwse-resize;
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5692
|
+
:host([data-resize-anchor="top-right"]) .resize-handle,
|
|
5693
|
+
:host([data-resize-anchor="bottom-left"]) .resize-handle {
|
|
5694
|
+
cursor: nesw-resize;
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
/* Docked: the placement owns the height, so only the inner edge is the user's --
|
|
5698
|
+
an edge, not a corner, so this outranks the measured anchor by coming after. */
|
|
5423
5699
|
:host([placement="sidebar"]) .resize-handle,
|
|
5424
5700
|
:host([placement="side"]) .resize-handle {
|
|
5701
|
+
top: 0;
|
|
5702
|
+
bottom: auto;
|
|
5425
5703
|
width: 8px;
|
|
5426
5704
|
height: 100%;
|
|
5427
5705
|
cursor: ew-resize;
|
|
@@ -5440,106 +5718,191 @@ var STYLES = `
|
|
|
5440
5718
|
}
|
|
5441
5719
|
|
|
5442
5720
|
.resize-handle[data-dragging] {
|
|
5443
|
-
background: var(--
|
|
5721
|
+
background: var(--_accent);
|
|
5444
5722
|
opacity: 0.35;
|
|
5445
5723
|
}
|
|
5446
5724
|
|
|
5725
|
+
/* \u2500\u2500 Composer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5726
|
+
One surface owns the border, the background and the focus ring; the field and
|
|
5727
|
+
its tool row sit inside it. The row used to be four siblings stretched to the
|
|
5728
|
+
textarea's height, which gave a paperclip the same visual weight as the field
|
|
5729
|
+
and turned Send into a full-height slab. */
|
|
5447
5730
|
.input-row {
|
|
5448
5731
|
display: flex;
|
|
5449
|
-
gap: 8px;
|
|
5450
5732
|
padding: 12px;
|
|
5451
|
-
border-top: 1px solid var(--
|
|
5733
|
+
border-top: 1px solid var(--_border);
|
|
5452
5734
|
}
|
|
5453
5735
|
|
|
5454
|
-
.
|
|
5736
|
+
.composer {
|
|
5455
5737
|
flex: 1;
|
|
5738
|
+
min-width: 0;
|
|
5739
|
+
display: flex;
|
|
5740
|
+
flex-direction: column;
|
|
5741
|
+
gap: 4px;
|
|
5742
|
+
padding: 6px 6px 6px 10px;
|
|
5743
|
+
background: var(--_input-bg);
|
|
5744
|
+
border: 1px solid var(--_border);
|
|
5745
|
+
border-radius: var(--_composer-radius);
|
|
5746
|
+
transition: border-color var(--_motion) var(--_ease);
|
|
5747
|
+
}
|
|
5748
|
+
|
|
5749
|
+
.composer:focus-within {
|
|
5750
|
+
border-color: var(--_accent);
|
|
5751
|
+
}
|
|
5752
|
+
|
|
5753
|
+
/* The field grows with its content (sized by #autoGrow) up to the ceiling,
|
|
5754
|
+
then scrolls. border-box keeps that measurement stable: with content-box the
|
|
5755
|
+
padding would be added to every scrollHeight read and the field would creep
|
|
5756
|
+
taller on each keystroke. */
|
|
5757
|
+
.input {
|
|
5758
|
+
box-sizing: border-box;
|
|
5456
5759
|
resize: none;
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5760
|
+
max-height: var(--_composer-max-height);
|
|
5761
|
+
overflow-y: auto;
|
|
5762
|
+
padding: 6px 4px 2px;
|
|
5763
|
+
background: transparent;
|
|
5764
|
+
border: none;
|
|
5461
5765
|
font: inherit;
|
|
5462
5766
|
color: inherit;
|
|
5463
5767
|
outline: none;
|
|
5464
5768
|
}
|
|
5465
5769
|
|
|
5466
|
-
.
|
|
5467
|
-
|
|
5770
|
+
.composer-tools {
|
|
5771
|
+
display: flex;
|
|
5772
|
+
align-items: center;
|
|
5773
|
+
gap: 2px;
|
|
5468
5774
|
}
|
|
5469
5775
|
|
|
5470
|
-
.
|
|
5776
|
+
/* Icon buttons: quiet at rest, so the field is what the eye lands on. */
|
|
5777
|
+
.attach-btn,
|
|
5778
|
+
.voice-btn {
|
|
5779
|
+
display: inline-flex;
|
|
5780
|
+
align-items: center;
|
|
5781
|
+
justify-content: center;
|
|
5782
|
+
width: var(--_tool-btn-size);
|
|
5783
|
+
height: var(--_tool-btn-size);
|
|
5784
|
+
padding: 0;
|
|
5471
5785
|
border: none;
|
|
5472
5786
|
border-radius: 8px;
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
color: #ffffff;
|
|
5787
|
+
background: transparent;
|
|
5788
|
+
color: var(--_muted);
|
|
5476
5789
|
font: inherit;
|
|
5477
|
-
|
|
5790
|
+
line-height: 1;
|
|
5478
5791
|
cursor: pointer;
|
|
5792
|
+
transition:
|
|
5793
|
+
background-color var(--_motion) var(--_ease),
|
|
5794
|
+
color var(--_motion) var(--_ease);
|
|
5479
5795
|
}
|
|
5480
5796
|
|
|
5481
|
-
.
|
|
5482
|
-
|
|
5483
|
-
|
|
5797
|
+
.attach-btn:hover,
|
|
5798
|
+
.voice-btn:hover {
|
|
5799
|
+
background: var(--_hover);
|
|
5800
|
+
color: var(--_fg);
|
|
5484
5801
|
}
|
|
5485
5802
|
|
|
5486
|
-
|
|
5487
|
-
.
|
|
5488
|
-
|
|
5803
|
+
.attach-btn:disabled,
|
|
5804
|
+
.voice-btn:disabled {
|
|
5805
|
+
cursor: default;
|
|
5806
|
+
opacity: 0.6;
|
|
5489
5807
|
}
|
|
5490
5808
|
|
|
5491
|
-
/*
|
|
5492
|
-
|
|
5493
|
-
.
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5809
|
+
/* Send closes the row on the right: a circle, the only filled control in the
|
|
5810
|
+
composer, so "the thing that acts" reads at a glance. */
|
|
5811
|
+
.send {
|
|
5812
|
+
margin-left: auto;
|
|
5813
|
+
display: inline-flex;
|
|
5814
|
+
align-items: center;
|
|
5815
|
+
justify-content: center;
|
|
5816
|
+
flex: none;
|
|
5817
|
+
width: var(--_send-size);
|
|
5818
|
+
height: var(--_send-size);
|
|
5819
|
+
padding: 0;
|
|
5820
|
+
border: none;
|
|
5821
|
+
border-radius: 50%;
|
|
5822
|
+
background: var(--_accent);
|
|
5823
|
+
color: #ffffff;
|
|
5499
5824
|
font: inherit;
|
|
5825
|
+
line-height: 1;
|
|
5500
5826
|
cursor: pointer;
|
|
5827
|
+
transition:
|
|
5828
|
+
background-color var(--_motion) var(--_ease),
|
|
5829
|
+
transform var(--_motion) var(--_ease-pop);
|
|
5501
5830
|
}
|
|
5502
5831
|
|
|
5503
|
-
.
|
|
5504
|
-
|
|
5832
|
+
.send:hover {
|
|
5833
|
+
transform: scale(1.08);
|
|
5505
5834
|
}
|
|
5506
5835
|
|
|
5507
|
-
.
|
|
5836
|
+
.send:active {
|
|
5837
|
+
transform: scale(0.92);
|
|
5838
|
+
}
|
|
5839
|
+
|
|
5840
|
+
.send:disabled {
|
|
5841
|
+
opacity: 0.5;
|
|
5842
|
+
cursor: default;
|
|
5843
|
+
transform: none;
|
|
5844
|
+
}
|
|
5845
|
+
|
|
5846
|
+
/* The composer button doubles as the Stop control while a run is in flight \u2014
|
|
5847
|
+
same circle, different glyph, so nothing moves when a run starts. */
|
|
5848
|
+
.send[data-state="running"] {
|
|
5849
|
+
background: var(--_muted);
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5852
|
+
.send[data-state="idle"] .send-stop,
|
|
5853
|
+
.send[data-state="running"] .send-send {
|
|
5508
5854
|
display: none;
|
|
5509
5855
|
}
|
|
5510
5856
|
|
|
5511
|
-
/*
|
|
5512
|
-
.
|
|
5513
|
-
|
|
5857
|
+
/* Glyphs \u2014 one class, painted from the button's own colour so every state
|
|
5858
|
+
(hover, recording, running) carries the icon with it. */
|
|
5859
|
+
.glyph {
|
|
5860
|
+
width: var(--_glyph-size);
|
|
5861
|
+
height: var(--_glyph-size);
|
|
5862
|
+
fill: none;
|
|
5863
|
+
stroke: currentColor;
|
|
5864
|
+
stroke-width: var(--_glyph-stroke);
|
|
5865
|
+
stroke-linecap: round;
|
|
5866
|
+
stroke-linejoin: round;
|
|
5514
5867
|
}
|
|
5515
5868
|
|
|
5516
|
-
.
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
padding: 0 10px;
|
|
5520
|
-
background: var(--ag-ui-input-bg);
|
|
5521
|
-
color: inherit;
|
|
5522
|
-
font: inherit;
|
|
5523
|
-
cursor: pointer;
|
|
5869
|
+
.glyph--solid {
|
|
5870
|
+
fill: currentColor;
|
|
5871
|
+
stroke: none;
|
|
5524
5872
|
}
|
|
5525
5873
|
|
|
5526
|
-
|
|
5527
|
-
|
|
5874
|
+
/* In an icon holder (header brand, launcher) the glyph takes the holder's size
|
|
5875
|
+
rather than the composer's. */
|
|
5876
|
+
.icon-holder .glyph {
|
|
5877
|
+
width: 100%;
|
|
5878
|
+
height: 100%;
|
|
5528
5879
|
}
|
|
5529
5880
|
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5881
|
+
/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5882
|
+
/* The picker button sits in the composer's tool row; hidden until
|
|
5883
|
+
data-attachments-url. */
|
|
5884
|
+
.attach-input {
|
|
5885
|
+
display: none;
|
|
5886
|
+
}
|
|
5887
|
+
|
|
5888
|
+
/* The mic button's mount point; filled only once #wireVoice mounts the
|
|
5889
|
+
control. */
|
|
5890
|
+
.voice-slot {
|
|
5891
|
+
display: contents;
|
|
5533
5892
|
}
|
|
5534
5893
|
|
|
5535
5894
|
/* Recording: a red tint + a gentle pulse so it's clearly "live". */
|
|
5536
5895
|
.voice-btn[data-state="recording"] {
|
|
5537
|
-
|
|
5538
|
-
background: var(--ag-ui-danger);
|
|
5896
|
+
background: var(--_danger);
|
|
5539
5897
|
color: #ffffff;
|
|
5540
5898
|
animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;
|
|
5541
5899
|
}
|
|
5542
5900
|
|
|
5901
|
+
.voice-btn[data-state="recording"]:hover {
|
|
5902
|
+
background: var(--_danger);
|
|
5903
|
+
color: #ffffff;
|
|
5904
|
+
}
|
|
5905
|
+
|
|
5543
5906
|
@keyframes ag-ui-voice-pulse {
|
|
5544
5907
|
0%, 100% { opacity: 1; }
|
|
5545
5908
|
50% { opacity: 0.6; }
|
|
@@ -5578,10 +5941,10 @@ var STYLES = `
|
|
|
5578
5941
|
max-width: 100%;
|
|
5579
5942
|
margin: 2px 0;
|
|
5580
5943
|
padding: 3px 10px;
|
|
5581
|
-
border: 1px dashed var(--
|
|
5944
|
+
border: 1px dashed var(--_border);
|
|
5582
5945
|
border-radius: 999px;
|
|
5583
5946
|
background: transparent;
|
|
5584
|
-
color: var(--
|
|
5947
|
+
color: var(--_muted);
|
|
5585
5948
|
font-size: 0.8em;
|
|
5586
5949
|
line-height: 1.4;
|
|
5587
5950
|
}
|
|
@@ -5602,16 +5965,16 @@ var STYLES = `
|
|
|
5602
5965
|
gap: 6px;
|
|
5603
5966
|
max-width: 100%;
|
|
5604
5967
|
padding: 4px 8px;
|
|
5605
|
-
border: 1px solid var(--
|
|
5968
|
+
border: 1px solid var(--_border);
|
|
5606
5969
|
border-radius: 999px;
|
|
5607
|
-
background: var(--
|
|
5970
|
+
background: var(--_assistant-bg);
|
|
5608
5971
|
font-size: 0.85em;
|
|
5609
5972
|
position: relative;
|
|
5610
5973
|
}
|
|
5611
5974
|
|
|
5612
5975
|
.attachment-chip--error {
|
|
5613
|
-
border-color: var(--
|
|
5614
|
-
color: var(--
|
|
5976
|
+
border-color: var(--_danger);
|
|
5977
|
+
color: var(--_danger);
|
|
5615
5978
|
}
|
|
5616
5979
|
|
|
5617
5980
|
.attachment-chip-name {
|
|
@@ -5622,12 +5985,12 @@ var STYLES = `
|
|
|
5622
5985
|
}
|
|
5623
5986
|
|
|
5624
5987
|
.attachment-chip-size {
|
|
5625
|
-
color: var(--
|
|
5988
|
+
color: var(--_muted);
|
|
5626
5989
|
white-space: nowrap;
|
|
5627
5990
|
}
|
|
5628
5991
|
|
|
5629
5992
|
.attachment-chip--error .attachment-chip-size {
|
|
5630
|
-
color: var(--
|
|
5993
|
+
color: var(--_danger);
|
|
5631
5994
|
}
|
|
5632
5995
|
|
|
5633
5996
|
/* The progress bar fills as the file uploads. */
|
|
@@ -5635,13 +5998,13 @@ var STYLES = `
|
|
|
5635
5998
|
flex-basis: 100%;
|
|
5636
5999
|
height: 3px;
|
|
5637
6000
|
border-radius: 2px;
|
|
5638
|
-
background: var(--
|
|
6001
|
+
background: var(--_border);
|
|
5639
6002
|
overflow: hidden;
|
|
5640
6003
|
}
|
|
5641
6004
|
|
|
5642
6005
|
.attachment-chip-bar-fill {
|
|
5643
6006
|
height: 100%;
|
|
5644
|
-
background: var(--
|
|
6007
|
+
background: var(--_accent);
|
|
5645
6008
|
transition: width 0.15s ease;
|
|
5646
6009
|
}
|
|
5647
6010
|
|
|
@@ -5663,14 +6026,14 @@ var STYLES = `
|
|
|
5663
6026
|
|
|
5664
6027
|
/* A subtle outline while a file is dragged over the shell. */
|
|
5665
6028
|
.chat--dragover {
|
|
5666
|
-
outline: 2px dashed var(--
|
|
6029
|
+
outline: 2px dashed var(--_accent);
|
|
5667
6030
|
outline-offset: -4px;
|
|
5668
6031
|
}
|
|
5669
6032
|
|
|
5670
6033
|
/* Muted "\u23F9 Stopped" line after a cancelled run \u2014 a note, not an error bubble. */
|
|
5671
6034
|
.stopped-note {
|
|
5672
6035
|
align-self: flex-start;
|
|
5673
|
-
color: var(--
|
|
6036
|
+
color: var(--_muted);
|
|
5674
6037
|
font-size: 12px;
|
|
5675
6038
|
padding: 2px 4px;
|
|
5676
6039
|
}
|
|
@@ -5683,14 +6046,14 @@ var STYLES = `
|
|
|
5683
6046
|
flex-direction: column;
|
|
5684
6047
|
gap: 8px;
|
|
5685
6048
|
padding: 12px;
|
|
5686
|
-
background: var(--
|
|
5687
|
-
border: 1px solid var(--
|
|
6049
|
+
background: var(--_bg);
|
|
6050
|
+
border: 1px solid var(--_accent);
|
|
5688
6051
|
border-radius: 10px;
|
|
5689
6052
|
}
|
|
5690
6053
|
|
|
5691
6054
|
.confirm[data-resolved] {
|
|
5692
6055
|
opacity: 0.7;
|
|
5693
|
-
border-color: var(--
|
|
6056
|
+
border-color: var(--_border);
|
|
5694
6057
|
}
|
|
5695
6058
|
|
|
5696
6059
|
.confirm-body {
|
|
@@ -5704,7 +6067,7 @@ var STYLES = `
|
|
|
5704
6067
|
overflow: auto;
|
|
5705
6068
|
font-size: 12px;
|
|
5706
6069
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
5707
|
-
background: var(--
|
|
6070
|
+
background: var(--_assistant-bg);
|
|
5708
6071
|
border-radius: 8px;
|
|
5709
6072
|
white-space: pre-wrap;
|
|
5710
6073
|
word-break: break-word;
|
|
@@ -5717,14 +6080,14 @@ var STYLES = `
|
|
|
5717
6080
|
}
|
|
5718
6081
|
|
|
5719
6082
|
.confirm-btn {
|
|
5720
|
-
border: 1px solid var(--
|
|
6083
|
+
border: 1px solid var(--_border);
|
|
5721
6084
|
border-radius: 8px;
|
|
5722
6085
|
padding: 8px 14px;
|
|
5723
6086
|
font: inherit;
|
|
5724
6087
|
font-weight: 600;
|
|
5725
6088
|
cursor: pointer;
|
|
5726
|
-
background: var(--
|
|
5727
|
-
color: var(--
|
|
6089
|
+
background: var(--_bg);
|
|
6090
|
+
color: var(--_fg);
|
|
5728
6091
|
}
|
|
5729
6092
|
|
|
5730
6093
|
.confirm-btn:disabled {
|
|
@@ -5733,8 +6096,8 @@ var STYLES = `
|
|
|
5733
6096
|
}
|
|
5734
6097
|
|
|
5735
6098
|
.confirm-btn--confirm {
|
|
5736
|
-
border-color: var(--
|
|
5737
|
-
background: var(--
|
|
6099
|
+
border-color: var(--_accent);
|
|
6100
|
+
background: var(--_accent);
|
|
5738
6101
|
color: #ffffff;
|
|
5739
6102
|
}
|
|
5740
6103
|
|
|
@@ -5746,14 +6109,14 @@ var STYLES = `
|
|
|
5746
6109
|
flex-direction: column;
|
|
5747
6110
|
gap: 8px;
|
|
5748
6111
|
padding: 12px;
|
|
5749
|
-
background: var(--
|
|
5750
|
-
border: 1px solid var(--
|
|
6112
|
+
background: var(--_bg);
|
|
6113
|
+
border: 1px solid var(--_accent);
|
|
5751
6114
|
border-radius: 10px;
|
|
5752
6115
|
}
|
|
5753
6116
|
|
|
5754
6117
|
.approval[data-resolved] {
|
|
5755
6118
|
opacity: 0.7;
|
|
5756
|
-
border-color: var(--
|
|
6119
|
+
border-color: var(--_border);
|
|
5757
6120
|
}
|
|
5758
6121
|
|
|
5759
6122
|
.approval-body {
|
|
@@ -5767,14 +6130,14 @@ var STYLES = `
|
|
|
5767
6130
|
}
|
|
5768
6131
|
|
|
5769
6132
|
.approval-btn {
|
|
5770
|
-
border: 1px solid var(--
|
|
6133
|
+
border: 1px solid var(--_border);
|
|
5771
6134
|
border-radius: 8px;
|
|
5772
6135
|
padding: 8px 14px;
|
|
5773
6136
|
font: inherit;
|
|
5774
6137
|
font-weight: 600;
|
|
5775
6138
|
cursor: pointer;
|
|
5776
|
-
background: var(--
|
|
5777
|
-
color: var(--
|
|
6139
|
+
background: var(--_bg);
|
|
6140
|
+
color: var(--_fg);
|
|
5778
6141
|
}
|
|
5779
6142
|
|
|
5780
6143
|
.approval-btn:disabled {
|
|
@@ -5783,8 +6146,8 @@ var STYLES = `
|
|
|
5783
6146
|
}
|
|
5784
6147
|
|
|
5785
6148
|
.approval-btn--approve {
|
|
5786
|
-
border-color: var(--
|
|
5787
|
-
background: var(--
|
|
6149
|
+
border-color: var(--_accent);
|
|
6150
|
+
background: var(--_accent);
|
|
5788
6151
|
color: #ffffff;
|
|
5789
6152
|
}
|
|
5790
6153
|
|
|
@@ -5796,14 +6159,14 @@ var STYLES = `
|
|
|
5796
6159
|
flex-direction: column;
|
|
5797
6160
|
gap: 8px;
|
|
5798
6161
|
padding: 12px;
|
|
5799
|
-
background: var(--
|
|
5800
|
-
border: 1px solid var(--
|
|
6162
|
+
background: var(--_bg);
|
|
6163
|
+
border: 1px solid var(--_accent);
|
|
5801
6164
|
border-radius: 10px;
|
|
5802
6165
|
}
|
|
5803
6166
|
|
|
5804
6167
|
.question[data-resolved] {
|
|
5805
6168
|
opacity: 0.7;
|
|
5806
|
-
border-color: var(--
|
|
6169
|
+
border-color: var(--_border);
|
|
5807
6170
|
}
|
|
5808
6171
|
|
|
5809
6172
|
.question-body {
|
|
@@ -5828,9 +6191,9 @@ var STYLES = `
|
|
|
5828
6191
|
width: 100%;
|
|
5829
6192
|
padding: 8px 10px;
|
|
5830
6193
|
font: inherit;
|
|
5831
|
-
color: var(--
|
|
5832
|
-
background: var(--
|
|
5833
|
-
border: 1px solid var(--
|
|
6194
|
+
color: var(--_fg);
|
|
6195
|
+
background: var(--_bg);
|
|
6196
|
+
border: 1px solid var(--_border);
|
|
5834
6197
|
border-radius: 8px;
|
|
5835
6198
|
}
|
|
5836
6199
|
|
|
@@ -5844,13 +6207,13 @@ var STYLES = `
|
|
|
5844
6207
|
}
|
|
5845
6208
|
|
|
5846
6209
|
.question-btn {
|
|
5847
|
-
border: 1px solid var(--
|
|
6210
|
+
border: 1px solid var(--_accent);
|
|
5848
6211
|
border-radius: 8px;
|
|
5849
6212
|
padding: 8px 14px;
|
|
5850
6213
|
font: inherit;
|
|
5851
6214
|
font-weight: 600;
|
|
5852
6215
|
cursor: pointer;
|
|
5853
|
-
background: var(--
|
|
6216
|
+
background: var(--_accent);
|
|
5854
6217
|
color: #ffffff;
|
|
5855
6218
|
}
|
|
5856
6219
|
|
|
@@ -5868,18 +6231,18 @@ var STYLES = `
|
|
|
5868
6231
|
}
|
|
5869
6232
|
|
|
5870
6233
|
.skill-chip {
|
|
5871
|
-
border: 1px solid var(--
|
|
6234
|
+
border: 1px solid var(--_border);
|
|
5872
6235
|
border-radius: 999px;
|
|
5873
6236
|
padding: 4px 12px;
|
|
5874
6237
|
font: inherit;
|
|
5875
6238
|
font-size: 0.9em;
|
|
5876
6239
|
cursor: pointer;
|
|
5877
|
-
background: var(--
|
|
5878
|
-
color: var(--
|
|
6240
|
+
background: var(--_assistant-bg);
|
|
6241
|
+
color: var(--_fg);
|
|
5879
6242
|
}
|
|
5880
6243
|
|
|
5881
6244
|
.skill-chip:hover {
|
|
5882
|
-
border-color: var(--
|
|
6245
|
+
border-color: var(--_accent);
|
|
5883
6246
|
}
|
|
5884
6247
|
|
|
5885
6248
|
.skill-palette {
|
|
@@ -5888,8 +6251,8 @@ var STYLES = `
|
|
|
5888
6251
|
flex-direction: column;
|
|
5889
6252
|
max-height: 220px;
|
|
5890
6253
|
overflow: auto;
|
|
5891
|
-
background: var(--
|
|
5892
|
-
border: 1px solid var(--
|
|
6254
|
+
background: var(--_bg);
|
|
6255
|
+
border: 1px solid var(--_border);
|
|
5893
6256
|
border-radius: 8px;
|
|
5894
6257
|
box-shadow: 0 8px 24px rgba(20, 20, 50, 0.16);
|
|
5895
6258
|
}
|
|
@@ -5905,11 +6268,11 @@ var STYLES = `
|
|
|
5905
6268
|
font: inherit;
|
|
5906
6269
|
text-align: left;
|
|
5907
6270
|
cursor: pointer;
|
|
5908
|
-
color: var(--
|
|
6271
|
+
color: var(--_fg);
|
|
5909
6272
|
}
|
|
5910
6273
|
|
|
5911
6274
|
.skill-item[aria-selected="true"] {
|
|
5912
|
-
background: var(--
|
|
6275
|
+
background: var(--_assistant-bg);
|
|
5913
6276
|
}
|
|
5914
6277
|
|
|
5915
6278
|
.skill-item-title {
|
|
@@ -5918,7 +6281,7 @@ var STYLES = `
|
|
|
5918
6281
|
|
|
5919
6282
|
.skill-item-desc {
|
|
5920
6283
|
font-size: 0.85em;
|
|
5921
|
-
color: var(--
|
|
6284
|
+
color: var(--_muted);
|
|
5922
6285
|
}
|
|
5923
6286
|
|
|
5924
6287
|
/* The hint sits directly above the composer's top border, so a zero bottom
|
|
@@ -5927,19 +6290,38 @@ var STYLES = `
|
|
|
5927
6290
|
margin: 8px 12px;
|
|
5928
6291
|
font-size: 0.85em;
|
|
5929
6292
|
line-height: 1.4;
|
|
5930
|
-
color: var(--
|
|
6293
|
+
color: var(--_danger);
|
|
5931
6294
|
}
|
|
5932
6295
|
|
|
5933
|
-
/* Chat-history drawer \u2014 a slide-over within the chat panel.
|
|
6296
|
+
/* Chat-history drawer \u2014 a slide-over within the chat panel.
|
|
6297
|
+
The hidden attribute stays the single source of truth for open/closed (no
|
|
6298
|
+
JS-driven animation state): display is transitioned discretely, so on close
|
|
6299
|
+
the drawer stays displayed for the whole exit and is removed only at the end,
|
|
6300
|
+
which is what lets the backdrop fade and the panel slide out. */
|
|
5934
6301
|
.drawer {
|
|
5935
6302
|
position: absolute;
|
|
5936
6303
|
inset: 0;
|
|
5937
6304
|
z-index: 5;
|
|
5938
6305
|
display: flex;
|
|
6306
|
+
transition: visibility var(--_motion) var(--_ease);
|
|
5939
6307
|
}
|
|
5940
6308
|
|
|
6309
|
+
/* Closed. The overlay keeps its box (display, not none) so the backdrop and
|
|
6310
|
+
panel inside it stay rendered and can transition both ways; visibility is
|
|
6311
|
+
what takes the whole subtree out of the tab order, the a11y tree and hit
|
|
6312
|
+
testing at rest, and it holds off until the slide has finished. */
|
|
5941
6313
|
.drawer[hidden] {
|
|
5942
|
-
display:
|
|
6314
|
+
display: flex;
|
|
6315
|
+
visibility: hidden;
|
|
6316
|
+
pointer-events: none;
|
|
6317
|
+
}
|
|
6318
|
+
|
|
6319
|
+
.drawer[hidden] .drawer-backdrop {
|
|
6320
|
+
opacity: 0;
|
|
6321
|
+
}
|
|
6322
|
+
|
|
6323
|
+
.drawer[hidden] .drawer-panel {
|
|
6324
|
+
transform: translateX(-100%);
|
|
5943
6325
|
}
|
|
5944
6326
|
|
|
5945
6327
|
.checkpoints {
|
|
@@ -5951,16 +6333,27 @@ var STYLES = `
|
|
|
5951
6333
|
flex-direction: column;
|
|
5952
6334
|
gap: 0.25rem;
|
|
5953
6335
|
padding: 0.5rem;
|
|
5954
|
-
border: 1px solid var(--
|
|
6336
|
+
border: 1px solid var(--_border);
|
|
5955
6337
|
border-radius: 0.5rem;
|
|
5956
|
-
background: var(--
|
|
6338
|
+
background: var(--_assistant-bg);
|
|
5957
6339
|
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
5958
6340
|
max-height: 60%;
|
|
5959
6341
|
overflow-y: auto;
|
|
6342
|
+
transform-origin: top center;
|
|
6343
|
+
transition:
|
|
6344
|
+
opacity var(--_motion) var(--_ease),
|
|
6345
|
+
transform var(--_motion) var(--_ease),
|
|
6346
|
+
visibility var(--_motion) var(--_ease);
|
|
5960
6347
|
}
|
|
5961
6348
|
|
|
6349
|
+
/* Same idiom as the drawer: laid out at rest, hidden by visibility, so the
|
|
6350
|
+
popover can animate open and closed. */
|
|
5962
6351
|
.checkpoints[hidden] {
|
|
5963
|
-
display:
|
|
6352
|
+
display: flex;
|
|
6353
|
+
visibility: hidden;
|
|
6354
|
+
pointer-events: none;
|
|
6355
|
+
opacity: 0;
|
|
6356
|
+
transform: scale(0.96) translateY(-6px);
|
|
5964
6357
|
}
|
|
5965
6358
|
|
|
5966
6359
|
.checkpoints-title {
|
|
@@ -5984,7 +6377,7 @@ var STYLES = `
|
|
|
5984
6377
|
}
|
|
5985
6378
|
|
|
5986
6379
|
.checkpoint-row:hover {
|
|
5987
|
-
background: var(--
|
|
6380
|
+
background: var(--_hover);
|
|
5988
6381
|
}
|
|
5989
6382
|
|
|
5990
6383
|
.checkpoint-label {
|
|
@@ -5999,7 +6392,7 @@ var STYLES = `
|
|
|
5999
6392
|
font-size: 0.6875rem;
|
|
6000
6393
|
padding: 0 0.375rem;
|
|
6001
6394
|
border-radius: 999px;
|
|
6002
|
-
background: var(--
|
|
6395
|
+
background: var(--_hover);
|
|
6003
6396
|
opacity: 0.8;
|
|
6004
6397
|
}
|
|
6005
6398
|
|
|
@@ -6008,20 +6401,22 @@ var STYLES = `
|
|
|
6008
6401
|
font-size: 0.75rem;
|
|
6009
6402
|
cursor: pointer;
|
|
6010
6403
|
padding: 0.125rem 0.5rem;
|
|
6011
|
-
border: 1px solid var(--
|
|
6404
|
+
border: 1px solid var(--_border);
|
|
6012
6405
|
border-radius: 0.375rem;
|
|
6013
6406
|
background: transparent;
|
|
6014
6407
|
color: inherit;
|
|
6015
6408
|
}
|
|
6016
6409
|
|
|
6017
6410
|
.checkpoint-action:hover {
|
|
6018
|
-
background: var(--
|
|
6411
|
+
background: var(--_hover);
|
|
6019
6412
|
}
|
|
6020
6413
|
|
|
6021
6414
|
.drawer-backdrop {
|
|
6022
6415
|
position: absolute;
|
|
6023
6416
|
inset: 0;
|
|
6024
6417
|
background: rgba(20, 20, 50, 0.32);
|
|
6418
|
+
opacity: 1;
|
|
6419
|
+
transition: opacity var(--_motion) var(--_ease);
|
|
6025
6420
|
}
|
|
6026
6421
|
|
|
6027
6422
|
.drawer-panel {
|
|
@@ -6030,19 +6425,21 @@ var STYLES = `
|
|
|
6030
6425
|
flex-direction: column;
|
|
6031
6426
|
width: min(300px, 85%);
|
|
6032
6427
|
height: 100%;
|
|
6033
|
-
background: var(--
|
|
6034
|
-
border-right: 1px solid var(--
|
|
6035
|
-
box-shadow: var(--
|
|
6428
|
+
background: var(--_bg);
|
|
6429
|
+
border-right: 1px solid var(--_border);
|
|
6430
|
+
box-shadow: var(--_shadow);
|
|
6036
6431
|
overflow: hidden;
|
|
6432
|
+
transform: none;
|
|
6433
|
+
transition: transform var(--_motion) var(--_ease);
|
|
6037
6434
|
}
|
|
6038
6435
|
|
|
6039
6436
|
.drawer-header {
|
|
6040
6437
|
display: flex;
|
|
6041
6438
|
align-items: center;
|
|
6042
6439
|
justify-content: space-between;
|
|
6043
|
-
gap: var(--
|
|
6044
|
-
padding: var(--
|
|
6045
|
-
border-bottom: 1px solid var(--
|
|
6440
|
+
gap: var(--_space);
|
|
6441
|
+
padding: var(--_pad);
|
|
6442
|
+
border-bottom: 1px solid var(--_border);
|
|
6046
6443
|
}
|
|
6047
6444
|
|
|
6048
6445
|
.drawer-title {
|
|
@@ -6050,10 +6447,10 @@ var STYLES = `
|
|
|
6050
6447
|
}
|
|
6051
6448
|
|
|
6052
6449
|
.drawer-new {
|
|
6053
|
-
border: 1px solid var(--
|
|
6054
|
-
border-radius: var(--
|
|
6055
|
-
background: var(--
|
|
6056
|
-
color: var(--
|
|
6450
|
+
border: 1px solid var(--_border);
|
|
6451
|
+
border-radius: var(--_radius);
|
|
6452
|
+
background: var(--_bg);
|
|
6453
|
+
color: var(--_accent);
|
|
6057
6454
|
padding: 4px 10px;
|
|
6058
6455
|
font: inherit;
|
|
6059
6456
|
font-size: 0.85em;
|
|
@@ -6067,19 +6464,19 @@ var STYLES = `
|
|
|
6067
6464
|
}
|
|
6068
6465
|
|
|
6069
6466
|
.drawer-empty {
|
|
6070
|
-
padding: var(--
|
|
6467
|
+
padding: var(--_pad);
|
|
6071
6468
|
font-size: 0.9em;
|
|
6072
|
-
color: var(--
|
|
6469
|
+
color: var(--_muted);
|
|
6073
6470
|
}
|
|
6074
6471
|
|
|
6075
6472
|
.drawer-row {
|
|
6076
6473
|
display: flex;
|
|
6077
6474
|
align-items: stretch;
|
|
6078
|
-
border-bottom: 1px solid var(--
|
|
6475
|
+
border-bottom: 1px solid var(--_border);
|
|
6079
6476
|
}
|
|
6080
6477
|
|
|
6081
6478
|
.drawer-row--active {
|
|
6082
|
-
background: var(--
|
|
6479
|
+
background: var(--_assistant-bg);
|
|
6083
6480
|
}
|
|
6084
6481
|
|
|
6085
6482
|
.drawer-row-select {
|
|
@@ -6106,12 +6503,12 @@ var STYLES = `
|
|
|
6106
6503
|
|
|
6107
6504
|
.drawer-row-time {
|
|
6108
6505
|
font-size: 0.72em;
|
|
6109
|
-
color: var(--
|
|
6506
|
+
color: var(--_muted);
|
|
6110
6507
|
}
|
|
6111
6508
|
|
|
6112
6509
|
.drawer-row-preview {
|
|
6113
6510
|
font-size: 0.8em;
|
|
6114
|
-
color: var(--
|
|
6511
|
+
color: var(--_muted);
|
|
6115
6512
|
overflow: hidden;
|
|
6116
6513
|
white-space: nowrap;
|
|
6117
6514
|
text-overflow: ellipsis;
|
|
@@ -6128,7 +6525,7 @@ var STYLES = `
|
|
|
6128
6525
|
.drawer-row-delete {
|
|
6129
6526
|
border: none;
|
|
6130
6527
|
background: none;
|
|
6131
|
-
color: var(--
|
|
6528
|
+
color: var(--_muted);
|
|
6132
6529
|
font-size: 0.9em;
|
|
6133
6530
|
padding: 4px;
|
|
6134
6531
|
cursor: pointer;
|
|
@@ -6139,10 +6536,10 @@ var STYLES = `
|
|
|
6139
6536
|
min-width: 0;
|
|
6140
6537
|
margin: 6px 10px;
|
|
6141
6538
|
padding: 4px 8px;
|
|
6142
|
-
border: 1px solid var(--
|
|
6539
|
+
border: 1px solid var(--_accent);
|
|
6143
6540
|
border-radius: 6px;
|
|
6144
|
-
background: var(--
|
|
6145
|
-
color: var(--
|
|
6541
|
+
background: var(--_input-bg);
|
|
6542
|
+
color: var(--_fg);
|
|
6146
6543
|
font: inherit;
|
|
6147
6544
|
}
|
|
6148
6545
|
|
|
@@ -6155,13 +6552,13 @@ var STYLES = `
|
|
|
6155
6552
|
}
|
|
6156
6553
|
|
|
6157
6554
|
.drawer-confirm-label {
|
|
6158
|
-
color: var(--
|
|
6555
|
+
color: var(--_danger);
|
|
6159
6556
|
}
|
|
6160
6557
|
|
|
6161
6558
|
.drawer-confirm-yes {
|
|
6162
6559
|
border: none;
|
|
6163
6560
|
border-radius: 6px;
|
|
6164
|
-
background: var(--
|
|
6561
|
+
background: var(--_danger);
|
|
6165
6562
|
color: #ffffff;
|
|
6166
6563
|
padding: 3px 10px;
|
|
6167
6564
|
font: inherit;
|
|
@@ -6169,7 +6566,7 @@ var STYLES = `
|
|
|
6169
6566
|
}
|
|
6170
6567
|
|
|
6171
6568
|
.drawer-confirm-no {
|
|
6172
|
-
border: 1px solid var(--
|
|
6569
|
+
border: 1px solid var(--_border);
|
|
6173
6570
|
border-radius: 6px;
|
|
6174
6571
|
background: none;
|
|
6175
6572
|
color: inherit;
|
|
@@ -6663,7 +7060,10 @@ var VoiceInput = class {
|
|
|
6663
7060
|
this.element.type = "button";
|
|
6664
7061
|
this.element.className = "voice-btn";
|
|
6665
7062
|
this.element.setAttribute("part", "voice-button");
|
|
6666
|
-
|
|
7063
|
+
const glyph = document.createElement("slot");
|
|
7064
|
+
glyph.name = "icon-voice";
|
|
7065
|
+
glyph.innerHTML = ICON_VOICE;
|
|
7066
|
+
this.element.append(glyph);
|
|
6667
7067
|
this.#setState("idle");
|
|
6668
7068
|
this.element.addEventListener("click", () => {
|
|
6669
7069
|
void this.toggle();
|
|
@@ -7232,6 +7632,13 @@ function truncate(text2, limit) {
|
|
|
7232
7632
|
|
|
7233
7633
|
// src/core/create_http_agent.ts
|
|
7234
7634
|
import { HttpAgent } from "@ag-ui/client";
|
|
7635
|
+
|
|
7636
|
+
// src/core/utils.ts
|
|
7637
|
+
function withCredentials(init, credentials) {
|
|
7638
|
+
return credentials === void 0 ? init : { ...init, credentials };
|
|
7639
|
+
}
|
|
7640
|
+
|
|
7641
|
+
// src/core/create_http_agent.ts
|
|
7235
7642
|
function createHttpAgent(options) {
|
|
7236
7643
|
return new HttpAgent({
|
|
7237
7644
|
url: options.endpoint,
|
|
@@ -7242,17 +7649,18 @@ function createHttpAgent(options) {
|
|
|
7242
7649
|
// "Illegal invocation" in browsers. Wrap it so `fetch` is always called as
|
|
7243
7650
|
// a free function with the correct receiver. The wrapper also overlays
|
|
7244
7651
|
// `getHeaders()` per request, so header rotation (CSRF, short-lived JWT)
|
|
7245
|
-
// reaches the stream even though the agent instance is cached
|
|
7652
|
+
// reaches the stream even though the agent instance is cached, and applies
|
|
7653
|
+
// the configured cookie policy — the agent's own config has no seam for it.
|
|
7246
7654
|
fetch: (url, init) => {
|
|
7247
7655
|
const fresh = options.getHeaders?.();
|
|
7248
7656
|
if (fresh === void 0) {
|
|
7249
|
-
return fetch(url, init);
|
|
7657
|
+
return fetch(url, withCredentials(init, options.credentials));
|
|
7250
7658
|
}
|
|
7251
7659
|
const headers = new Headers(init?.headers);
|
|
7252
7660
|
for (const [name, value] of Object.entries(fresh)) {
|
|
7253
7661
|
headers.set(name, value);
|
|
7254
7662
|
}
|
|
7255
|
-
return fetch(url, { ...init, headers });
|
|
7663
|
+
return fetch(url, withCredentials({ ...init, headers }, options.credentials));
|
|
7256
7664
|
},
|
|
7257
7665
|
// Spread conditionally: under `exactOptionalPropertyTypes` an explicit
|
|
7258
7666
|
// `undefined` is not assignable to these optional config fields.
|
|
@@ -7266,12 +7674,14 @@ var RemoteConversationStore = class {
|
|
|
7266
7674
|
#url;
|
|
7267
7675
|
#headers;
|
|
7268
7676
|
#local;
|
|
7677
|
+
#credentials;
|
|
7269
7678
|
#dropped = /* @__PURE__ */ new Set();
|
|
7270
7679
|
#renamed = /* @__PURE__ */ new Map();
|
|
7271
|
-
constructor(url, headers = () => ({}), local = new SessionStorageStore()) {
|
|
7680
|
+
constructor(url, headers = () => ({}), local = new SessionStorageStore(), credentials = () => void 0) {
|
|
7272
7681
|
this.#url = url.endsWith("/") ? url : `${url}/`;
|
|
7273
7682
|
this.#headers = headers;
|
|
7274
7683
|
this.#local = local;
|
|
7684
|
+
this.#credentials = credentials;
|
|
7275
7685
|
}
|
|
7276
7686
|
threadId() {
|
|
7277
7687
|
return this.#local.threadId();
|
|
@@ -7349,7 +7759,7 @@ var RemoteConversationStore = class {
|
|
|
7349
7759
|
/** GET that resolves to the `Response`, or `null` on a network error. */
|
|
7350
7760
|
async #get(url) {
|
|
7351
7761
|
try {
|
|
7352
|
-
return await fetch(url, { headers: this.#headers() });
|
|
7762
|
+
return await fetch(url, withCredentials({ headers: this.#headers() }, this.#credentials()));
|
|
7353
7763
|
} catch {
|
|
7354
7764
|
return null;
|
|
7355
7765
|
}
|
|
@@ -7358,11 +7768,17 @@ var RemoteConversationStore = class {
|
|
|
7358
7768
|
async #mutate(threadId, method, body) {
|
|
7359
7769
|
const headers = this.#headers();
|
|
7360
7770
|
try {
|
|
7361
|
-
await fetch(
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7771
|
+
await fetch(
|
|
7772
|
+
`${this.#url}${encodeURIComponent(threadId)}/`,
|
|
7773
|
+
withCredentials(
|
|
7774
|
+
{
|
|
7775
|
+
method,
|
|
7776
|
+
headers: body === void 0 ? headers : { ...headers, "content-type": "application/json" },
|
|
7777
|
+
body: body === void 0 ? null : JSON.stringify(body)
|
|
7778
|
+
},
|
|
7779
|
+
this.#credentials()
|
|
7780
|
+
)
|
|
7781
|
+
);
|
|
7366
7782
|
} catch {
|
|
7367
7783
|
}
|
|
7368
7784
|
}
|
|
@@ -7372,9 +7788,11 @@ var RemoteConversationStore = class {
|
|
|
7372
7788
|
var RunIndex = class {
|
|
7373
7789
|
#url;
|
|
7374
7790
|
#headers;
|
|
7375
|
-
|
|
7791
|
+
#credentials;
|
|
7792
|
+
constructor(url, headers = () => ({}), credentials = () => void 0) {
|
|
7376
7793
|
this.#url = url.endsWith("/") ? url : `${url}/`;
|
|
7377
7794
|
this.#headers = headers;
|
|
7795
|
+
this.#credentials = credentials;
|
|
7378
7796
|
}
|
|
7379
7797
|
/**
|
|
7380
7798
|
* The user's runs, or `[]` when the endpoint is unreachable or answers with
|
|
@@ -7384,10 +7802,16 @@ var RunIndex = class {
|
|
|
7384
7802
|
*/
|
|
7385
7803
|
async list() {
|
|
7386
7804
|
try {
|
|
7387
|
-
const response = await fetch(
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7805
|
+
const response = await fetch(
|
|
7806
|
+
this.#url,
|
|
7807
|
+
withCredentials(
|
|
7808
|
+
{
|
|
7809
|
+
method: "GET",
|
|
7810
|
+
headers: { Accept: "application/json", ...this.#headers() }
|
|
7811
|
+
},
|
|
7812
|
+
this.#credentials()
|
|
7813
|
+
)
|
|
7814
|
+
);
|
|
7391
7815
|
if (!response.ok) {
|
|
7392
7816
|
return [];
|
|
7393
7817
|
}
|
|
@@ -7426,11 +7850,17 @@ var RunIndex = class {
|
|
|
7426
7850
|
async function transcribeAudio(audio, options) {
|
|
7427
7851
|
const form = new FormData();
|
|
7428
7852
|
form.append("audio", audio, "recording.webm");
|
|
7429
|
-
const response = await fetch(
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7853
|
+
const response = await fetch(
|
|
7854
|
+
options.url,
|
|
7855
|
+
withCredentials(
|
|
7856
|
+
{
|
|
7857
|
+
method: "POST",
|
|
7858
|
+
headers: { ...options.headers ?? {} },
|
|
7859
|
+
body: form
|
|
7860
|
+
},
|
|
7861
|
+
options.credentials
|
|
7862
|
+
)
|
|
7863
|
+
);
|
|
7434
7864
|
if (!response.ok) {
|
|
7435
7865
|
throw new Error(await errorMessage(response));
|
|
7436
7866
|
}
|
|
@@ -7458,6 +7888,7 @@ function uploadAttachment(file, options) {
|
|
|
7458
7888
|
form.append("file", file);
|
|
7459
7889
|
const xhr = new XMLHttpRequest();
|
|
7460
7890
|
xhr.open("POST", options.url);
|
|
7891
|
+
xhr.withCredentials = options.credentials === "include";
|
|
7461
7892
|
for (const [key, value] of Object.entries(options.headers ?? {})) {
|
|
7462
7893
|
xhr.setRequestHeader(key, value);
|
|
7463
7894
|
}
|
|
@@ -7531,14 +7962,42 @@ var CONNECT_TIME_ATTRIBUTES = [
|
|
|
7531
7962
|
"data-strings",
|
|
7532
7963
|
"data-icon-url"
|
|
7533
7964
|
];
|
|
7965
|
+
var CREDENTIALS_MODES = ["omit", "same-origin", "include"];
|
|
7966
|
+
function isCredentialsMode(value) {
|
|
7967
|
+
return CREDENTIALS_MODES.includes(value);
|
|
7968
|
+
}
|
|
7534
7969
|
var COLLAPSED_KEY = "ag-ui-chat:collapsed";
|
|
7535
7970
|
var SIZE_KEY = "ag-ui-chat:size";
|
|
7536
7971
|
var THEME_KEY = "ag-ui-chat:theme";
|
|
7537
7972
|
var AgUiChat = class extends HTMLElement {
|
|
7538
7973
|
/** Agent factory; override to inject a custom or fake agent (tests). */
|
|
7539
7974
|
agentFactory = createHttpAgent;
|
|
7540
|
-
/**
|
|
7975
|
+
/**
|
|
7976
|
+
* Static extra HTTP headers, sent with **every** request this element makes —
|
|
7977
|
+
* the agent run, the thread index and its messages, the tool and skill
|
|
7978
|
+
* catalogs, the run index, uploads and transcription.
|
|
7979
|
+
*
|
|
7980
|
+
* Right for values fixed for the element's lifetime. A credential that
|
|
7981
|
+
* rotates (a short-lived JWT, a re-issued CSRF token) belongs in
|
|
7982
|
+
* {@link getHeaders} instead: this is read at request time, but only a
|
|
7983
|
+
* re-assignment updates it, so a token captured here is pinned until the host
|
|
7984
|
+
* remembers to assign again.
|
|
7985
|
+
*/
|
|
7541
7986
|
headers = {};
|
|
7987
|
+
/**
|
|
7988
|
+
* Live header source, consulted immediately before every request — the way to
|
|
7989
|
+
* supply rotating credentials.
|
|
7990
|
+
*
|
|
7991
|
+
* Set it to a function and each request calls it afresh: a token refreshed by
|
|
7992
|
+
* the host between two requests reaches the second one, with nothing to
|
|
7993
|
+
* re-assign and nothing to keep in sync.
|
|
7994
|
+
*
|
|
7995
|
+
* Composes with {@link headers} rather than replacing it: the two are merged
|
|
7996
|
+
* per key with `getHeaders()` winning, so a static `X-Client` and a rotating
|
|
7997
|
+
* `Authorization` can be configured independently and neither silently drops
|
|
7998
|
+
* the other.
|
|
7999
|
+
*/
|
|
8000
|
+
getHeaders = null;
|
|
7542
8001
|
/**
|
|
7543
8002
|
* Permit `<img>` in rendered assistant markdown. **Off by default**: a
|
|
7544
8003
|
* model-controlled image URL is fetched with no user interaction, which
|
|
@@ -7721,8 +8180,12 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7721
8180
|
#attachSlot;
|
|
7722
8181
|
/** Optional built-in header theme toggle; shown only with `data-theme-toggle`. */
|
|
7723
8182
|
#themeToggle;
|
|
7724
|
-
/**
|
|
7725
|
-
#
|
|
8183
|
+
/** What the collapsed widget shrinks to: the floating launcher, or the sidebar rail. */
|
|
8184
|
+
#launcher;
|
|
8185
|
+
/** The launcher's unread badge; hidden at zero, and when the host opts out. */
|
|
8186
|
+
#badge;
|
|
8187
|
+
// Answers that finished while the widget was collapsed. Expanding clears it.
|
|
8188
|
+
#unread = 0;
|
|
7726
8189
|
/** Empty-state region at the top of the message list; hidden once anything renders. */
|
|
7727
8190
|
#emptyWrap;
|
|
7728
8191
|
/** Upload tray; created on connect only when `data-attachments-url` is set. */
|
|
@@ -7795,7 +8258,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7795
8258
|
this.#attachSlot = document.createElement("div");
|
|
7796
8259
|
this.#voiceSlot = document.createElement("span");
|
|
7797
8260
|
this.#themeToggle = document.createElement("button");
|
|
7798
|
-
this.#
|
|
8261
|
+
this.#launcher = document.createElement("button");
|
|
8262
|
+
this.#badge = document.createElement("span");
|
|
7799
8263
|
this.#emptyWrap = document.createElement("div");
|
|
7800
8264
|
this.#skillsMenu = new SkillsMenu((skill) => this.#applySkill(skill));
|
|
7801
8265
|
this.#drawer = new ThreadDrawer({
|
|
@@ -7825,7 +8289,11 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7825
8289
|
return null;
|
|
7826
8290
|
}
|
|
7827
8291
|
if (this.#runIndex === null) {
|
|
7828
|
-
this.#runIndex = new RunIndex(
|
|
8292
|
+
this.#runIndex = new RunIndex(
|
|
8293
|
+
url,
|
|
8294
|
+
() => this.#requestHeaders(),
|
|
8295
|
+
() => this.#requestCredentials()
|
|
8296
|
+
);
|
|
7829
8297
|
}
|
|
7830
8298
|
return this.#runIndex;
|
|
7831
8299
|
}
|
|
@@ -7854,11 +8322,13 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7854
8322
|
return;
|
|
7855
8323
|
}
|
|
7856
8324
|
this.#input.value = "";
|
|
8325
|
+
this.#autoGrow();
|
|
7857
8326
|
const endpoint = verb === "resume" ? index.resumeUrl(runId) : index.forkUrl(runId);
|
|
7858
8327
|
const agent = this.agentFactory({
|
|
7859
8328
|
endpoint,
|
|
7860
|
-
headers: this
|
|
7861
|
-
getHeaders: () => this
|
|
8329
|
+
headers: this.#requestHeaders(),
|
|
8330
|
+
getHeaders: () => this.#requestHeaders(),
|
|
8331
|
+
...this.#credentialsOption(),
|
|
7862
8332
|
threadId: this.#threadId,
|
|
7863
8333
|
// The seed the endpoints assume: nothing. The snapshot is the history.
|
|
7864
8334
|
initialMessages: []
|
|
@@ -7881,9 +8351,17 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7881
8351
|
}
|
|
7882
8352
|
/** Attributes the element reacts to after it has been connected. */
|
|
7883
8353
|
static get observedAttributes() {
|
|
7884
|
-
return ["title-text", "placement", ...CONNECT_TIME_ATTRIBUTES];
|
|
8354
|
+
return ["title-text", "placement", "credentials", ...CONNECT_TIME_ATTRIBUTES];
|
|
7885
8355
|
}
|
|
7886
8356
|
attributeChangedCallback(name, previous, value) {
|
|
8357
|
+
if (name === "credentials") {
|
|
8358
|
+
if (value !== null && !isCredentialsMode(value)) {
|
|
8359
|
+
console.error(
|
|
8360
|
+
`<ag-ui-chat>: credentials="${value}" is not a fetch credentials mode (${CREDENTIALS_MODES.join(" / ")}) \u2014 it is being ignored, so requests use the browser default and cross-origin cookies will not be sent.`
|
|
8361
|
+
);
|
|
8362
|
+
}
|
|
8363
|
+
return;
|
|
8364
|
+
}
|
|
7887
8365
|
if (name === "placement") {
|
|
7888
8366
|
this.#releaseOwnedAxes();
|
|
7889
8367
|
requestAnimationFrame(() => this.#syncResizeAnchor());
|
|
@@ -8074,6 +8552,72 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8074
8552
|
set endpoint(value) {
|
|
8075
8553
|
this.setAttribute("endpoint", value);
|
|
8076
8554
|
}
|
|
8555
|
+
/**
|
|
8556
|
+
* Cookie policy for **every** request this element makes, as `fetch`'s own
|
|
8557
|
+
* `credentials` mode (`"omit"` / `"same-origin"` / `"include"`). Mirrored to
|
|
8558
|
+
* the `credentials` attribute, so markup embeds can set it without script.
|
|
8559
|
+
*
|
|
8560
|
+
* `null` (the default) leaves the browser's default of `same-origin` in
|
|
8561
|
+
* place. That default sends **no cookies at all** when the endpoints live on
|
|
8562
|
+
* a different origin from the page — app.example.com calling
|
|
8563
|
+
* api.example.com is cross-origin — and the request goes out anonymously
|
|
8564
|
+
* rather than failing, so the symptom is a 401 from a server that looks
|
|
8565
|
+
* correctly configured. A cookie-authenticated cross-origin deployment wants
|
|
8566
|
+
* `"include"`, plus `Access-Control-Allow-Credentials: true` and a concrete
|
|
8567
|
+
* (non-wildcard) `Access-Control-Allow-Origin` on the server.
|
|
8568
|
+
*
|
|
8569
|
+
* Read per request, so a late assignment applies to everything after it.
|
|
8570
|
+
* `"omit"` cannot be honoured by the built-in **upload** transport, which is
|
|
8571
|
+
* an `XMLHttpRequest` and only has a two-state cookie switch; every other
|
|
8572
|
+
* endpoint honours all three modes.
|
|
8573
|
+
*/
|
|
8574
|
+
get credentials() {
|
|
8575
|
+
const attr = this.getAttribute("credentials");
|
|
8576
|
+
return attr !== null && isCredentialsMode(attr) ? attr : null;
|
|
8577
|
+
}
|
|
8578
|
+
set credentials(value) {
|
|
8579
|
+
if (value === null) {
|
|
8580
|
+
this.removeAttribute("credentials");
|
|
8581
|
+
return;
|
|
8582
|
+
}
|
|
8583
|
+
if (!isCredentialsMode(value)) {
|
|
8584
|
+
throw new TypeError(
|
|
8585
|
+
`<ag-ui-chat>: credentials must be one of ${CREDENTIALS_MODES.map((mode) => `"${mode}"`).join(", ")} (got ${JSON.stringify(value)}).`
|
|
8586
|
+
);
|
|
8587
|
+
}
|
|
8588
|
+
this.setAttribute("credentials", value);
|
|
8589
|
+
}
|
|
8590
|
+
/**
|
|
8591
|
+
* The headers for the request about to go out: the static {@link headers}
|
|
8592
|
+
* with {@link getHeaders}'s live values overlaid, per key.
|
|
8593
|
+
*
|
|
8594
|
+
* Every request site goes through here, so "how this element authenticates"
|
|
8595
|
+
* is one answer rather than one per endpoint.
|
|
8596
|
+
*/
|
|
8597
|
+
#requestHeaders() {
|
|
8598
|
+
return { ...this.headers, ...this.getHeaders?.() };
|
|
8599
|
+
}
|
|
8600
|
+
/** The configured cookie policy as `fetch` spells it; `undefined` when unset. */
|
|
8601
|
+
#requestCredentials() {
|
|
8602
|
+
return this.credentials ?? void 0;
|
|
8603
|
+
}
|
|
8604
|
+
/**
|
|
8605
|
+
* The `credentials` entry for an {@link AgentFactory} call, or nothing at all.
|
|
8606
|
+
*
|
|
8607
|
+
* Spread rather than assigned: `exactOptionalPropertyTypes` rejects an
|
|
8608
|
+
* explicit `credentials: undefined`, and a factory should see the field
|
|
8609
|
+
* absent — not present-and-empty — when no policy is configured. The agent
|
|
8610
|
+
* reads it when it is built (first send, thread switch, continuation), by
|
|
8611
|
+
* which time any host configuration has landed.
|
|
8612
|
+
*/
|
|
8613
|
+
#credentialsOption() {
|
|
8614
|
+
const credentials = this.#requestCredentials();
|
|
8615
|
+
return credentials === void 0 ? {} : { credentials };
|
|
8616
|
+
}
|
|
8617
|
+
/** The `fetch` init for the element's own plain GETs (catalogs). */
|
|
8618
|
+
#fetchInit() {
|
|
8619
|
+
return withCredentials({ headers: this.#requestHeaders() }, this.#requestCredentials());
|
|
8620
|
+
}
|
|
8077
8621
|
/**
|
|
8078
8622
|
* How much detail tool-call cards show, from the `data-tool-display`
|
|
8079
8623
|
* attribute (`minimal` / `inline` / `compact` / `full`). Defaults to `full`.
|
|
@@ -8109,9 +8653,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8109
8653
|
if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
|
|
8110
8654
|
this.setAttribute("collapsed", "");
|
|
8111
8655
|
}
|
|
8112
|
-
this.#
|
|
8656
|
+
this.#syncLauncher();
|
|
8113
8657
|
this.#initSkills();
|
|
8114
|
-
void this.#fetchToolCatalog();
|
|
8115
8658
|
if (this.#storageNs !== "" && this.conversationStore instanceof SessionStorageStore) {
|
|
8116
8659
|
this.conversationStore = new SessionStorageStore(this.#storageNs);
|
|
8117
8660
|
}
|
|
@@ -8119,9 +8662,62 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8119
8662
|
this.#wireAttachments();
|
|
8120
8663
|
this.#wireVoice();
|
|
8121
8664
|
this.#threadId = this.conversationStore.threadId();
|
|
8665
|
+
queueMicrotask(() => this.#startup());
|
|
8122
8666
|
void this.#rehydrate();
|
|
8123
8667
|
this.#connected = true;
|
|
8124
8668
|
}
|
|
8669
|
+
/**
|
|
8670
|
+
* The catalog requests the element issues on startup: the tool labels
|
|
8671
|
+
* (`data-tools-url`) and the backend skills (`data-skills-url`).
|
|
8672
|
+
*
|
|
8673
|
+
* Deliberately one microtask behind `connectedCallback`. A host that
|
|
8674
|
+
* configures the element through a framework ref necessarily does so *after*
|
|
8675
|
+
* inserting the node — React attaches refs and runs layout effects in the
|
|
8676
|
+
* same commit as the insertion, but strictly afterwards — so a request issued
|
|
8677
|
+
* from `connectedCallback` itself goes out before `headers`,
|
|
8678
|
+
* {@link getHeaders} or {@link credentials} exist, and comes back 401 in a
|
|
8679
|
+
* way that reads as a server fault rather than a mis-timed assignment. A
|
|
8680
|
+
* microtask lands after that commit and still before paint.
|
|
8681
|
+
*
|
|
8682
|
+
* Two things it is **not**. It is not a fix for configuration that arrives
|
|
8683
|
+
* later than the commit (a passive `useEffect`, an awaited token fetch):
|
|
8684
|
+
* configure before insertion (`createElement` → configure → `append`) or call
|
|
8685
|
+
* {@link reload} once configured, because a longer timer would hide that race
|
|
8686
|
+
* rather than close it. And it deliberately excludes the *history* replay,
|
|
8687
|
+
* which stays in `connectedCallback`: the replay renders into the transcript,
|
|
8688
|
+
* so deferring it lets a `sendMessage()` issued in the same task land first
|
|
8689
|
+
* and the replay then duplicate it. The thread history is therefore the one
|
|
8690
|
+
* request that can still go out before a ref is attached — {@link reload}
|
|
8691
|
+
* covers it.
|
|
8692
|
+
*/
|
|
8693
|
+
#startup() {
|
|
8694
|
+
if (!this.#connected) {
|
|
8695
|
+
return;
|
|
8696
|
+
}
|
|
8697
|
+
void this.#fetchToolCatalog();
|
|
8698
|
+
void this.#fetchSkills();
|
|
8699
|
+
}
|
|
8700
|
+
/**
|
|
8701
|
+
* Re-run everything the element loads on startup — the tool-label catalog,
|
|
8702
|
+
* the backend skill catalog and the thread's history — with the transport
|
|
8703
|
+
* configuration as it stands now.
|
|
8704
|
+
*
|
|
8705
|
+
* This is the answer for a host that can only configure the element after the
|
|
8706
|
+
* fact (a token fetched in a passive effect, an async auth handshake): the
|
|
8707
|
+
* startup requests already went out with whatever was set then, and this says
|
|
8708
|
+
* "try again, properly authenticated" without removing and re-inserting the
|
|
8709
|
+
* node.
|
|
8710
|
+
*
|
|
8711
|
+
* A reload, not a merge — the in-flight run is cancelled and the transcript
|
|
8712
|
+
* is rebuilt from the persisted history, so anything streamed since is
|
|
8713
|
+
* dropped. Call it once, when configuration lands; not between turns.
|
|
8714
|
+
*/
|
|
8715
|
+
async reload() {
|
|
8716
|
+
this.#cancelRun();
|
|
8717
|
+
this.#resetState();
|
|
8718
|
+
this.#setRunning(false);
|
|
8719
|
+
await Promise.all([this.#fetchToolCatalog(), this.#fetchSkills(), this.#rehydrate()]);
|
|
8720
|
+
}
|
|
8125
8721
|
/**
|
|
8126
8722
|
* Tear down live resources when the element leaves the DOM (a removed node, a
|
|
8127
8723
|
* client-side route swap): cancel the in-flight run so its SSE stream closes,
|
|
@@ -8203,7 +8799,13 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8203
8799
|
if (url === null) {
|
|
8204
8800
|
return null;
|
|
8205
8801
|
}
|
|
8206
|
-
return (file, onProgress, signal) => uploadAttachment(file, {
|
|
8802
|
+
return (file, onProgress, signal) => uploadAttachment(file, {
|
|
8803
|
+
url,
|
|
8804
|
+
headers: this.#requestHeaders(),
|
|
8805
|
+
...this.#credentialsOption(),
|
|
8806
|
+
onProgress,
|
|
8807
|
+
signal
|
|
8808
|
+
});
|
|
8207
8809
|
}
|
|
8208
8810
|
/**
|
|
8209
8811
|
* Reveal the composer's 🎤 mic button when transcription is possible — either
|
|
@@ -8230,7 +8832,11 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8230
8832
|
if (url === null) {
|
|
8231
8833
|
return null;
|
|
8232
8834
|
}
|
|
8233
|
-
return (audio) => transcribeAudio(audio, {
|
|
8835
|
+
return (audio) => transcribeAudio(audio, {
|
|
8836
|
+
url,
|
|
8837
|
+
headers: this.#requestHeaders(),
|
|
8838
|
+
...this.#credentialsOption()
|
|
8839
|
+
});
|
|
8234
8840
|
}
|
|
8235
8841
|
/** Drop a voice transcript into the composer (appended to any typed text). */
|
|
8236
8842
|
#insertVoiceText(text2) {
|
|
@@ -8305,8 +8911,9 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8305
8911
|
if (url !== null) {
|
|
8306
8912
|
this.conversationStore = new RemoteConversationStore(
|
|
8307
8913
|
url,
|
|
8308
|
-
() => this
|
|
8309
|
-
this.conversationStore
|
|
8914
|
+
() => this.#requestHeaders(),
|
|
8915
|
+
this.conversationStore,
|
|
8916
|
+
() => this.#requestCredentials()
|
|
8310
8917
|
);
|
|
8311
8918
|
}
|
|
8312
8919
|
}
|
|
@@ -8317,7 +8924,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8317
8924
|
return;
|
|
8318
8925
|
}
|
|
8319
8926
|
try {
|
|
8320
|
-
const response = await fetch(url,
|
|
8927
|
+
const response = await fetch(url, this.#fetchInit());
|
|
8321
8928
|
this.#toolCatalog = parseToolCatalog(await response.json());
|
|
8322
8929
|
} catch {
|
|
8323
8930
|
}
|
|
@@ -8330,13 +8937,16 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8330
8937
|
this.#clientSkills = skills;
|
|
8331
8938
|
this.#recomputeSkills();
|
|
8332
8939
|
}
|
|
8333
|
-
/**
|
|
8940
|
+
/**
|
|
8941
|
+
* Wire the skill surfaces: opt-in flags and the embedded catalog. The backend
|
|
8942
|
+
* catalog is fetched from `#startup`, a microtask later, so it carries the
|
|
8943
|
+
* host's transport configuration.
|
|
8944
|
+
*/
|
|
8334
8945
|
#initSkills() {
|
|
8335
8946
|
this.#skillsMenu.enableChips(this.#flag("data-prompt-chips"));
|
|
8336
8947
|
this.#skillsMenu.enableSlash(this.#flag("data-slash-commands"));
|
|
8337
8948
|
this.#embedSkills = this.#readEmbeddedSkills();
|
|
8338
8949
|
this.#recomputeSkills();
|
|
8339
|
-
void this.#fetchSkills();
|
|
8340
8950
|
}
|
|
8341
8951
|
/** Parse the inline `data-skills` JSON catalog (empty when absent/malformed). */
|
|
8342
8952
|
#readEmbeddedSkills() {
|
|
@@ -8357,7 +8967,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8357
8967
|
return;
|
|
8358
8968
|
}
|
|
8359
8969
|
try {
|
|
8360
|
-
const response = await fetch(url,
|
|
8970
|
+
const response = await fetch(url, this.#fetchInit());
|
|
8361
8971
|
this.#backendSkills = parseSkills(await response.json());
|
|
8362
8972
|
this.#recomputeSkills();
|
|
8363
8973
|
} catch {
|
|
@@ -8401,12 +9011,14 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8401
9011
|
this.#skillHint.textContent = this.#strings.skillNeeds.replace("{title}", skill.title).replace("{fields}", missing.join(", "));
|
|
8402
9012
|
this.#skillHint.hidden = false;
|
|
8403
9013
|
this.#input.value = text2;
|
|
9014
|
+
this.#autoGrow();
|
|
8404
9015
|
this.#input.focus();
|
|
8405
9016
|
this.#selectFirstPlaceholder(text2);
|
|
8406
9017
|
return;
|
|
8407
9018
|
}
|
|
8408
9019
|
this.#skillHint.hidden = true;
|
|
8409
9020
|
this.#input.value = text2;
|
|
9021
|
+
this.#autoGrow();
|
|
8410
9022
|
if (skill.sendImmediately === false) {
|
|
8411
9023
|
this.#input.focus();
|
|
8412
9024
|
return;
|
|
@@ -8444,7 +9056,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8444
9056
|
this.removeAttribute("collapsed");
|
|
8445
9057
|
}
|
|
8446
9058
|
sessionStorage.setItem(this.#storageKey(COLLAPSED_KEY), collapsed ? "1" : "0");
|
|
8447
|
-
this.#
|
|
9059
|
+
this.#setUnread(0);
|
|
8448
9060
|
this.dispatchEvent(
|
|
8449
9061
|
new CustomEvent(TOGGLE_EVENT, {
|
|
8450
9062
|
detail: { collapsed },
|
|
@@ -8453,6 +9065,15 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8453
9065
|
})
|
|
8454
9066
|
);
|
|
8455
9067
|
}
|
|
9068
|
+
/**
|
|
9069
|
+
* Answers that finished while the widget was collapsed, and that the user has
|
|
9070
|
+
* therefore not seen. Expanding (or {@link newChat}) clears it. The launcher's
|
|
9071
|
+
* badge renders this; {@link UNREAD_EVENT} announces every change, so a host
|
|
9072
|
+
* chrome can render its own instead.
|
|
9073
|
+
*/
|
|
9074
|
+
get unread() {
|
|
9075
|
+
return this.#unread;
|
|
9076
|
+
}
|
|
8456
9077
|
/** Flip the collapsed state. Bound to the built-in header toggle. */
|
|
8457
9078
|
toggleCollapsed() {
|
|
8458
9079
|
this.setCollapsed(!this.collapsed);
|
|
@@ -8610,6 +9231,32 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8610
9231
|
const dark = this.getAttribute("theme") === "dark";
|
|
8611
9232
|
this.#themeToggle.textContent = dark ? "\u2600\uFE0F" : "\u{1F319}";
|
|
8612
9233
|
}
|
|
9234
|
+
/**
|
|
9235
|
+
* Open the thread-history drawer: the imperative route to the control that
|
|
9236
|
+
* renders as `::part(history-button)`.
|
|
9237
|
+
*
|
|
9238
|
+
* A host that hides `::part(header)` to render its own title bar hides the
|
|
9239
|
+
* history, new-chat and collapse buttons with it — and thread switching then
|
|
9240
|
+
* has no route at all, because those controls live inside the header. Each of
|
|
9241
|
+
* them has a method, so a host chrome can rebuild the set: this one,
|
|
9242
|
+
* {@link openCheckpoints}, {@link newChat}, {@link toggleCollapsed} and
|
|
9243
|
+
* {@link toggleTheme}.
|
|
9244
|
+
*/
|
|
9245
|
+
openThreads() {
|
|
9246
|
+
void this.#refreshDrawer();
|
|
9247
|
+
this.#drawer.open();
|
|
9248
|
+
}
|
|
9249
|
+
/**
|
|
9250
|
+
* Open the checkpoints panel (the `::part(checkpoints-button)` route).
|
|
9251
|
+
*
|
|
9252
|
+
* It lists the runs the `data-runs-url` server reports as continuable;
|
|
9253
|
+
* without that attribute the built-in button is never rendered and this opens
|
|
9254
|
+
* an empty panel.
|
|
9255
|
+
*/
|
|
9256
|
+
openCheckpoints() {
|
|
9257
|
+
void this.#refreshCheckpoints();
|
|
9258
|
+
this.#checkpoints.open();
|
|
9259
|
+
}
|
|
8613
9260
|
/**
|
|
8614
9261
|
* Start a fresh conversation: forget the persisted history, drop the
|
|
8615
9262
|
* in-memory run state, clear the transcript, and mint a new thread id.
|
|
@@ -8620,6 +9267,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8620
9267
|
this.#resetState();
|
|
8621
9268
|
this.#threadId = this.conversationStore.threadId();
|
|
8622
9269
|
this.#setRunning(false);
|
|
9270
|
+
this.#setUnread(0);
|
|
8623
9271
|
}
|
|
8624
9272
|
/** Drop the in-memory run + transcript, leaving the thread id untouched. */
|
|
8625
9273
|
#resetState() {
|
|
@@ -8880,15 +9528,9 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8880
9528
|
controls.className = "header-controls";
|
|
8881
9529
|
controls.setAttribute("part", "header-controls");
|
|
8882
9530
|
const history = this.#headerButton("history", this.#strings.chatHistory, "\u2630");
|
|
8883
|
-
history.addEventListener("click", () =>
|
|
8884
|
-
void this.#refreshDrawer();
|
|
8885
|
-
this.#drawer.open();
|
|
8886
|
-
});
|
|
9531
|
+
history.addEventListener("click", () => this.openThreads());
|
|
8887
9532
|
const checkpoints = this.#headerButton("checkpoints", this.#strings.checkpoints, "\u2B6F");
|
|
8888
|
-
checkpoints.addEventListener("click", () =>
|
|
8889
|
-
void this.#refreshCheckpoints();
|
|
8890
|
-
this.#checkpoints.open();
|
|
8891
|
-
});
|
|
9533
|
+
checkpoints.addEventListener("click", () => this.openCheckpoints());
|
|
8892
9534
|
const newChat = this.#headerButton("new", this.#strings.newChat, "\u271A");
|
|
8893
9535
|
newChat.addEventListener("click", () => this.newChat());
|
|
8894
9536
|
const collapse = this.#headerButton("collapse", this.#strings.collapse, "\u2014");
|
|
@@ -8924,17 +9566,27 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8924
9566
|
const inputRow = document.createElement("div");
|
|
8925
9567
|
inputRow.className = "input-row";
|
|
8926
9568
|
inputRow.setAttribute("part", "composer");
|
|
9569
|
+
const composer = document.createElement("div");
|
|
9570
|
+
composer.className = "composer";
|
|
9571
|
+
composer.setAttribute("part", "composer-surface");
|
|
9572
|
+
const tools = document.createElement("div");
|
|
9573
|
+
tools.className = "composer-tools";
|
|
9574
|
+
tools.setAttribute("part", "composer-tools");
|
|
8927
9575
|
this.#input.className = "input";
|
|
8928
9576
|
this.#input.setAttribute("part", "input");
|
|
8929
9577
|
this.#input.setAttribute("aria-label", this.#strings.message);
|
|
8930
|
-
this.#input.rows =
|
|
9578
|
+
this.#input.rows = 1;
|
|
8931
9579
|
this.#input.placeholder = this.#strings.inputPlaceholder;
|
|
8932
9580
|
this.#input.addEventListener("keydown", (event) => this.#onKeydown(event));
|
|
8933
9581
|
this.#input.addEventListener("input", () => this.#onInput());
|
|
8934
9582
|
this.#send.className = "send";
|
|
8935
9583
|
this.#send.type = "button";
|
|
8936
9584
|
this.#send.setAttribute("part", "send");
|
|
8937
|
-
this.#send.
|
|
9585
|
+
this.#send.append(
|
|
9586
|
+
this.#glyphSlot("icon-send", "send-send", ICON_SEND),
|
|
9587
|
+
this.#glyphSlot("icon-stop", "send-stop", ICON_STOP)
|
|
9588
|
+
);
|
|
9589
|
+
this.#send.title = this.#strings.send;
|
|
8938
9590
|
this.#send.setAttribute("aria-label", this.#strings.send);
|
|
8939
9591
|
this.#send.dataset["state"] = "idle";
|
|
8940
9592
|
this.#send.addEventListener("click", () => {
|
|
@@ -8950,7 +9602,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8950
9602
|
this.#attachButton.className = "attach-btn";
|
|
8951
9603
|
this.#attachButton.type = "button";
|
|
8952
9604
|
this.#attachButton.setAttribute("part", "attach-button");
|
|
8953
|
-
this.#attachButton.
|
|
9605
|
+
this.#attachButton.append(this.#glyphSlot("icon-attach", "attach-glyph", ICON_ATTACH));
|
|
8954
9606
|
this.#attachButton.title = this.#strings.attachFiles;
|
|
8955
9607
|
this.#attachButton.setAttribute("aria-label", this.#strings.attachFiles);
|
|
8956
9608
|
this.#attachButton.hidden = true;
|
|
@@ -8964,7 +9616,9 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8964
9616
|
this.#voiceSlot.className = "voice-slot";
|
|
8965
9617
|
const footer = document.createElement("slot");
|
|
8966
9618
|
footer.name = "footer";
|
|
8967
|
-
|
|
9619
|
+
tools.append(this.#attachButton, this.#voiceSlot, this.#send);
|
|
9620
|
+
composer.append(this.#input, tools);
|
|
9621
|
+
inputRow.append(composer, this.#fileInput);
|
|
8968
9622
|
this.#chat.append(
|
|
8969
9623
|
header,
|
|
8970
9624
|
this.#messages,
|
|
@@ -8977,12 +9631,19 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8977
9631
|
this.#drawer.element,
|
|
8978
9632
|
this.#checkpoints.element
|
|
8979
9633
|
);
|
|
8980
|
-
this.#
|
|
8981
|
-
this.#
|
|
8982
|
-
this.#
|
|
8983
|
-
this.#
|
|
8984
|
-
this.#
|
|
8985
|
-
this.#
|
|
9634
|
+
this.#launcher.className = "launcher";
|
|
9635
|
+
this.#launcher.type = "button";
|
|
9636
|
+
this.#launcher.setAttribute("part", "launcher");
|
|
9637
|
+
this.#launcher.setAttribute("aria-label", this.#strings.expand);
|
|
9638
|
+
this.#badge.className = "launcher-badge";
|
|
9639
|
+
this.#badge.setAttribute("part", "launcher-badge");
|
|
9640
|
+
this.#badge.setAttribute("aria-hidden", "true");
|
|
9641
|
+
this.#badge.hidden = true;
|
|
9642
|
+
this.#launcher.append(
|
|
9643
|
+
this.#iconElement("launcher", "launcher-icon", ICON_LAUNCHER, this.#launcherIconUrl()),
|
|
9644
|
+
this.#badge
|
|
9645
|
+
);
|
|
9646
|
+
this.#launcher.addEventListener("click", () => this.setCollapsed(false));
|
|
8986
9647
|
this.#chat.append(
|
|
8987
9648
|
createResizeHandle({
|
|
8988
9649
|
axis: () => this.#resizeAxis(),
|
|
@@ -8996,7 +9657,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8996
9657
|
label: this.#strings.resizePanel
|
|
8997
9658
|
})
|
|
8998
9659
|
);
|
|
8999
|
-
this.#root.append(style, this.#chat, this.#
|
|
9660
|
+
this.#root.append(style, this.#chat, this.#launcher);
|
|
9000
9661
|
}
|
|
9001
9662
|
/**
|
|
9002
9663
|
* Build a header control button: a named slot a host can project markup into,
|
|
@@ -9023,16 +9684,37 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9023
9684
|
return button2;
|
|
9024
9685
|
}
|
|
9025
9686
|
/**
|
|
9026
|
-
*
|
|
9027
|
-
*
|
|
9687
|
+
* A `<slot>` a host can project its own mark into, falling back to one of the
|
|
9688
|
+
* built-in glyphs. The markup is an author-written constant, never user or
|
|
9689
|
+
* server data, so it is assigned directly rather than sanitised.
|
|
9690
|
+
*/
|
|
9691
|
+
#glyphSlot(slotName, className, markup) {
|
|
9692
|
+
const slot = document.createElement("slot");
|
|
9693
|
+
slot.name = slotName;
|
|
9694
|
+
slot.className = className;
|
|
9695
|
+
slot.innerHTML = markup;
|
|
9696
|
+
return slot;
|
|
9697
|
+
}
|
|
9698
|
+
/**
|
|
9699
|
+
* The launcher's own image URL. `data-launcher-icon-url` lets the collapsed
|
|
9700
|
+
* button carry a different mark from the header's — a product logo reads at
|
|
9701
|
+
* 22px in a header bar but rarely at 26px in a circle — and falls back to the
|
|
9702
|
+
* header icon so a single `data-icon-url` still feeds both.
|
|
9703
|
+
*/
|
|
9704
|
+
#launcherIconUrl() {
|
|
9705
|
+
return this.getAttribute("data-launcher-icon-url") ?? this.getAttribute("data-icon-url");
|
|
9706
|
+
}
|
|
9707
|
+
/**
|
|
9708
|
+
* An icon holder wrapping a `<slot>` so a host can project custom markup;
|
|
9709
|
+
* with an `<img>` as the slot's fallback when an icon URL is configured, or
|
|
9710
|
+
* the given glyph markup when it is not.
|
|
9028
9711
|
*/
|
|
9029
|
-
#iconElement(slotName, part, fallbackGlyph) {
|
|
9712
|
+
#iconElement(slotName, part, fallbackGlyph, iconUrl = this.getAttribute("data-icon-url")) {
|
|
9030
9713
|
const holder = document.createElement("span");
|
|
9031
9714
|
holder.className = "icon-holder";
|
|
9032
9715
|
holder.setAttribute("part", part);
|
|
9033
9716
|
const slot = document.createElement("slot");
|
|
9034
9717
|
slot.name = slotName;
|
|
9035
|
-
const iconUrl = this.getAttribute("data-icon-url");
|
|
9036
9718
|
if (iconUrl !== null) {
|
|
9037
9719
|
const img = document.createElement("img");
|
|
9038
9720
|
img.className = "icon-img";
|
|
@@ -9040,14 +9722,63 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9040
9722
|
img.alt = "";
|
|
9041
9723
|
slot.append(img);
|
|
9042
9724
|
} else if (fallbackGlyph !== null) {
|
|
9043
|
-
slot.
|
|
9725
|
+
slot.innerHTML = fallbackGlyph;
|
|
9044
9726
|
}
|
|
9045
9727
|
holder.append(slot);
|
|
9046
9728
|
return holder;
|
|
9047
9729
|
}
|
|
9048
|
-
/**
|
|
9049
|
-
|
|
9050
|
-
|
|
9730
|
+
/**
|
|
9731
|
+
* Reflect the collapsed state and the unread count on the launcher.
|
|
9732
|
+
*
|
|
9733
|
+
* The count is also the launcher's accessible name: a badge that only exists
|
|
9734
|
+
* as a coloured dot says nothing to a screen reader, and "Expand" alone would
|
|
9735
|
+
* be a lie once answers are waiting behind it.
|
|
9736
|
+
*/
|
|
9737
|
+
#syncLauncher() {
|
|
9738
|
+
this.#launcher.setAttribute("aria-expanded", String(!this.collapsed));
|
|
9739
|
+
const unread = this.#unread;
|
|
9740
|
+
this.#badge.textContent = unread > 9 ? "9+" : String(unread);
|
|
9741
|
+
this.#badge.hidden = unread === 0 || !this.#badgeEnabled();
|
|
9742
|
+
const label = this.#badge.hidden ? this.#strings.expand : this.#strings.expandUnread.replace("{count}", String(unread));
|
|
9743
|
+
this.#launcher.setAttribute("aria-label", label);
|
|
9744
|
+
this.#launcher.title = label;
|
|
9745
|
+
}
|
|
9746
|
+
/**
|
|
9747
|
+
* The unread badge, unlike every other affordance here, is on by default:
|
|
9748
|
+
* a collapsed widget is the one state where an answer can arrive with nothing
|
|
9749
|
+
* on screen to say so. `data-unread-badge="false"` turns it off for a host
|
|
9750
|
+
* that drives its own chrome from the `ag-ui-unread` event.
|
|
9751
|
+
*/
|
|
9752
|
+
#badgeEnabled() {
|
|
9753
|
+
return this.getAttribute("data-unread-badge") !== "false";
|
|
9754
|
+
}
|
|
9755
|
+
/**
|
|
9756
|
+
* Set the unread count, repaint the badge, and tell the host.
|
|
9757
|
+
*
|
|
9758
|
+
* The count is kept whether or not the badge renders it, so `unread` stays
|
|
9759
|
+
* truthful for a host chrome and switching the badge on mid-session doesn't
|
|
9760
|
+
* start from a number that was never counted.
|
|
9761
|
+
*/
|
|
9762
|
+
#setUnread(count) {
|
|
9763
|
+
this.#unread = count;
|
|
9764
|
+
this.#syncLauncher();
|
|
9765
|
+
this.dispatchEvent(
|
|
9766
|
+
new CustomEvent(UNREAD_EVENT, {
|
|
9767
|
+
detail: { unread: count },
|
|
9768
|
+
bubbles: true,
|
|
9769
|
+
composed: true
|
|
9770
|
+
})
|
|
9771
|
+
);
|
|
9772
|
+
}
|
|
9773
|
+
/**
|
|
9774
|
+
* Count an answer the user cannot have seen: one that finished while the
|
|
9775
|
+
* widget was collapsed. Expanding is what marks them read.
|
|
9776
|
+
*/
|
|
9777
|
+
#noteUnread() {
|
|
9778
|
+
if (!this.collapsed) {
|
|
9779
|
+
return;
|
|
9780
|
+
}
|
|
9781
|
+
this.#setUnread(this.#unread + 1);
|
|
9051
9782
|
}
|
|
9052
9783
|
/** Hide the empty-state region once the message list holds anything else. */
|
|
9053
9784
|
#updateEmptyState() {
|
|
@@ -9057,6 +9788,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9057
9788
|
#onInput() {
|
|
9058
9789
|
this.#skillsMenu.onInput(this.#input.value);
|
|
9059
9790
|
this.#skillHint.hidden = true;
|
|
9791
|
+
this.#autoGrow();
|
|
9060
9792
|
}
|
|
9061
9793
|
#onKeydown(event) {
|
|
9062
9794
|
if (this.#skillsMenu.onKeydown(event)) {
|
|
@@ -9083,14 +9815,32 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9083
9815
|
this.#confirmAbort?.abort();
|
|
9084
9816
|
this.#client?.cancel();
|
|
9085
9817
|
}
|
|
9086
|
-
/**
|
|
9818
|
+
/**
|
|
9819
|
+
* Swap the composer button between Send (idle) and Stop (running).
|
|
9820
|
+
*
|
|
9821
|
+
* The glyph is swapped by CSS from `data-state` — both are mounted — so this
|
|
9822
|
+
* only has to move the accessible name, which is the button's whole label now
|
|
9823
|
+
* that it carries no text.
|
|
9824
|
+
*/
|
|
9087
9825
|
#setRunning(running) {
|
|
9088
9826
|
this.#running = running;
|
|
9089
9827
|
const label = running ? this.#strings.stop : this.#strings.send;
|
|
9090
|
-
this.#send.
|
|
9828
|
+
this.#send.title = label;
|
|
9091
9829
|
this.#send.setAttribute("aria-label", label);
|
|
9092
9830
|
this.#send.dataset["state"] = running ? "running" : "idle";
|
|
9093
9831
|
}
|
|
9832
|
+
/**
|
|
9833
|
+
* Size the field to its content: one row when empty, growing with what is
|
|
9834
|
+
* typed until the CSS ceiling takes over and it scrolls.
|
|
9835
|
+
*
|
|
9836
|
+
* Resetting to `auto` first is what makes it shrink again — `scrollHeight`
|
|
9837
|
+
* never reports less than the current height, so measuring without the reset
|
|
9838
|
+
* would ratchet the composer taller and never back down.
|
|
9839
|
+
*/
|
|
9840
|
+
#autoGrow() {
|
|
9841
|
+
this.#input.style.height = "auto";
|
|
9842
|
+
this.#input.style.height = `${this.#input.scrollHeight}px`;
|
|
9843
|
+
}
|
|
9094
9844
|
async #submit() {
|
|
9095
9845
|
if (this.#running) {
|
|
9096
9846
|
return;
|
|
@@ -9101,6 +9851,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9101
9851
|
return;
|
|
9102
9852
|
}
|
|
9103
9853
|
this.#input.value = "";
|
|
9854
|
+
this.#autoGrow();
|
|
9104
9855
|
if (this.#attachTray?.hasPending() === true) {
|
|
9105
9856
|
this.#appendNotice(
|
|
9106
9857
|
"\u{1F4CE}",
|
|
@@ -9189,11 +9940,12 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9189
9940
|
if (this.#client === null) {
|
|
9190
9941
|
const agent = this.agentFactory({
|
|
9191
9942
|
endpoint: this.endpoint,
|
|
9192
|
-
headers: this
|
|
9943
|
+
headers: this.#requestHeaders(),
|
|
9193
9944
|
// Live getter: the client is built once and cached, but a rotated
|
|
9194
9945
|
// token must still reach every request — the factory's fetch wrapper
|
|
9195
9946
|
// re-reads this on each call.
|
|
9196
|
-
getHeaders: () => this
|
|
9947
|
+
getHeaders: () => this.#requestHeaders(),
|
|
9948
|
+
...this.#credentialsOption(),
|
|
9197
9949
|
threadId: this.#threadId,
|
|
9198
9950
|
initialMessages: this.#initialMessages,
|
|
9199
9951
|
initialState: this.#sharedState
|
|
@@ -9368,6 +10120,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9368
10120
|
}
|
|
9369
10121
|
attachCopyButtons(bubble, this.#strings);
|
|
9370
10122
|
this.#streamingBubble = null;
|
|
10123
|
+
this.#noteUnread();
|
|
9371
10124
|
},
|
|
9372
10125
|
onToolCall: (call) => {
|
|
9373
10126
|
this.#hidePending();
|
|
@@ -9563,24 +10316,24 @@ function defineAgUiChat() {
|
|
|
9563
10316
|
|
|
9564
10317
|
// src/dom/dom_driver.ts
|
|
9565
10318
|
async function fillField(el, value, options = {}) {
|
|
9566
|
-
scrollIntoCenterView(el);
|
|
9567
|
-
await focusWithFlash(el, { flashMs: options.flashMs ?? 0 });
|
|
10319
|
+
await scrollIntoCenterView(el);
|
|
10320
|
+
await focusWithFlash(el, { ...options, flashMs: options.flashMs ?? 0 });
|
|
9568
10321
|
await typeInto(el, value, options);
|
|
9569
10322
|
}
|
|
9570
10323
|
async function clickElement(el, options = {}) {
|
|
9571
|
-
scrollIntoCenterView(el);
|
|
10324
|
+
await scrollIntoCenterView(el);
|
|
9572
10325
|
await highlightThenClick(el, options);
|
|
9573
10326
|
}
|
|
9574
10327
|
async function pressButton(el, options = {}) {
|
|
9575
|
-
scrollIntoCenterView(el);
|
|
10328
|
+
await scrollIntoCenterView(el);
|
|
9576
10329
|
await pressThenClick(el, options);
|
|
9577
10330
|
}
|
|
9578
10331
|
async function selectControl(el, value, options = {}) {
|
|
9579
|
-
scrollIntoCenterView(el);
|
|
10332
|
+
await scrollIntoCenterView(el);
|
|
9580
10333
|
await selectOption(el, value, options);
|
|
9581
10334
|
}
|
|
9582
10335
|
async function toggleCheckbox(el, checked, options = {}) {
|
|
9583
|
-
scrollIntoCenterView(el);
|
|
10336
|
+
await scrollIntoCenterView(el);
|
|
9584
10337
|
await toggleControl(el, checked, options);
|
|
9585
10338
|
}
|
|
9586
10339
|
function setControlValue(el, value) {
|
|
@@ -9594,7 +10347,7 @@ function setControlValue(el, value) {
|
|
|
9594
10347
|
}
|
|
9595
10348
|
|
|
9596
10349
|
// src/version.ts
|
|
9597
|
-
var VERSION = "0.
|
|
10350
|
+
var VERSION = "0.22.0";
|
|
9598
10351
|
export {
|
|
9599
10352
|
ATTACHMENT_EVENT,
|
|
9600
10353
|
AgUiChat,
|
|
@@ -9618,6 +10371,7 @@ export {
|
|
|
9618
10371
|
TOOL_CALL_STATUS,
|
|
9619
10372
|
TOOL_DISPLAY,
|
|
9620
10373
|
ToolCallCard,
|
|
10374
|
+
UNREAD_EVENT,
|
|
9621
10375
|
VERSION,
|
|
9622
10376
|
X_CONFIRM_KEY,
|
|
9623
10377
|
X_DESTRUCTIVE_KEY,
|
|
@@ -9632,6 +10386,7 @@ export {
|
|
|
9632
10386
|
createStateHookTools,
|
|
9633
10387
|
defineAgUiChat,
|
|
9634
10388
|
fillField,
|
|
10389
|
+
flash,
|
|
9635
10390
|
focusWithFlash,
|
|
9636
10391
|
highlightThenClick,
|
|
9637
10392
|
isDestructive,
|