@artooi/ag-ui-web-component 0.46.0 → 0.48.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 +221 -1
- package/README.md +298 -31
- package/dist/ag-ui-web-component.bundle.js +597 -177
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +37 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +73 -2
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/chat_registry.d.ts +23 -0
- package/dist/core/chat_registry.d.ts.map +1 -0
- package/dist/core/events/bar_height_detail.d.ts +6 -0
- package/dist/core/events/bar_height_detail.d.ts.map +1 -0
- package/dist/core/events/run_finished_detail.d.ts +6 -0
- package/dist/core/events/run_finished_detail.d.ts.map +1 -1
- package/dist/core/events/thread_detail.d.ts +27 -0
- package/dist/core/events/thread_detail.d.ts.map +1 -0
- package/dist/core/run_handlers.d.ts +6 -1
- package/dist/core/run_handlers.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +21 -6
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/highlight_overlay.d.ts +8 -1
- package/dist/dom/highlight_overlay.d.ts.map +1 -1
- package/dist/dom/ring_color.d.ts +22 -0
- package/dist/dom/ring_color.d.ts.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1164 -95
- package/dist/index.js.map +4 -4
- package/dist/tools/chat_surface_tools.d.ts +8 -1
- package/dist/tools/chat_surface_tools.d.ts.map +1 -1
- package/dist/ui/history/conversation_history.d.ts +24 -2
- package/dist/ui/history/conversation_history.d.ts.map +1 -1
- package/dist/ui/placement/collapse_styles.d.ts.map +1 -1
- package/dist/ui/placement/dock_styles.d.ts +2 -0
- package/dist/ui/placement/dock_styles.d.ts.map +1 -0
- package/dist/ui/placement/panel_placement.d.ts +17 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -1
- package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -1
- package/dist/ui/placement/surface_for.d.ts +21 -17
- package/dist/ui/placement/surface_for.d.ts.map +1 -1
- package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
- package/dist/ui/progress/tool_group_styles.d.ts.map +1 -1
- package/dist/ui/progress/tool_step_group.d.ts.map +1 -1
- package/dist/ui/progress/tool_step_styles.d.ts.map +1 -1
- package/dist/ui/shell/build_launcher.d.ts +15 -4
- package/dist/ui/shell/build_launcher.d.ts.map +1 -1
- package/dist/ui/shell/token_styles.d.ts.map +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/themes/scheme_styles.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +41 -2
- package/src/core/ag_ui_chat.ts +430 -27
- package/src/core/chat_registry.ts +53 -0
- package/src/core/events/bar_height_detail.ts +5 -0
- package/src/core/events/run_finished_detail.ts +6 -0
- package/src/core/events/thread_detail.ts +26 -0
- package/src/core/run_handlers.ts +9 -4
- package/src/dom/animations.ts +30 -31
- package/src/dom/highlight_overlay.ts +10 -5
- package/src/dom/ring_color.ts +222 -0
- package/src/index.ts +7 -0
- package/src/tools/chat_surface_tools.ts +18 -8
- package/src/ui/history/conversation_history.ts +75 -7
- package/src/ui/placement/collapse_styles.ts +248 -2
- package/src/ui/placement/dock_styles.ts +91 -0
- package/src/ui/placement/embedded_styles.ts +2 -2
- package/src/ui/placement/panel_placement.ts +105 -12
- package/src/ui/placement/preset_styles.ts +1 -1
- package/src/ui/placement/sidebar_styles.ts +1 -1
- package/src/ui/placement/small_viewport_styles.ts +14 -0
- package/src/ui/placement/surface_for.ts +29 -18
- package/src/ui/progress/tool_call_card.ts +18 -8
- package/src/ui/progress/tool_group_styles.ts +49 -0
- package/src/ui/progress/tool_step_group.ts +3 -0
- package/src/ui/progress/tool_step_styles.ts +5 -3
- package/src/ui/shell/build_launcher.ts +32 -5
- package/src/ui/shell/token_styles.ts +30 -3
- package/src/ui/styles.ts +4 -0
- package/src/ui/themes/scheme_styles.ts +10 -1
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
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 BAR_HEIGHT_EVENT = "ag-ui-bar-height";
|
|
5
6
|
var UNREAD_EVENT = "ag-ui-unread";
|
|
6
7
|
var STATE_EVENT = "ag-ui-state";
|
|
7
8
|
var ATTACHMENT_EVENT = "ag-ui-attachments";
|
|
8
9
|
var RUN_FINISHED_EVENT = "ag-ui-run-finished";
|
|
10
|
+
var THREAD_EVENT = "ag-ui-thread";
|
|
9
11
|
var CUSTOM_AGENT_EVENT = "ag-ui-custom";
|
|
10
12
|
var INVALIDATE_EVENT = "ag-ui-invalidate";
|
|
11
13
|
var FEEDBACK_EVENT = "ag-ui-feedback";
|
|
@@ -61,6 +63,12 @@ var TOOL_DISPLAY = {
|
|
|
61
63
|
COMPACT: "compact",
|
|
62
64
|
FULL: "full"
|
|
63
65
|
};
|
|
66
|
+
var TOOL_GROUP_DISPLAY = {
|
|
67
|
+
INLINE: "inline",
|
|
68
|
+
MINIMAL: "minimal",
|
|
69
|
+
COMPACT: "compact",
|
|
70
|
+
FULL: "full"
|
|
71
|
+
};
|
|
64
72
|
var COMPACTION_ACTIVITY_TYPE = "compaction";
|
|
65
73
|
var LOAD_CAPABILITY_TOOL = "load_capability";
|
|
66
74
|
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>`;
|
|
@@ -276,7 +284,7 @@ function createChatSurfaceTools(surface) {
|
|
|
276
284
|
if (!report.movable) {
|
|
277
285
|
return {
|
|
278
286
|
moved: false,
|
|
279
|
-
reason: report.fullBleed ? "the panel fills the screen, so there is nowhere to move it to" : report.draggable === false ? "this page has turned off moving the panel" : `the "${report.placement ?? ""}" placement owns the panel's position`,
|
|
287
|
+
reason: report.fullBleed ? "the panel fills the screen, so there is nowhere to move it to" : report.collapseTo === "bar" || report.collapseTo === "none" ? "the panel is docked: it rises from the page's bottom bar and stays there" : report.draggable === false ? "this page has turned off moving the panel" : `the "${report.placement ?? ""}" placement owns the panel's position`,
|
|
280
288
|
suggestion: report.collapsible ? "minimise_chat" : null
|
|
281
289
|
};
|
|
282
290
|
}
|
|
@@ -285,7 +293,7 @@ function createChatSurfaceTools(surface) {
|
|
|
285
293
|
},
|
|
286
294
|
{
|
|
287
295
|
name: "minimise_chat",
|
|
288
|
-
description: "Collapse your own panel
|
|
296
|
+
description: "Collapse your own panel, so the user can see the whole page. The user reopens it from where it collapsed to (a launcher, a rail, a bottom bar or the page's own control). Answers `minimised: false` when the placement has no collapsed state, which is the case for a full-page chat.",
|
|
289
297
|
parameters: {
|
|
290
298
|
type: "object",
|
|
291
299
|
properties: {},
|
|
@@ -297,7 +305,7 @@ function createChatSurfaceTools(surface) {
|
|
|
297
305
|
if (!report.collapsible) {
|
|
298
306
|
return {
|
|
299
307
|
minimised: false,
|
|
300
|
-
reason: "this placement has no collapsed state, so there is
|
|
308
|
+
reason: "this placement has no collapsed state, so there is nothing to minimise to"
|
|
301
309
|
};
|
|
302
310
|
}
|
|
303
311
|
surface.setCollapsed(true, { announce: true });
|
|
@@ -354,8 +362,163 @@ var ClientToolRegistry = class {
|
|
|
354
362
|
}
|
|
355
363
|
};
|
|
356
364
|
|
|
365
|
+
// src/core/chat_registry.ts
|
|
366
|
+
var CONNECTED_CHATS = /* @__PURE__ */ new Set();
|
|
367
|
+
var ChatRegistration = class {
|
|
368
|
+
#chat;
|
|
369
|
+
constructor(chat) {
|
|
370
|
+
this.#chat = chat;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Join, as the most recently connected chat. An element moved in the DOM
|
|
374
|
+
* left on disconnect before it reconnected, so it rejoins at the end and no
|
|
375
|
+
* entry needs removing first.
|
|
376
|
+
*/
|
|
377
|
+
join() {
|
|
378
|
+
CONNECTED_CHATS.add(this.#chat);
|
|
379
|
+
}
|
|
380
|
+
/** Leave. The previous chat, if one is still connected, becomes the latest. */
|
|
381
|
+
leave() {
|
|
382
|
+
CONNECTED_CHATS.delete(this.#chat);
|
|
383
|
+
}
|
|
384
|
+
/** The most recently connected chat still in the document, or `null`. */
|
|
385
|
+
static latest() {
|
|
386
|
+
let latest = null;
|
|
387
|
+
for (const chat of CONNECTED_CHATS) {
|
|
388
|
+
latest = chat;
|
|
389
|
+
}
|
|
390
|
+
return latest;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// src/dom/ring_color.ts
|
|
395
|
+
var RING_TOKEN = "--ag-ui-highlight-ring-color";
|
|
396
|
+
var ACCENT_TOKEN = "--ag-ui-accent";
|
|
397
|
+
var CHAT_ACCENT = "--_accent";
|
|
398
|
+
var PACKAGE_ACCENT = "#4f46e5";
|
|
399
|
+
function ringColor(target, chat) {
|
|
400
|
+
const onTarget = readToken(target, RING_TOKEN);
|
|
401
|
+
if (onTarget !== "") {
|
|
402
|
+
return onTarget;
|
|
403
|
+
}
|
|
404
|
+
const owner = chat ?? ChatRegistration.latest();
|
|
405
|
+
const onChat = owner === null ? "" : resolveOn(owner, RING_TOKEN);
|
|
406
|
+
if (onChat !== "") {
|
|
407
|
+
return onChat;
|
|
408
|
+
}
|
|
409
|
+
const accent = readToken(target, ACCENT_TOKEN);
|
|
410
|
+
if (accent !== "") {
|
|
411
|
+
return accent;
|
|
412
|
+
}
|
|
413
|
+
if (owner === null) {
|
|
414
|
+
return PACKAGE_ACCENT;
|
|
415
|
+
}
|
|
416
|
+
const chatAccent = resolveOn(owner, CHAT_ACCENT);
|
|
417
|
+
return chatAccent === "" ? PACKAGE_ACCENT : legible(owner, chatAccent, target);
|
|
418
|
+
}
|
|
419
|
+
var RING_CONTRAST = 3;
|
|
420
|
+
var STEP = 0.1;
|
|
421
|
+
function legible(chat, accent, target) {
|
|
422
|
+
const paint = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
|
|
423
|
+
if (paint === null) {
|
|
424
|
+
return accent;
|
|
425
|
+
}
|
|
426
|
+
const bytes = (color) => {
|
|
427
|
+
paint.clearRect(0, 0, 1, 1);
|
|
428
|
+
paint.fillStyle = "rgb(0 0 0 / 0)";
|
|
429
|
+
paint.fillStyle = color;
|
|
430
|
+
paint.fillRect(0, 0, 1, 1);
|
|
431
|
+
const [r2 = 0, g2 = 0, b2 = 0, a3 = 0] = paint.getImageData(0, 0, 1, 1).data;
|
|
432
|
+
return [r2, g2, b2, a3 / 255];
|
|
433
|
+
};
|
|
434
|
+
const surface = surfaceBehind(target, bytes);
|
|
435
|
+
const holds = (color) => contrast(over(color, surface), surface) >= RING_CONTRAST;
|
|
436
|
+
const own = [accent, resolveOn(chat, CHAT_ACCENT, "light"), resolveOn(chat, CHAT_ACCENT, "dark")];
|
|
437
|
+
const fit = own.find((candidate) => holds(bytes(candidate)));
|
|
438
|
+
if (fit !== void 0) {
|
|
439
|
+
return fit;
|
|
440
|
+
}
|
|
441
|
+
const from = over(bytes(accent), surface);
|
|
442
|
+
const black = [0, 0, 0, 1];
|
|
443
|
+
const white = [255, 255, 255, 1];
|
|
444
|
+
const toward = contrast(black, surface) > contrast(white, surface) ? black : white;
|
|
445
|
+
let step = 1;
|
|
446
|
+
while (!holds(mix(from, toward, step * STEP))) {
|
|
447
|
+
step += 1;
|
|
448
|
+
}
|
|
449
|
+
const [r, g, b] = mix(from, toward, step * STEP);
|
|
450
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
451
|
+
}
|
|
452
|
+
function surfaceBehind(target, bytes) {
|
|
453
|
+
const layers = [];
|
|
454
|
+
for (let el2 = parentOf(target); el2 !== null; el2 = parentOf(el2)) {
|
|
455
|
+
const layer = bytes(window.getComputedStyle(el2).backgroundColor);
|
|
456
|
+
layers.push(layer);
|
|
457
|
+
if (layer[3] === 1) {
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const probe = document.createElement("span");
|
|
462
|
+
probe.hidden = true;
|
|
463
|
+
probe.style.color = "Canvas";
|
|
464
|
+
document.documentElement.appendChild(probe);
|
|
465
|
+
let surface = bytes(window.getComputedStyle(probe).color);
|
|
466
|
+
probe.remove();
|
|
467
|
+
for (const layer of layers.reverse()) {
|
|
468
|
+
surface = over(layer, surface);
|
|
469
|
+
}
|
|
470
|
+
return surface;
|
|
471
|
+
}
|
|
472
|
+
function parentOf(el2) {
|
|
473
|
+
if (el2.parentElement !== null) {
|
|
474
|
+
return el2.parentElement;
|
|
475
|
+
}
|
|
476
|
+
const root = el2.getRootNode();
|
|
477
|
+
return root instanceof ShadowRoot ? root.host : null;
|
|
478
|
+
}
|
|
479
|
+
function over(top, under) {
|
|
480
|
+
return mix(under, top, top[3]);
|
|
481
|
+
}
|
|
482
|
+
function mix(from, to, share) {
|
|
483
|
+
const channel = (index) => Math.round(from[index] + (to[index] - from[index]) * share);
|
|
484
|
+
return [channel(0), channel(1), channel(2), 1];
|
|
485
|
+
}
|
|
486
|
+
function luminance([r, g, b]) {
|
|
487
|
+
const linear = (channel) => {
|
|
488
|
+
const c = channel / 255;
|
|
489
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
490
|
+
};
|
|
491
|
+
return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
|
|
492
|
+
}
|
|
493
|
+
function contrast(a3, b) {
|
|
494
|
+
const [la, lb] = [luminance(a3), luminance(b)];
|
|
495
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
496
|
+
}
|
|
497
|
+
function readToken(el2, name) {
|
|
498
|
+
return window.getComputedStyle(el2).getPropertyValue(name).trim();
|
|
499
|
+
}
|
|
500
|
+
function resolveOn(chat, name, scheme) {
|
|
501
|
+
const value = readToken(chat, name);
|
|
502
|
+
const root = chat.shadowRoot;
|
|
503
|
+
if (value === "" || root === null) {
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
const probe = document.createElement("span");
|
|
507
|
+
probe.hidden = true;
|
|
508
|
+
if (scheme !== void 0) {
|
|
509
|
+
probe.style.colorScheme = scheme;
|
|
510
|
+
}
|
|
511
|
+
probe.style.color = value;
|
|
512
|
+
if (probe.style.color === "") {
|
|
513
|
+
return "";
|
|
514
|
+
}
|
|
515
|
+
root.appendChild(probe);
|
|
516
|
+
const resolved = window.getComputedStyle(probe).color;
|
|
517
|
+
probe.remove();
|
|
518
|
+
return resolved;
|
|
519
|
+
}
|
|
520
|
+
|
|
357
521
|
// src/dom/highlight_overlay.ts
|
|
358
|
-
var ACCENT = "#4f46e5";
|
|
359
522
|
function tokenOn(target, name, fallback) {
|
|
360
523
|
const value = window.getComputedStyle(target).getPropertyValue(name).trim();
|
|
361
524
|
return value === "" ? fallback : value;
|
|
@@ -440,7 +603,7 @@ function safeColor(value) {
|
|
|
440
603
|
return CSS.supports("color", value) ? value : null;
|
|
441
604
|
}
|
|
442
605
|
function ringPaint(target, options) {
|
|
443
|
-
const color = safeColor(options.color) ??
|
|
606
|
+
const color = safeColor(options.color) ?? ringColor(target, options.chat);
|
|
444
607
|
if (options.gradient !== true) {
|
|
445
608
|
return `border-color: ${color}`;
|
|
446
609
|
}
|
|
@@ -504,9 +667,6 @@ function setNativeChecked(el2, checked) {
|
|
|
504
667
|
}
|
|
505
668
|
|
|
506
669
|
// src/dom/animations.ts
|
|
507
|
-
var ACCENT2 = "#4f46e5";
|
|
508
|
-
var ACCENT_HALO = "rgba(79, 70, 229, 0.4)";
|
|
509
|
-
var ACCENT_PROPERTY = "--ag-ui-accent";
|
|
510
670
|
function delay(ms) {
|
|
511
671
|
return new Promise((resolve) => {
|
|
512
672
|
setTimeout(resolve, ms);
|
|
@@ -521,12 +681,8 @@ function motionDelay(ms) {
|
|
|
521
681
|
}
|
|
522
682
|
return delay(ms);
|
|
523
683
|
}
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
return themed === "" ? fallback : themed;
|
|
527
|
-
}
|
|
528
|
-
function ringShadow(el2) {
|
|
529
|
-
return `0 0 0 3px ${accentColor(el2, ACCENT_HALO)}`;
|
|
684
|
+
function ringShadow(el2, chat) {
|
|
685
|
+
return `0 0 0 3px color-mix(in srgb, ${ringColor(el2, chat)} 40%, transparent)`;
|
|
530
686
|
}
|
|
531
687
|
async function typeInto(el2, value, options = {}) {
|
|
532
688
|
const charDelayMs = options.charDelayMs ?? 35;
|
|
@@ -545,7 +701,7 @@ async function highlightThenClick(el2, options = {}) {
|
|
|
545
701
|
const highlightMs = options.highlightMs ?? 280;
|
|
546
702
|
const previousOutline = el2.style.outline;
|
|
547
703
|
const previousOffset = el2.style.outlineOffset;
|
|
548
|
-
el2.style.outline = `2px solid ${
|
|
704
|
+
el2.style.outline = `2px solid ${ringColor(el2, options.chat)}`;
|
|
549
705
|
el2.style.outlineOffset = "2px";
|
|
550
706
|
await delay(highlightMs);
|
|
551
707
|
el2.style.outline = previousOutline;
|
|
@@ -597,7 +753,8 @@ async function flashRing(el2, options, focusByDefault) {
|
|
|
597
753
|
scrim: options.scrim === true,
|
|
598
754
|
gradient: options.gradient === true,
|
|
599
755
|
...options.color === void 0 ? {} : { color: options.color },
|
|
600
|
-
...options.ringPadding === void 0 ? {} : { padding: options.ringPadding }
|
|
756
|
+
...options.ringPadding === void 0 ? {} : { padding: options.ringPadding },
|
|
757
|
+
...options.chat === void 0 ? {} : { chat: options.chat }
|
|
601
758
|
});
|
|
602
759
|
await delay(flashMs);
|
|
603
760
|
dismiss();
|
|
@@ -606,7 +763,7 @@ async function flashRing(el2, options, focusByDefault) {
|
|
|
606
763
|
const previousOutline = el2.style.outline;
|
|
607
764
|
const previousOffset = el2.style.outlineOffset;
|
|
608
765
|
const previousTransition = el2.style.transition;
|
|
609
|
-
const color = options.color ??
|
|
766
|
+
const color = options.color ?? ringColor(el2, options.chat);
|
|
610
767
|
el2.style.outline = `3px solid ${color}`;
|
|
611
768
|
el2.style.outlineOffset = "2px";
|
|
612
769
|
const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
|
|
@@ -633,7 +790,7 @@ async function pressThenClick(el2, options = {}) {
|
|
|
633
790
|
const previousShadow = el2.style.boxShadow;
|
|
634
791
|
el2.style.transition = "transform 80ms ease";
|
|
635
792
|
el2.style.transform = "scale(0.96)";
|
|
636
|
-
el2.style.boxShadow = ringShadow(el2);
|
|
793
|
+
el2.style.boxShadow = ringShadow(el2, options.chat);
|
|
637
794
|
await motionDelay(pressMs);
|
|
638
795
|
el2.style.transform = previousTransform;
|
|
639
796
|
el2.style.transition = previousTransition;
|
|
@@ -656,7 +813,7 @@ async function selectOption(el2, value, options = {}) {
|
|
|
656
813
|
const highlightMs = options.highlightMs ?? 220;
|
|
657
814
|
const previousOutline = el2.style.outline;
|
|
658
815
|
const previousOffset = el2.style.outlineOffset;
|
|
659
|
-
el2.style.outline = `2px solid ${
|
|
816
|
+
el2.style.outline = `2px solid ${ringColor(el2, options.chat)}`;
|
|
660
817
|
el2.style.outlineOffset = "2px";
|
|
661
818
|
await motionDelay(highlightMs);
|
|
662
819
|
setNativeValue(el2, option.value);
|
|
@@ -668,7 +825,7 @@ async function selectOption(el2, value, options = {}) {
|
|
|
668
825
|
async function toggleControl(el2, checked, options = {}) {
|
|
669
826
|
const flashMs = options.flashMs ?? 200;
|
|
670
827
|
const previousShadow = el2.style.boxShadow;
|
|
671
|
-
el2.style.boxShadow = ringShadow(el2);
|
|
828
|
+
el2.style.boxShadow = ringShadow(el2, options.chat);
|
|
672
829
|
await motionDelay(flashMs);
|
|
673
830
|
setNativeChecked(el2, checked);
|
|
674
831
|
el2.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4330,6 +4487,8 @@ var ConversationHistory = class {
|
|
|
4330
4487
|
* Also what tells a pick refused for a load from one refused for a run.
|
|
4331
4488
|
*/
|
|
4332
4489
|
#loading = null;
|
|
4490
|
+
/** The thread switch under way and the promise that settles it, or `null`. */
|
|
4491
|
+
#switching = null;
|
|
4333
4492
|
constructor(host) {
|
|
4334
4493
|
this.#host = host;
|
|
4335
4494
|
}
|
|
@@ -4412,17 +4571,49 @@ var ConversationHistory = class {
|
|
|
4412
4571
|
void this.#refreshCheckpoints();
|
|
4413
4572
|
this.#host.checkpoints.open();
|
|
4414
4573
|
}
|
|
4415
|
-
/**
|
|
4416
|
-
|
|
4574
|
+
/**
|
|
4575
|
+
* Switch the active conversation to an existing thread and replay it.
|
|
4576
|
+
*
|
|
4577
|
+
* A call for the thread a switch is already loading is handed that switch's
|
|
4578
|
+
* own promise, so it settles when the thread is drawn rather than at once,
|
|
4579
|
+
* before the replay.
|
|
4580
|
+
*
|
|
4581
|
+
* A store that rejects the load leaves the element on the thread it was on:
|
|
4582
|
+
* the switch has already cleared the transcript, so it points back at the
|
|
4583
|
+
* old thread and draws it again, saying nothing, and then rejects with the
|
|
4584
|
+
* store's error. Left as it was, the element named a thread it was not
|
|
4585
|
+
* showing and never reported it.
|
|
4586
|
+
*/
|
|
4587
|
+
switchThread(threadId) {
|
|
4417
4588
|
if (threadId === this.#threadId) {
|
|
4418
|
-
return;
|
|
4589
|
+
return this.#switching?.threadId === threadId ? this.#switching.done : Promise.resolve();
|
|
4419
4590
|
}
|
|
4591
|
+
const previous = this.#threadId;
|
|
4592
|
+
const done = this.#switchTo(threadId, previous).finally(() => {
|
|
4593
|
+
if (this.#switching === entry) {
|
|
4594
|
+
this.#switching = null;
|
|
4595
|
+
}
|
|
4596
|
+
});
|
|
4597
|
+
const entry = { threadId, done };
|
|
4598
|
+
this.#switching = entry;
|
|
4599
|
+
return done;
|
|
4600
|
+
}
|
|
4601
|
+
async #switchTo(threadId, previous) {
|
|
4420
4602
|
this.#host.cancelRun();
|
|
4421
4603
|
this.#host.resetState();
|
|
4422
4604
|
this.#host.conversationStore().setActiveThread(threadId);
|
|
4423
4605
|
this.#threadId = threadId;
|
|
4424
4606
|
this.#host.setRunning(false);
|
|
4425
|
-
|
|
4607
|
+
try {
|
|
4608
|
+
await this.rehydrate("switch");
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
if (this.#threadId === threadId) {
|
|
4611
|
+
this.#threadId = previous;
|
|
4612
|
+
this.#host.conversationStore().setActiveThread(previous);
|
|
4613
|
+
await this.rehydrate(null).catch(() => void 0);
|
|
4614
|
+
}
|
|
4615
|
+
throw error;
|
|
4616
|
+
}
|
|
4426
4617
|
}
|
|
4427
4618
|
/** Rename a thread, then reload the list that shows its title. */
|
|
4428
4619
|
renameThread(threadId, title) {
|
|
@@ -4440,6 +4631,7 @@ var ConversationHistory = class {
|
|
|
4440
4631
|
this.#host.resetState();
|
|
4441
4632
|
this.adoptActiveThread();
|
|
4442
4633
|
this.#host.setRunning(false);
|
|
4634
|
+
this.#host.threadChanged(this.#threadId, "delete");
|
|
4443
4635
|
}
|
|
4444
4636
|
void this.refreshDrawer();
|
|
4445
4637
|
}
|
|
@@ -4616,8 +4808,12 @@ var ConversationHistory = class {
|
|
|
4616
4808
|
* for the run it would have resumed -- unless what replaced it is a restore
|
|
4617
4809
|
* of the same conversation, still current, which answers for that run
|
|
4618
4810
|
* instead; see `#forgetStopped`.
|
|
4811
|
+
*
|
|
4812
|
+
* `reason` is what the thread event this restore ends with reports as why
|
|
4813
|
+
* the conversation changed. A restore that is stopped reports nothing, and
|
|
4814
|
+
* neither does one given `null`, which draws the thread already reported.
|
|
4619
4815
|
*/
|
|
4620
|
-
async rehydrate() {
|
|
4816
|
+
async rehydrate(reason) {
|
|
4621
4817
|
this.#generation += 1;
|
|
4622
4818
|
const generation = this.#generation;
|
|
4623
4819
|
const threadId = this.#threadId;
|
|
@@ -4667,11 +4863,19 @@ var ConversationHistory = class {
|
|
|
4667
4863
|
}
|
|
4668
4864
|
}
|
|
4669
4865
|
}
|
|
4866
|
+
if (checkpoint === null) {
|
|
4867
|
+
this.#noticeIfRunUnfinished(messages);
|
|
4868
|
+
}
|
|
4869
|
+
if (reason !== null) {
|
|
4870
|
+
this.#host.threadChanged(threadId, reason);
|
|
4871
|
+
}
|
|
4670
4872
|
if (checkpoint !== null) {
|
|
4873
|
+
if (!this.#restoring(generation)) {
|
|
4874
|
+
this.#forgetStopped(store, threadId, checkpoint);
|
|
4875
|
+
return;
|
|
4876
|
+
}
|
|
4671
4877
|
await this.#resumeFrom(checkpoint, generation);
|
|
4672
|
-
return;
|
|
4673
4878
|
}
|
|
4674
|
-
this.#noticeIfRunUnfinished(messages);
|
|
4675
4879
|
} finally {
|
|
4676
4880
|
hold.release();
|
|
4677
4881
|
if (this.#loading === waiting) {
|
|
@@ -5304,7 +5508,7 @@ function clampPanel(host, viewport, margin = EDGE_MARGIN) {
|
|
|
5304
5508
|
|
|
5305
5509
|
// src/ui/placement/launcher_drag.ts
|
|
5306
5510
|
var DRAG_THRESHOLD = 4;
|
|
5307
|
-
var
|
|
5511
|
+
var STEP2 = 16;
|
|
5308
5512
|
var COARSE_STEP = 64;
|
|
5309
5513
|
function enableLauncherDrag(launcher, options) {
|
|
5310
5514
|
let suppressClick = false;
|
|
@@ -5386,7 +5590,7 @@ function enableLauncherDrag(launcher, options) {
|
|
|
5386
5590
|
if (!options.enabled()) {
|
|
5387
5591
|
return;
|
|
5388
5592
|
}
|
|
5389
|
-
const step = event.shiftKey ? COARSE_STEP :
|
|
5593
|
+
const step = event.shiftKey ? COARSE_STEP : STEP2;
|
|
5390
5594
|
const rect = options.rect();
|
|
5391
5595
|
let moved = null;
|
|
5392
5596
|
if (event.key === "ArrowLeft") {
|
|
@@ -5513,7 +5717,7 @@ function onControl(event, handle) {
|
|
|
5513
5717
|
// src/ui/placement/resize_handle.ts
|
|
5514
5718
|
var MIN_WIDTH2 = 280;
|
|
5515
5719
|
var MIN_HEIGHT2 = 240;
|
|
5516
|
-
var
|
|
5720
|
+
var STEP3 = 16;
|
|
5517
5721
|
var COARSE_STEP2 = 64;
|
|
5518
5722
|
function createResizeHandle(grip, options) {
|
|
5519
5723
|
const handle = document.createElement("div");
|
|
@@ -5561,7 +5765,7 @@ function createResizeHandle(grip, options) {
|
|
|
5561
5765
|
if (axis === "none" || !movable(grip, axis)) {
|
|
5562
5766
|
return;
|
|
5563
5767
|
}
|
|
5564
|
-
const step = event.shiftKey ? COARSE_STEP2 :
|
|
5768
|
+
const step = event.shiftKey ? COARSE_STEP2 : STEP3;
|
|
5565
5769
|
const rect = options.rect();
|
|
5566
5770
|
const delta = ARROWS[event.key];
|
|
5567
5771
|
if (delta === void 0) {
|
|
@@ -5646,12 +5850,23 @@ var UNKNOWN = {
|
|
|
5646
5850
|
resizeAxis: "both",
|
|
5647
5851
|
startsCollapsed: false
|
|
5648
5852
|
};
|
|
5649
|
-
function surfaceFor(placement) {
|
|
5650
|
-
|
|
5853
|
+
function surfaceFor(placement, collapseTo = null) {
|
|
5854
|
+
const surface = SURFACES.get(placement) ?? UNKNOWN;
|
|
5855
|
+
if (collapseTo === "bar" && surface.collapseTo === "launcher") {
|
|
5856
|
+
return { ...surface, collapseTo: "bar", launcherDraggable: false };
|
|
5857
|
+
}
|
|
5858
|
+
if (collapseTo === "none" && surface.collapseTo !== null) {
|
|
5859
|
+
return { ...surface, collapseTo: "none", launcherDraggable: false };
|
|
5860
|
+
}
|
|
5861
|
+
return surface;
|
|
5651
5862
|
}
|
|
5652
5863
|
|
|
5653
5864
|
// src/ui/placement/panel_placement.ts
|
|
5654
5865
|
var SIZE_KEY = "ag-ui-chat:size";
|
|
5866
|
+
var DRAG_WIDTH = "--_drag-width";
|
|
5867
|
+
var DRAG_HEIGHT = "--_drag-height";
|
|
5868
|
+
var DOCK_BOTTOM = "--_dock-bottom";
|
|
5869
|
+
var DOCK_RIGHT = "--_dock-right";
|
|
5655
5870
|
var LAUNCHER_KEY = "ag-ui-chat:launcher";
|
|
5656
5871
|
function asPoint(value) {
|
|
5657
5872
|
if (typeof value !== "object" || value === null) {
|
|
@@ -5735,6 +5950,17 @@ var PanelPlacement = class {
|
|
|
5735
5950
|
restoreSize() {
|
|
5736
5951
|
this.#applySize(this.#readSize());
|
|
5737
5952
|
}
|
|
5953
|
+
/**
|
|
5954
|
+
* Drop the dragged size and its stored copy, so the placement's own size and
|
|
5955
|
+
* the host's tokens apply again. For a host entering a sized state of its
|
|
5956
|
+
* own, which a user's drag would otherwise outrank.
|
|
5957
|
+
*/
|
|
5958
|
+
resetSize() {
|
|
5959
|
+
this.#host.element.style.removeProperty(DRAG_WIDTH);
|
|
5960
|
+
this.#host.element.style.removeProperty(DRAG_HEIGHT);
|
|
5961
|
+
this.#host.clearPreference(SIZE_KEY);
|
|
5962
|
+
this.syncResizeAnchor();
|
|
5963
|
+
}
|
|
5738
5964
|
/**
|
|
5739
5965
|
* A zero-sized box carrying the host's viewport insets as padding, so they
|
|
5740
5966
|
* can be read back as used pixel lengths. See the `.viewport-probe` rule for
|
|
@@ -5816,6 +6042,7 @@ var PanelPlacement = class {
|
|
|
5816
6042
|
const fullBleed = coversViewport(box, viewport);
|
|
5817
6043
|
return {
|
|
5818
6044
|
placement: this.#host.element.getAttribute("placement"),
|
|
6045
|
+
collapseTo: this.#surface().collapseTo,
|
|
5819
6046
|
collapsed: this.#host.collapsed(),
|
|
5820
6047
|
collapsible: this.#host.collapsible(),
|
|
5821
6048
|
movable: this.#launcherDraggable() && !fullBleed,
|
|
@@ -5910,7 +6137,7 @@ var PanelPlacement = class {
|
|
|
5910
6137
|
* per interaction, because `placement` is a live attribute.
|
|
5911
6138
|
*/
|
|
5912
6139
|
#resizeAxis() {
|
|
5913
|
-
return
|
|
6140
|
+
return this.#surface().resizeAxis;
|
|
5914
6141
|
}
|
|
5915
6142
|
/**
|
|
5916
6143
|
* Which edges the layout is holding still, by measuring rather than guessing:
|
|
@@ -5923,12 +6150,12 @@ var PanelPlacement = class {
|
|
|
5923
6150
|
*/
|
|
5924
6151
|
#measureAnchor() {
|
|
5925
6152
|
const before = this.#host.element.getBoundingClientRect();
|
|
5926
|
-
const width = this.#host.element.style.getPropertyValue(
|
|
5927
|
-
const height = this.#host.element.style.getPropertyValue(
|
|
6153
|
+
const width = this.#host.element.style.getPropertyValue(DRAG_WIDTH);
|
|
6154
|
+
const height = this.#host.element.style.getPropertyValue(DRAG_HEIGHT);
|
|
5928
6155
|
const shrunk = this.#probeAnchor(before, -1);
|
|
5929
6156
|
const grown = shrunk.x === null || shrunk.y === null ? this.#probeAnchor(before, 1) : shrunk;
|
|
5930
|
-
this.#restoreProperty(
|
|
5931
|
-
this.#restoreProperty(
|
|
6157
|
+
this.#restoreProperty(DRAG_WIDTH, width);
|
|
6158
|
+
this.#restoreProperty(DRAG_HEIGHT, height);
|
|
5932
6159
|
return {
|
|
5933
6160
|
// Neither direction moved it: the axis cannot be resized at all, so the
|
|
5934
6161
|
// floating default is the best answer available and is the one the
|
|
@@ -5974,7 +6201,7 @@ var PanelPlacement = class {
|
|
|
5974
6201
|
/**
|
|
5975
6202
|
* Write a dragged size onto the host, on the axes this placement leaves free.
|
|
5976
6203
|
*
|
|
5977
|
-
* Writing
|
|
6204
|
+
* Writing a custom property rather than inline `width` / `height` does not
|
|
5978
6205
|
* by itself leave placement in charge: an inline custom property still
|
|
5979
6206
|
* outranks a `:host([placement=…])` rule setting the same property, so a
|
|
5980
6207
|
* height dragged while floating would cap a docked sidebar asking for
|
|
@@ -5988,10 +6215,10 @@ var PanelPlacement = class {
|
|
|
5988
6215
|
return;
|
|
5989
6216
|
}
|
|
5990
6217
|
if (size.width !== void 0) {
|
|
5991
|
-
this.#host.element.style.setProperty(
|
|
6218
|
+
this.#host.element.style.setProperty(DRAG_WIDTH, `${size.width}px`);
|
|
5992
6219
|
}
|
|
5993
6220
|
if (size.height !== void 0 && axis === "both") {
|
|
5994
|
-
this.#host.element.style.setProperty(
|
|
6221
|
+
this.#host.element.style.setProperty(DRAG_HEIGHT, `${size.height}px`);
|
|
5995
6222
|
}
|
|
5996
6223
|
}
|
|
5997
6224
|
/**
|
|
@@ -6004,11 +6231,51 @@ var PanelPlacement = class {
|
|
|
6004
6231
|
releaseOwnedAxes() {
|
|
6005
6232
|
const axis = this.#resizeAxis();
|
|
6006
6233
|
if (axis !== "both") {
|
|
6007
|
-
this.#host.element.style.removeProperty(
|
|
6234
|
+
this.#host.element.style.removeProperty(DRAG_HEIGHT);
|
|
6008
6235
|
}
|
|
6009
6236
|
if (axis === "none") {
|
|
6010
|
-
this.#host.element.style.removeProperty(
|
|
6237
|
+
this.#host.element.style.removeProperty(DRAG_WIDTH);
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
/** The surface the live placement and collapse-to attributes describe. */
|
|
6241
|
+
#surface() {
|
|
6242
|
+
const element = this.#host.element;
|
|
6243
|
+
return surfaceFor(element.getAttribute("placement"), element.getAttribute("collapse-to"));
|
|
6244
|
+
}
|
|
6245
|
+
/** Whether the panel rises from the bottom bar or the host's anchor. */
|
|
6246
|
+
#docked() {
|
|
6247
|
+
const target = this.#surface().collapseTo;
|
|
6248
|
+
return target === "bar" || target === "none";
|
|
6249
|
+
}
|
|
6250
|
+
/**
|
|
6251
|
+
* Write where a docked panel stands, and return the bar's height.
|
|
6252
|
+
*
|
|
6253
|
+
* Under the bar it is the strip's border box, which is 0 whenever the strip
|
|
6254
|
+
* is not drawn (a phone with the panel open, where the panel is the screen).
|
|
6255
|
+
* Under a host anchor it is the anchor's top and right, measured against the
|
|
6256
|
+
* layout viewport a fixed element is placed in; an anchor of null leaves the
|
|
6257
|
+
* panel in its placement's own corner. Every other surface carries neither
|
|
6258
|
+
* property, so nothing stale is left behind by a live switch.
|
|
6259
|
+
*/
|
|
6260
|
+
syncDock(bar, anchor) {
|
|
6261
|
+
const element = this.#host.element;
|
|
6262
|
+
const target = this.#surface().collapseTo;
|
|
6263
|
+
const height = target === "bar" ? bar.offsetHeight : 0;
|
|
6264
|
+
if (target === "bar") {
|
|
6265
|
+
element.style.setProperty(DOCK_BOTTOM, `${height}px`);
|
|
6266
|
+
} else if (target === "none" && anchor !== null) {
|
|
6267
|
+
const rect = anchor.getBoundingClientRect();
|
|
6268
|
+
const screen = this.#screen();
|
|
6269
|
+
element.style.setProperty(DOCK_BOTTOM, `${Math.max(0, screen.height - rect.top)}px`);
|
|
6270
|
+
element.style.setProperty(DOCK_RIGHT, `${Math.max(0, screen.width - rect.right)}px`);
|
|
6271
|
+
} else {
|
|
6272
|
+
element.style.removeProperty(DOCK_BOTTOM);
|
|
6011
6273
|
}
|
|
6274
|
+
if (target !== "none" || anchor === null) {
|
|
6275
|
+
element.style.removeProperty(DOCK_RIGHT);
|
|
6276
|
+
}
|
|
6277
|
+
element.toggleAttribute("data-dock-anchored", target === "none" && anchor !== null);
|
|
6278
|
+
return height;
|
|
6012
6279
|
}
|
|
6013
6280
|
/**
|
|
6014
6281
|
* Whether this placement lets the launcher be dragged, read per interaction
|
|
@@ -6016,7 +6283,7 @@ var PanelPlacement = class {
|
|
|
6016
6283
|
* a host out without giving up the launcher itself.
|
|
6017
6284
|
*/
|
|
6018
6285
|
#launcherDraggable() {
|
|
6019
|
-
return this.#host.element.getAttribute("data-launcher-drag") !== "false" &&
|
|
6286
|
+
return this.#host.element.getAttribute("data-launcher-drag") !== "false" && this.#surface().launcherDraggable;
|
|
6020
6287
|
}
|
|
6021
6288
|
/**
|
|
6022
6289
|
* The viewport the launcher and the panel both have to fit inside.
|
|
@@ -6374,6 +6641,17 @@ var PanelPlacement = class {
|
|
|
6374
6641
|
*/
|
|
6375
6642
|
#applyResize(grip, box) {
|
|
6376
6643
|
box = this.#withinViewport(box);
|
|
6644
|
+
if (this.#docked()) {
|
|
6645
|
+
const now = this.#host.element.getBoundingClientRect();
|
|
6646
|
+
box = {
|
|
6647
|
+
left: grip.x === this.#anchor.x ? now.left : box.left,
|
|
6648
|
+
right: grip.x === this.#anchor.x ? now.right : box.right,
|
|
6649
|
+
top: grip.y === this.#anchor.y ? now.top : box.top,
|
|
6650
|
+
bottom: grip.y === this.#anchor.y ? now.bottom : box.bottom
|
|
6651
|
+
};
|
|
6652
|
+
this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
|
|
6653
|
+
return box;
|
|
6654
|
+
}
|
|
6377
6655
|
this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
|
|
6378
6656
|
if (grip.x !== this.#anchor.x && grip.y !== this.#anchor.y) {
|
|
6379
6657
|
return box;
|
|
@@ -6910,9 +7188,7 @@ var ToolCallCard = class {
|
|
|
6910
7188
|
this.#formatPayload = options.formatPayload ?? null;
|
|
6911
7189
|
this.element = document.createElement("div");
|
|
6912
7190
|
this.element.className = "tool-call";
|
|
6913
|
-
this.element.setAttribute("part", "tool-card");
|
|
6914
7191
|
this.element.setAttribute("data-tool-name", name);
|
|
6915
|
-
this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
|
|
6916
7192
|
this.element.setAttribute("data-expanded", "false");
|
|
6917
7193
|
const head = document.createElement("div");
|
|
6918
7194
|
head.className = "tool-call-head";
|
|
@@ -6927,8 +7203,7 @@ var ToolCallCard = class {
|
|
|
6927
7203
|
label.textContent = summary ?? name;
|
|
6928
7204
|
this.#status = document.createElement("span");
|
|
6929
7205
|
this.#status.className = "tool-call-status";
|
|
6930
|
-
this.#
|
|
6931
|
-
this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
|
|
7206
|
+
this.#setStatus(TOOL_CALL_STATUS.PENDING);
|
|
6932
7207
|
this.#decision = document.createElement("span");
|
|
6933
7208
|
this.#decision.className = "tool-call-decision";
|
|
6934
7209
|
this.#decision.setAttribute("part", "tool-card-decision");
|
|
@@ -6976,8 +7251,7 @@ var ToolCallCard = class {
|
|
|
6976
7251
|
if (this.#settled) {
|
|
6977
7252
|
return;
|
|
6978
7253
|
}
|
|
6979
|
-
this
|
|
6980
|
-
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
7254
|
+
this.#setStatus(status);
|
|
6981
7255
|
}
|
|
6982
7256
|
/**
|
|
6983
7257
|
* Say whether a prompt is open on this call, a question it asked or a
|
|
@@ -7012,8 +7286,7 @@ var ToolCallCard = class {
|
|
|
7012
7286
|
return;
|
|
7013
7287
|
}
|
|
7014
7288
|
this.#settled = true;
|
|
7015
|
-
this
|
|
7016
|
-
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
7289
|
+
this.#setStatus(status);
|
|
7017
7290
|
this.#resultLabel.textContent = resultLabels(this.#strings)[status];
|
|
7018
7291
|
this.#renderPayload(
|
|
7019
7292
|
this.#resultBody,
|
|
@@ -7022,6 +7295,20 @@ var ToolCallCard = class {
|
|
|
7022
7295
|
);
|
|
7023
7296
|
this.#resultSection.hidden = false;
|
|
7024
7297
|
}
|
|
7298
|
+
/**
|
|
7299
|
+
* Write the status everywhere it is read from, in one place so they cannot
|
|
7300
|
+
* disagree: `data-status` for the shadow CSS, the pill's text, and the part
|
|
7301
|
+
* lists. `::part()` takes no attribute selector, so the card and its pill also
|
|
7302
|
+
* name the status as a part (`tool-card-error`, `tool-card-status-error`), the
|
|
7303
|
+
* modifier convention a run notice uses, and a host styles failures alone.
|
|
7304
|
+
* Setting the whole list replaces the previous modifier.
|
|
7305
|
+
*/
|
|
7306
|
+
#setStatus(status) {
|
|
7307
|
+
this.element.setAttribute("data-status", status);
|
|
7308
|
+
this.element.setAttribute("part", `tool-card tool-card-${status}`);
|
|
7309
|
+
this.#status.setAttribute("part", `tool-card-status tool-card-status-${status}`);
|
|
7310
|
+
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
7311
|
+
}
|
|
7025
7312
|
/**
|
|
7026
7313
|
* Fill one body region, offering it to the host formatter first.
|
|
7027
7314
|
*
|
|
@@ -8332,12 +8619,18 @@ var COLLAPSE_STYLES = `
|
|
|
8332
8619
|
:host([collapsed][placement="embedded"]) .messages-wrap,
|
|
8333
8620
|
:host([collapsed][placement="embedded"]) .messages,
|
|
8334
8621
|
:host([collapsed][placement="embedded"]) .input-row,
|
|
8622
|
+
:host([collapsed][placement="embedded"]) slot[name="above-composer"],
|
|
8335
8623
|
:host([collapsed][placement="embedded"]) .skill-chips,
|
|
8336
8624
|
:host([collapsed][placement="embedded"]) .skill-palette,
|
|
8337
8625
|
:host([collapsed][placement="embedded"]) .skill-hint {
|
|
8338
8626
|
display: none;
|
|
8339
8627
|
}
|
|
8340
8628
|
|
|
8629
|
+
/* The above-composer slot is in the list above because it is the host's content
|
|
8630
|
+
against the composer: a bare slot has no box of its own, so hiding the input
|
|
8631
|
+
row alone left the slotted row showing under the header bar. Its sibling slot
|
|
8632
|
+
below the composer is a footer and is left to the host. */
|
|
8633
|
+
|
|
8341
8634
|
/* The page placement has no collapsed state, so it offers no control for one.
|
|
8342
8635
|
A dedicated route has no "away" to go to: shrinking it left a strip of
|
|
8343
8636
|
application chrome fixed over a route that no longer had an owner, and this
|
|
@@ -8365,6 +8658,334 @@ var COLLAPSE_STYLES = `
|
|
|
8365
8658
|
transform: none;
|
|
8366
8659
|
visibility: visible;
|
|
8367
8660
|
}
|
|
8661
|
+
|
|
8662
|
+
/* \u2500\u2500 Bottom bar (collapse-to="bar") \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8663
|
+
The round launcher drawn as a strip along the bottom edge: the title, the
|
|
8664
|
+
unread badge and a running mark, with a history button beside it. The
|
|
8665
|
+
wrapper draws no box anywhere else, so the launcher lays out against the
|
|
8666
|
+
host as it always has; under the bar it is fixed to the bottom of the
|
|
8667
|
+
viewport, outside the host's box, which sits where the panel opens.
|
|
8668
|
+
|
|
8669
|
+
Here rather than with the launcher because it restyles the launcher's
|
|
8670
|
+
collapsed and pressed states, which this module follows in the cascade.
|
|
8671
|
+
The strip stays drawn while the panel is open on a wide viewport, as the
|
|
8672
|
+
way to minimise it; the phone block hides it then. */
|
|
8673
|
+
.dock-bar {
|
|
8674
|
+
display: contents;
|
|
8675
|
+
}
|
|
8676
|
+
|
|
8677
|
+
.bar-history {
|
|
8678
|
+
display: none;
|
|
8679
|
+
}
|
|
8680
|
+
|
|
8681
|
+
:host([data-collapsed-to="bar"]) .dock-bar {
|
|
8682
|
+
position: fixed;
|
|
8683
|
+
inset: auto var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left);
|
|
8684
|
+
display: flex;
|
|
8685
|
+
align-items: center;
|
|
8686
|
+
justify-content: flex-end;
|
|
8687
|
+
gap: 4px;
|
|
8688
|
+
box-sizing: border-box;
|
|
8689
|
+
min-height: 40px;
|
|
8690
|
+
padding: 4px 8px;
|
|
8691
|
+
border-top: 1px solid var(--_border);
|
|
8692
|
+
background: var(--_bg);
|
|
8693
|
+
color: var(--_fg);
|
|
8694
|
+
pointer-events: auto;
|
|
8695
|
+
}
|
|
8696
|
+
|
|
8697
|
+
/* A left-hand chat keeps its side: its controls and its panel at the left end. */
|
|
8698
|
+
:host([data-collapsed-to="bar"][placement="bottom-left"]) .dock-bar {
|
|
8699
|
+
justify-content: flex-start;
|
|
8700
|
+
}
|
|
8701
|
+
|
|
8702
|
+
/* The launcher as the strip's title button. Its resting state hides it behind
|
|
8703
|
+
an open panel; the bar keeps it on screen, so every part of that state is
|
|
8704
|
+
restated, and the hover and press scales are cancelled at the weight of the
|
|
8705
|
+
rules that set them. The title truncates rather than taking a fixed width,
|
|
8706
|
+
leaving room beside it for whatever a later release adds to the row. */
|
|
8707
|
+
:host([data-collapsed-to="bar"]) .launcher {
|
|
8708
|
+
position: static;
|
|
8709
|
+
inset: auto;
|
|
8710
|
+
flex: 0 1 auto;
|
|
8711
|
+
justify-content: flex-start;
|
|
8712
|
+
gap: 8px;
|
|
8713
|
+
width: auto;
|
|
8714
|
+
min-width: 0;
|
|
8715
|
+
max-width: 100%;
|
|
8716
|
+
height: auto;
|
|
8717
|
+
min-height: 32px;
|
|
8718
|
+
padding: 4px 10px;
|
|
8719
|
+
border-radius: 8px;
|
|
8720
|
+
background: transparent;
|
|
8721
|
+
color: var(--_fg);
|
|
8722
|
+
box-shadow: none;
|
|
8723
|
+
touch-action: manipulation;
|
|
8724
|
+
opacity: 1;
|
|
8725
|
+
transform: none;
|
|
8726
|
+
visibility: visible;
|
|
8727
|
+
}
|
|
8728
|
+
|
|
8729
|
+
:host([data-collapsed-to="bar"]) .launcher:hover {
|
|
8730
|
+
background: var(--_wash);
|
|
8731
|
+
}
|
|
8732
|
+
|
|
8733
|
+
/* Bare at rest, like the history button beside it. The themes frame the
|
|
8734
|
+
round launcher with a border or a shadow at the weight of the rule above
|
|
8735
|
+
and later in the cascade, so the child combinator on the bar is there for
|
|
8736
|
+
weight, not scope: the launcher is always the bar's direct child. */
|
|
8737
|
+
:host([data-collapsed-to="bar"]) .dock-bar > .launcher {
|
|
8738
|
+
border: 0;
|
|
8739
|
+
box-shadow: none;
|
|
8740
|
+
}
|
|
8741
|
+
|
|
8742
|
+
:host([data-collapsed-to="bar"][collapsed]) .launcher:hover,
|
|
8743
|
+
:host([data-collapsed-to="bar"][collapsed]) .launcher:active {
|
|
8744
|
+
transform: none;
|
|
8745
|
+
}
|
|
8746
|
+
|
|
8747
|
+
:host([data-collapsed-to="bar"]) .launcher .icon-holder {
|
|
8748
|
+
flex: none;
|
|
8749
|
+
width: 18px;
|
|
8750
|
+
height: 18px;
|
|
8751
|
+
}
|
|
8752
|
+
|
|
8753
|
+
:host([data-collapsed-to="bar"]) .rail-label {
|
|
8754
|
+
display: block;
|
|
8755
|
+
min-width: 0;
|
|
8756
|
+
overflow: hidden;
|
|
8757
|
+
font-size: 0.9em;
|
|
8758
|
+
font-weight: 600;
|
|
8759
|
+
white-space: nowrap;
|
|
8760
|
+
text-overflow: ellipsis;
|
|
8761
|
+
}
|
|
8762
|
+
|
|
8763
|
+
:host([data-collapsed-to="bar"]) .launcher-badge {
|
|
8764
|
+
position: static;
|
|
8765
|
+
flex: none;
|
|
8766
|
+
box-shadow: none;
|
|
8767
|
+
}
|
|
8768
|
+
|
|
8769
|
+
/* The running mark: the element already reflects a run as data-running, so
|
|
8770
|
+
the bar shows one without a state of its own. */
|
|
8771
|
+
:host([data-collapsed-to="bar"][data-running]) .launcher::after {
|
|
8772
|
+
content: "";
|
|
8773
|
+
flex: none;
|
|
8774
|
+
width: 8px;
|
|
8775
|
+
height: 8px;
|
|
8776
|
+
border-radius: 50%;
|
|
8777
|
+
background: var(--_accent);
|
|
8778
|
+
animation: ag-ui-dock-running 1.2s ease-in-out infinite;
|
|
8779
|
+
}
|
|
8780
|
+
|
|
8781
|
+
@keyframes ag-ui-dock-running {
|
|
8782
|
+
50% {
|
|
8783
|
+
opacity: 0.35;
|
|
8784
|
+
}
|
|
8785
|
+
}
|
|
8786
|
+
|
|
8787
|
+
@media (prefers-reduced-motion: reduce) {
|
|
8788
|
+
:host([data-collapsed-to="bar"][data-running]) .launcher::after {
|
|
8789
|
+
animation: none;
|
|
8790
|
+
}
|
|
8791
|
+
}
|
|
8792
|
+
|
|
8793
|
+
:host([data-collapsed-to="bar"]) .bar-history {
|
|
8794
|
+
display: inline-flex;
|
|
8795
|
+
flex: none;
|
|
8796
|
+
align-items: center;
|
|
8797
|
+
justify-content: center;
|
|
8798
|
+
width: 32px;
|
|
8799
|
+
height: 32px;
|
|
8800
|
+
padding: 0;
|
|
8801
|
+
border: 0;
|
|
8802
|
+
border-radius: 8px;
|
|
8803
|
+
background: transparent;
|
|
8804
|
+
color: var(--_muted);
|
|
8805
|
+
font: inherit;
|
|
8806
|
+
cursor: pointer;
|
|
8807
|
+
}
|
|
8808
|
+
|
|
8809
|
+
:host([data-collapsed-to="bar"]) .bar-history:hover {
|
|
8810
|
+
background: var(--_wash);
|
|
8811
|
+
color: var(--_fg);
|
|
8812
|
+
}
|
|
8813
|
+
|
|
8814
|
+
/* Both declare a display of their own, so each needs its own hidden rule. */
|
|
8815
|
+
:host([data-collapsed-to]) .dock-bar[hidden],
|
|
8816
|
+
:host([data-collapsed-to]) .bar-history[hidden] {
|
|
8817
|
+
display: none;
|
|
8818
|
+
}
|
|
8819
|
+
|
|
8820
|
+
/* History from the bar: the panel's own drawer, moved beside the bar while
|
|
8821
|
+
the panel is away, drawn as a popover standing on the bar's end. There is
|
|
8822
|
+
no backdrop; a press outside the bar closes it, as Escape does.
|
|
8823
|
+
|
|
8824
|
+
Its outer edge keeps the panel's gutter, so the popover and the panel line
|
|
8825
|
+
up on the side they share. The bar is already inset by the host's reserved
|
|
8826
|
+
edges, so the gutter alone is the whole offset from it, and the width cap
|
|
8827
|
+
leaves the gutter free on both sides. A phone keeps a narrow margin
|
|
8828
|
+
instead, in the block below: the panel there is the whole screen, so there
|
|
8829
|
+
is no edge to line up with. */
|
|
8830
|
+
:host([data-collapsed-to="bar"]) .dock-bar > .drawer {
|
|
8831
|
+
inset: auto var(--_edge-gutter) calc(100% + 8px) auto;
|
|
8832
|
+
width: min(320px, calc(var(--_viewport-width) - 2 * var(--_edge-gutter)));
|
|
8833
|
+
height: min(440px, calc(var(--_viewport-height) - var(--_dock-bottom, 0px) - 16px));
|
|
8834
|
+
border-radius: var(--_panel-radius);
|
|
8835
|
+
box-shadow: var(--_shadow);
|
|
8836
|
+
overflow: hidden;
|
|
8837
|
+
}
|
|
8838
|
+
|
|
8839
|
+
:host([data-collapsed-to="bar"][placement="bottom-left"]) .dock-bar > .drawer {
|
|
8840
|
+
inset: auto auto calc(100% + 8px) var(--_edge-gutter);
|
|
8841
|
+
}
|
|
8842
|
+
|
|
8843
|
+
@media (max-width: 600px) {
|
|
8844
|
+
:host([data-collapsed-to="bar"]:not([data-small-viewport="off"])) .dock-bar > .drawer {
|
|
8845
|
+
inset: auto 8px calc(100% + 8px) auto;
|
|
8846
|
+
width: min(320px, calc(100vw - 16px));
|
|
8847
|
+
}
|
|
8848
|
+
|
|
8849
|
+
:host([data-collapsed-to="bar"][placement="bottom-left"]:not([data-small-viewport="off"])) .dock-bar > .drawer {
|
|
8850
|
+
inset: auto auto calc(100% + 8px) 8px;
|
|
8851
|
+
}
|
|
8852
|
+
}
|
|
8853
|
+
|
|
8854
|
+
:host([data-collapsed-to="bar"]) .dock-bar > .drawer .drawer-backdrop {
|
|
8855
|
+
display: none;
|
|
8856
|
+
}
|
|
8857
|
+
|
|
8858
|
+
/* The popover closes in one step. The in-panel drawer slides its panel out
|
|
8859
|
+
and holds the overlay's visibility until the slide ends; here the overlay
|
|
8860
|
+
is the popover's own box, rounded and clipped, so the slide moved the list
|
|
8861
|
+
inside a box that stayed put (read as a shrink) and the box went only once
|
|
8862
|
+
the slide had finished. This outweighs the drawer's own hidden rule, so the
|
|
8863
|
+
box hides at once, and whatever the list inside does is out of sight. The
|
|
8864
|
+
way in needs no rule: the drawer is moved into the bar as it opens, and a
|
|
8865
|
+
moved element starts no transition. Nothing is left for reduced motion. */
|
|
8866
|
+
:host([data-collapsed-to="bar"]) .dock-bar > .drawer {
|
|
8867
|
+
transition: none;
|
|
8868
|
+
}
|
|
8869
|
+
|
|
8870
|
+
/* Border-box, so the list's own border sits inside the popover's clipped box
|
|
8871
|
+
rather than 2px past its bottom edge. */
|
|
8872
|
+
:host([data-collapsed-to="bar"]) .dock-bar > .drawer .drawer-panel {
|
|
8873
|
+
box-sizing: border-box;
|
|
8874
|
+
width: 100%;
|
|
8875
|
+
border: 1px solid var(--_border);
|
|
8876
|
+
border-radius: inherit;
|
|
8877
|
+
box-shadow: none;
|
|
8878
|
+
}
|
|
8879
|
+
|
|
8880
|
+
/* \u2500\u2500 The host's own bar (collapse-to="none") \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8881
|
+
Collapsed is hidden: no launcher, no rail, no header bar. The host's control
|
|
8882
|
+
calls the public API, and the panel rises from its anchor (the dock module)
|
|
8883
|
+
or from the placement's corner. Each placement's own collapsed rule is
|
|
8884
|
+
overridden at its weight or above, later in the cascade. */
|
|
8885
|
+
:host([data-collapsed-to="none"][collapsed]) {
|
|
8886
|
+
pointer-events: none;
|
|
8887
|
+
}
|
|
8888
|
+
|
|
8889
|
+
:host([data-collapsed-to="none"][collapsed]) .launcher {
|
|
8890
|
+
display: none;
|
|
8891
|
+
}
|
|
8892
|
+
|
|
8893
|
+
:host([data-collapsed-to="none"][collapsed][placement="sidebar"]) {
|
|
8894
|
+
width: 0;
|
|
8895
|
+
}
|
|
8896
|
+
|
|
8897
|
+
:host([data-collapsed-to="none"][collapsed][placement="embedded"]) {
|
|
8898
|
+
display: none;
|
|
8899
|
+
}
|
|
8900
|
+
`;
|
|
8901
|
+
|
|
8902
|
+
// src/ui/placement/dock_styles.ts
|
|
8903
|
+
var DOCK_STYLES = `
|
|
8904
|
+
/* \u2500\u2500 Dock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8905
|
+
A docked panel rises from the bar along the bottom edge, or from the host's
|
|
8906
|
+
own control, instead of resting in a corner of its own. The element stamps
|
|
8907
|
+
what it collapses to as data-collapsed-to, and writes where the dock is as
|
|
8908
|
+
two private properties: --_dock-bottom, the bar's height or the distance
|
|
8909
|
+
from the layout viewport's bottom to the anchor's top, and --_dock-right,
|
|
8910
|
+
the anchor's distance from the right edge. Both are read with a 0px
|
|
8911
|
+
fallback, so a panel measured before the bar has laid out stands where a
|
|
8912
|
+
bar of no height would put it.
|
|
8913
|
+
|
|
8914
|
+
The cap follows the token block's rule: what the host left free less the
|
|
8915
|
+
one gutter the anchored edge spends, so a panel grown to the cap runs from
|
|
8916
|
+
just above the bar to the far edge. A host therefore needs no inset to keep
|
|
8917
|
+
the panel off its bar; --ag-ui-inset still wins when set.
|
|
8918
|
+
|
|
8919
|
+
These blocks restate tokens and nothing else, and sit ahead of the phone
|
|
8920
|
+
block at no more weight than it has, so on a phone the panel is the screen
|
|
8921
|
+
as every other placement is there. The bar measures 0 while it is not drawn
|
|
8922
|
+
(a phone with the panel open), which is what lets the side and full shapes
|
|
8923
|
+
below fall back to the whole height without a phone rule of their own.
|
|
8924
|
+
|
|
8925
|
+
Each placement keeps its own side and shape; only the bottom edge moves. */
|
|
8926
|
+
:host([data-collapsed-to="bar"]) {
|
|
8927
|
+
--_inset: var(
|
|
8928
|
+
--ag-ui-inset,
|
|
8929
|
+
auto calc(var(--_edge-gutter) + var(--_viewport-inset-right))
|
|
8930
|
+
calc(
|
|
8931
|
+
var(--_edge-gutter) + var(--_viewport-inset-bottom) +
|
|
8932
|
+
max(var(--_dock-bottom, 0px), var(--_keyboard-inset))
|
|
8933
|
+
)
|
|
8934
|
+
auto
|
|
8935
|
+
);
|
|
8936
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px) - var(--_edge-gutter)));
|
|
8937
|
+
}
|
|
8938
|
+
|
|
8939
|
+
:host([data-collapsed-to="bar"][placement="bottom-left"]) {
|
|
8940
|
+
--_inset: var(
|
|
8941
|
+
--ag-ui-inset,
|
|
8942
|
+
auto auto
|
|
8943
|
+
calc(
|
|
8944
|
+
var(--_edge-gutter) + var(--_viewport-inset-bottom) +
|
|
8945
|
+
max(var(--_dock-bottom, 0px), var(--_keyboard-inset))
|
|
8946
|
+
)
|
|
8947
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-left))
|
|
8948
|
+
);
|
|
8949
|
+
}
|
|
8950
|
+
|
|
8951
|
+
/* The column and the full-screen panel stop at the bar rather than covering
|
|
8952
|
+
it, so the bar stays where the user minimises from. */
|
|
8953
|
+
:host([data-collapsed-to="bar"][placement="side"]) {
|
|
8954
|
+
--_inset: var(
|
|
8955
|
+
--ag-ui-inset,
|
|
8956
|
+
calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right)
|
|
8957
|
+
calc(var(--_viewport-inset-bottom) + var(--_dock-bottom, 0px)) auto
|
|
8958
|
+
);
|
|
8959
|
+
--_height: var(--ag-ui-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
8960
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
8961
|
+
}
|
|
8962
|
+
|
|
8963
|
+
:host([data-collapsed-to="bar"][placement="full"]) {
|
|
8964
|
+
--_inset: var(
|
|
8965
|
+
--ag-ui-inset,
|
|
8966
|
+
calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right)
|
|
8967
|
+
calc(var(--_viewport-inset-bottom) + var(--_dock-bottom, 0px)) var(--_viewport-inset-left)
|
|
8968
|
+
);
|
|
8969
|
+
--_height: var(--ag-ui-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
8970
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
8971
|
+
}
|
|
8972
|
+
|
|
8973
|
+
/* The host's own bar: the panel stands the anchor gap (8px unless the host
|
|
8974
|
+
says otherwise) above the anchor and lines up with its right edge. Only
|
|
8975
|
+
once the element found the anchor; until then, and when it never does, the
|
|
8976
|
+
placement's own corner stands.
|
|
8977
|
+
|
|
8978
|
+
The cap spends the larger of the gap and the gutter. The gutter alone is
|
|
8979
|
+
what this always spent, so the default is unchanged; but a host that opens
|
|
8980
|
+
the gap past the gutter to clear a tall toolbar would otherwise push a
|
|
8981
|
+
panel grown to the cap off the top of the window by the difference. */
|
|
8982
|
+
:host([data-collapsed-to="none"][data-dock-anchored]) {
|
|
8983
|
+
--_inset: var(--ag-ui-inset, auto var(--_dock-right, 0px) calc(var(--_dock-bottom, 0px) + var(--_anchor-gap)) auto);
|
|
8984
|
+
--_max-height: var(
|
|
8985
|
+
--ag-ui-max-height,
|
|
8986
|
+
calc(var(--_viewport-height) - var(--_dock-bottom, 0px) - max(var(--_edge-gutter), var(--_anchor-gap)))
|
|
8987
|
+
);
|
|
8988
|
+
}
|
|
8368
8989
|
`;
|
|
8369
8990
|
|
|
8370
8991
|
// src/ui/placement/embedded_styles.ts
|
|
@@ -8373,8 +8994,8 @@ var EMBEDDED_STYLES = `
|
|
|
8373
8994
|
the widget lives in the host's own layout (fixes overlay/z-index clashes). */
|
|
8374
8995
|
:host([placement="embedded"]) {
|
|
8375
8996
|
--_position: var(--ag-ui-position, static);
|
|
8376
|
-
--_width: var(--ag-ui-width, 100%);
|
|
8377
|
-
--_height: var(--ag-ui-height, 100%);
|
|
8997
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 100%));
|
|
8998
|
+
--_height: var(--_drag-height, var(--ag-ui-height, 100%));
|
|
8378
8999
|
--_max-width: var(--ag-ui-max-width, 100%);
|
|
8379
9000
|
--_max-height: var(--ag-ui-max-height, 100%);
|
|
8380
9001
|
--_shadow: var(--ag-ui-shadow, none);
|
|
@@ -8632,7 +9253,7 @@ var PRESET_STYLES = `
|
|
|
8632
9253
|
|
|
8633
9254
|
:host([placement="side"]) {
|
|
8634
9255
|
--_inset: var(--ag-ui-inset, calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
8635
|
-
--_width: var(--ag-ui-width, 420px);
|
|
9256
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 420px));
|
|
8636
9257
|
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
8637
9258
|
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
8638
9259
|
--_panel-radius: var(--ag-ui-panel-radius, 0);
|
|
@@ -8848,7 +9469,7 @@ var SIDEBAR_STYLES = `
|
|
|
8848
9469
|
host-managed push instead. */
|
|
8849
9470
|
:host([placement="sidebar"]) {
|
|
8850
9471
|
--_inset: var(--ag-ui-inset, calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
8851
|
-
--_width: var(--ag-ui-width, 420px);
|
|
9472
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 420px));
|
|
8852
9473
|
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
8853
9474
|
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
8854
9475
|
--_panel-radius: var(--ag-ui-panel-radius, 0);
|
|
@@ -8947,6 +9568,11 @@ var SMALL_VIEWPORT_STYLES = `
|
|
|
8947
9568
|
fine-pointered and small, and conflating the two gets both wrong. What the
|
|
8948
9569
|
pointer decides is which controls make sense, further down.
|
|
8949
9570
|
|
|
9571
|
+
The size the user dragged is deliberately not read here. The desktop
|
|
9572
|
+
placements read it before the host token, and this block restates the sizes
|
|
9573
|
+
without it, so a persisted or freshly dragged size cannot change the shape
|
|
9574
|
+
of a phone, and a window narrowed after a drag goes full-bleed.
|
|
9575
|
+
|
|
8950
9576
|
Two placements are left alone. "page" is already this shape. "embedded" sits
|
|
8951
9577
|
in a box the host sized and placed, and taking that over would break the app
|
|
8952
9578
|
shell it was embedded into -- the host is the only party that knows whether
|
|
@@ -8982,6 +9608,15 @@ var SMALL_VIEWPORT_STYLES = `
|
|
|
8982
9608
|
:host([placement=""]:not([data-small-viewport="off"])) .resize-handle {
|
|
8983
9609
|
display: none;
|
|
8984
9610
|
}
|
|
9611
|
+
|
|
9612
|
+
/* The bottom bar shows only while collapsed. With the panel open the panel
|
|
9613
|
+
is the screen, its header's minimise control is the way back, and a
|
|
9614
|
+
second strip fixed to the bottom edge would sit under the keyboard while
|
|
9615
|
+
the composer has focus. Hidden by display, so the bar measures 0 and the
|
|
9616
|
+
host's padding goes with it. */
|
|
9617
|
+
:host([data-collapsed-to="bar"]:not([collapsed]):not([data-small-viewport="off"])) .dock-bar {
|
|
9618
|
+
display: none;
|
|
9619
|
+
}
|
|
8985
9620
|
}
|
|
8986
9621
|
`;
|
|
8987
9622
|
|
|
@@ -9113,6 +9748,9 @@ var CARD_MODES = [
|
|
|
9113
9748
|
].join(", ");
|
|
9114
9749
|
var GROUPED = `:host(:where(:not(${CARD_MODES})))`;
|
|
9115
9750
|
var CARDS = `:host(:is(${CARD_MODES}))`;
|
|
9751
|
+
function inner(mode) {
|
|
9752
|
+
return `:host(:where(:not(${CARD_MODES}))[data-tool-group-display="${mode}"]) .tool-group-list .tool-call`;
|
|
9753
|
+
}
|
|
9116
9754
|
var TOOL_GROUP_STYLES = `
|
|
9117
9755
|
/* \u2500\u2500 The folded tool-step group \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9118
9756
|
One quiet row per run of consecutive calls, the cards one click down as
|
|
@@ -9297,6 +9935,43 @@ ${GROUPED} .tool-group .tool-call-subagent {
|
|
|
9297
9935
|
flex-basis: 100%;
|
|
9298
9936
|
}
|
|
9299
9937
|
|
|
9938
|
+
/* The inner display mode: how an opened group draws its calls. The same
|
|
9939
|
+
visibility rules the card modes apply to a flat card, so a call inside the
|
|
9940
|
+
group shows the regions the flat card of that mode shows. The flat inline
|
|
9941
|
+
mode's chrome rule is left out on purpose: a card in the group has already
|
|
9942
|
+
lost its box and is laid out as a row of the list, and that rule's padding
|
|
9943
|
+
and gap would undo the row. Selected from the host attribute, so changing it
|
|
9944
|
+
restyles groups already on screen. */
|
|
9945
|
+
${inner("compact")}[data-expanded="false"] .tool-call-section {
|
|
9946
|
+
display: none;
|
|
9947
|
+
}
|
|
9948
|
+
|
|
9949
|
+
${inner("inline")} .tool-call-section--args {
|
|
9950
|
+
display: none;
|
|
9951
|
+
}
|
|
9952
|
+
|
|
9953
|
+
${inner("minimal")} .tool-call-toggle,
|
|
9954
|
+
${inner("minimal")} .tool-call-body {
|
|
9955
|
+
display: none;
|
|
9956
|
+
}
|
|
9957
|
+
|
|
9958
|
+
/* A call waiting on an approval shows its arguments in every inner mode, as it
|
|
9959
|
+
does in every card mode, and for the same reason: the arguments are what
|
|
9960
|
+
tell three gated calls of one tool apart. These come after the hides above,
|
|
9961
|
+
which they tie with on weight. The arguments region only, never every
|
|
9962
|
+
section, since the result region carries the hidden attribute until a
|
|
9963
|
+
result exists and a display value here would override it. The arguments
|
|
9964
|
+
region carries it too when the call has none, so it is excluded while
|
|
9965
|
+
hidden rather than framed empty. */
|
|
9966
|
+
${inner("minimal")}[data-status="deferred"] .tool-call-body {
|
|
9967
|
+
display: flex;
|
|
9968
|
+
}
|
|
9969
|
+
|
|
9970
|
+
${inner("compact")}[data-status="deferred"] .tool-call-section--args:not([hidden]),
|
|
9971
|
+
${inner("inline")}[data-status="deferred"] .tool-call-section--args:not([hidden]) {
|
|
9972
|
+
display: flex;
|
|
9973
|
+
}
|
|
9974
|
+
|
|
9300
9975
|
/* Grouping is decided as each card is built, so a card mode set afterwards
|
|
9301
9976
|
finds groups already drawn. It flattens them: the group and its list stop
|
|
9302
9977
|
generating boxes, the row and the repeat ordinals go, and every card lays
|
|
@@ -9600,9 +10275,11 @@ var TOOL_STEP_STYLES = `
|
|
|
9600
10275
|
|
|
9601
10276
|
/* The arguments region only, never every section: the result region carries the
|
|
9602
10277
|
hidden attribute until a result exists, and a display value here overrides it,
|
|
9603
|
-
framing an empty RESULT heading under the question.
|
|
9604
|
-
|
|
9605
|
-
|
|
10278
|
+
framing an empty RESULT heading under the question. The arguments region is
|
|
10279
|
+
hidden too when the call has none, and is left alone then, or it would frame
|
|
10280
|
+
an empty ARGUMENTS heading. */
|
|
10281
|
+
:host([data-tool-display="compact"]) .tool-call[data-status="deferred"] .tool-call-section--args:not([hidden]),
|
|
10282
|
+
:host([data-tool-display="inline"]) .tool-call[data-status="deferred"] .tool-call-section--args:not([hidden]) {
|
|
9606
10283
|
display: flex;
|
|
9607
10284
|
}
|
|
9608
10285
|
|
|
@@ -9964,7 +10641,15 @@ var TOKEN_STYLES = `
|
|
|
9964
10641
|
--_line-strong: var(--ag-ui-line-strong, color-mix(in oklch, var(--_fg) 20%, var(--_bg)));
|
|
9965
10642
|
--_wash: var(--ag-ui-wash, color-mix(in oklch, var(--_fg) 4.5%, var(--_bg)));
|
|
9966
10643
|
--_accent-ink: var(--ag-ui-accent-ink, color-mix(in oklch, var(--_accent) 82%, var(--_fg)));
|
|
9967
|
-
|
|
10644
|
+
/* The focus ring is drawn on the panel, so it has to hold 3:1 against
|
|
10645
|
+
--_bg whatever accent a host brings (WCAG 2.4.13). Two parts accent to
|
|
10646
|
+
three parts ink does that at the worst case, an accent at the far end
|
|
10647
|
+
from the ink (white on a light panel, black on a dark one): the ink
|
|
10648
|
+
alone sits far above 3:1, and three fifths of it keeps the mix there
|
|
10649
|
+
while the hue still reads as the accent. Half and half fell to 2.3:1
|
|
10650
|
+
for black on the code theme. The accent over transparent, as before,
|
|
10651
|
+
measured only the accent, and a light host accent fell to 1.6:1. */
|
|
10652
|
+
--_ring: var(--ag-ui-ring, color-mix(in oklch, var(--_accent) 40%, var(--_fg)));
|
|
9968
10653
|
/* The user's turn is the accent at a whisper. With Quiet's graphite accent
|
|
9969
10654
|
that is a neutral wash; once a host sets a brand accent it becomes a tint
|
|
9970
10655
|
of the brand, which is the one thing Soft does that Quiet keeps. */
|
|
@@ -10227,8 +10912,27 @@ var TOKEN_STYLES = `
|
|
|
10227
10912
|
grid/flex layout) to embed it in the page flow instead of floating. */
|
|
10228
10913
|
--_position: var(--ag-ui-position, fixed);
|
|
10229
10914
|
--_z-index: var(--ag-ui-z-index, 2147483000);
|
|
10230
|
-
|
|
10231
|
-
|
|
10915
|
+
/* A size the user dragged, written inline on the host by the placement
|
|
10916
|
+
code, and read ahead of the host's tokens so the drag wins. There is no
|
|
10917
|
+
public token behind it; it is declared initial here only so a chat that
|
|
10918
|
+
was never dragged does not inherit one, from a dragged chat it is
|
|
10919
|
+
slotted into or from any page rule naming it. The inline value still
|
|
10920
|
+
beats this default. */
|
|
10921
|
+
--_drag-width: initial;
|
|
10922
|
+
--_drag-height: initial;
|
|
10923
|
+
/* Where a docked panel stands, written inline the same way by the placement
|
|
10924
|
+
code: the bottom bar's height, or the host anchor's distance from the
|
|
10925
|
+
bottom and right edges. Read only by the dock rules, each with a 0px
|
|
10926
|
+
fallback; declared initial so it never inherits either. */
|
|
10927
|
+
--_dock-bottom: initial;
|
|
10928
|
+
--_dock-right: initial;
|
|
10929
|
+
/* The gap between a panel and the host's own button it rises from under
|
|
10930
|
+
collapse-to="none". The button usually sits inside a taller toolbar, so a
|
|
10931
|
+
host sets this to the button's distance from the toolbar's top plus the
|
|
10932
|
+
clearance it wants, and the panel stands clear of the toolbar. */
|
|
10933
|
+
--_anchor-gap: var(--ag-ui-anchor-gap, 8px);
|
|
10934
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 380px));
|
|
10935
|
+
--_height: var(--_drag-height, var(--ag-ui-height, 560px));
|
|
10232
10936
|
/* The gutter a resting floating panel keeps between itself and the edge of
|
|
10233
10937
|
the box the host left free. One number, because the inset below spends it
|
|
10234
10938
|
and the cap beneath has to know it was spent. */
|
|
@@ -10842,9 +11546,18 @@ var SCHEME_STYLES = `
|
|
|
10842
11546
|
dark) and beside a theme name (soft dark). auto follows the operating
|
|
10843
11547
|
system through color-scheme itself, with no media query to keep in step.
|
|
10844
11548
|
code is a dark-only theme and has no light half. */
|
|
11549
|
+
/* Important, so a scheme word holds against a host rule that sets the
|
|
11550
|
+
element's color-scheme (a page that follows its own dark mode). A rule
|
|
11551
|
+
from outside the shadow tree beats every :host rule at normal weight, and
|
|
11552
|
+
the theme toggle writes the word; without this its press changed nothing.
|
|
11553
|
+
code stays last, so code light is still dark. */
|
|
11554
|
+
:host([theme~="light"]) {
|
|
11555
|
+
color-scheme: light !important;
|
|
11556
|
+
}
|
|
11557
|
+
|
|
10845
11558
|
:host([theme~="dark"]),
|
|
10846
11559
|
:host([theme~="code"]) {
|
|
10847
|
-
color-scheme: dark;
|
|
11560
|
+
color-scheme: dark !important;
|
|
10848
11561
|
}
|
|
10849
11562
|
|
|
10850
11563
|
:host([theme~="auto"]) {
|
|
@@ -11885,6 +12598,9 @@ var STYLES = [
|
|
|
11885
12598
|
THEME_STYLES,
|
|
11886
12599
|
PRESET_STYLES,
|
|
11887
12600
|
PAGE_STYLES,
|
|
12601
|
+
// Ahead of the phone block and no heavier than it: a docked panel is still
|
|
12602
|
+
// full-bleed on a phone.
|
|
12603
|
+
DOCK_STYLES,
|
|
11888
12604
|
SMALL_VIEWPORT_STYLES,
|
|
11889
12605
|
SIDEBAR_STYLES,
|
|
11890
12606
|
LAUNCHER_STYLES,
|
|
@@ -12014,7 +12730,7 @@ function buildHeader(options) {
|
|
|
12014
12730
|
|
|
12015
12731
|
// src/ui/shell/build_launcher.ts
|
|
12016
12732
|
function buildLauncher(options) {
|
|
12017
|
-
const { launcher, badge, railLabel } = options;
|
|
12733
|
+
const { launcher, badge, railLabel, bar, barHistory } = options;
|
|
12018
12734
|
launcher.className = "launcher";
|
|
12019
12735
|
launcher.type = "button";
|
|
12020
12736
|
launcher.setAttribute("part", "launcher");
|
|
@@ -12033,6 +12749,20 @@ function buildLauncher(options) {
|
|
|
12033
12749
|
badge
|
|
12034
12750
|
);
|
|
12035
12751
|
launcher.addEventListener("click", options.onOpen, { signal: options.signal });
|
|
12752
|
+
bar.className = "dock-bar";
|
|
12753
|
+
bar.setAttribute("part", "bar");
|
|
12754
|
+
barHistory.className = "bar-history";
|
|
12755
|
+
barHistory.type = "button";
|
|
12756
|
+
barHistory.setAttribute("part", "bar-history");
|
|
12757
|
+
barHistory.title = options.strings.chatHistory;
|
|
12758
|
+
barHistory.setAttribute("aria-label", options.strings.chatHistory);
|
|
12759
|
+
const glyph = document.createElement("span");
|
|
12760
|
+
glyph.className = "bar-history-glyph";
|
|
12761
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
12762
|
+
glyph.textContent = "\u2630";
|
|
12763
|
+
barHistory.replaceChildren(glyph);
|
|
12764
|
+
barHistory.addEventListener("click", options.onHistory, { signal: options.signal });
|
|
12765
|
+
bar.replaceChildren(launcher, barHistory);
|
|
12036
12766
|
}
|
|
12037
12767
|
|
|
12038
12768
|
// src/ui/shell/is_unread_badge_enabled.ts
|
|
@@ -12113,12 +12843,12 @@ ${listText(element, tag === "OL")}
|
|
|
12113
12843
|
|
|
12114
12844
|
`;
|
|
12115
12845
|
}
|
|
12116
|
-
const
|
|
12846
|
+
const inner2 = children(element);
|
|
12117
12847
|
return BLOCK_TAGS.has(tag) ? `
|
|
12118
12848
|
|
|
12119
|
-
${
|
|
12849
|
+
${inner2}
|
|
12120
12850
|
|
|
12121
|
-
` :
|
|
12851
|
+
` : inner2;
|
|
12122
12852
|
}
|
|
12123
12853
|
function children(element) {
|
|
12124
12854
|
let joined = "";
|
|
@@ -16292,6 +17022,7 @@ var ToolStepGroup = class {
|
|
|
16292
17022
|
state = "error";
|
|
16293
17023
|
}
|
|
16294
17024
|
this.element.setAttribute("data-state", state);
|
|
17025
|
+
this.element.setAttribute("part", `tool-group tool-group-${state}`);
|
|
16295
17026
|
this.element.setAttribute("data-count", String(total));
|
|
16296
17027
|
const only = total === 1 ? steps[0] : void 0;
|
|
16297
17028
|
if (only !== void 0) {
|
|
@@ -17998,8 +18729,13 @@ var RunHandlers = class {
|
|
|
17998
18729
|
* A handler table for one AG-UI client. Every table built for the current
|
|
17999
18730
|
* conversation draws into the same transcript and shares its bookkeeping, so
|
|
18000
18731
|
* a continuation run reports into the interaction the user is looking at.
|
|
18732
|
+
*
|
|
18733
|
+
* `threadId` is the thread the client was built for, named on the
|
|
18734
|
+
* run-finished detail. Taken here rather than read when the run settles,
|
|
18735
|
+
* because a stopped run settles after the reset that stopped it, when the
|
|
18736
|
+
* thread on screen is already the next one.
|
|
18001
18737
|
*/
|
|
18002
|
-
forClient() {
|
|
18738
|
+
forClient(threadId) {
|
|
18003
18739
|
const ledger = this.#ledger;
|
|
18004
18740
|
const attached = () => ledger === this.#ledger;
|
|
18005
18741
|
const { onSettled, ...drawing } = this.#tableFor(ledger);
|
|
@@ -18009,7 +18745,7 @@ var RunHandlers = class {
|
|
|
18009
18745
|
if (attached()) {
|
|
18010
18746
|
onSettled();
|
|
18011
18747
|
}
|
|
18012
|
-
this.#dispatchRunFinished(ledger);
|
|
18748
|
+
this.#dispatchRunFinished(ledger, threadId);
|
|
18013
18749
|
}
|
|
18014
18750
|
};
|
|
18015
18751
|
}
|
|
@@ -18162,7 +18898,7 @@ var RunHandlers = class {
|
|
|
18162
18898
|
* server is a fact about the run, and a name can appear on both sides across a
|
|
18163
18899
|
* conversation.
|
|
18164
18900
|
*/
|
|
18165
|
-
#dispatchRunFinished(ledger) {
|
|
18901
|
+
#dispatchRunFinished(ledger, threadId) {
|
|
18166
18902
|
const tools = ledger.tools.map(({ id, name }) => ({
|
|
18167
18903
|
name,
|
|
18168
18904
|
side: ledger.serverSettled.has(id) ? "server" : "client"
|
|
@@ -18173,7 +18909,7 @@ var RunHandlers = class {
|
|
|
18173
18909
|
ledger.invalidated.clear();
|
|
18174
18910
|
this.#host.element.dispatchEvent(
|
|
18175
18911
|
new CustomEvent(RUN_FINISHED_EVENT, {
|
|
18176
|
-
detail: { tools, invalidated },
|
|
18912
|
+
detail: { threadId, tools, invalidated },
|
|
18177
18913
|
bubbles: true,
|
|
18178
18914
|
composed: true
|
|
18179
18915
|
})
|
|
@@ -18473,6 +19209,14 @@ function withScheme(theme, scheme) {
|
|
|
18473
19209
|
const names = theme.split(/\s+/).filter((word) => word !== "" && !SCHEME_WORDS.includes(word));
|
|
18474
19210
|
return [...names, scheme].join(" ");
|
|
18475
19211
|
}
|
|
19212
|
+
function findAnchor(root, selector) {
|
|
19213
|
+
try {
|
|
19214
|
+
const scope = root instanceof ShadowRoot ? root : document;
|
|
19215
|
+
return scope.querySelector(selector) ?? document.querySelector(selector);
|
|
19216
|
+
} catch {
|
|
19217
|
+
return null;
|
|
19218
|
+
}
|
|
19219
|
+
}
|
|
18476
19220
|
var AgUiChat = class extends HTMLElement {
|
|
18477
19221
|
/** Agent factory; override to inject a custom or fake agent (tests). */
|
|
18478
19222
|
agentFactory = createHttpAgent;
|
|
@@ -18794,8 +19538,21 @@ var AgUiChat = class extends HTMLElement {
|
|
|
18794
19538
|
#launcher;
|
|
18795
19539
|
/** The launcher's unread badge; hidden at zero, and when the host opts out. */
|
|
18796
19540
|
#badge;
|
|
18797
|
-
/** The rail's vertical caption. Rendered only by the sidebar's edge rail. */
|
|
19541
|
+
/** The rail's vertical caption. Rendered only by the sidebar's edge rail and the bottom bar. */
|
|
18798
19542
|
#railLabel = document.createElement("span");
|
|
19543
|
+
/**
|
|
19544
|
+
* The bottom bar under collapse-to="bar": the launcher drawn as a strip, and
|
|
19545
|
+
* the history button beside it. Outside the bar it draws no box of its own.
|
|
19546
|
+
*/
|
|
19547
|
+
#dockBar = document.createElement("div");
|
|
19548
|
+
/** The bottom bar's history button, which opens the drawer as a popover. */
|
|
19549
|
+
#barHistory = document.createElement("button");
|
|
19550
|
+
/** The bar height last reported, so the event fires on a change only. */
|
|
19551
|
+
#barHeight = 0;
|
|
19552
|
+
/** The element the panel rises from under collapse-to="none", set as a property. */
|
|
19553
|
+
#anchor = null;
|
|
19554
|
+
/** Whether a missing anchor has been reported; once per anchor given. */
|
|
19555
|
+
#anchorWarned = false;
|
|
18799
19556
|
// Answers that finished while the widget was collapsed. Expanding clears it.
|
|
18800
19557
|
#unread = 0;
|
|
18801
19558
|
/** Empty-state region at the top of the message list; hidden once anything renders. */
|
|
@@ -18856,6 +19613,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
18856
19613
|
*/
|
|
18857
19614
|
#onViewportResize = () => {
|
|
18858
19615
|
this.#placement.publishVisualViewport();
|
|
19616
|
+
this.#syncDock();
|
|
18859
19617
|
if (this.#placement.dragging()) {
|
|
18860
19618
|
return;
|
|
18861
19619
|
}
|
|
@@ -18867,6 +19625,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
18867
19625
|
#voice;
|
|
18868
19626
|
/** Whether the element is currently in the DOM; gates the connect-time warning. */
|
|
18869
19627
|
#connected = false;
|
|
19628
|
+
/** A {@link newChat} asked for before the element connected, to start at connect. */
|
|
19629
|
+
#newChatOnConnect = false;
|
|
18870
19630
|
/**
|
|
18871
19631
|
* Aborted when the element leaves the document, and replaced when it comes
|
|
18872
19632
|
* back.
|
|
@@ -18992,6 +19752,11 @@ var AgUiChat = class extends HTMLElement {
|
|
|
18992
19752
|
* then a host's {@link sendMessage} waits for it rather than refusing.
|
|
18993
19753
|
*/
|
|
18994
19754
|
#restoreHold = null;
|
|
19755
|
+
/**
|
|
19756
|
+
* The thread the latest {@link THREAD_EVENT} reported, which the next one
|
|
19757
|
+
* names as the thread it replaced; `null` before the first.
|
|
19758
|
+
*/
|
|
19759
|
+
#shownThread = null;
|
|
18995
19760
|
/** The decision a run is suspended on, which a Stop abandons. */
|
|
18996
19761
|
#decision = new PendingDecision();
|
|
18997
19762
|
/**
|
|
@@ -19023,6 +19788,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19023
19788
|
allowImages: () => this.allowImages,
|
|
19024
19789
|
follow: () => this.#transcript.follow()
|
|
19025
19790
|
});
|
|
19791
|
+
// This chat's place among the connected ones, which a free highlight ring reads.
|
|
19792
|
+
#chatRegistration = new ChatRegistration(this);
|
|
19026
19793
|
/**
|
|
19027
19794
|
* Which storage keys are this element's: the namespace it claims, the keys
|
|
19028
19795
|
* its layout preferences live under, and the built-in store scoped to them.
|
|
@@ -19110,7 +19877,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19110
19877
|
root: this.#root,
|
|
19111
19878
|
connected: () => this.#connected,
|
|
19112
19879
|
collapsed: () => this.collapsed,
|
|
19113
|
-
collapsible: () =>
|
|
19880
|
+
collapsible: () => this.#surface().collapseTo !== null,
|
|
19114
19881
|
strings: () => this.#strings,
|
|
19115
19882
|
readPreference: (base) => this.#storage.readPreference(base),
|
|
19116
19883
|
writePreference: (base, value) => this.#storage.writePreference(base, value),
|
|
@@ -19186,7 +19953,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19186
19953
|
});
|
|
19187
19954
|
this.#drawer = new ThreadDrawer({
|
|
19188
19955
|
onSelect: (threadId) => {
|
|
19189
|
-
|
|
19956
|
+
this.#history.switchThread(threadId).catch(() => void 0);
|
|
19190
19957
|
},
|
|
19191
19958
|
onNew: () => {
|
|
19192
19959
|
this.newChat();
|
|
@@ -19270,7 +20037,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19270
20037
|
},
|
|
19271
20038
|
cancelRun: () => this.#cancelRun(),
|
|
19272
20039
|
resetState: () => this.#resetState(),
|
|
19273
|
-
setRunning: (running) => this.#setRunning(running)
|
|
20040
|
+
setRunning: (running) => this.#setRunning(running),
|
|
20041
|
+
threadChanged: (threadId, reason) => this.#dispatchThread(threadId, reason)
|
|
19274
20042
|
});
|
|
19275
20043
|
}
|
|
19276
20044
|
/** Attributes the element reacts to after it has been connected. */
|
|
@@ -19278,6 +20046,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19278
20046
|
return [
|
|
19279
20047
|
"title-text",
|
|
19280
20048
|
"placement",
|
|
20049
|
+
"collapse-to",
|
|
20050
|
+
"anchor",
|
|
19281
20051
|
"credentials",
|
|
19282
20052
|
"user-key",
|
|
19283
20053
|
"user-name",
|
|
@@ -19293,12 +20063,25 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19293
20063
|
}
|
|
19294
20064
|
return;
|
|
19295
20065
|
}
|
|
19296
|
-
if (name === "
|
|
20066
|
+
if (name === "anchor") {
|
|
20067
|
+
this.#anchorWarned = false;
|
|
20068
|
+
this.#syncDock();
|
|
20069
|
+
return;
|
|
20070
|
+
}
|
|
20071
|
+
if (name === "placement" || name === "collapse-to") {
|
|
19297
20072
|
this.#placement.releaseOwnedAxes();
|
|
19298
20073
|
this.#placement.releaseLauncherPosition();
|
|
19299
|
-
if (
|
|
20074
|
+
if (this.#surface().collapseTo === null && this.collapsed) {
|
|
19300
20075
|
this.setCollapsed(false);
|
|
19301
20076
|
}
|
|
20077
|
+
this.#stampCollapseTarget();
|
|
20078
|
+
if (name === "collapse-to") {
|
|
20079
|
+
this.#syncLauncher();
|
|
20080
|
+
if (this.#surface().collapseTo !== "bar") {
|
|
20081
|
+
this.#homeDrawer();
|
|
20082
|
+
}
|
|
20083
|
+
}
|
|
20084
|
+
this.#syncDock();
|
|
19302
20085
|
requestAnimationFrame(() => this.#placement.syncResizeAnchor());
|
|
19303
20086
|
return;
|
|
19304
20087
|
}
|
|
@@ -19555,6 +20338,26 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19555
20338
|
set toolDisplay(value) {
|
|
19556
20339
|
this.setAttribute("data-tool-display", value);
|
|
19557
20340
|
}
|
|
20341
|
+
/**
|
|
20342
|
+
* How an opened step group draws its calls, from the
|
|
20343
|
+
* `data-tool-group-display` attribute (`full` / `compact` / `minimal` /
|
|
20344
|
+
* `inline`). Defaults to `full`, for an absent attribute and for a value the
|
|
20345
|
+
* element does not know, which is what a group drew before the attribute.
|
|
20346
|
+
*
|
|
20347
|
+
* Read only while `toolDisplay` is `grouped`: under a card mode the groups
|
|
20348
|
+
* are flattened into that mode's cards. Applied by the shadow CSS from the
|
|
20349
|
+
* attribute itself, so changing it restyles groups already on screen.
|
|
20350
|
+
*/
|
|
20351
|
+
get toolGroupDisplay() {
|
|
20352
|
+
const attr = this.getAttribute("data-tool-group-display");
|
|
20353
|
+
if (attr === TOOL_GROUP_DISPLAY.INLINE || attr === TOOL_GROUP_DISPLAY.MINIMAL || attr === TOOL_GROUP_DISPLAY.COMPACT) {
|
|
20354
|
+
return attr;
|
|
20355
|
+
}
|
|
20356
|
+
return TOOL_GROUP_DISPLAY.FULL;
|
|
20357
|
+
}
|
|
20358
|
+
set toolGroupDisplay(value) {
|
|
20359
|
+
this.setAttribute("data-tool-group-display", value);
|
|
20360
|
+
}
|
|
19558
20361
|
/**
|
|
19559
20362
|
* The host's own constructed stylesheets, adopted after the built-in one.
|
|
19560
20363
|
*
|
|
@@ -19583,6 +20386,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19583
20386
|
}
|
|
19584
20387
|
this.#connectedBefore = true;
|
|
19585
20388
|
this.#storage.claim();
|
|
20389
|
+
this.#chatRegistration.join();
|
|
19586
20390
|
this.#placement.restoreSize();
|
|
19587
20391
|
requestAnimationFrame(() => {
|
|
19588
20392
|
this.#placement.restoreLauncherPosition();
|
|
@@ -19599,10 +20403,24 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19599
20403
|
this.#render();
|
|
19600
20404
|
this.#drawer.setStrings(this.#strings);
|
|
19601
20405
|
this.#checkpoints.setStrings(this.#strings);
|
|
19602
|
-
if (
|
|
20406
|
+
if (this.#surface().collapseTo !== null && this.#startsCollapsed()) {
|
|
19603
20407
|
this.setAttribute("collapsed", "");
|
|
19604
20408
|
}
|
|
19605
20409
|
this.#syncLauncher();
|
|
20410
|
+
this.#stampCollapseTarget();
|
|
20411
|
+
const dockObserver = new ResizeObserver(() => this.#syncDock());
|
|
20412
|
+
dockObserver.observe(this.#dockBar);
|
|
20413
|
+
this.#connection.signal.addEventListener("abort", () => dockObserver.disconnect());
|
|
20414
|
+
this.#syncDock();
|
|
20415
|
+
if (this.#surface().collapseTo === "none") {
|
|
20416
|
+
this.dispatchEvent(
|
|
20417
|
+
new CustomEvent(TOGGLE_EVENT, {
|
|
20418
|
+
detail: { collapsed: this.collapsed },
|
|
20419
|
+
bubbles: true,
|
|
20420
|
+
composed: true
|
|
20421
|
+
})
|
|
20422
|
+
);
|
|
20423
|
+
}
|
|
19606
20424
|
this.#skills.init();
|
|
19607
20425
|
const held = this.#principalKey;
|
|
19608
20426
|
const changed = held !== null && held !== this.userKey;
|
|
@@ -19620,11 +20438,15 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19620
20438
|
this.#voice.wire();
|
|
19621
20439
|
this.#history.adoptActiveThread();
|
|
19622
20440
|
queueMicrotask(() => this.#startup());
|
|
19623
|
-
void this.#history.rehydrate();
|
|
20441
|
+
void this.#history.rehydrate(changed && held !== "" ? "rescope" : "restore");
|
|
19624
20442
|
if (changed) {
|
|
19625
20443
|
void this.#history.refreshDrawer();
|
|
19626
20444
|
}
|
|
19627
20445
|
this.#connected = true;
|
|
20446
|
+
if (this.#newChatOnConnect) {
|
|
20447
|
+
this.#newChatOnConnect = false;
|
|
20448
|
+
this.newChat();
|
|
20449
|
+
}
|
|
19628
20450
|
}
|
|
19629
20451
|
/**
|
|
19630
20452
|
* The catalog requests the element issues on startup: the tool labels
|
|
@@ -19673,7 +20495,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19673
20495
|
await Promise.all([
|
|
19674
20496
|
this.#tools.fetchCatalog(),
|
|
19675
20497
|
this.#skills.fetch(),
|
|
19676
|
-
this.#history.rehydrate()
|
|
20498
|
+
this.#history.rehydrate("restore")
|
|
19677
20499
|
]);
|
|
19678
20500
|
}
|
|
19679
20501
|
/**
|
|
@@ -19689,6 +20511,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19689
20511
|
window.visualViewport?.removeEventListener("resize", this.#onViewportResize);
|
|
19690
20512
|
window.visualViewport?.removeEventListener("scroll", this.#onViewportResize);
|
|
19691
20513
|
this.#storage.release();
|
|
20514
|
+
this.#chatRegistration.leave();
|
|
19692
20515
|
this.#cancelRun();
|
|
19693
20516
|
this.#excerpts.detachPageOffer();
|
|
19694
20517
|
this.#attachments.tray?.dispose();
|
|
@@ -19756,15 +20579,15 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19756
20579
|
this.#threadStore = null;
|
|
19757
20580
|
const url = this.getAttribute("data-threads-url");
|
|
19758
20581
|
if (url !== null) {
|
|
19759
|
-
const
|
|
20582
|
+
const inner2 = this.conversationStore;
|
|
19760
20583
|
const remote = new RemoteConversationStore(
|
|
19761
20584
|
url,
|
|
19762
20585
|
() => this.#headersFor(url),
|
|
19763
|
-
|
|
20586
|
+
inner2,
|
|
19764
20587
|
() => this.#requestCredentials(),
|
|
19765
20588
|
this.getAttribute("data-threads-cache") !== "false"
|
|
19766
20589
|
);
|
|
19767
|
-
this.#threadStore = { remote, inner };
|
|
20590
|
+
this.#threadStore = { remote, inner: inner2 };
|
|
19768
20591
|
this.conversationStore = remote;
|
|
19769
20592
|
}
|
|
19770
20593
|
}
|
|
@@ -19803,7 +20626,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19803
20626
|
* its own chrome.
|
|
19804
20627
|
*/
|
|
19805
20628
|
setCollapsed(collapsed, options = {}) {
|
|
19806
|
-
if (collapsed &&
|
|
20629
|
+
if (collapsed && this.#surface().collapseTo === null) {
|
|
19807
20630
|
return;
|
|
19808
20631
|
}
|
|
19809
20632
|
if (options.announce === true && collapsed && !this.collapsed) {
|
|
@@ -19815,8 +20638,10 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19815
20638
|
if (collapsed) {
|
|
19816
20639
|
this.setAttribute("collapsed", "");
|
|
19817
20640
|
} else {
|
|
20641
|
+
this.#homeDrawer();
|
|
19818
20642
|
this.removeAttribute("collapsed");
|
|
19819
20643
|
}
|
|
20644
|
+
this.#syncDock();
|
|
19820
20645
|
writeStoredItem(this.#storage.key(COLLAPSED_KEY), collapsed ? "1" : "0");
|
|
19821
20646
|
this.#setUnread(0);
|
|
19822
20647
|
this.dispatchEvent(
|
|
@@ -19836,6 +20661,114 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19836
20661
|
get unread() {
|
|
19837
20662
|
return this.#unread;
|
|
19838
20663
|
}
|
|
20664
|
+
/**
|
|
20665
|
+
* The thread on screen; `null` before the element first connects.
|
|
20666
|
+
*
|
|
20667
|
+
* Only reads. `conversationStore.threadId()` mints and stores a thread when
|
|
20668
|
+
* the store points at none, so a host asking which conversation is showing
|
|
20669
|
+
* could start one; this never does. {@link THREAD_EVENT} says when it changes.
|
|
20670
|
+
*/
|
|
20671
|
+
get threadId() {
|
|
20672
|
+
const threadId = this.#history.threadId;
|
|
20673
|
+
return threadId === "" ? null : threadId;
|
|
20674
|
+
}
|
|
20675
|
+
/**
|
|
20676
|
+
* Whether an interaction is in flight: the composer's own state, the one that
|
|
20677
|
+
* shows Stop. It spans every tool round, a retry and a checkpoint
|
|
20678
|
+
* continuation as well as a send, which {@link SUBMIT_EVENT} alone does not.
|
|
20679
|
+
* Reflected as `data-running` on the element, for a host's CSS.
|
|
20680
|
+
*/
|
|
20681
|
+
get running() {
|
|
20682
|
+
return this.#running;
|
|
20683
|
+
}
|
|
20684
|
+
/**
|
|
20685
|
+
* The height of the bottom bar in CSS px, and 0 when none is drawn: under
|
|
20686
|
+
* any collapse-to but "bar", and on a phone while the panel is open, where
|
|
20687
|
+
* the panel is the screen. The bar is fixed over the page, so a host pads
|
|
20688
|
+
* its own page by this much; {@link BAR_HEIGHT_EVENT} says when it changes.
|
|
20689
|
+
*/
|
|
20690
|
+
get barHeight() {
|
|
20691
|
+
return this.#barHeight;
|
|
20692
|
+
}
|
|
20693
|
+
/**
|
|
20694
|
+
* The element the panel rises from under collapse-to="none", right-aligned
|
|
20695
|
+
* to it: typically the host's own button in its own bar. Takes precedence
|
|
20696
|
+
* over the anchor attribute, which names one by selector. One that resolves
|
|
20697
|
+
* to nothing leaves the panel in its placement's own corner, with a console
|
|
20698
|
+
* warning.
|
|
20699
|
+
*/
|
|
20700
|
+
get anchor() {
|
|
20701
|
+
return this.#anchor;
|
|
20702
|
+
}
|
|
20703
|
+
set anchor(value) {
|
|
20704
|
+
this.#anchor = value;
|
|
20705
|
+
this.#anchorWarned = false;
|
|
20706
|
+
this.#syncDock();
|
|
20707
|
+
}
|
|
20708
|
+
/** The surface the live placement and collapse-to attributes describe. */
|
|
20709
|
+
#surface() {
|
|
20710
|
+
return surfaceFor(this.getAttribute("placement"), this.getAttribute("collapse-to"));
|
|
20711
|
+
}
|
|
20712
|
+
/**
|
|
20713
|
+
* Stamp what the panel collapses to as data-collapsed-to, which is what the
|
|
20714
|
+
* stylesheet draws the bar and the hidden state from. Resolved here rather
|
|
20715
|
+
* than in CSS, because "bar" over a sidebar is its rail and "none" over a
|
|
20716
|
+
* page is no collapse at all, and the stylesheet would have to restate that
|
|
20717
|
+
* table.
|
|
20718
|
+
*/
|
|
20719
|
+
#stampCollapseTarget() {
|
|
20720
|
+
const target = this.#surface().collapseTo;
|
|
20721
|
+
if (target === null) {
|
|
20722
|
+
this.removeAttribute("data-collapsed-to");
|
|
20723
|
+
} else {
|
|
20724
|
+
this.setAttribute("data-collapsed-to", target);
|
|
20725
|
+
}
|
|
20726
|
+
}
|
|
20727
|
+
/** Re-measure the bar or the anchor, and tell the host when the bar's height changed. */
|
|
20728
|
+
#syncDock() {
|
|
20729
|
+
if (!this.isConnected) {
|
|
20730
|
+
return;
|
|
20731
|
+
}
|
|
20732
|
+
const height = this.#placement.syncDock(this.#dockBar, this.#resolveAnchor());
|
|
20733
|
+
if (height === this.#barHeight) {
|
|
20734
|
+
return;
|
|
20735
|
+
}
|
|
20736
|
+
this.#barHeight = height;
|
|
20737
|
+
this.dispatchEvent(
|
|
20738
|
+
new CustomEvent(BAR_HEIGHT_EVENT, {
|
|
20739
|
+
detail: { height },
|
|
20740
|
+
bubbles: true,
|
|
20741
|
+
composed: true
|
|
20742
|
+
})
|
|
20743
|
+
);
|
|
20744
|
+
}
|
|
20745
|
+
/**
|
|
20746
|
+
* The anchor in force under collapse-to="none": the property, else the
|
|
20747
|
+
* attribute's selector, looked up in the root this element lives in and
|
|
20748
|
+
* then the document, so a chat inside a framework's shadow root can name a
|
|
20749
|
+
* button beside it. Null under any other collapse-to, with nothing given,
|
|
20750
|
+
* or when what was given is not in the document -- reported once per anchor.
|
|
20751
|
+
*/
|
|
20752
|
+
#resolveAnchor() {
|
|
20753
|
+
if (this.#surface().collapseTo !== "none") {
|
|
20754
|
+
return null;
|
|
20755
|
+
}
|
|
20756
|
+
const selector = this.getAttribute("anchor") ?? "";
|
|
20757
|
+
if (this.#anchor === null && selector === "") {
|
|
20758
|
+
return null;
|
|
20759
|
+
}
|
|
20760
|
+
const found = this.#anchor ?? findAnchor(this.getRootNode(), selector);
|
|
20761
|
+
if (found?.isConnected === true) {
|
|
20762
|
+
return found;
|
|
20763
|
+
}
|
|
20764
|
+
if (!this.#anchorWarned) {
|
|
20765
|
+
this.#anchorWarned = true;
|
|
20766
|
+
console.warn(
|
|
20767
|
+
`<ag-ui-chat>: the anchor ${this.#anchor === null ? `"${selector}"` : "element"} is not in the document, so the panel opens from its placement's own corner.`
|
|
20768
|
+
);
|
|
20769
|
+
}
|
|
20770
|
+
return null;
|
|
20771
|
+
}
|
|
19839
20772
|
/**
|
|
19840
20773
|
* Whether to mount collapsed.
|
|
19841
20774
|
*
|
|
@@ -19858,7 +20791,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19858
20791
|
if (stored !== null) {
|
|
19859
20792
|
return stored === "1";
|
|
19860
20793
|
}
|
|
19861
|
-
return
|
|
20794
|
+
return this.#surface().startsCollapsed && !this.hasAttribute("data-start-open");
|
|
19862
20795
|
}
|
|
19863
20796
|
/**
|
|
19864
20797
|
* Describe the panel to whoever is asking -- in practice the agent, through
|
|
@@ -19886,6 +20819,13 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19886
20819
|
moveTo(corner, options = {}) {
|
|
19887
20820
|
return this.#placement.moveTo(corner, options);
|
|
19888
20821
|
}
|
|
20822
|
+
/**
|
|
20823
|
+
* Drop a size the user dragged, and its stored copy, so the placement's
|
|
20824
|
+
* default and the host's `--ag-ui-width` / `--ag-ui-height` apply again.
|
|
20825
|
+
*/
|
|
20826
|
+
resetSize() {
|
|
20827
|
+
this.#placement.resetSize();
|
|
20828
|
+
}
|
|
19889
20829
|
/** Flip the collapsed state. Bound to the built-in header toggle. */
|
|
19890
20830
|
toggleCollapsed() {
|
|
19891
20831
|
this.setCollapsed(!this.collapsed);
|
|
@@ -19894,12 +20834,13 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19894
20834
|
* Flip the scheme word of the `theme` attribute between `light` and `dark`
|
|
19895
20835
|
* and persist the choice per tab. Bound to the optional built-in header theme
|
|
19896
20836
|
* toggle (`data-theme-toggle`). The theme name and any host words are kept:
|
|
19897
|
-
* `soft` becomes `soft dark`, and `soft dark` becomes `soft light`.
|
|
19898
|
-
* scheme
|
|
20837
|
+
* `soft` becomes `soft dark`, and `soft dark` becomes `soft light`. With no
|
|
20838
|
+
* scheme word (`auto`, or a host rule setting `color-scheme` on the element),
|
|
20839
|
+
* it flips away from the scheme the panel is drawn in.
|
|
19899
20840
|
*/
|
|
19900
20841
|
toggleTheme() {
|
|
19901
20842
|
const theme = this.getAttribute("theme") ?? "";
|
|
19902
|
-
const scheme =
|
|
20843
|
+
const scheme = this.#drawnScheme() === "dark" ? "light" : "dark";
|
|
19903
20844
|
this.setAttribute("theme", withScheme(theme, scheme));
|
|
19904
20845
|
this.#storage.writePreference(THEME_KEY, scheme);
|
|
19905
20846
|
this.#syncThemeGlyph();
|
|
@@ -19955,7 +20896,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19955
20896
|
this.#voice.dispose();
|
|
19956
20897
|
this.#voice.wire();
|
|
19957
20898
|
this.#history.adoptActiveThread();
|
|
19958
|
-
void this.#history.rehydrate();
|
|
20899
|
+
void this.#history.rehydrate("rescope");
|
|
19959
20900
|
void this.#history.refreshDrawer();
|
|
19960
20901
|
}
|
|
19961
20902
|
/**
|
|
@@ -19979,9 +20920,36 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19979
20920
|
this.conversationStore = store;
|
|
19980
20921
|
this.#wireThreadStore();
|
|
19981
20922
|
}
|
|
20923
|
+
/**
|
|
20924
|
+
* The scheme the panel is drawn in, which the toggle flips and its glyph
|
|
20925
|
+
* offers the way out of. A scheme word decides it, since the scheme rules
|
|
20926
|
+
* hold against a host's. Without one the panel follows whatever sets its
|
|
20927
|
+
* color-scheme: a host rule on the element, auto's "light dark", or the
|
|
20928
|
+
* light default. Both schemes allowed means the system picks. Read before
|
|
20929
|
+
* the shadow styles apply too (the glyph is drawn first), when the computed
|
|
20930
|
+
* value carries only a host rule, so auto is answered from its word.
|
|
20931
|
+
*/
|
|
20932
|
+
#drawnScheme() {
|
|
20933
|
+
const words = (this.getAttribute("theme") ?? "").split(/\s+/);
|
|
20934
|
+
if (words.includes("dark")) {
|
|
20935
|
+
return "dark";
|
|
20936
|
+
}
|
|
20937
|
+
if (words.includes("light")) {
|
|
20938
|
+
return "light";
|
|
20939
|
+
}
|
|
20940
|
+
const declared = getComputedStyle(this).colorScheme.split(/\s+/);
|
|
20941
|
+
const dark = declared.includes("dark");
|
|
20942
|
+
if (dark !== declared.includes("light")) {
|
|
20943
|
+
return dark ? "dark" : "light";
|
|
20944
|
+
}
|
|
20945
|
+
if (dark || words.includes("auto")) {
|
|
20946
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
20947
|
+
}
|
|
20948
|
+
return "light";
|
|
20949
|
+
}
|
|
19982
20950
|
/** Reflect the current theme on the toggle: show the destination's glyph. */
|
|
19983
20951
|
#syncThemeGlyph() {
|
|
19984
|
-
const dark =
|
|
20952
|
+
const dark = this.#drawnScheme() === "dark";
|
|
19985
20953
|
this.#themeToggle.replaceChildren(
|
|
19986
20954
|
iconElement("theme", "theme-icon", dark ? ICON_SUN : ICON_MOON, null)
|
|
19987
20955
|
);
|
|
@@ -19996,8 +20964,25 @@ var AgUiChat = class extends HTMLElement {
|
|
|
19996
20964
|
* {@link toggleCollapsed} and {@link toggleTheme}.
|
|
19997
20965
|
*/
|
|
19998
20966
|
openThreads() {
|
|
20967
|
+
if (this.collapsed && this.#surface().collapseTo === "bar") {
|
|
20968
|
+
this.#dockBar.append(this.#drawer.element);
|
|
20969
|
+
} else {
|
|
20970
|
+
this.#homeDrawer();
|
|
20971
|
+
}
|
|
19999
20972
|
this.#history.openThreads();
|
|
20000
20973
|
}
|
|
20974
|
+
/**
|
|
20975
|
+
* Put the drawer back in the panel, where every surface but the bar's
|
|
20976
|
+
* popover draws it. A popover still open is closed first: the list was
|
|
20977
|
+
* opened over the bar, and carried into the panel it would cover the
|
|
20978
|
+
* conversation the user asked to see.
|
|
20979
|
+
*/
|
|
20980
|
+
#homeDrawer() {
|
|
20981
|
+
if (this.#drawer.element.parentNode === this.#dockBar) {
|
|
20982
|
+
this.#drawer.close();
|
|
20983
|
+
this.#chat.insertBefore(this.#drawer.element, this.#checkpoints.element);
|
|
20984
|
+
}
|
|
20985
|
+
}
|
|
20001
20986
|
/**
|
|
20002
20987
|
* Open the checkpoints panel (the `::part(checkpoints-button)` route).
|
|
20003
20988
|
*
|
|
@@ -20016,6 +21001,37 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20016
21001
|
closeCheckpoints() {
|
|
20017
21002
|
this.#checkpoints.close();
|
|
20018
21003
|
}
|
|
21004
|
+
/**
|
|
21005
|
+
* Put a stored thread on screen, as picking it in the conversation list does,
|
|
21006
|
+
* for a host that lists conversations in its own chrome.
|
|
21007
|
+
*
|
|
21008
|
+
* Resolves once the thread has been replayed, when {@link THREAD_EVENT} has
|
|
21009
|
+
* fired; a second call for the thread already loading settles with that load.
|
|
21010
|
+
* If the store rejects the load, the element stays on the thread it was on,
|
|
21011
|
+
* fires nothing, and the promise rejects with the store's error.
|
|
21012
|
+
* **A run in flight is cancelled**, as the list and {@link newChat} do;
|
|
21013
|
+
* a host that would rather wait reads {@link running} first. The thread
|
|
21014
|
+
* already on screen is left alone, and so is everything before the element
|
|
21015
|
+
* connects, since until then it has no store to read the thread from.
|
|
21016
|
+
*/
|
|
21017
|
+
switchThread(threadId) {
|
|
21018
|
+
if (!this.#connected) {
|
|
21019
|
+
return Promise.resolve();
|
|
21020
|
+
}
|
|
21021
|
+
return this.#history.switchThread(threadId);
|
|
21022
|
+
}
|
|
21023
|
+
/**
|
|
21024
|
+
* Delete a stored thread, as the conversation list's delete does. Deleting the
|
|
21025
|
+
* thread on screen cancels its run and puts a fresh thread in its place, and
|
|
21026
|
+
* {@link THREAD_EVENT} reports it; deleting another leaves the screen alone.
|
|
21027
|
+
* Does nothing before the element connects.
|
|
21028
|
+
*/
|
|
21029
|
+
deleteThread(threadId) {
|
|
21030
|
+
if (!this.#connected) {
|
|
21031
|
+
return;
|
|
21032
|
+
}
|
|
21033
|
+
this.#history.deleteThread(threadId);
|
|
21034
|
+
}
|
|
20019
21035
|
/**
|
|
20020
21036
|
* Open the checkpoints panel, or close it if it is already open — what the
|
|
20021
21037
|
* built-in ⭯ button does, because a control that opens a panel is read as the
|
|
@@ -20042,12 +21058,17 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20042
21058
|
* one. The page is not scrolled to bring the composer into view.
|
|
20043
21059
|
*/
|
|
20044
21060
|
newChat() {
|
|
21061
|
+
if (!this.#connected) {
|
|
21062
|
+
this.#newChatOnConnect = true;
|
|
21063
|
+
return;
|
|
21064
|
+
}
|
|
20045
21065
|
this.#cancelRun();
|
|
20046
21066
|
this.#history.reapUnsent();
|
|
20047
21067
|
this.#resetState();
|
|
20048
21068
|
this.#history.startThread();
|
|
20049
21069
|
this.#setRunning(false);
|
|
20050
21070
|
this.#setUnread(0);
|
|
21071
|
+
this.#dispatchThread(this.#history.threadId, "new");
|
|
20051
21072
|
if (!this.collapsed) {
|
|
20052
21073
|
this.#input.focus({ preventScroll: true });
|
|
20053
21074
|
}
|
|
@@ -20240,6 +21261,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20240
21261
|
},
|
|
20241
21262
|
onFilesPicked: () => this.#attachments.onFilesPicked()
|
|
20242
21263
|
});
|
|
21264
|
+
const aboveComposer = document.createElement("slot");
|
|
21265
|
+
aboveComposer.name = "above-composer";
|
|
20243
21266
|
const footer = document.createElement("slot");
|
|
20244
21267
|
footer.name = "footer";
|
|
20245
21268
|
this.#messagesWrap.className = "messages-wrap";
|
|
@@ -20252,6 +21275,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20252
21275
|
this.#composerHint,
|
|
20253
21276
|
this.#queuedRow,
|
|
20254
21277
|
this.#attachSlot,
|
|
21278
|
+
aboveComposer,
|
|
20255
21279
|
inputRow,
|
|
20256
21280
|
footer,
|
|
20257
21281
|
this.#drawer.element,
|
|
@@ -20275,19 +21299,45 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20275
21299
|
launcher: this.#launcher,
|
|
20276
21300
|
badge: this.#badge,
|
|
20277
21301
|
railLabel: this.#railLabel,
|
|
21302
|
+
bar: this.#dockBar,
|
|
21303
|
+
barHistory: this.#barHistory,
|
|
20278
21304
|
titleText,
|
|
20279
21305
|
iconUrl: readLauncherIconUrl(this),
|
|
20280
21306
|
strings: this.#strings,
|
|
20281
21307
|
signal,
|
|
20282
|
-
|
|
21308
|
+
// The bar stays on screen with the panel open, as the way to minimise
|
|
21309
|
+
// it, so its strip toggles; everywhere else the launcher is only drawn
|
|
21310
|
+
// while collapsed and only ever opens.
|
|
21311
|
+
onOpen: () => this.setCollapsed(this.#surface().collapseTo === "bar" && !this.collapsed),
|
|
21312
|
+
// The bar's history button toggles: the popover has no backdrop, and a
|
|
21313
|
+
// press on the bar is not a press outside it, so this is how the button
|
|
21314
|
+
// that opened the list closes it again. openThreads() stays open-only.
|
|
21315
|
+
onHistory: () => {
|
|
21316
|
+
if (this.#drawer.isOpen()) {
|
|
21317
|
+
this.#drawer.close();
|
|
21318
|
+
} else {
|
|
21319
|
+
this.openThreads();
|
|
21320
|
+
}
|
|
21321
|
+
}
|
|
20283
21322
|
});
|
|
21323
|
+
document.addEventListener(
|
|
21324
|
+
"pointerdown",
|
|
21325
|
+
(event) => {
|
|
21326
|
+
if (this.#drawer.element.parentNode === this.#dockBar && this.#drawer.isOpen()) {
|
|
21327
|
+
if (!event.composedPath().includes(this.#dockBar)) {
|
|
21328
|
+
this.#drawer.close();
|
|
21329
|
+
}
|
|
21330
|
+
}
|
|
21331
|
+
},
|
|
21332
|
+
{ signal }
|
|
21333
|
+
);
|
|
20284
21334
|
this.#placement.enableLauncherDrag(signal);
|
|
20285
21335
|
this.#placement.mountResizeGrips(this.#chat);
|
|
20286
21336
|
this.#builtInSheet = adoptStyles(this.#root, this.#extraStyles);
|
|
20287
21337
|
const probe = this.#placement.probe;
|
|
20288
21338
|
probe.className = "viewport-probe";
|
|
20289
21339
|
probe.setAttribute("aria-hidden", "true");
|
|
20290
|
-
this.#root.replaceChildren(probe, this.#announcer.region, this.#chat, this.#
|
|
21340
|
+
this.#root.replaceChildren(probe, this.#announcer.region, this.#chat, this.#dockBar);
|
|
20291
21341
|
}
|
|
20292
21342
|
/**
|
|
20293
21343
|
* Reflect the collapsed state and the unread count on the launcher.
|
|
@@ -20301,7 +21351,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20301
21351
|
const unread = this.#unread;
|
|
20302
21352
|
this.#badge.textContent = unread > 9 ? "9+" : String(unread);
|
|
20303
21353
|
this.#badge.hidden = unread === 0 || !isUnreadBadgeEnabled(this);
|
|
20304
|
-
const label = this.#badge.hidden ? this.#strings.expand : fillUiString(this.#strings.expandUnread, { count: unread });
|
|
21354
|
+
const label = !this.collapsed && this.#surface().collapseTo === "bar" ? this.#strings.collapse : this.#badge.hidden ? this.#strings.expand : fillUiString(this.#strings.expandUnread, { count: unread });
|
|
20305
21355
|
this.#launcher.setAttribute("aria-label", label);
|
|
20306
21356
|
this.#launcher.title = label;
|
|
20307
21357
|
}
|
|
@@ -20419,6 +21469,21 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20419
21469
|
this.#sending = null;
|
|
20420
21470
|
this.#history.stopContinuation();
|
|
20421
21471
|
}
|
|
21472
|
+
/**
|
|
21473
|
+
* Say the conversation on screen changed: {@link THREAD_EVENT}.
|
|
21474
|
+
*
|
|
21475
|
+
* The thread it replaced is the one last reported, rather than the one the
|
|
21476
|
+
* element last pointed at: a switch superseded before it replayed never drew
|
|
21477
|
+
* its thread, so naming it as the previous one left a host's own list
|
|
21478
|
+
* marking a thread that was never shown and still marking the one that was.
|
|
21479
|
+
*/
|
|
21480
|
+
#dispatchThread(threadId, reason) {
|
|
21481
|
+
const detail = { threadId, previousThreadId: this.#shownThread, reason };
|
|
21482
|
+
this.#shownThread = threadId;
|
|
21483
|
+
this.dispatchEvent(
|
|
21484
|
+
new CustomEvent(THREAD_EVENT, { detail, bubbles: true, composed: true })
|
|
21485
|
+
);
|
|
21486
|
+
}
|
|
20422
21487
|
/**
|
|
20423
21488
|
* Swap the composer button between Send (idle) and Stop (running).
|
|
20424
21489
|
*
|
|
@@ -20436,6 +21501,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20436
21501
|
this.#send.title = label;
|
|
20437
21502
|
this.#send.setAttribute("aria-label", label);
|
|
20438
21503
|
this.#send.dataset["state"] = running ? "running" : "idle";
|
|
21504
|
+
this.toggleAttribute("data-running", running);
|
|
20439
21505
|
if (settled) {
|
|
20440
21506
|
this.#flushQueued();
|
|
20441
21507
|
}
|
|
@@ -20724,7 +21790,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
20724
21790
|
});
|
|
20725
21791
|
return new AgUiClient({
|
|
20726
21792
|
agent,
|
|
20727
|
-
handlers: this.#runHandlers.forClient(),
|
|
21793
|
+
handlers: this.#runHandlers.forClient(threadId),
|
|
20728
21794
|
getTools: () => this.#tools.advertise(),
|
|
20729
21795
|
getContext: () => this.#dispatch.buildContext(),
|
|
20730
21796
|
executeTool: (call) => this.#dispatch.execute(call),
|
|
@@ -20918,11 +21984,12 @@ function setControlValue(el2, value) {
|
|
|
20918
21984
|
}
|
|
20919
21985
|
|
|
20920
21986
|
// src/version.ts
|
|
20921
|
-
var VERSION = "0.
|
|
21987
|
+
var VERSION = "0.48.0";
|
|
20922
21988
|
export {
|
|
20923
21989
|
ATTACHMENT_EVENT,
|
|
20924
21990
|
AgUiChat,
|
|
20925
21991
|
AgUiClient,
|
|
21992
|
+
BAR_HEIGHT_EVENT,
|
|
20926
21993
|
CHART_ACTIVITY_TYPE,
|
|
20927
21994
|
CHART_TOOL_NAME,
|
|
20928
21995
|
CHAT_CORNERS,
|
|
@@ -20951,9 +22018,11 @@ export {
|
|
|
20951
22018
|
SUBMIT_EVENT,
|
|
20952
22019
|
SUGGESTIONS_ACTIVITY_TYPE,
|
|
20953
22020
|
SessionStorageStore,
|
|
22021
|
+
THREAD_EVENT,
|
|
20954
22022
|
TOGGLE_EVENT,
|
|
20955
22023
|
TOOL_CALL_STATUS,
|
|
20956
22024
|
TOOL_DISPLAY,
|
|
22025
|
+
TOOL_GROUP_DISPLAY,
|
|
20957
22026
|
TOOL_OUTCOME,
|
|
20958
22027
|
ToolCallCard,
|
|
20959
22028
|
UNREAD_EVENT,
|