@artooi/ag-ui-web-component 0.16.0 → 0.18.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 +132 -1
- package/README.md +40 -5
- package/dist/ag-ui-web-component.bundle.js +109 -31
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +12 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +41 -2
- 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 +361 -67
- package/dist/index.js.map +4 -4
- package/dist/ui/attach_copy_buttons.d.ts +19 -0
- package/dist/ui/attach_copy_buttons.d.ts.map +1 -0
- package/dist/ui/attachment_tray.d.ts +2 -0
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/checkpoint_menu.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +8 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +13 -0
- package/src/core/ag_ui_chat.ts +181 -14
- package/src/index.ts +2 -0
- package/src/ui/attach_copy_buttons.ts +82 -0
- package/src/ui/attachment_tray.ts +5 -0
- package/src/ui/checkpoint_menu.ts +59 -6
- package/src/ui/styles.ts +84 -6
- package/src/ui/ui_strings.ts +13 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ var ELEMENT_TAG = "ag-ui-chat";
|
|
|
3
3
|
var SUBMIT_EVENT = "ag-ui-submit";
|
|
4
4
|
var TOGGLE_EVENT = "ag-ui-toggle";
|
|
5
5
|
var STATE_EVENT = "ag-ui-state";
|
|
6
|
+
var ATTACHMENT_EVENT = "ag-ui-attachments";
|
|
6
7
|
var MESSAGE_ROLE = {
|
|
7
8
|
USER: "user",
|
|
8
9
|
ASSISTANT: "assistant"
|
|
@@ -449,6 +450,9 @@ var DEFAULT_UI_STRINGS = {
|
|
|
449
450
|
collapse: "Collapse",
|
|
450
451
|
expand: "Expand",
|
|
451
452
|
toggleTheme: "Toggle theme",
|
|
453
|
+
copyCode: "Copy",
|
|
454
|
+
copied: "Copied",
|
|
455
|
+
copyFailed: "Copy failed",
|
|
452
456
|
checkpoints: "Continue a run",
|
|
453
457
|
noCheckpoints: "Nothing to continue yet.",
|
|
454
458
|
resumeRun: "Resume",
|
|
@@ -466,6 +470,7 @@ var DEFAULT_UI_STRINGS = {
|
|
|
466
470
|
usingSkill: "Using skill {name}",
|
|
467
471
|
runInterrupted: "The previous response didn\u2019t finish \u2014 the page changed before it arrived.",
|
|
468
472
|
pageMoved: "The page changed since you last looked at it. Call read_page to see the current page, then retry.",
|
|
473
|
+
attachmentsStillUploading: "{n} file still uploading \u2014 it was not sent with this message and is still attached.",
|
|
469
474
|
skillNeeds: "\u201C{title}\u201D needs: {fields}",
|
|
470
475
|
message: "Message",
|
|
471
476
|
inputPlaceholder: "Ask anything\u2026",
|
|
@@ -529,12 +534,12 @@ function mergeUiStrings(overrides) {
|
|
|
529
534
|
|
|
530
535
|
// src/ui/approval_card.ts
|
|
531
536
|
function actionButton(modifier, label) {
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
return
|
|
537
|
+
const button2 = document.createElement("button");
|
|
538
|
+
button2.type = "button";
|
|
539
|
+
button2.className = `approval-btn approval-btn--${modifier}`;
|
|
540
|
+
button2.setAttribute("part", `approval-button approval-${modifier}`);
|
|
541
|
+
button2.textContent = label;
|
|
542
|
+
return button2;
|
|
538
543
|
}
|
|
539
544
|
function requestApproval(host, request, options = {}) {
|
|
540
545
|
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
@@ -581,6 +586,55 @@ function requestApproval(host, request, options = {}) {
|
|
|
581
586
|
});
|
|
582
587
|
}
|
|
583
588
|
|
|
589
|
+
// src/ui/attach_copy_buttons.ts
|
|
590
|
+
var CONFIRM_MS = 1500;
|
|
591
|
+
function attachCopyButtons(root, strings) {
|
|
592
|
+
for (const pre of Array.from(root.querySelectorAll("pre"))) {
|
|
593
|
+
const code = pre.querySelector("code");
|
|
594
|
+
if (code === null || pre.querySelector(".code-copy") !== null) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
pre.classList.add("has-copy");
|
|
598
|
+
pre.append(button(code, strings));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function button(code, strings) {
|
|
602
|
+
const text2 = code.textContent;
|
|
603
|
+
const copy = document.createElement("button");
|
|
604
|
+
copy.type = "button";
|
|
605
|
+
copy.className = "code-copy";
|
|
606
|
+
copy.setAttribute("part", "code-copy");
|
|
607
|
+
copy.textContent = strings.copyCode;
|
|
608
|
+
copy.title = strings.copyCode;
|
|
609
|
+
copy.setAttribute("aria-label", strings.copyCode);
|
|
610
|
+
copy.addEventListener("click", () => {
|
|
611
|
+
void writeText(text2).then((ok) => {
|
|
612
|
+
confirm(copy, ok ? strings.copied : strings.copyFailed, strings);
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
return copy;
|
|
616
|
+
}
|
|
617
|
+
async function writeText(text2) {
|
|
618
|
+
const clipboard = navigator.clipboard;
|
|
619
|
+
if (clipboard === void 0) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
await clipboard.writeText(text2);
|
|
624
|
+
return true;
|
|
625
|
+
} catch {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function confirm(copy, message, strings) {
|
|
630
|
+
copy.textContent = message;
|
|
631
|
+
copy.dataset["state"] = message === strings.copied ? "copied" : "failed";
|
|
632
|
+
setTimeout(() => {
|
|
633
|
+
copy.textContent = strings.copyCode;
|
|
634
|
+
delete copy.dataset["state"];
|
|
635
|
+
}, CONFIRM_MS);
|
|
636
|
+
}
|
|
637
|
+
|
|
584
638
|
// src/ui/attachment_chips.ts
|
|
585
639
|
function renderAttachmentChips(refs) {
|
|
586
640
|
const list = document.createElement("div");
|
|
@@ -693,6 +747,10 @@ var AttachmentTray = class {
|
|
|
693
747
|
hasPending() {
|
|
694
748
|
return this.#items.some((item) => item.status === ATTACHMENT_STATUS.UPLOADING);
|
|
695
749
|
}
|
|
750
|
+
/** How many chips are still uploading — the count a send-time notice names. */
|
|
751
|
+
pendingCount() {
|
|
752
|
+
return this.#items.filter((item) => item.status === ATTACHMENT_STATUS.UPLOADING).length;
|
|
753
|
+
}
|
|
696
754
|
/** Whether the tray holds no chips. */
|
|
697
755
|
isEmpty() {
|
|
698
756
|
return this.#items.length === 0;
|
|
@@ -879,6 +937,8 @@ var CheckpointMenu = class {
|
|
|
879
937
|
#onPick;
|
|
880
938
|
#list;
|
|
881
939
|
#heading;
|
|
940
|
+
/** What had focus before the panel opened, restored on close. */
|
|
941
|
+
#lastFocused = null;
|
|
882
942
|
#strings;
|
|
883
943
|
#runs = [];
|
|
884
944
|
constructor(onPick, strings = DEFAULT_UI_STRINGS) {
|
|
@@ -889,24 +949,21 @@ var CheckpointMenu = class {
|
|
|
889
949
|
this.element.setAttribute("part", "checkpoints");
|
|
890
950
|
this.element.setAttribute("role", "dialog");
|
|
891
951
|
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
952
|
+
this.element.tabIndex = -1;
|
|
892
953
|
this.element.hidden = true;
|
|
893
954
|
const header = document.createElement("div");
|
|
894
955
|
header.className = "checkpoints-header";
|
|
895
956
|
header.setAttribute("part", "checkpoints-header");
|
|
896
957
|
this.#heading = document.createElement("span");
|
|
897
958
|
this.#heading.className = "checkpoints-title";
|
|
959
|
+
this.#heading.setAttribute("part", "checkpoints-title");
|
|
898
960
|
this.#heading.textContent = strings.checkpoints;
|
|
899
961
|
header.append(this.#heading);
|
|
900
962
|
this.#list = document.createElement("div");
|
|
901
963
|
this.#list.className = "checkpoints-list";
|
|
902
964
|
this.#list.setAttribute("part", "checkpoints-list");
|
|
903
965
|
this.element.append(header, this.#list);
|
|
904
|
-
this.element.addEventListener("keydown", (event) =>
|
|
905
|
-
if (event.key === "Escape") {
|
|
906
|
-
event.stopPropagation();
|
|
907
|
-
this.close();
|
|
908
|
-
}
|
|
909
|
-
});
|
|
966
|
+
this.element.addEventListener("keydown", (event) => this.#onKeydown(event));
|
|
910
967
|
}
|
|
911
968
|
/** Replace the rows. The host passes only `continuable` runs. */
|
|
912
969
|
setRuns(runs) {
|
|
@@ -921,10 +978,52 @@ var CheckpointMenu = class {
|
|
|
921
978
|
this.#render();
|
|
922
979
|
}
|
|
923
980
|
open() {
|
|
981
|
+
if (this.open_) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
this.#lastFocused = this.#activeElement();
|
|
924
985
|
this.element.hidden = false;
|
|
986
|
+
(this.#focusables()[0] ?? this.element).focus();
|
|
925
987
|
}
|
|
926
988
|
close() {
|
|
989
|
+
if (!this.open_) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
927
992
|
this.element.hidden = true;
|
|
993
|
+
this.#lastFocused?.focus();
|
|
994
|
+
this.#lastFocused = null;
|
|
995
|
+
}
|
|
996
|
+
/** The focused element within this panel's root (shadow-aware). */
|
|
997
|
+
#activeElement() {
|
|
998
|
+
return this.element.getRootNode().activeElement;
|
|
999
|
+
}
|
|
1000
|
+
/** The panel's tabbable controls, in document order. */
|
|
1001
|
+
#focusables() {
|
|
1002
|
+
return Array.from(this.element.querySelectorAll("button, [tabindex]")).filter(
|
|
1003
|
+
(el) => !el.hidden
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
/** Escape closes; Tab is trapped so focus cannot wander behind the dialog. */
|
|
1007
|
+
#onKeydown(event) {
|
|
1008
|
+
if (event.key === "Escape") {
|
|
1009
|
+
event.stopPropagation();
|
|
1010
|
+
this.close();
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (event.key !== "Tab") {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const focusables = this.#focusables();
|
|
1017
|
+
const first = focusables[0];
|
|
1018
|
+
const last = focusables[focusables.length - 1];
|
|
1019
|
+
const active = this.#activeElement();
|
|
1020
|
+
if (event.shiftKey && active === first) {
|
|
1021
|
+
event.preventDefault();
|
|
1022
|
+
last?.focus();
|
|
1023
|
+
} else if (!event.shiftKey && active === last) {
|
|
1024
|
+
event.preventDefault();
|
|
1025
|
+
first?.focus();
|
|
1026
|
+
}
|
|
928
1027
|
}
|
|
929
1028
|
get open_() {
|
|
930
1029
|
return !this.element.hidden;
|
|
@@ -949,6 +1048,7 @@ var CheckpointMenu = class {
|
|
|
949
1048
|
row.setAttribute("part", "checkpoint-row");
|
|
950
1049
|
const label = document.createElement("span");
|
|
951
1050
|
label.className = "checkpoint-label";
|
|
1051
|
+
label.setAttribute("part", "checkpoint-label");
|
|
952
1052
|
label.textContent = run.started_at === null ? run.run_id : relativeTime(Date.parse(run.started_at), Date.now(), this.#strings);
|
|
953
1053
|
label.title = run.run_id;
|
|
954
1054
|
row.append(label);
|
|
@@ -967,27 +1067,27 @@ var CheckpointMenu = class {
|
|
|
967
1067
|
return row;
|
|
968
1068
|
}
|
|
969
1069
|
#action(runId, verb, label) {
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1070
|
+
const button2 = document.createElement("button");
|
|
1071
|
+
button2.type = "button";
|
|
1072
|
+
button2.className = `checkpoint-action checkpoint-${verb}`;
|
|
1073
|
+
button2.setAttribute("part", `checkpoint-action checkpoint-${verb}`);
|
|
1074
|
+
button2.textContent = label;
|
|
1075
|
+
button2.addEventListener("click", () => {
|
|
976
1076
|
this.close();
|
|
977
1077
|
this.#onPick(runId, verb);
|
|
978
1078
|
});
|
|
979
|
-
return
|
|
1079
|
+
return button2;
|
|
980
1080
|
}
|
|
981
1081
|
};
|
|
982
1082
|
|
|
983
1083
|
// src/ui/confirmation_card.ts
|
|
984
1084
|
function actionButton2(modifier, label) {
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
return
|
|
1085
|
+
const button2 = document.createElement("button");
|
|
1086
|
+
button2.type = "button";
|
|
1087
|
+
button2.className = `confirm-btn confirm-btn--${modifier}`;
|
|
1088
|
+
button2.setAttribute("part", `confirm-button confirm-${modifier}`);
|
|
1089
|
+
button2.textContent = label;
|
|
1090
|
+
return button2;
|
|
991
1091
|
}
|
|
992
1092
|
function requestConfirmation(host, request, options = {}) {
|
|
993
1093
|
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
@@ -1010,7 +1110,7 @@ function requestConfirmation(host, request, options = {}) {
|
|
|
1010
1110
|
actions.className = "confirm-actions";
|
|
1011
1111
|
actions.setAttribute("part", "confirm-actions");
|
|
1012
1112
|
const cancel = actionButton2("cancel", strings.cancel);
|
|
1013
|
-
const
|
|
1113
|
+
const confirm2 = actionButton2("confirm", strings.confirm);
|
|
1014
1114
|
let settled = false;
|
|
1015
1115
|
const close = (accepted) => {
|
|
1016
1116
|
if (settled) {
|
|
@@ -1018,21 +1118,21 @@ function requestConfirmation(host, request, options = {}) {
|
|
|
1018
1118
|
}
|
|
1019
1119
|
settled = true;
|
|
1020
1120
|
cancel.disabled = true;
|
|
1021
|
-
|
|
1121
|
+
confirm2.disabled = true;
|
|
1022
1122
|
card.setAttribute("data-resolved", accepted ? "confirmed" : "declined");
|
|
1023
1123
|
resolve(accepted);
|
|
1024
1124
|
};
|
|
1025
1125
|
cancel.addEventListener("click", () => close(false));
|
|
1026
|
-
|
|
1126
|
+
confirm2.addEventListener("click", () => close(true));
|
|
1027
1127
|
options.signal?.addEventListener("abort", () => close(false), { once: true });
|
|
1028
|
-
actions.append(cancel,
|
|
1128
|
+
actions.append(cancel, confirm2);
|
|
1029
1129
|
card.append(body, args, actions);
|
|
1030
1130
|
host.appendChild(card);
|
|
1031
1131
|
if (options.signal?.aborted === true) {
|
|
1032
1132
|
close(false);
|
|
1033
1133
|
return;
|
|
1034
1134
|
}
|
|
1035
|
-
|
|
1135
|
+
confirm2.focus();
|
|
1036
1136
|
});
|
|
1037
1137
|
}
|
|
1038
1138
|
|
|
@@ -4185,13 +4285,13 @@ var SkillsMenu = class {
|
|
|
4185
4285
|
const chipSkills = this.#chipsEnabled ? this.#skills.filter((skill) => skill.chip === true) : [];
|
|
4186
4286
|
this.chips.hidden = chipSkills.length === 0;
|
|
4187
4287
|
for (const skill of chipSkills) {
|
|
4188
|
-
const
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
this.chips.appendChild(
|
|
4288
|
+
const button2 = document.createElement("button");
|
|
4289
|
+
button2.type = "button";
|
|
4290
|
+
button2.className = "skill-chip";
|
|
4291
|
+
button2.setAttribute("part", "skill-chip");
|
|
4292
|
+
button2.textContent = skill.title;
|
|
4293
|
+
button2.addEventListener("click", () => this.#pick(skill));
|
|
4294
|
+
this.chips.appendChild(button2);
|
|
4195
4295
|
}
|
|
4196
4296
|
}
|
|
4197
4297
|
#renderPalette() {
|
|
@@ -4231,6 +4331,7 @@ var STYLES = `
|
|
|
4231
4331
|
--ag-ui-user-bg: #4f46e5;
|
|
4232
4332
|
--ag-ui-user-fg: #ffffff;
|
|
4233
4333
|
--ag-ui-assistant-bg: #f1f1f6;
|
|
4334
|
+
--ag-ui-hover: #e7e7ee;
|
|
4234
4335
|
--ag-ui-input-bg: var(--ag-ui-bg);
|
|
4235
4336
|
--ag-ui-tool-bg: var(--ag-ui-assistant-bg);
|
|
4236
4337
|
--ag-ui-tool-fg: var(--ag-ui-accent);
|
|
@@ -4300,6 +4401,7 @@ var STYLES = `
|
|
|
4300
4401
|
--ag-ui-bg: #15151f;
|
|
4301
4402
|
--ag-ui-fg: #e8e8f2;
|
|
4302
4403
|
--ag-ui-assistant-bg: #25253a;
|
|
4404
|
+
--ag-ui-hover: #303049;
|
|
4303
4405
|
--ag-ui-header-bg: #1f1f30;
|
|
4304
4406
|
--ag-ui-header-fg: #e8e8f2;
|
|
4305
4407
|
--ag-ui-border: #33334a;
|
|
@@ -4312,6 +4414,7 @@ var STYLES = `
|
|
|
4312
4414
|
--ag-ui-bg: #15151f;
|
|
4313
4415
|
--ag-ui-fg: #e8e8f2;
|
|
4314
4416
|
--ag-ui-assistant-bg: #25253a;
|
|
4417
|
+
--ag-ui-hover: #303049;
|
|
4315
4418
|
--ag-ui-header-bg: #1f1f30;
|
|
4316
4419
|
--ag-ui-header-fg: #e8e8f2;
|
|
4317
4420
|
--ag-ui-border: #33334a;
|
|
@@ -4327,6 +4430,7 @@ var STYLES = `
|
|
|
4327
4430
|
--ag-ui-accent: #3fb950;
|
|
4328
4431
|
--ag-ui-user-bg: #238636;
|
|
4329
4432
|
--ag-ui-assistant-bg: #161b22;
|
|
4433
|
+
--ag-ui-hover: #21262d;
|
|
4330
4434
|
--ag-ui-header-bg: #010409;
|
|
4331
4435
|
--ag-ui-header-fg: #c9d1d9;
|
|
4332
4436
|
--ag-ui-border: #30363d;
|
|
@@ -4728,12 +4832,86 @@ var STYLES = `
|
|
|
4728
4832
|
padding: 0;
|
|
4729
4833
|
}
|
|
4730
4834
|
|
|
4835
|
+
/* The copy button sits inside the block, so it scrolls with wide code rather
|
|
4836
|
+
than floating over the bubble. Positioning is on the pre; the button only
|
|
4837
|
+
appears once one has been attached. */
|
|
4838
|
+
.message--assistant pre.has-copy {
|
|
4839
|
+
position: relative;
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
.code-copy {
|
|
4843
|
+
position: absolute;
|
|
4844
|
+
top: 4px;
|
|
4845
|
+
right: 4px;
|
|
4846
|
+
padding: 2px 8px;
|
|
4847
|
+
font: inherit;
|
|
4848
|
+
font-size: 0.75em;
|
|
4849
|
+
line-height: 1.6;
|
|
4850
|
+
color: var(--ag-ui-muted);
|
|
4851
|
+
background: var(--ag-ui-surface);
|
|
4852
|
+
border: 1px solid var(--ag-ui-border);
|
|
4853
|
+
border-radius: 4px;
|
|
4854
|
+
cursor: pointer;
|
|
4855
|
+
opacity: 0;
|
|
4856
|
+
transition: opacity 0.12s ease;
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4859
|
+
/* Revealed on hover or keyboard focus. Focus matters as much as hover here:
|
|
4860
|
+
hidden-until-hover is invisible to a keyboard user otherwise. */
|
|
4861
|
+
.message--assistant pre.has-copy:hover .code-copy,
|
|
4862
|
+
.code-copy:focus-visible {
|
|
4863
|
+
opacity: 1;
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
.code-copy:hover {
|
|
4867
|
+
color: var(--ag-ui-text);
|
|
4868
|
+
background: var(--ag-ui-hover);
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
.code-copy[data-state="copied"] {
|
|
4872
|
+
opacity: 1;
|
|
4873
|
+
color: var(--ag-ui-text);
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
.code-copy[data-state="failed"] {
|
|
4877
|
+
opacity: 1;
|
|
4878
|
+
color: var(--ag-ui-danger, var(--ag-ui-text));
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4731
4881
|
.message--assistant blockquote {
|
|
4732
4882
|
padding-left: 10px;
|
|
4733
4883
|
border-left: 3px solid var(--ag-ui-border);
|
|
4734
4884
|
color: var(--ag-ui-muted);
|
|
4735
4885
|
}
|
|
4736
4886
|
|
|
4887
|
+
/* Markdown tables. table/thead/tbody/tr/th/td are all in
|
|
4888
|
+
the sanitizer's ALLOWED_TAGS, so an agent emitting one renders it \u2014 and until
|
|
4889
|
+
now rendered it entirely unstyled, overflowing the bubble. A wide table
|
|
4890
|
+
scrolls inside its own box rather than stretching the message: the bubble is
|
|
4891
|
+
width-constrained, so without this the columns either crush or push the
|
|
4892
|
+
layout sideways. */
|
|
4893
|
+
.message--assistant table {
|
|
4894
|
+
display: block;
|
|
4895
|
+
width: fit-content;
|
|
4896
|
+
max-width: 100%;
|
|
4897
|
+
overflow-x: auto;
|
|
4898
|
+
border-collapse: collapse;
|
|
4899
|
+
font-size: 0.95em;
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
.message--assistant th,
|
|
4903
|
+
.message--assistant td {
|
|
4904
|
+
padding: 6px 10px;
|
|
4905
|
+
border: 1px solid var(--ag-ui-border);
|
|
4906
|
+
text-align: left;
|
|
4907
|
+
vertical-align: top;
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4910
|
+
.message--assistant th {
|
|
4911
|
+
background: var(--ag-ui-hover);
|
|
4912
|
+
font-weight: 600;
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4737
4915
|
.pending {
|
|
4738
4916
|
align-self: flex-start;
|
|
4739
4917
|
display: inline-flex;
|
|
@@ -5484,9 +5662,9 @@ var STYLES = `
|
|
|
5484
5662
|
flex-direction: column;
|
|
5485
5663
|
gap: 0.25rem;
|
|
5486
5664
|
padding: 0.5rem;
|
|
5487
|
-
border: 1px solid var(--
|
|
5665
|
+
border: 1px solid var(--ag-ui-border);
|
|
5488
5666
|
border-radius: 0.5rem;
|
|
5489
|
-
background: var(--
|
|
5667
|
+
background: var(--ag-ui-assistant-bg);
|
|
5490
5668
|
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
5491
5669
|
max-height: 60%;
|
|
5492
5670
|
overflow-y: auto;
|
|
@@ -5517,7 +5695,7 @@ var STYLES = `
|
|
|
5517
5695
|
}
|
|
5518
5696
|
|
|
5519
5697
|
.checkpoint-row:hover {
|
|
5520
|
-
background: var(--
|
|
5698
|
+
background: var(--ag-ui-hover);
|
|
5521
5699
|
}
|
|
5522
5700
|
|
|
5523
5701
|
.checkpoint-label {
|
|
@@ -5532,7 +5710,7 @@ var STYLES = `
|
|
|
5532
5710
|
font-size: 0.6875rem;
|
|
5533
5711
|
padding: 0 0.375rem;
|
|
5534
5712
|
border-radius: 999px;
|
|
5535
|
-
background: var(--
|
|
5713
|
+
background: var(--ag-ui-hover);
|
|
5536
5714
|
opacity: 0.8;
|
|
5537
5715
|
}
|
|
5538
5716
|
|
|
@@ -5541,14 +5719,14 @@ var STYLES = `
|
|
|
5541
5719
|
font-size: 0.75rem;
|
|
5542
5720
|
cursor: pointer;
|
|
5543
5721
|
padding: 0.125rem 0.5rem;
|
|
5544
|
-
border: 1px solid var(--
|
|
5722
|
+
border: 1px solid var(--ag-ui-border);
|
|
5545
5723
|
border-radius: 0.375rem;
|
|
5546
5724
|
background: transparent;
|
|
5547
5725
|
color: inherit;
|
|
5548
5726
|
}
|
|
5549
5727
|
|
|
5550
5728
|
.checkpoint-action:hover {
|
|
5551
|
-
background: var(--
|
|
5729
|
+
background: var(--ag-ui-hover);
|
|
5552
5730
|
}
|
|
5553
5731
|
|
|
5554
5732
|
.drawer-backdrop {
|
|
@@ -6013,9 +6191,9 @@ var ThreadDrawer = class {
|
|
|
6013
6191
|
}
|
|
6014
6192
|
/** Swap a row for an inline "Delete? [Delete] [Cancel]" confirm. */
|
|
6015
6193
|
#confirmDelete(row, meta) {
|
|
6016
|
-
const
|
|
6017
|
-
|
|
6018
|
-
|
|
6194
|
+
const confirm2 = document.createElement("div");
|
|
6195
|
+
confirm2.className = "drawer-confirm";
|
|
6196
|
+
confirm2.setAttribute("part", "drawer-confirm");
|
|
6019
6197
|
const label = document.createElement("span");
|
|
6020
6198
|
label.className = "drawer-confirm-label";
|
|
6021
6199
|
label.setAttribute("part", "drawer-confirm-label");
|
|
@@ -6032,8 +6210,8 @@ var ThreadDrawer = class {
|
|
|
6032
6210
|
no.setAttribute("part", "drawer-confirm-no");
|
|
6033
6211
|
no.textContent = this.#strings.cancel;
|
|
6034
6212
|
no.addEventListener("click", () => this.#renderList());
|
|
6035
|
-
|
|
6036
|
-
row.replaceChildren(
|
|
6213
|
+
confirm2.append(label, yes, no);
|
|
6214
|
+
row.replaceChildren(confirm2);
|
|
6037
6215
|
}
|
|
6038
6216
|
};
|
|
6039
6217
|
|
|
@@ -6999,6 +7177,21 @@ function errorMessage2(xhr) {
|
|
|
6999
7177
|
}
|
|
7000
7178
|
|
|
7001
7179
|
// src/core/ag_ui_chat.ts
|
|
7180
|
+
var CONNECT_TIME_ATTRIBUTES = [
|
|
7181
|
+
"data-attachments-url",
|
|
7182
|
+
"data-attachment-accept",
|
|
7183
|
+
"data-attachment-max-bytes",
|
|
7184
|
+
"data-transcribe-url",
|
|
7185
|
+
"data-threads-url",
|
|
7186
|
+
"data-tools-url",
|
|
7187
|
+
"data-skills-url",
|
|
7188
|
+
"data-skills",
|
|
7189
|
+
"data-prompt-chips",
|
|
7190
|
+
"data-slash-commands",
|
|
7191
|
+
"data-theme-toggle",
|
|
7192
|
+
"data-strings",
|
|
7193
|
+
"data-icon-url"
|
|
7194
|
+
];
|
|
7002
7195
|
var COLLAPSED_KEY = "ag-ui-chat:collapsed";
|
|
7003
7196
|
var THEME_KEY = "ag-ui-chat:theme";
|
|
7004
7197
|
var AgUiChat = class extends HTMLElement {
|
|
@@ -7198,6 +7391,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7198
7391
|
#voiceSlot;
|
|
7199
7392
|
/** Voice-input control; created on connect when transcription is available. */
|
|
7200
7393
|
#voice = null;
|
|
7394
|
+
/** Whether the element is currently in the DOM; gates the connect-time warning. */
|
|
7395
|
+
#connected = false;
|
|
7201
7396
|
/** Refs attached to the message currently being sent (the context manifest). */
|
|
7202
7397
|
#runAttachments = [];
|
|
7203
7398
|
#client = null;
|
|
@@ -7344,12 +7539,21 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7344
7539
|
const index = this.#runs();
|
|
7345
7540
|
this.#checkpoints.setRuns(index === null ? [] : await index.continuable());
|
|
7346
7541
|
}
|
|
7347
|
-
/** Attributes
|
|
7542
|
+
/** Attributes the element reacts to after it has been connected. */
|
|
7348
7543
|
static get observedAttributes() {
|
|
7349
|
-
return ["title-text"];
|
|
7544
|
+
return ["title-text", ...CONNECT_TIME_ATTRIBUTES];
|
|
7350
7545
|
}
|
|
7351
|
-
attributeChangedCallback(
|
|
7352
|
-
|
|
7546
|
+
attributeChangedCallback(name, previous, value) {
|
|
7547
|
+
if (name === "title-text") {
|
|
7548
|
+
this.#title.textContent = value ?? this.#strings.title;
|
|
7549
|
+
return;
|
|
7550
|
+
}
|
|
7551
|
+
if (previous === value || !this.#connected) {
|
|
7552
|
+
return;
|
|
7553
|
+
}
|
|
7554
|
+
console.warn(
|
|
7555
|
+
`<ag-ui-chat>: "${name}" was changed after the element connected, and is read only while connecting \u2014 this assignment has no effect. Set it before the element enters the DOM (in the markup, or on the element before appending it); frameworks that patch attributes after mount should bind it at creation. To apply a new value now, remove and re-insert the element.`
|
|
7556
|
+
);
|
|
7353
7557
|
}
|
|
7354
7558
|
/** Declare a frontend tool the agent may call. */
|
|
7355
7559
|
registerTool(tool) {
|
|
@@ -7565,6 +7769,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7565
7769
|
this.#wireVoice();
|
|
7566
7770
|
this.#threadId = this.conversationStore.threadId();
|
|
7567
7771
|
void this.#rehydrate();
|
|
7772
|
+
this.#connected = true;
|
|
7568
7773
|
}
|
|
7569
7774
|
/**
|
|
7570
7775
|
* Tear down live resources when the element leaves the DOM (a removed node, a
|
|
@@ -7575,10 +7780,27 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7575
7780
|
* `MediaRecorder`.
|
|
7576
7781
|
*/
|
|
7577
7782
|
disconnectedCallback() {
|
|
7783
|
+
this.#connected = false;
|
|
7578
7784
|
this.#cancelRun();
|
|
7579
7785
|
this.#attachTray?.dispose();
|
|
7580
7786
|
this.#voice?.dispose();
|
|
7581
7787
|
}
|
|
7788
|
+
/**
|
|
7789
|
+
* Read an opt-in flag attribute the way HTML reads a boolean attribute.
|
|
7790
|
+
*
|
|
7791
|
+
* Present means on — bare (`data-prompt-chips`), empty
|
|
7792
|
+
* (`data-prompt-chips=""`) or any value except the literal `"false"`. These
|
|
7793
|
+
* were compared against the string `"true"`, so writing the attribute bare —
|
|
7794
|
+
* the spelling every native boolean attribute uses, and the one a reader
|
|
7795
|
+
* reaches for first — silently *disabled* the feature it names, with nothing
|
|
7796
|
+
* to indicate why the chips never appeared.
|
|
7797
|
+
*
|
|
7798
|
+
* `="false"` still turns it off, so an explicit opt-out keeps working.
|
|
7799
|
+
*/
|
|
7800
|
+
#flag(name) {
|
|
7801
|
+
const value = this.getAttribute(name);
|
|
7802
|
+
return value !== null && value !== "false";
|
|
7803
|
+
}
|
|
7582
7804
|
/** Parse the inline `data-strings` JSON overrides (empty when absent/malformed). */
|
|
7583
7805
|
#readStringOverrides() {
|
|
7584
7806
|
const raw = this.getAttribute("data-strings");
|
|
@@ -7608,12 +7830,18 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7608
7830
|
return;
|
|
7609
7831
|
}
|
|
7610
7832
|
const accept = this.getAttribute("data-attachment-accept") ?? "";
|
|
7611
|
-
|
|
7833
|
+
const tray = new AttachmentTray({
|
|
7612
7834
|
upload,
|
|
7613
7835
|
maxBytes: this.#attachmentMaxBytes(),
|
|
7614
7836
|
accept,
|
|
7615
|
-
strings: this.#strings
|
|
7837
|
+
strings: this.#strings,
|
|
7838
|
+
// The tray's change hook, surfaced to the host as an event. A host
|
|
7839
|
+
// driving its own composer could otherwise not tell a settled upload from
|
|
7840
|
+
// one still in flight, which is the state sendMessage() has to be called
|
|
7841
|
+
// with knowledge of.
|
|
7842
|
+
onChange: () => this.#dispatchAttachments(tray)
|
|
7616
7843
|
});
|
|
7844
|
+
this.#attachTray = tray;
|
|
7617
7845
|
this.#attachSlot.appendChild(this.#attachTray.element);
|
|
7618
7846
|
this.#fileInput.accept = accept;
|
|
7619
7847
|
this.#attachButton.hidden = false;
|
|
@@ -7753,8 +7981,8 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
7753
7981
|
}
|
|
7754
7982
|
/** Wire the skill surfaces: opt-in flags, embedded catalog, optional fetch. */
|
|
7755
7983
|
#initSkills() {
|
|
7756
|
-
this.#skillsMenu.enableChips(this
|
|
7757
|
-
this.#skillsMenu.enableSlash(this
|
|
7984
|
+
this.#skillsMenu.enableChips(this.#flag("data-prompt-chips"));
|
|
7985
|
+
this.#skillsMenu.enableSlash(this.#flag("data-slash-commands"));
|
|
7758
7986
|
this.#embedSkills = this.#readEmbeddedSkills();
|
|
7759
7987
|
this.#recomputeSkills();
|
|
7760
7988
|
void this.#fetchSkills();
|
|
@@ -8095,6 +8323,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8095
8323
|
bubble.setAttribute("part", `message message-${role}`);
|
|
8096
8324
|
if (role === MESSAGE_ROLE.ASSISTANT) {
|
|
8097
8325
|
bubble.innerHTML = renderMarkdown(content, { allowImages: this.allowImages });
|
|
8326
|
+
attachCopyButtons(bubble, this.#strings);
|
|
8098
8327
|
this.#ensureGroup().appendChild(bubble);
|
|
8099
8328
|
} else {
|
|
8100
8329
|
this.#currentGroup = null;
|
|
@@ -8251,14 +8480,14 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8251
8480
|
}
|
|
8252
8481
|
/** Build a header control button (icon glyph + localized title/aria). */
|
|
8253
8482
|
#headerButton(modifier, label, glyph) {
|
|
8254
|
-
const
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
return
|
|
8483
|
+
const button2 = document.createElement("button");
|
|
8484
|
+
button2.type = "button";
|
|
8485
|
+
button2.className = `header-btn header-btn--${modifier}`;
|
|
8486
|
+
button2.setAttribute("part", `header-button ${modifier}-button`);
|
|
8487
|
+
button2.title = label;
|
|
8488
|
+
button2.setAttribute("aria-label", label);
|
|
8489
|
+
button2.textContent = glyph;
|
|
8490
|
+
return button2;
|
|
8262
8491
|
}
|
|
8263
8492
|
/**
|
|
8264
8493
|
* An icon holder wrapping a `<slot>` so a host can project custom markup; with
|
|
@@ -8338,12 +8567,46 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8338
8567
|
if (content === "" && attachments.length === 0) {
|
|
8339
8568
|
return;
|
|
8340
8569
|
}
|
|
8570
|
+
this.#input.value = "";
|
|
8571
|
+
if (this.#attachTray?.hasPending() === true) {
|
|
8572
|
+
this.#appendNotice(
|
|
8573
|
+
"\u{1F4CE}",
|
|
8574
|
+
this.#strings.attachmentsStillUploading.replace(
|
|
8575
|
+
"{n}",
|
|
8576
|
+
String(this.#attachTray.pendingCount())
|
|
8577
|
+
),
|
|
8578
|
+
"attachment-pending"
|
|
8579
|
+
);
|
|
8580
|
+
}
|
|
8581
|
+
this.#attachTray?.clearReady();
|
|
8582
|
+
await this.sendMessage(content, attachments);
|
|
8583
|
+
}
|
|
8584
|
+
/**
|
|
8585
|
+
* Send a message as if the user had typed it — renders the user bubble,
|
|
8586
|
+
* dispatches {@link SUBMIT_EVENT}, and starts the run.
|
|
8587
|
+
*
|
|
8588
|
+
* The programmatic half of the composer, for a host driving its own input
|
|
8589
|
+
* (a "Ask about this order" button, a command palette, a custom composer
|
|
8590
|
+
* replacing the built-in one). Everything the built-in Send does happens
|
|
8591
|
+
* here; Send itself now reads the composer, clears it, and calls this.
|
|
8592
|
+
*
|
|
8593
|
+
* `attachments` are durable {@link AttachmentRef}s — the shape
|
|
8594
|
+
* {@link attachFile}'s upload resolves to, and the shape
|
|
8595
|
+
* {@link ATTACHMENT_EVENT} reports. Pass them to attach files to the message.
|
|
8596
|
+
*
|
|
8597
|
+
* No-ops while a run is in flight (a second concurrent run would orphan the
|
|
8598
|
+
* first) and for an entirely empty message. Unlike the built-in Send, this
|
|
8599
|
+
* does **not** consult the tray: what you pass is what is sent, so a host
|
|
8600
|
+
* composer stays in charge of its own state.
|
|
8601
|
+
*/
|
|
8602
|
+
async sendMessage(content, attachments = []) {
|
|
8603
|
+
if (this.#running || content === "" && attachments.length === 0) {
|
|
8604
|
+
return;
|
|
8605
|
+
}
|
|
8341
8606
|
const bubble = this.appendMessage(MESSAGE_ROLE.USER, content);
|
|
8342
8607
|
if (attachments.length > 0) {
|
|
8343
8608
|
bubble.appendChild(renderAttachmentChips(attachments));
|
|
8344
8609
|
}
|
|
8345
|
-
this.#input.value = "";
|
|
8346
|
-
this.#attachTray?.clearReady();
|
|
8347
8610
|
this.#runAttachments = attachments;
|
|
8348
8611
|
this.dispatchEvent(
|
|
8349
8612
|
new CustomEvent(SUBMIT_EVENT, {
|
|
@@ -8354,6 +8617,35 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8354
8617
|
);
|
|
8355
8618
|
await this.#client_send(content, attachments);
|
|
8356
8619
|
}
|
|
8620
|
+
/**
|
|
8621
|
+
* Queue a file for upload into the attachment tray, exactly as the file
|
|
8622
|
+
* picker and drag-and-drop do — validation, progress chip, and all.
|
|
8623
|
+
*
|
|
8624
|
+
* Returns `false` when uploads are not configured (no `data-attachments-url`
|
|
8625
|
+
* and no {@link uploadHandler}), which is the only way for a host to tell;
|
|
8626
|
+
* the tray does not exist to report anything in that case.
|
|
8627
|
+
*
|
|
8628
|
+
* Uploading is asynchronous: watch {@link ATTACHMENT_EVENT} for the resulting
|
|
8629
|
+
* {@link AttachmentRef}, and pass it to {@link sendMessage} once `pending`
|
|
8630
|
+
* reaches zero.
|
|
8631
|
+
*/
|
|
8632
|
+
attachFile(file) {
|
|
8633
|
+
if (this.#attachTray === null) {
|
|
8634
|
+
return false;
|
|
8635
|
+
}
|
|
8636
|
+
this.#attachTray.add(file);
|
|
8637
|
+
return true;
|
|
8638
|
+
}
|
|
8639
|
+
/** Tell the host what the tray now holds — see {@link ATTACHMENT_EVENT}. */
|
|
8640
|
+
#dispatchAttachments(tray) {
|
|
8641
|
+
this.dispatchEvent(
|
|
8642
|
+
new CustomEvent(ATTACHMENT_EVENT, {
|
|
8643
|
+
detail: { attachments: tray.readyRefs(), pending: tray.pendingCount() },
|
|
8644
|
+
bubbles: true,
|
|
8645
|
+
composed: true
|
|
8646
|
+
})
|
|
8647
|
+
);
|
|
8648
|
+
}
|
|
8357
8649
|
async #client_send(content, attachments) {
|
|
8358
8650
|
if (this.endpoint === "") {
|
|
8359
8651
|
return;
|
|
@@ -8539,6 +8831,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8539
8831
|
if (this.#streamDeltas <= 1) {
|
|
8540
8832
|
this.#revealWords(bubble);
|
|
8541
8833
|
}
|
|
8834
|
+
attachCopyButtons(bubble, this.#strings);
|
|
8542
8835
|
this.#streamingBubble = null;
|
|
8543
8836
|
},
|
|
8544
8837
|
onToolCall: (call) => {
|
|
@@ -8766,8 +9059,9 @@ function setControlValue(el, value) {
|
|
|
8766
9059
|
}
|
|
8767
9060
|
|
|
8768
9061
|
// src/version.ts
|
|
8769
|
-
var VERSION = "0.
|
|
9062
|
+
var VERSION = "0.18.0";
|
|
8770
9063
|
export {
|
|
9064
|
+
ATTACHMENT_EVENT,
|
|
8771
9065
|
AgUiChat,
|
|
8772
9066
|
AgUiClient,
|
|
8773
9067
|
COMPACTION_ACTIVITY_TYPE,
|