@artooi/ag-ui-web-component 0.21.0 → 0.23.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 +142 -1
- package/README.md +127 -12
- package/dist/ag-ui-web-component.bundle.js +459 -108
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/constants.d.ts +45 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +19 -4
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +619 -141
- package/dist/index.js.map +2 -2
- package/dist/ui/attachment_chips.d.ts +12 -1
- package/dist/ui/attachment_chips.d.ts.map +1 -1
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +8 -1
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +55 -0
- package/src/core/ag_ui_chat.ts +205 -62
- package/src/index.ts +2 -0
- package/src/ui/attachment_chips.ts +18 -6
- package/src/ui/attachment_tray.ts +2 -1
- package/src/ui/styles.ts +433 -80
- package/src/ui/ui_strings.ts +9 -1
- package/src/ui/voice_input.ts +7 -1
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var ELEMENT_TAG = "ag-ui-chat";
|
|
3
3
|
var SUBMIT_EVENT = "ag-ui-submit";
|
|
4
4
|
var TOGGLE_EVENT = "ag-ui-toggle";
|
|
5
|
+
var UNREAD_EVENT = "ag-ui-unread";
|
|
5
6
|
var STATE_EVENT = "ag-ui-state";
|
|
6
7
|
var ATTACHMENT_EVENT = "ag-ui-attachments";
|
|
7
8
|
var MESSAGE_ROLE = {
|
|
@@ -34,6 +35,15 @@ var TOOL_DISPLAY = {
|
|
|
34
35
|
};
|
|
35
36
|
var COMPACTION_ACTIVITY_TYPE = "compaction";
|
|
36
37
|
var LOAD_CAPABILITY_TOOL = "load_capability";
|
|
38
|
+
var ICON_SEND = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19.5V5m-6.5 6.5L12 5l6.5 6.5"/></svg>`;
|
|
39
|
+
var ICON_STOP = `<svg class="glyph glyph--solid" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2.5"/></svg>`;
|
|
40
|
+
var ICON_ATTACH = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8.5V15a5 5 0 0 1-10 0V7a3 3 0 0 1 6 0v7.5a1 1 0 0 1-2 0V8.5"/></svg>`;
|
|
41
|
+
var ICON_VOICE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V7a3 3 0 0 1 3-3z"/><path d="M5 11v1a7 7 0 0 0 14 0v-1"/><path d="M12 19v3"/></svg>`;
|
|
42
|
+
var ICON_LAUNCHER = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-5 4z"/></svg>`;
|
|
43
|
+
var ICON_FILE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg>`;
|
|
44
|
+
var ICON_FILE_IMAGE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5"/><circle cx="9" cy="10" r="1.5"/><path d="M4.5 17.5 9 13.5l3.5 3 3-2.5 4.5 4"/></svg>`;
|
|
45
|
+
var ICON_FILE_PDF = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><rect class="glyph--solid" x="7.5" y="13.5" width="9" height="4.5" rx="1"/></svg>`;
|
|
46
|
+
var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 13.5h7M8.5 17h4.5"/></svg>`;
|
|
37
47
|
|
|
38
48
|
// src/skills/fill_template.ts
|
|
39
49
|
var PLACEHOLDER_RE = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
@@ -509,6 +519,7 @@ var DEFAULT_UI_STRINGS = {
|
|
|
509
519
|
newChat: "New chat",
|
|
510
520
|
collapse: "Collapse",
|
|
511
521
|
expand: "Expand",
|
|
522
|
+
expandUnread: "Expand \u2014 {count} unread",
|
|
512
523
|
toggleTheme: "Toggle theme",
|
|
513
524
|
copyCode: "Copy",
|
|
514
525
|
copied: "Copied",
|
|
@@ -716,7 +727,7 @@ function renderChip(ref) {
|
|
|
716
727
|
const icon = document.createElement("span");
|
|
717
728
|
icon.className = "attachment-chip-icon";
|
|
718
729
|
icon.setAttribute("part", "attachment-chip-icon");
|
|
719
|
-
icon.
|
|
730
|
+
icon.innerHTML = iconFor(ref.mime);
|
|
720
731
|
icon.setAttribute("aria-hidden", "true");
|
|
721
732
|
const name = document.createElement("span");
|
|
722
733
|
name.className = "attachment-chip-name";
|
|
@@ -732,15 +743,15 @@ function renderChip(ref) {
|
|
|
732
743
|
}
|
|
733
744
|
function iconFor(mime) {
|
|
734
745
|
if (mime.startsWith("image/")) {
|
|
735
|
-
return
|
|
746
|
+
return ICON_FILE_IMAGE;
|
|
736
747
|
}
|
|
737
748
|
if (mime === "application/pdf") {
|
|
738
|
-
return
|
|
749
|
+
return ICON_FILE_PDF;
|
|
739
750
|
}
|
|
740
751
|
if (mime.startsWith("text/")) {
|
|
741
|
-
return
|
|
752
|
+
return ICON_FILE_TEXT;
|
|
742
753
|
}
|
|
743
|
-
return
|
|
754
|
+
return ICON_FILE;
|
|
744
755
|
}
|
|
745
756
|
function formatBytes(bytes) {
|
|
746
757
|
if (bytes < 1024) {
|
|
@@ -906,7 +917,7 @@ var AttachmentTray = class {
|
|
|
906
917
|
const icon = document.createElement("span");
|
|
907
918
|
icon.className = "attachment-chip-icon";
|
|
908
919
|
icon.setAttribute("part", "attachment-chip-icon");
|
|
909
|
-
icon.
|
|
920
|
+
icon.innerHTML = iconFor(item.file.type);
|
|
910
921
|
icon.setAttribute("aria-hidden", "true");
|
|
911
922
|
const name = document.createElement("span");
|
|
912
923
|
name.className = "attachment-chip-name";
|
|
@@ -4532,6 +4543,36 @@ var STYLES = `
|
|
|
4532
4543
|
--_icon-size: var(--ag-ui-icon-size, 22px);
|
|
4533
4544
|
--_icon-radius: var(--ag-ui-icon-radius, 4px);
|
|
4534
4545
|
|
|
4546
|
+
/* Composer \u2014 one bordered surface holding the field and its tool row. */
|
|
4547
|
+
--_composer-radius: var(--ag-ui-composer-radius, 14px);
|
|
4548
|
+
--_composer-max-height: var(--ag-ui-composer-max-height, 40vh);
|
|
4549
|
+
--_tool-btn-size: var(--ag-ui-tool-btn-size, 30px);
|
|
4550
|
+
--_send-size: var(--ag-ui-send-size, 30px);
|
|
4551
|
+
--_glyph-size: var(--ag-ui-glyph-size, 18px);
|
|
4552
|
+
--_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);
|
|
4553
|
+
|
|
4554
|
+
/* Unread badge on the launcher. */
|
|
4555
|
+
--_badge-bg: var(--ag-ui-badge-bg, var(--_danger));
|
|
4556
|
+
--_badge-fg: var(--ag-ui-badge-fg, #ffffff);
|
|
4557
|
+
--_badge-size: var(--ag-ui-badge-size, 18px);
|
|
4558
|
+
--_badge-font-size: var(--ag-ui-badge-font-size, 11px);
|
|
4559
|
+
|
|
4560
|
+
/* The floating launcher a collapsed widget shrinks to. */
|
|
4561
|
+
--_launcher-size: var(--ag-ui-launcher-size, 56px);
|
|
4562
|
+
--_launcher-bg: var(--ag-ui-launcher-bg, var(--_header-bg));
|
|
4563
|
+
--_launcher-fg: var(--ag-ui-launcher-fg, var(--_header-fg));
|
|
4564
|
+
--_launcher-radius: var(--ag-ui-launcher-radius, 50%);
|
|
4565
|
+
--_launcher-icon-size: var(--ag-ui-launcher-icon-size, 26px);
|
|
4566
|
+
--_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);
|
|
4567
|
+
|
|
4568
|
+
/* Motion. One duration and two curves drive every collapse, expand and
|
|
4569
|
+
slide-over, so the whole widget accelerates and settles as one thing.
|
|
4570
|
+
The default curve is a long-tailed ease-out (a fast start that decelerates
|
|
4571
|
+
into place); the pop curve overshoots slightly, for something arriving. */
|
|
4572
|
+
--_motion: var(--ag-ui-motion, 0.28s);
|
|
4573
|
+
--_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));
|
|
4574
|
+
--_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));
|
|
4575
|
+
|
|
4535
4576
|
/* Spacing \u2014 the density preset overrides these. */
|
|
4536
4577
|
--_space: var(--ag-ui-space, 10px);
|
|
4537
4578
|
--_pad: var(--ag-ui-pad, 16px);
|
|
@@ -4696,56 +4737,161 @@ var STYLES = `
|
|
|
4696
4737
|
--_height: var(--ag-ui-height, 100vh);
|
|
4697
4738
|
--_max-height: var(--ag-ui-max-height, 100vh);
|
|
4698
4739
|
--_radius: var(--ag-ui-radius, 0);
|
|
4699
|
-
transition: width
|
|
4740
|
+
transition: width var(--_motion) var(--_ease);
|
|
4700
4741
|
}
|
|
4701
4742
|
|
|
4702
4743
|
:host([placement="sidebar"][data-side="left"]) {
|
|
4703
4744
|
--_inset: var(--ag-ui-inset, 0 auto 0 0);
|
|
4704
4745
|
}
|
|
4705
4746
|
|
|
4747
|
+
/* The docked panel is pinned to the edge it docks against rather than filling
|
|
4748
|
+
the host as a flex child. Collapsing shrinks the host to the rail width, and
|
|
4749
|
+
a flex child would be squashed to 52px on the way out instead of sliding out
|
|
4750
|
+
at full width. */
|
|
4706
4751
|
:host([placement="sidebar"]) .chat {
|
|
4707
|
-
|
|
4752
|
+
position: absolute;
|
|
4753
|
+
inset: 0 0 0 auto;
|
|
4754
|
+
width: var(--_width);
|
|
4755
|
+
transform-origin: right center;
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4758
|
+
:host([placement="sidebar"][data-side="left"]) .chat {
|
|
4759
|
+
inset: 0 auto 0 0;
|
|
4760
|
+
transform-origin: left center;
|
|
4708
4761
|
}
|
|
4709
4762
|
|
|
4710
|
-
/* Collapsed sidebar: shrink the host to the rail width
|
|
4711
|
-
|
|
4712
|
-
wins regardless of source order. */
|
|
4763
|
+
/* Collapsed sidebar: shrink the host to the rail width and slide the panel out
|
|
4764
|
+
through the edge it docks against. Higher specificity than the generic
|
|
4765
|
+
collapse rules, so it wins regardless of source order. */
|
|
4713
4766
|
:host([placement="sidebar"][collapsed]) {
|
|
4714
4767
|
width: var(--_rail-width);
|
|
4715
4768
|
height: 100vh;
|
|
4716
4769
|
max-height: 100vh;
|
|
4717
4770
|
bottom: 0;
|
|
4771
|
+
pointer-events: auto;
|
|
4718
4772
|
}
|
|
4719
4773
|
|
|
4720
4774
|
:host([placement="sidebar"][collapsed]) .chat {
|
|
4721
|
-
|
|
4775
|
+
transform: translateX(100%);
|
|
4722
4776
|
}
|
|
4723
4777
|
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4778
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .chat {
|
|
4779
|
+
transform: translateX(-100%);
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4782
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4783
|
+
:host([placement="sidebar"]),
|
|
4784
|
+
:host([placement="sidebar"]) .chat {
|
|
4785
|
+
transition: none;
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
/* \u2500\u2500 Launcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4790
|
+
What a collapsed widget shrinks to: a round floating button by default, the
|
|
4791
|
+
sidebar's slim edge rail under that placement. A sibling of the panel, so it
|
|
4792
|
+
survives the panel being hidden, and the only part of a collapsed widget
|
|
4793
|
+
that takes pointer events. */
|
|
4794
|
+
.launcher {
|
|
4795
|
+
display: flex;
|
|
4796
|
+
position: absolute;
|
|
4797
|
+
inset: var(--_launcher-inset);
|
|
4798
|
+
align-items: center;
|
|
4799
|
+
justify-content: center;
|
|
4800
|
+
width: var(--_launcher-size);
|
|
4801
|
+
height: var(--_launcher-size);
|
|
4802
|
+
padding: 0;
|
|
4728
4803
|
border: none;
|
|
4804
|
+
border-radius: var(--_launcher-radius);
|
|
4805
|
+
background: var(--_launcher-bg);
|
|
4806
|
+
color: var(--_launcher-fg);
|
|
4807
|
+
box-shadow: var(--_shadow);
|
|
4729
4808
|
font: inherit;
|
|
4809
|
+
cursor: pointer;
|
|
4810
|
+
pointer-events: auto;
|
|
4811
|
+
opacity: 0;
|
|
4812
|
+
transform: scale(0.4);
|
|
4813
|
+
visibility: hidden;
|
|
4814
|
+
transition:
|
|
4815
|
+
opacity var(--_motion) var(--_ease),
|
|
4816
|
+
transform var(--_motion) var(--_ease-pop),
|
|
4817
|
+
visibility var(--_motion) var(--_ease);
|
|
4818
|
+
}
|
|
4819
|
+
|
|
4820
|
+
/* The launcher grows out of the corner the panel shrank into.
|
|
4821
|
+
It is laid out at rest rather than display:none, which is what lets it
|
|
4822
|
+
animate in *and* out: an element that was not rendered has no before-change
|
|
4823
|
+
style to transition from, and one whose display flips to none cannot
|
|
4824
|
+
transition at all. visibility keeps it unpaintable, untabbable and
|
|
4825
|
+
unclickable in between -- the expanded panel's own controls sit under it and
|
|
4826
|
+
must stay reachable. */
|
|
4827
|
+
:host([collapsed]) .launcher {
|
|
4828
|
+
opacity: 1;
|
|
4829
|
+
transform: none;
|
|
4830
|
+
visibility: visible;
|
|
4730
4831
|
}
|
|
4731
4832
|
|
|
4732
|
-
:host([
|
|
4733
|
-
|
|
4833
|
+
:host([collapsed]) .launcher:hover {
|
|
4834
|
+
transform: scale(1.06);
|
|
4835
|
+
}
|
|
4836
|
+
|
|
4837
|
+
:host([collapsed]) .launcher:active {
|
|
4838
|
+
transform: scale(0.94);
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
.launcher .icon-holder {
|
|
4842
|
+
width: var(--_launcher-icon-size);
|
|
4843
|
+
height: var(--_launcher-icon-size);
|
|
4844
|
+
}
|
|
4845
|
+
|
|
4846
|
+
/* The unread badge rides the launcher's top-right corner. The ring in the
|
|
4847
|
+
widget's own background is what separates it from the launcher underneath,
|
|
4848
|
+
whatever colour the host themes either one. */
|
|
4849
|
+
.launcher-badge {
|
|
4734
4850
|
position: absolute;
|
|
4851
|
+
top: -2px;
|
|
4852
|
+
right: -2px;
|
|
4853
|
+
display: inline-flex;
|
|
4854
|
+
align-items: center;
|
|
4855
|
+
justify-content: center;
|
|
4856
|
+
box-sizing: border-box;
|
|
4857
|
+
min-width: var(--_badge-size);
|
|
4858
|
+
height: var(--_badge-size);
|
|
4859
|
+
padding: 0 5px;
|
|
4860
|
+
border-radius: 999px;
|
|
4861
|
+
background: var(--_badge-bg);
|
|
4862
|
+
color: var(--_badge-fg);
|
|
4863
|
+
font-size: var(--_badge-font-size);
|
|
4864
|
+
font-weight: 600;
|
|
4865
|
+
line-height: 1;
|
|
4866
|
+
box-shadow: 0 0 0 2px var(--_bg);
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
.launcher-badge[hidden] {
|
|
4870
|
+
display: none;
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
/* The sidebar collapses to an edge rail instead: full height, square, flush
|
|
4874
|
+
against the dock. It slides in with the panel rather than popping. */
|
|
4875
|
+
:host([placement="sidebar"][collapsed]) .launcher {
|
|
4735
4876
|
inset: 0;
|
|
4877
|
+
width: auto;
|
|
4878
|
+
height: auto;
|
|
4736
4879
|
align-items: flex-start;
|
|
4737
|
-
justify-content: center;
|
|
4738
4880
|
padding-top: 16px;
|
|
4739
4881
|
border: 1px solid var(--_border);
|
|
4882
|
+
border-radius: 0;
|
|
4740
4883
|
background: var(--_header-bg);
|
|
4741
4884
|
color: var(--_header-fg);
|
|
4742
|
-
|
|
4885
|
+
box-shadow: none;
|
|
4886
|
+
transform: none;
|
|
4743
4887
|
}
|
|
4744
4888
|
|
|
4889
|
+
/* Every transition in this file is timed by --_motion, so collapsing it to a
|
|
4890
|
+
frame is the whole reduced-motion story: states still change (and display
|
|
4891
|
+
still flips at the end of its discrete transition), nothing travels. */
|
|
4745
4892
|
@media (prefers-reduced-motion: reduce) {
|
|
4746
|
-
:host
|
|
4747
|
-
|
|
4748
|
-
transition: none;
|
|
4893
|
+
:host {
|
|
4894
|
+
--_motion: var(--ag-ui-motion, 0.001s);
|
|
4749
4895
|
}
|
|
4750
4896
|
}
|
|
4751
4897
|
|
|
@@ -4836,27 +4982,78 @@ var STYLES = `
|
|
|
4836
4982
|
background: rgba(255, 255, 255, 0.18);
|
|
4837
4983
|
}
|
|
4838
4984
|
|
|
4839
|
-
/*
|
|
4840
|
-
|
|
4985
|
+
/* \u2500\u2500 Collapse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4986
|
+
Collapsing shrinks the widget to the round floating launcher: the panel
|
|
4987
|
+
scales down toward the launcher's corner and fades out, the launcher pops in
|
|
4988
|
+
from that same point. Both halves are transform and opacity only, so the
|
|
4989
|
+
morph runs on the compositor and never reflows the host page.
|
|
4990
|
+
|
|
4991
|
+
The host box keeps its expanded size. Animating it would animate layout, and
|
|
4992
|
+
a dragged --ag-ui-width would then fight the launcher's own size. Nothing
|
|
4993
|
+
paints there once the panel is gone, so the box only has to stop swallowing
|
|
4994
|
+
clicks: pointer events go to none, and the launcher takes them back.
|
|
4995
|
+
|
|
4996
|
+
Two placements collapse to something else and restore what they need:
|
|
4997
|
+
"sidebar" slides to its rail (below), while "embedded" and "page" keep the
|
|
4998
|
+
original header bar \u2014 embedded is laid out by the host page, where a
|
|
4999
|
+
floating circle would escape the layout, and page is a full-screen route
|
|
5000
|
+
with no corner to float in. */
|
|
4841
5001
|
:host([collapsed]) {
|
|
5002
|
+
pointer-events: none;
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
:host([collapsed]) .chat {
|
|
5006
|
+
opacity: 0;
|
|
5007
|
+
transform: scale(0.94);
|
|
5008
|
+
visibility: hidden;
|
|
5009
|
+
}
|
|
5010
|
+
|
|
5011
|
+
/* visibility is what keeps the panel out of the tab order and the a11y tree at
|
|
5012
|
+
rest without display:none killing the transition. It interpolates so that any
|
|
5013
|
+
progress below 1 still counts as visible: the panel stays on screen for the
|
|
5014
|
+
whole collapse and flips hidden exactly at the end, and on expand it is
|
|
5015
|
+
visible from the first frame. */
|
|
5016
|
+
.chat {
|
|
5017
|
+
transform-origin: bottom right;
|
|
5018
|
+
transition:
|
|
5019
|
+
opacity var(--_motion) var(--_ease),
|
|
5020
|
+
transform var(--_motion) var(--_ease),
|
|
5021
|
+
visibility var(--_motion) var(--_ease);
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
:host([placement="bottom-left"]) .chat {
|
|
5025
|
+
transform-origin: bottom left;
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
/* The two in-flow placements keep the original collapse: hide the body, let
|
|
5029
|
+
the host shrink to the header bar. */
|
|
5030
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) {
|
|
4842
5031
|
height: auto;
|
|
4843
5032
|
max-height: none;
|
|
5033
|
+
pointer-events: auto;
|
|
4844
5034
|
}
|
|
4845
5035
|
|
|
4846
|
-
:host([collapsed]) .
|
|
4847
|
-
:
|
|
4848
|
-
:
|
|
4849
|
-
:
|
|
4850
|
-
:host([collapsed]) .skill-hint {
|
|
4851
|
-
display: none;
|
|
5036
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .chat {
|
|
5037
|
+
opacity: 1;
|
|
5038
|
+
transform: none;
|
|
5039
|
+
visibility: visible;
|
|
4852
5040
|
}
|
|
4853
5041
|
|
|
4854
|
-
/*
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
:host([collapsed][placement="
|
|
4859
|
-
|
|
5042
|
+
/* These two keep the header bar, so the launcher must stay out of the way \u2014
|
|
5043
|
+
an embedded host is position: static, which would otherwise leave an
|
|
5044
|
+
absolutely-positioned circle to escape the layout entirely and land against
|
|
5045
|
+
whatever the page happens to position. */
|
|
5046
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
|
|
5047
|
+
visibility: hidden;
|
|
5048
|
+
opacity: 0;
|
|
5049
|
+
}
|
|
5050
|
+
|
|
5051
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .messages,
|
|
5052
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .input-row,
|
|
5053
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-chips,
|
|
5054
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-palette,
|
|
5055
|
+
:host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-hint {
|
|
5056
|
+
display: none;
|
|
4860
5057
|
}
|
|
4861
5058
|
|
|
4862
5059
|
.messages {
|
|
@@ -5529,102 +5726,187 @@ var STYLES = `
|
|
|
5529
5726
|
opacity: 0.35;
|
|
5530
5727
|
}
|
|
5531
5728
|
|
|
5729
|
+
/* \u2500\u2500 Composer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5730
|
+
One surface owns the border, the background and the focus ring; the field and
|
|
5731
|
+
its tool row sit inside it. The row used to be four siblings stretched to the
|
|
5732
|
+
textarea's height, which gave a paperclip the same visual weight as the field
|
|
5733
|
+
and turned Send into a full-height slab. */
|
|
5532
5734
|
.input-row {
|
|
5533
5735
|
display: flex;
|
|
5534
|
-
gap: 8px;
|
|
5535
5736
|
padding: 12px;
|
|
5536
5737
|
border-top: 1px solid var(--_border);
|
|
5537
5738
|
}
|
|
5538
5739
|
|
|
5539
|
-
.
|
|
5740
|
+
.composer {
|
|
5540
5741
|
flex: 1;
|
|
5541
|
-
|
|
5742
|
+
min-width: 0;
|
|
5743
|
+
display: flex;
|
|
5744
|
+
flex-direction: column;
|
|
5745
|
+
gap: 4px;
|
|
5746
|
+
padding: 6px 6px 6px 10px;
|
|
5542
5747
|
background: var(--_input-bg);
|
|
5543
5748
|
border: 1px solid var(--_border);
|
|
5544
|
-
border-radius:
|
|
5545
|
-
|
|
5749
|
+
border-radius: var(--_composer-radius);
|
|
5750
|
+
transition: border-color var(--_motion) var(--_ease);
|
|
5751
|
+
}
|
|
5752
|
+
|
|
5753
|
+
.composer:focus-within {
|
|
5754
|
+
border-color: var(--_accent);
|
|
5755
|
+
}
|
|
5756
|
+
|
|
5757
|
+
/* The field grows with its content (sized by #autoGrow) up to the ceiling,
|
|
5758
|
+
then scrolls. border-box keeps that measurement stable: with content-box the
|
|
5759
|
+
padding would be added to every scrollHeight read and the field would creep
|
|
5760
|
+
taller on each keystroke. */
|
|
5761
|
+
.input {
|
|
5762
|
+
box-sizing: border-box;
|
|
5763
|
+
resize: none;
|
|
5764
|
+
max-height: var(--_composer-max-height);
|
|
5765
|
+
overflow-y: auto;
|
|
5766
|
+
padding: 6px 4px 2px;
|
|
5767
|
+
background: transparent;
|
|
5768
|
+
border: none;
|
|
5546
5769
|
font: inherit;
|
|
5547
5770
|
color: inherit;
|
|
5548
5771
|
outline: none;
|
|
5549
5772
|
}
|
|
5550
5773
|
|
|
5551
|
-
.
|
|
5552
|
-
|
|
5774
|
+
.composer-tools {
|
|
5775
|
+
display: flex;
|
|
5776
|
+
align-items: center;
|
|
5777
|
+
gap: 2px;
|
|
5553
5778
|
}
|
|
5554
5779
|
|
|
5555
|
-
.
|
|
5780
|
+
/* Icon buttons: quiet at rest, so the field is what the eye lands on. */
|
|
5781
|
+
.attach-btn,
|
|
5782
|
+
.voice-btn {
|
|
5783
|
+
display: inline-flex;
|
|
5784
|
+
align-items: center;
|
|
5785
|
+
justify-content: center;
|
|
5786
|
+
width: var(--_tool-btn-size);
|
|
5787
|
+
height: var(--_tool-btn-size);
|
|
5788
|
+
padding: 0;
|
|
5556
5789
|
border: none;
|
|
5557
5790
|
border-radius: 8px;
|
|
5558
|
-
|
|
5791
|
+
background: transparent;
|
|
5792
|
+
color: var(--_muted);
|
|
5793
|
+
font: inherit;
|
|
5794
|
+
line-height: 1;
|
|
5795
|
+
cursor: pointer;
|
|
5796
|
+
transition:
|
|
5797
|
+
background-color var(--_motion) var(--_ease),
|
|
5798
|
+
color var(--_motion) var(--_ease);
|
|
5799
|
+
}
|
|
5800
|
+
|
|
5801
|
+
.attach-btn:hover,
|
|
5802
|
+
.voice-btn:hover {
|
|
5803
|
+
background: var(--_hover);
|
|
5804
|
+
color: var(--_fg);
|
|
5805
|
+
}
|
|
5806
|
+
|
|
5807
|
+
.attach-btn:disabled,
|
|
5808
|
+
.voice-btn:disabled {
|
|
5809
|
+
cursor: default;
|
|
5810
|
+
opacity: 0.6;
|
|
5811
|
+
}
|
|
5812
|
+
|
|
5813
|
+
/* Send closes the row on the right: a circle, the only filled control in the
|
|
5814
|
+
composer, so "the thing that acts" reads at a glance. */
|
|
5815
|
+
.send {
|
|
5816
|
+
margin-left: auto;
|
|
5817
|
+
display: inline-flex;
|
|
5818
|
+
align-items: center;
|
|
5819
|
+
justify-content: center;
|
|
5820
|
+
flex: none;
|
|
5821
|
+
width: var(--_send-size);
|
|
5822
|
+
height: var(--_send-size);
|
|
5823
|
+
padding: 0;
|
|
5824
|
+
border: none;
|
|
5825
|
+
border-radius: 50%;
|
|
5559
5826
|
background: var(--_accent);
|
|
5560
5827
|
color: #ffffff;
|
|
5561
5828
|
font: inherit;
|
|
5562
|
-
|
|
5829
|
+
line-height: 1;
|
|
5563
5830
|
cursor: pointer;
|
|
5831
|
+
transition:
|
|
5832
|
+
background-color var(--_motion) var(--_ease),
|
|
5833
|
+
transform var(--_motion) var(--_ease-pop);
|
|
5834
|
+
}
|
|
5835
|
+
|
|
5836
|
+
.send:hover {
|
|
5837
|
+
transform: scale(1.08);
|
|
5838
|
+
}
|
|
5839
|
+
|
|
5840
|
+
.send:active {
|
|
5841
|
+
transform: scale(0.92);
|
|
5564
5842
|
}
|
|
5565
5843
|
|
|
5566
5844
|
.send:disabled {
|
|
5567
5845
|
opacity: 0.5;
|
|
5568
5846
|
cursor: default;
|
|
5847
|
+
transform: none;
|
|
5569
5848
|
}
|
|
5570
5849
|
|
|
5571
|
-
/* The composer button doubles as the Stop control while a run is in flight
|
|
5850
|
+
/* The composer button doubles as the Stop control while a run is in flight \u2014
|
|
5851
|
+
same circle, different glyph, so nothing moves when a run starts. */
|
|
5572
5852
|
.send[data-state="running"] {
|
|
5573
5853
|
background: var(--_muted);
|
|
5574
5854
|
}
|
|
5575
5855
|
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
border: 1px solid var(--_border);
|
|
5580
|
-
border-radius: 8px;
|
|
5581
|
-
padding: 0 10px;
|
|
5582
|
-
background: var(--_input-bg);
|
|
5583
|
-
color: inherit;
|
|
5584
|
-
font: inherit;
|
|
5585
|
-
cursor: pointer;
|
|
5586
|
-
}
|
|
5587
|
-
|
|
5588
|
-
.attach-btn:hover {
|
|
5589
|
-
border-color: var(--_accent);
|
|
5856
|
+
.send[data-state="idle"] .send-stop,
|
|
5857
|
+
.send[data-state="running"] .send-send {
|
|
5858
|
+
display: none;
|
|
5590
5859
|
}
|
|
5591
5860
|
|
|
5592
|
-
|
|
5593
|
-
|
|
5861
|
+
/* Glyphs \u2014 one class, painted from the button's own colour so every state
|
|
5862
|
+
(hover, recording, running) carries the icon with it. */
|
|
5863
|
+
.glyph {
|
|
5864
|
+
width: var(--_glyph-size);
|
|
5865
|
+
height: var(--_glyph-size);
|
|
5866
|
+
fill: none;
|
|
5867
|
+
stroke: currentColor;
|
|
5868
|
+
stroke-width: var(--_glyph-stroke);
|
|
5869
|
+
stroke-linecap: round;
|
|
5870
|
+
stroke-linejoin: round;
|
|
5594
5871
|
}
|
|
5595
5872
|
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5873
|
+
.glyph--solid {
|
|
5874
|
+
fill: currentColor;
|
|
5875
|
+
stroke: none;
|
|
5599
5876
|
}
|
|
5600
5877
|
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
color: inherit;
|
|
5607
|
-
font: inherit;
|
|
5608
|
-
cursor: pointer;
|
|
5878
|
+
/* In an icon holder (header brand, launcher) the glyph takes the holder's size
|
|
5879
|
+
rather than the composer's. */
|
|
5880
|
+
.icon-holder .glyph {
|
|
5881
|
+
width: 100%;
|
|
5882
|
+
height: 100%;
|
|
5609
5883
|
}
|
|
5610
5884
|
|
|
5611
|
-
|
|
5612
|
-
|
|
5885
|
+
/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5886
|
+
/* The picker button sits in the composer's tool row; hidden until
|
|
5887
|
+
data-attachments-url. */
|
|
5888
|
+
.attach-input {
|
|
5889
|
+
display: none;
|
|
5613
5890
|
}
|
|
5614
5891
|
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5892
|
+
/* The mic button's mount point; filled only once #wireVoice mounts the
|
|
5893
|
+
control. */
|
|
5894
|
+
.voice-slot {
|
|
5895
|
+
display: contents;
|
|
5618
5896
|
}
|
|
5619
5897
|
|
|
5620
5898
|
/* Recording: a red tint + a gentle pulse so it's clearly "live". */
|
|
5621
5899
|
.voice-btn[data-state="recording"] {
|
|
5622
|
-
border-color: var(--_danger);
|
|
5623
5900
|
background: var(--_danger);
|
|
5624
5901
|
color: #ffffff;
|
|
5625
5902
|
animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;
|
|
5626
5903
|
}
|
|
5627
5904
|
|
|
5905
|
+
.voice-btn[data-state="recording"]:hover {
|
|
5906
|
+
background: var(--_danger);
|
|
5907
|
+
color: #ffffff;
|
|
5908
|
+
}
|
|
5909
|
+
|
|
5628
5910
|
@keyframes ag-ui-voice-pulse {
|
|
5629
5911
|
0%, 100% { opacity: 1; }
|
|
5630
5912
|
50% { opacity: 0.6; }
|
|
@@ -5641,11 +5923,25 @@ var STYLES = `
|
|
|
5641
5923
|
display: contents;
|
|
5642
5924
|
}
|
|
5643
5925
|
|
|
5926
|
+
/* The padding is the only separation the tray gets: its slot is display:
|
|
5927
|
+
contents, so the tray is a direct child of the .chat column, and that column
|
|
5928
|
+
sets no gap. The bottom value keeps a chip off the composer's top edge.
|
|
5929
|
+
The inline value here is only the default: the page placement overrides
|
|
5930
|
+
padding-inline further down to compute its reading-column gutter, and wins
|
|
5931
|
+
on specificity whichever way this declaration is written. Read the two
|
|
5932
|
+
together before changing either. */
|
|
5644
5933
|
.attachment-tray {
|
|
5645
5934
|
display: flex;
|
|
5646
5935
|
flex-wrap: wrap;
|
|
5647
5936
|
gap: 6px;
|
|
5648
|
-
padding: 8px 12px
|
|
5937
|
+
padding: 8px 12px;
|
|
5938
|
+
}
|
|
5939
|
+
|
|
5940
|
+
/* The tray sets the hidden property while empty, and an author display beats
|
|
5941
|
+
the UA stylesheet's rule for it, so without this the empty tray keeps
|
|
5942
|
+
laying out and its padding is permanent dead space above the composer. */
|
|
5943
|
+
.attachment-tray[hidden] {
|
|
5944
|
+
display: none;
|
|
5649
5945
|
}
|
|
5650
5946
|
|
|
5651
5947
|
.attachment-chips {
|
|
@@ -5681,15 +5977,29 @@ var STYLES = `
|
|
|
5681
5977
|
overflow-wrap: anywhere;
|
|
5682
5978
|
}
|
|
5683
5979
|
|
|
5980
|
+
/* A chip carries its own text colour because it carries its own background.
|
|
5981
|
+
The same chip renders in two places with opposite inherited colours: in the
|
|
5982
|
+
composer tray it inherits the panel's, but on a sent user bubble it inherits
|
|
5983
|
+
the user foreground, which is white on the stock light theme. Against the
|
|
5984
|
+
assistant surface underneath that was a 1.13:1 filename \u2014 invisible, and only
|
|
5985
|
+
the size stayed legible because it sets its own muted colour. Pair the colour
|
|
5986
|
+
with the background it belongs to, and both placements read the same.
|
|
5987
|
+
Overridden below for an errored chip, which must keep its red. */
|
|
5684
5988
|
.attachment-chip {
|
|
5685
5989
|
display: inline-flex;
|
|
5686
5990
|
align-items: center;
|
|
5687
5991
|
gap: 6px;
|
|
5992
|
+
/* Without this the 100% below caps the content box, so the chip still
|
|
5993
|
+
overflows its container by its own padding and border. Invisible while a
|
|
5994
|
+
character cap kept names short; routine once the container is what bounds
|
|
5995
|
+
the name. */
|
|
5996
|
+
box-sizing: border-box;
|
|
5688
5997
|
max-width: 100%;
|
|
5689
5998
|
padding: 4px 8px;
|
|
5690
5999
|
border: 1px solid var(--_border);
|
|
5691
6000
|
border-radius: 999px;
|
|
5692
6001
|
background: var(--_assistant-bg);
|
|
6002
|
+
color: var(--_text);
|
|
5693
6003
|
font-size: 0.85em;
|
|
5694
6004
|
position: relative;
|
|
5695
6005
|
}
|
|
@@ -5699,11 +6009,31 @@ var STYLES = `
|
|
|
5699
6009
|
color: var(--_danger);
|
|
5700
6010
|
}
|
|
5701
6011
|
|
|
6012
|
+
/* The type mark. Painted from currentColor, so it carries the chip's state
|
|
6013
|
+
with it and turns red along with an errored one; muted by opacity rather
|
|
6014
|
+
than a colour, which is what keeps that true. Sized from the chip's own text
|
|
6015
|
+
rather than --ag-ui-glyph-size, the way an icon holder's glyph takes the
|
|
6016
|
+
holder's size: the composer's 18px buttons would make a chip button-height. */
|
|
6017
|
+
.attachment-chip-icon {
|
|
6018
|
+
display: inline-flex;
|
|
6019
|
+
flex: none;
|
|
6020
|
+
opacity: 0.75;
|
|
6021
|
+
}
|
|
6022
|
+
|
|
6023
|
+
.attachment-chip-icon .glyph {
|
|
6024
|
+
width: 1.25em;
|
|
6025
|
+
height: 1.25em;
|
|
6026
|
+
}
|
|
6027
|
+
|
|
6028
|
+
/* No character cap: the chip is already max-width 100%, so its container is
|
|
6029
|
+
what bounds the name, and a fixed cap only truncated names the chip had room
|
|
6030
|
+
for. min-width: 0 is what lets the flex item shrink past its content, so the
|
|
6031
|
+
ellipsis appears at the container edge instead of the chip overflowing. */
|
|
5702
6032
|
.attachment-chip-name {
|
|
5703
6033
|
overflow: hidden;
|
|
5704
6034
|
text-overflow: ellipsis;
|
|
5705
6035
|
white-space: nowrap;
|
|
5706
|
-
|
|
6036
|
+
min-width: 0;
|
|
5707
6037
|
}
|
|
5708
6038
|
|
|
5709
6039
|
.attachment-chip-size {
|
|
@@ -6015,16 +6345,35 @@ var STYLES = `
|
|
|
6015
6345
|
color: var(--_danger);
|
|
6016
6346
|
}
|
|
6017
6347
|
|
|
6018
|
-
/* Chat-history drawer \u2014 a slide-over within the chat panel.
|
|
6348
|
+
/* Chat-history drawer \u2014 a slide-over within the chat panel.
|
|
6349
|
+
The hidden attribute stays the single source of truth for open/closed (no
|
|
6350
|
+
JS-driven animation state): display is transitioned discretely, so on close
|
|
6351
|
+
the drawer stays displayed for the whole exit and is removed only at the end,
|
|
6352
|
+
which is what lets the backdrop fade and the panel slide out. */
|
|
6019
6353
|
.drawer {
|
|
6020
6354
|
position: absolute;
|
|
6021
6355
|
inset: 0;
|
|
6022
6356
|
z-index: 5;
|
|
6023
6357
|
display: flex;
|
|
6358
|
+
transition: visibility var(--_motion) var(--_ease);
|
|
6024
6359
|
}
|
|
6025
6360
|
|
|
6361
|
+
/* Closed. The overlay keeps its box (display, not none) so the backdrop and
|
|
6362
|
+
panel inside it stay rendered and can transition both ways; visibility is
|
|
6363
|
+
what takes the whole subtree out of the tab order, the a11y tree and hit
|
|
6364
|
+
testing at rest, and it holds off until the slide has finished. */
|
|
6026
6365
|
.drawer[hidden] {
|
|
6027
|
-
display:
|
|
6366
|
+
display: flex;
|
|
6367
|
+
visibility: hidden;
|
|
6368
|
+
pointer-events: none;
|
|
6369
|
+
}
|
|
6370
|
+
|
|
6371
|
+
.drawer[hidden] .drawer-backdrop {
|
|
6372
|
+
opacity: 0;
|
|
6373
|
+
}
|
|
6374
|
+
|
|
6375
|
+
.drawer[hidden] .drawer-panel {
|
|
6376
|
+
transform: translateX(-100%);
|
|
6028
6377
|
}
|
|
6029
6378
|
|
|
6030
6379
|
.checkpoints {
|
|
@@ -6042,10 +6391,21 @@ var STYLES = `
|
|
|
6042
6391
|
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
6043
6392
|
max-height: 60%;
|
|
6044
6393
|
overflow-y: auto;
|
|
6394
|
+
transform-origin: top center;
|
|
6395
|
+
transition:
|
|
6396
|
+
opacity var(--_motion) var(--_ease),
|
|
6397
|
+
transform var(--_motion) var(--_ease),
|
|
6398
|
+
visibility var(--_motion) var(--_ease);
|
|
6045
6399
|
}
|
|
6046
6400
|
|
|
6401
|
+
/* Same idiom as the drawer: laid out at rest, hidden by visibility, so the
|
|
6402
|
+
popover can animate open and closed. */
|
|
6047
6403
|
.checkpoints[hidden] {
|
|
6048
|
-
display:
|
|
6404
|
+
display: flex;
|
|
6405
|
+
visibility: hidden;
|
|
6406
|
+
pointer-events: none;
|
|
6407
|
+
opacity: 0;
|
|
6408
|
+
transform: scale(0.96) translateY(-6px);
|
|
6049
6409
|
}
|
|
6050
6410
|
|
|
6051
6411
|
.checkpoints-title {
|
|
@@ -6107,6 +6467,8 @@ var STYLES = `
|
|
|
6107
6467
|
position: absolute;
|
|
6108
6468
|
inset: 0;
|
|
6109
6469
|
background: rgba(20, 20, 50, 0.32);
|
|
6470
|
+
opacity: 1;
|
|
6471
|
+
transition: opacity var(--_motion) var(--_ease);
|
|
6110
6472
|
}
|
|
6111
6473
|
|
|
6112
6474
|
.drawer-panel {
|
|
@@ -6119,6 +6481,8 @@ var STYLES = `
|
|
|
6119
6481
|
border-right: 1px solid var(--_border);
|
|
6120
6482
|
box-shadow: var(--_shadow);
|
|
6121
6483
|
overflow: hidden;
|
|
6484
|
+
transform: none;
|
|
6485
|
+
transition: transform var(--_motion) var(--_ease);
|
|
6122
6486
|
}
|
|
6123
6487
|
|
|
6124
6488
|
.drawer-header {
|
|
@@ -6748,7 +7112,10 @@ var VoiceInput = class {
|
|
|
6748
7112
|
this.element.type = "button";
|
|
6749
7113
|
this.element.className = "voice-btn";
|
|
6750
7114
|
this.element.setAttribute("part", "voice-button");
|
|
6751
|
-
|
|
7115
|
+
const glyph = document.createElement("slot");
|
|
7116
|
+
glyph.name = "icon-voice";
|
|
7117
|
+
glyph.innerHTML = ICON_VOICE;
|
|
7118
|
+
this.element.append(glyph);
|
|
6752
7119
|
this.#setState("idle");
|
|
6753
7120
|
this.element.addEventListener("click", () => {
|
|
6754
7121
|
void this.toggle();
|
|
@@ -7742,14 +8109,17 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7742
8109
|
...this.#toolRegistry.tools()
|
|
7743
8110
|
];
|
|
7744
8111
|
/**
|
|
7745
|
-
* Per-run context provider. Defaults to the compact page map
|
|
7746
|
-
* {@link getPageMap} provider is set and {@link autoInjectPageMap} is on
|
|
7747
|
-
*
|
|
7748
|
-
*
|
|
8112
|
+
* Per-run context provider. Defaults to the compact page map, when a
|
|
8113
|
+
* {@link getPageMap} provider is set and {@link autoInjectPageMap} is on.
|
|
8114
|
+
*
|
|
8115
|
+
* It used to append a one-line manifest of the message's attachments too.
|
|
8116
|
+
* The server now derives that manifest from the refs riding the messages, so
|
|
8117
|
+
* the client's copy only duplicated it, on exactly the turn a file was
|
|
8118
|
+
* attached. Attachments still reach the agent; they reach it through the
|
|
8119
|
+
* message, which is where they were already.
|
|
7749
8120
|
*/
|
|
7750
8121
|
getContext = () => [
|
|
7751
|
-
...createPageMapContext(this.getPageMap, this.autoInjectPageMap)
|
|
7752
|
-
...this.#attachmentContext()
|
|
8122
|
+
...createPageMapContext(this.getPageMap, this.autoInjectPageMap)
|
|
7753
8123
|
];
|
|
7754
8124
|
/**
|
|
7755
8125
|
* Navigable routes the agent can jump to via the built-in `route.*` tools.
|
|
@@ -7865,8 +8235,12 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7865
8235
|
#attachSlot;
|
|
7866
8236
|
/** Optional built-in header theme toggle; shown only with `data-theme-toggle`. */
|
|
7867
8237
|
#themeToggle;
|
|
7868
|
-
/**
|
|
7869
|
-
#
|
|
8238
|
+
/** What the collapsed widget shrinks to: the floating launcher, or the sidebar rail. */
|
|
8239
|
+
#launcher;
|
|
8240
|
+
/** The launcher's unread badge; hidden at zero, and when the host opts out. */
|
|
8241
|
+
#badge;
|
|
8242
|
+
// Answers that finished while the widget was collapsed. Expanding clears it.
|
|
8243
|
+
#unread = 0;
|
|
7870
8244
|
/** Empty-state region at the top of the message list; hidden once anything renders. */
|
|
7871
8245
|
#emptyWrap;
|
|
7872
8246
|
/** Upload tray; created on connect only when `data-attachments-url` is set. */
|
|
@@ -7877,8 +8251,6 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7877
8251
|
#voice = null;
|
|
7878
8252
|
/** Whether the element is currently in the DOM; gates the connect-time warning. */
|
|
7879
8253
|
#connected = false;
|
|
7880
|
-
/** Refs attached to the message currently being sent (the context manifest). */
|
|
7881
|
-
#runAttachments = [];
|
|
7882
8254
|
#client = null;
|
|
7883
8255
|
// Seed for the next client. Once one exists it owns the live value (the
|
|
7884
8256
|
// agent applies STATE_SNAPSHOT / STATE_DELTA into it), so this is only the
|
|
@@ -7939,7 +8311,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7939
8311
|
this.#attachSlot = document.createElement("div");
|
|
7940
8312
|
this.#voiceSlot = document.createElement("span");
|
|
7941
8313
|
this.#themeToggle = document.createElement("button");
|
|
7942
|
-
this.#
|
|
8314
|
+
this.#launcher = document.createElement("button");
|
|
8315
|
+
this.#badge = document.createElement("span");
|
|
7943
8316
|
this.#emptyWrap = document.createElement("div");
|
|
7944
8317
|
this.#skillsMenu = new SkillsMenu((skill) => this.#applySkill(skill));
|
|
7945
8318
|
this.#drawer = new ThreadDrawer({
|
|
@@ -8002,6 +8375,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8002
8375
|
return;
|
|
8003
8376
|
}
|
|
8004
8377
|
this.#input.value = "";
|
|
8378
|
+
this.#autoGrow();
|
|
8005
8379
|
const endpoint = verb === "resume" ? index.resumeUrl(runId) : index.forkUrl(runId);
|
|
8006
8380
|
const agent = this.agentFactory({
|
|
8007
8381
|
endpoint,
|
|
@@ -8332,7 +8706,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8332
8706
|
if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
|
|
8333
8707
|
this.setAttribute("collapsed", "");
|
|
8334
8708
|
}
|
|
8335
|
-
this.#
|
|
8709
|
+
this.#syncLauncher();
|
|
8336
8710
|
this.#initSkills();
|
|
8337
8711
|
if (this.#storageNs !== "" && this.conversationStore instanceof SessionStorageStore) {
|
|
8338
8712
|
this.conversationStore = new SessionStorageStore(this.#storageNs);
|
|
@@ -8563,22 +8937,6 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8563
8937
|
}
|
|
8564
8938
|
});
|
|
8565
8939
|
}
|
|
8566
|
-
/** The one-line manifest of the message's attachments, for the run context. */
|
|
8567
|
-
#attachmentContext() {
|
|
8568
|
-
if (this.#runAttachments.length === 0) {
|
|
8569
|
-
return [];
|
|
8570
|
-
}
|
|
8571
|
-
const lines = this.#runAttachments.map(
|
|
8572
|
-
(ref) => `- ${ref.name} (id: ${ref.id}, ${ref.mime || "unknown type"}, ${ref.size} bytes)`
|
|
8573
|
-
);
|
|
8574
|
-
return [
|
|
8575
|
-
{
|
|
8576
|
-
description: "Files the user attached to this message",
|
|
8577
|
-
value: `${lines.join("\n")}
|
|
8578
|
-
Use the read_attachment tool with an id to read a file's contents.`
|
|
8579
|
-
}
|
|
8580
|
-
];
|
|
8581
|
-
}
|
|
8582
8940
|
/**
|
|
8583
8941
|
* When `data-threads-url` is set, route thread enumeration / load / rename /
|
|
8584
8942
|
* delete through that server endpoint (wrapping the current store as the
|
|
@@ -8690,12 +9048,14 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8690
9048
|
this.#skillHint.textContent = this.#strings.skillNeeds.replace("{title}", skill.title).replace("{fields}", missing.join(", "));
|
|
8691
9049
|
this.#skillHint.hidden = false;
|
|
8692
9050
|
this.#input.value = text2;
|
|
9051
|
+
this.#autoGrow();
|
|
8693
9052
|
this.#input.focus();
|
|
8694
9053
|
this.#selectFirstPlaceholder(text2);
|
|
8695
9054
|
return;
|
|
8696
9055
|
}
|
|
8697
9056
|
this.#skillHint.hidden = true;
|
|
8698
9057
|
this.#input.value = text2;
|
|
9058
|
+
this.#autoGrow();
|
|
8699
9059
|
if (skill.sendImmediately === false) {
|
|
8700
9060
|
this.#input.focus();
|
|
8701
9061
|
return;
|
|
@@ -8733,7 +9093,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8733
9093
|
this.removeAttribute("collapsed");
|
|
8734
9094
|
}
|
|
8735
9095
|
sessionStorage.setItem(this.#storageKey(COLLAPSED_KEY), collapsed ? "1" : "0");
|
|
8736
|
-
this.#
|
|
9096
|
+
this.#setUnread(0);
|
|
8737
9097
|
this.dispatchEvent(
|
|
8738
9098
|
new CustomEvent(TOGGLE_EVENT, {
|
|
8739
9099
|
detail: { collapsed },
|
|
@@ -8742,6 +9102,15 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8742
9102
|
})
|
|
8743
9103
|
);
|
|
8744
9104
|
}
|
|
9105
|
+
/**
|
|
9106
|
+
* Answers that finished while the widget was collapsed, and that the user has
|
|
9107
|
+
* therefore not seen. Expanding (or {@link newChat}) clears it. The launcher's
|
|
9108
|
+
* badge renders this; {@link UNREAD_EVENT} announces every change, so a host
|
|
9109
|
+
* chrome can render its own instead.
|
|
9110
|
+
*/
|
|
9111
|
+
get unread() {
|
|
9112
|
+
return this.#unread;
|
|
9113
|
+
}
|
|
8745
9114
|
/** Flip the collapsed state. Bound to the built-in header toggle. */
|
|
8746
9115
|
toggleCollapsed() {
|
|
8747
9116
|
this.setCollapsed(!this.collapsed);
|
|
@@ -8935,6 +9304,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8935
9304
|
this.#resetState();
|
|
8936
9305
|
this.#threadId = this.conversationStore.threadId();
|
|
8937
9306
|
this.#setRunning(false);
|
|
9307
|
+
this.#setUnread(0);
|
|
8938
9308
|
}
|
|
8939
9309
|
/** Drop the in-memory run + transcript, leaving the thread id untouched. */
|
|
8940
9310
|
#resetState() {
|
|
@@ -8946,7 +9316,6 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8946
9316
|
this.#toolCards.clear();
|
|
8947
9317
|
this.#serverSettled.clear();
|
|
8948
9318
|
this.#initialMessages = [];
|
|
8949
|
-
this.#runAttachments = [];
|
|
8950
9319
|
this.#attachTray?.clear();
|
|
8951
9320
|
this.#messages.replaceChildren(this.#emptyWrap);
|
|
8952
9321
|
this.#updateEmptyState();
|
|
@@ -9233,17 +9602,27 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9233
9602
|
const inputRow = document.createElement("div");
|
|
9234
9603
|
inputRow.className = "input-row";
|
|
9235
9604
|
inputRow.setAttribute("part", "composer");
|
|
9605
|
+
const composer = document.createElement("div");
|
|
9606
|
+
composer.className = "composer";
|
|
9607
|
+
composer.setAttribute("part", "composer-surface");
|
|
9608
|
+
const tools = document.createElement("div");
|
|
9609
|
+
tools.className = "composer-tools";
|
|
9610
|
+
tools.setAttribute("part", "composer-tools");
|
|
9236
9611
|
this.#input.className = "input";
|
|
9237
9612
|
this.#input.setAttribute("part", "input");
|
|
9238
9613
|
this.#input.setAttribute("aria-label", this.#strings.message);
|
|
9239
|
-
this.#input.rows =
|
|
9614
|
+
this.#input.rows = 1;
|
|
9240
9615
|
this.#input.placeholder = this.#strings.inputPlaceholder;
|
|
9241
9616
|
this.#input.addEventListener("keydown", (event) => this.#onKeydown(event));
|
|
9242
9617
|
this.#input.addEventListener("input", () => this.#onInput());
|
|
9243
9618
|
this.#send.className = "send";
|
|
9244
9619
|
this.#send.type = "button";
|
|
9245
9620
|
this.#send.setAttribute("part", "send");
|
|
9246
|
-
this.#send.
|
|
9621
|
+
this.#send.append(
|
|
9622
|
+
this.#glyphSlot("icon-send", "send-send", ICON_SEND),
|
|
9623
|
+
this.#glyphSlot("icon-stop", "send-stop", ICON_STOP)
|
|
9624
|
+
);
|
|
9625
|
+
this.#send.title = this.#strings.send;
|
|
9247
9626
|
this.#send.setAttribute("aria-label", this.#strings.send);
|
|
9248
9627
|
this.#send.dataset["state"] = "idle";
|
|
9249
9628
|
this.#send.addEventListener("click", () => {
|
|
@@ -9259,7 +9638,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9259
9638
|
this.#attachButton.className = "attach-btn";
|
|
9260
9639
|
this.#attachButton.type = "button";
|
|
9261
9640
|
this.#attachButton.setAttribute("part", "attach-button");
|
|
9262
|
-
this.#attachButton.
|
|
9641
|
+
this.#attachButton.append(this.#glyphSlot("icon-attach", "attach-glyph", ICON_ATTACH));
|
|
9263
9642
|
this.#attachButton.title = this.#strings.attachFiles;
|
|
9264
9643
|
this.#attachButton.setAttribute("aria-label", this.#strings.attachFiles);
|
|
9265
9644
|
this.#attachButton.hidden = true;
|
|
@@ -9273,7 +9652,9 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9273
9652
|
this.#voiceSlot.className = "voice-slot";
|
|
9274
9653
|
const footer = document.createElement("slot");
|
|
9275
9654
|
footer.name = "footer";
|
|
9276
|
-
|
|
9655
|
+
tools.append(this.#attachButton, this.#voiceSlot, this.#send);
|
|
9656
|
+
composer.append(this.#input, tools);
|
|
9657
|
+
inputRow.append(composer, this.#fileInput);
|
|
9277
9658
|
this.#chat.append(
|
|
9278
9659
|
header,
|
|
9279
9660
|
this.#messages,
|
|
@@ -9286,12 +9667,19 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9286
9667
|
this.#drawer.element,
|
|
9287
9668
|
this.#checkpoints.element
|
|
9288
9669
|
);
|
|
9289
|
-
this.#
|
|
9290
|
-
this.#
|
|
9291
|
-
this.#
|
|
9292
|
-
this.#
|
|
9293
|
-
this.#
|
|
9294
|
-
this.#
|
|
9670
|
+
this.#launcher.className = "launcher";
|
|
9671
|
+
this.#launcher.type = "button";
|
|
9672
|
+
this.#launcher.setAttribute("part", "launcher");
|
|
9673
|
+
this.#launcher.setAttribute("aria-label", this.#strings.expand);
|
|
9674
|
+
this.#badge.className = "launcher-badge";
|
|
9675
|
+
this.#badge.setAttribute("part", "launcher-badge");
|
|
9676
|
+
this.#badge.setAttribute("aria-hidden", "true");
|
|
9677
|
+
this.#badge.hidden = true;
|
|
9678
|
+
this.#launcher.append(
|
|
9679
|
+
this.#iconElement("launcher", "launcher-icon", ICON_LAUNCHER, this.#launcherIconUrl()),
|
|
9680
|
+
this.#badge
|
|
9681
|
+
);
|
|
9682
|
+
this.#launcher.addEventListener("click", () => this.setCollapsed(false));
|
|
9295
9683
|
this.#chat.append(
|
|
9296
9684
|
createResizeHandle({
|
|
9297
9685
|
axis: () => this.#resizeAxis(),
|
|
@@ -9305,7 +9693,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9305
9693
|
label: this.#strings.resizePanel
|
|
9306
9694
|
})
|
|
9307
9695
|
);
|
|
9308
|
-
this.#root.append(style, this.#chat, this.#
|
|
9696
|
+
this.#root.append(style, this.#chat, this.#launcher);
|
|
9309
9697
|
}
|
|
9310
9698
|
/**
|
|
9311
9699
|
* Build a header control button: a named slot a host can project markup into,
|
|
@@ -9332,16 +9720,37 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9332
9720
|
return button2;
|
|
9333
9721
|
}
|
|
9334
9722
|
/**
|
|
9335
|
-
*
|
|
9336
|
-
*
|
|
9723
|
+
* A `<slot>` a host can project its own mark into, falling back to one of the
|
|
9724
|
+
* built-in glyphs. The markup is an author-written constant, never user or
|
|
9725
|
+
* server data, so it is assigned directly rather than sanitised.
|
|
9726
|
+
*/
|
|
9727
|
+
#glyphSlot(slotName, className, markup) {
|
|
9728
|
+
const slot = document.createElement("slot");
|
|
9729
|
+
slot.name = slotName;
|
|
9730
|
+
slot.className = className;
|
|
9731
|
+
slot.innerHTML = markup;
|
|
9732
|
+
return slot;
|
|
9733
|
+
}
|
|
9734
|
+
/**
|
|
9735
|
+
* The launcher's own image URL. `data-launcher-icon-url` lets the collapsed
|
|
9736
|
+
* button carry a different mark from the header's — a product logo reads at
|
|
9737
|
+
* 22px in a header bar but rarely at 26px in a circle — and falls back to the
|
|
9738
|
+
* header icon so a single `data-icon-url` still feeds both.
|
|
9337
9739
|
*/
|
|
9338
|
-
#
|
|
9740
|
+
#launcherIconUrl() {
|
|
9741
|
+
return this.getAttribute("data-launcher-icon-url") ?? this.getAttribute("data-icon-url");
|
|
9742
|
+
}
|
|
9743
|
+
/**
|
|
9744
|
+
* An icon holder wrapping a `<slot>` so a host can project custom markup;
|
|
9745
|
+
* with an `<img>` as the slot's fallback when an icon URL is configured, or
|
|
9746
|
+
* the given glyph markup when it is not.
|
|
9747
|
+
*/
|
|
9748
|
+
#iconElement(slotName, part, fallbackGlyph, iconUrl = this.getAttribute("data-icon-url")) {
|
|
9339
9749
|
const holder = document.createElement("span");
|
|
9340
9750
|
holder.className = "icon-holder";
|
|
9341
9751
|
holder.setAttribute("part", part);
|
|
9342
9752
|
const slot = document.createElement("slot");
|
|
9343
9753
|
slot.name = slotName;
|
|
9344
|
-
const iconUrl = this.getAttribute("data-icon-url");
|
|
9345
9754
|
if (iconUrl !== null) {
|
|
9346
9755
|
const img = document.createElement("img");
|
|
9347
9756
|
img.className = "icon-img";
|
|
@@ -9349,14 +9758,63 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9349
9758
|
img.alt = "";
|
|
9350
9759
|
slot.append(img);
|
|
9351
9760
|
} else if (fallbackGlyph !== null) {
|
|
9352
|
-
slot.
|
|
9761
|
+
slot.innerHTML = fallbackGlyph;
|
|
9353
9762
|
}
|
|
9354
9763
|
holder.append(slot);
|
|
9355
9764
|
return holder;
|
|
9356
9765
|
}
|
|
9357
|
-
/**
|
|
9358
|
-
|
|
9359
|
-
|
|
9766
|
+
/**
|
|
9767
|
+
* Reflect the collapsed state and the unread count on the launcher.
|
|
9768
|
+
*
|
|
9769
|
+
* The count is also the launcher's accessible name: a badge that only exists
|
|
9770
|
+
* as a coloured dot says nothing to a screen reader, and "Expand" alone would
|
|
9771
|
+
* be a lie once answers are waiting behind it.
|
|
9772
|
+
*/
|
|
9773
|
+
#syncLauncher() {
|
|
9774
|
+
this.#launcher.setAttribute("aria-expanded", String(!this.collapsed));
|
|
9775
|
+
const unread = this.#unread;
|
|
9776
|
+
this.#badge.textContent = unread > 9 ? "9+" : String(unread);
|
|
9777
|
+
this.#badge.hidden = unread === 0 || !this.#badgeEnabled();
|
|
9778
|
+
const label = this.#badge.hidden ? this.#strings.expand : this.#strings.expandUnread.replace("{count}", String(unread));
|
|
9779
|
+
this.#launcher.setAttribute("aria-label", label);
|
|
9780
|
+
this.#launcher.title = label;
|
|
9781
|
+
}
|
|
9782
|
+
/**
|
|
9783
|
+
* The unread badge, unlike every other affordance here, is on by default:
|
|
9784
|
+
* a collapsed widget is the one state where an answer can arrive with nothing
|
|
9785
|
+
* on screen to say so. `data-unread-badge="false"` turns it off for a host
|
|
9786
|
+
* that drives its own chrome from the `ag-ui-unread` event.
|
|
9787
|
+
*/
|
|
9788
|
+
#badgeEnabled() {
|
|
9789
|
+
return this.getAttribute("data-unread-badge") !== "false";
|
|
9790
|
+
}
|
|
9791
|
+
/**
|
|
9792
|
+
* Set the unread count, repaint the badge, and tell the host.
|
|
9793
|
+
*
|
|
9794
|
+
* The count is kept whether or not the badge renders it, so `unread` stays
|
|
9795
|
+
* truthful for a host chrome and switching the badge on mid-session doesn't
|
|
9796
|
+
* start from a number that was never counted.
|
|
9797
|
+
*/
|
|
9798
|
+
#setUnread(count) {
|
|
9799
|
+
this.#unread = count;
|
|
9800
|
+
this.#syncLauncher();
|
|
9801
|
+
this.dispatchEvent(
|
|
9802
|
+
new CustomEvent(UNREAD_EVENT, {
|
|
9803
|
+
detail: { unread: count },
|
|
9804
|
+
bubbles: true,
|
|
9805
|
+
composed: true
|
|
9806
|
+
})
|
|
9807
|
+
);
|
|
9808
|
+
}
|
|
9809
|
+
/**
|
|
9810
|
+
* Count an answer the user cannot have seen: one that finished while the
|
|
9811
|
+
* widget was collapsed. Expanding is what marks them read.
|
|
9812
|
+
*/
|
|
9813
|
+
#noteUnread() {
|
|
9814
|
+
if (!this.collapsed) {
|
|
9815
|
+
return;
|
|
9816
|
+
}
|
|
9817
|
+
this.#setUnread(this.#unread + 1);
|
|
9360
9818
|
}
|
|
9361
9819
|
/** Hide the empty-state region once the message list holds anything else. */
|
|
9362
9820
|
#updateEmptyState() {
|
|
@@ -9366,6 +9824,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9366
9824
|
#onInput() {
|
|
9367
9825
|
this.#skillsMenu.onInput(this.#input.value);
|
|
9368
9826
|
this.#skillHint.hidden = true;
|
|
9827
|
+
this.#autoGrow();
|
|
9369
9828
|
}
|
|
9370
9829
|
#onKeydown(event) {
|
|
9371
9830
|
if (this.#skillsMenu.onKeydown(event)) {
|
|
@@ -9392,14 +9851,32 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9392
9851
|
this.#confirmAbort?.abort();
|
|
9393
9852
|
this.#client?.cancel();
|
|
9394
9853
|
}
|
|
9395
|
-
/**
|
|
9854
|
+
/**
|
|
9855
|
+
* Swap the composer button between Send (idle) and Stop (running).
|
|
9856
|
+
*
|
|
9857
|
+
* The glyph is swapped by CSS from `data-state` — both are mounted — so this
|
|
9858
|
+
* only has to move the accessible name, which is the button's whole label now
|
|
9859
|
+
* that it carries no text.
|
|
9860
|
+
*/
|
|
9396
9861
|
#setRunning(running) {
|
|
9397
9862
|
this.#running = running;
|
|
9398
9863
|
const label = running ? this.#strings.stop : this.#strings.send;
|
|
9399
|
-
this.#send.
|
|
9864
|
+
this.#send.title = label;
|
|
9400
9865
|
this.#send.setAttribute("aria-label", label);
|
|
9401
9866
|
this.#send.dataset["state"] = running ? "running" : "idle";
|
|
9402
9867
|
}
|
|
9868
|
+
/**
|
|
9869
|
+
* Size the field to its content: one row when empty, growing with what is
|
|
9870
|
+
* typed until the CSS ceiling takes over and it scrolls.
|
|
9871
|
+
*
|
|
9872
|
+
* Resetting to `auto` first is what makes it shrink again — `scrollHeight`
|
|
9873
|
+
* never reports less than the current height, so measuring without the reset
|
|
9874
|
+
* would ratchet the composer taller and never back down.
|
|
9875
|
+
*/
|
|
9876
|
+
#autoGrow() {
|
|
9877
|
+
this.#input.style.height = "auto";
|
|
9878
|
+
this.#input.style.height = `${this.#input.scrollHeight}px`;
|
|
9879
|
+
}
|
|
9403
9880
|
async #submit() {
|
|
9404
9881
|
if (this.#running) {
|
|
9405
9882
|
return;
|
|
@@ -9410,6 +9887,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9410
9887
|
return;
|
|
9411
9888
|
}
|
|
9412
9889
|
this.#input.value = "";
|
|
9890
|
+
this.#autoGrow();
|
|
9413
9891
|
if (this.#attachTray?.hasPending() === true) {
|
|
9414
9892
|
this.#appendNotice(
|
|
9415
9893
|
"\u{1F4CE}",
|
|
@@ -9449,7 +9927,6 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9449
9927
|
if (attachments.length > 0) {
|
|
9450
9928
|
bubble.appendChild(renderAttachmentChips(attachments));
|
|
9451
9929
|
}
|
|
9452
|
-
this.#runAttachments = attachments;
|
|
9453
9930
|
this.dispatchEvent(
|
|
9454
9931
|
new CustomEvent(SUBMIT_EVENT, {
|
|
9455
9932
|
detail: { content, attachments },
|
|
@@ -9678,6 +10155,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9678
10155
|
}
|
|
9679
10156
|
attachCopyButtons(bubble, this.#strings);
|
|
9680
10157
|
this.#streamingBubble = null;
|
|
10158
|
+
this.#noteUnread();
|
|
9681
10159
|
},
|
|
9682
10160
|
onToolCall: (call) => {
|
|
9683
10161
|
this.#hidePending();
|
|
@@ -9726,7 +10204,6 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
9726
10204
|
this.#hidePending();
|
|
9727
10205
|
this.#setRunning(false);
|
|
9728
10206
|
this.#streamingBubble = null;
|
|
9729
|
-
this.#runAttachments = [];
|
|
9730
10207
|
for (const card of this.#toolCards.values()) {
|
|
9731
10208
|
if (!card.settled) {
|
|
9732
10209
|
card.settle(TOOL_CALL_STATUS.DONE, this.#strings.noResult);
|
|
@@ -9904,7 +10381,7 @@ function setControlValue(el, value) {
|
|
|
9904
10381
|
}
|
|
9905
10382
|
|
|
9906
10383
|
// src/version.ts
|
|
9907
|
-
var VERSION = "0.
|
|
10384
|
+
var VERSION = "0.23.0";
|
|
9908
10385
|
export {
|
|
9909
10386
|
ATTACHMENT_EVENT,
|
|
9910
10387
|
AgUiChat,
|
|
@@ -9928,6 +10405,7 @@ export {
|
|
|
9928
10405
|
TOOL_CALL_STATUS,
|
|
9929
10406
|
TOOL_DISPLAY,
|
|
9930
10407
|
ToolCallCard,
|
|
10408
|
+
UNREAD_EVENT,
|
|
9931
10409
|
VERSION,
|
|
9932
10410
|
X_CONFIRM_KEY,
|
|
9933
10411
|
X_DESTRUCTIVE_KEY,
|