@artooi/ag-ui-web-component 0.17.0 → 0.19.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 +113 -1
- package/README.md +38 -5
- package/dist/ag-ui-web-component.bundle.js +171 -60
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/core/ag_ui_chat.d.ts +5 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/index.js +385 -108
- 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/checkpoint_menu.d.ts.map +1 -1
- package/dist/ui/confirmation_card.d.ts +8 -2
- package/dist/ui/confirmation_card.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/tool_call_card.d.ts +26 -14
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +16 -4
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/ag_ui_chat.ts +51 -7
- package/src/ui/attach_copy_buttons.ts +82 -0
- package/src/ui/checkpoint_menu.ts +57 -6
- package/src/ui/confirmation_card.ts +15 -5
- package/src/ui/styles.ts +117 -4
- package/src/ui/tool_call_card.ts +116 -58
- package/src/ui/ui_strings.ts +22 -4
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -450,6 +450,9 @@ var DEFAULT_UI_STRINGS = {
|
|
|
450
450
|
collapse: "Collapse",
|
|
451
451
|
expand: "Expand",
|
|
452
452
|
toggleTheme: "Toggle theme",
|
|
453
|
+
copyCode: "Copy",
|
|
454
|
+
copied: "Copied",
|
|
455
|
+
copyFailed: "Copy failed",
|
|
453
456
|
checkpoints: "Continue a run",
|
|
454
457
|
noCheckpoints: "Nothing to continue yet.",
|
|
455
458
|
resumeRun: "Resume",
|
|
@@ -482,6 +485,9 @@ var DEFAULT_UI_STRINGS = {
|
|
|
482
485
|
toolDone: "\u2713 done",
|
|
483
486
|
toolError: "\u26A0 error",
|
|
484
487
|
toolDeclined: "\u2298 declined",
|
|
488
|
+
decisionApproved: "approved by you",
|
|
489
|
+
decisionDeclined: "declined by you",
|
|
490
|
+
argumentsLabel: "Arguments",
|
|
485
491
|
resultLabel: "Result",
|
|
486
492
|
errorLabel: "Error",
|
|
487
493
|
declinedLabel: "Declined",
|
|
@@ -531,12 +537,12 @@ function mergeUiStrings(overrides) {
|
|
|
531
537
|
|
|
532
538
|
// src/ui/approval_card.ts
|
|
533
539
|
function actionButton(modifier, label) {
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return
|
|
540
|
+
const button2 = document.createElement("button");
|
|
541
|
+
button2.type = "button";
|
|
542
|
+
button2.className = `approval-btn approval-btn--${modifier}`;
|
|
543
|
+
button2.setAttribute("part", `approval-button approval-${modifier}`);
|
|
544
|
+
button2.textContent = label;
|
|
545
|
+
return button2;
|
|
540
546
|
}
|
|
541
547
|
function requestApproval(host, request, options = {}) {
|
|
542
548
|
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
@@ -583,6 +589,55 @@ function requestApproval(host, request, options = {}) {
|
|
|
583
589
|
});
|
|
584
590
|
}
|
|
585
591
|
|
|
592
|
+
// src/ui/attach_copy_buttons.ts
|
|
593
|
+
var CONFIRM_MS = 1500;
|
|
594
|
+
function attachCopyButtons(root, strings) {
|
|
595
|
+
for (const pre of Array.from(root.querySelectorAll("pre"))) {
|
|
596
|
+
const code = pre.querySelector("code");
|
|
597
|
+
if (code === null || pre.querySelector(".code-copy") !== null) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
pre.classList.add("has-copy");
|
|
601
|
+
pre.append(button(code, strings));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function button(code, strings) {
|
|
605
|
+
const text2 = code.textContent;
|
|
606
|
+
const copy = document.createElement("button");
|
|
607
|
+
copy.type = "button";
|
|
608
|
+
copy.className = "code-copy";
|
|
609
|
+
copy.setAttribute("part", "code-copy");
|
|
610
|
+
copy.textContent = strings.copyCode;
|
|
611
|
+
copy.title = strings.copyCode;
|
|
612
|
+
copy.setAttribute("aria-label", strings.copyCode);
|
|
613
|
+
copy.addEventListener("click", () => {
|
|
614
|
+
void writeText(text2).then((ok) => {
|
|
615
|
+
confirm(copy, ok ? strings.copied : strings.copyFailed, strings);
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
return copy;
|
|
619
|
+
}
|
|
620
|
+
async function writeText(text2) {
|
|
621
|
+
const clipboard = navigator.clipboard;
|
|
622
|
+
if (clipboard === void 0) {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
await clipboard.writeText(text2);
|
|
627
|
+
return true;
|
|
628
|
+
} catch {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function confirm(copy, message, strings) {
|
|
633
|
+
copy.textContent = message;
|
|
634
|
+
copy.dataset["state"] = message === strings.copied ? "copied" : "failed";
|
|
635
|
+
setTimeout(() => {
|
|
636
|
+
copy.textContent = strings.copyCode;
|
|
637
|
+
delete copy.dataset["state"];
|
|
638
|
+
}, CONFIRM_MS);
|
|
639
|
+
}
|
|
640
|
+
|
|
586
641
|
// src/ui/attachment_chips.ts
|
|
587
642
|
function renderAttachmentChips(refs) {
|
|
588
643
|
const list = document.createElement("div");
|
|
@@ -885,6 +940,8 @@ var CheckpointMenu = class {
|
|
|
885
940
|
#onPick;
|
|
886
941
|
#list;
|
|
887
942
|
#heading;
|
|
943
|
+
/** What had focus before the panel opened, restored on close. */
|
|
944
|
+
#lastFocused = null;
|
|
888
945
|
#strings;
|
|
889
946
|
#runs = [];
|
|
890
947
|
constructor(onPick, strings = DEFAULT_UI_STRINGS) {
|
|
@@ -895,6 +952,7 @@ var CheckpointMenu = class {
|
|
|
895
952
|
this.element.setAttribute("part", "checkpoints");
|
|
896
953
|
this.element.setAttribute("role", "dialog");
|
|
897
954
|
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
955
|
+
this.element.tabIndex = -1;
|
|
898
956
|
this.element.hidden = true;
|
|
899
957
|
const header = document.createElement("div");
|
|
900
958
|
header.className = "checkpoints-header";
|
|
@@ -908,12 +966,7 @@ var CheckpointMenu = class {
|
|
|
908
966
|
this.#list.className = "checkpoints-list";
|
|
909
967
|
this.#list.setAttribute("part", "checkpoints-list");
|
|
910
968
|
this.element.append(header, this.#list);
|
|
911
|
-
this.element.addEventListener("keydown", (event) =>
|
|
912
|
-
if (event.key === "Escape") {
|
|
913
|
-
event.stopPropagation();
|
|
914
|
-
this.close();
|
|
915
|
-
}
|
|
916
|
-
});
|
|
969
|
+
this.element.addEventListener("keydown", (event) => this.#onKeydown(event));
|
|
917
970
|
}
|
|
918
971
|
/** Replace the rows. The host passes only `continuable` runs. */
|
|
919
972
|
setRuns(runs) {
|
|
@@ -928,10 +981,52 @@ var CheckpointMenu = class {
|
|
|
928
981
|
this.#render();
|
|
929
982
|
}
|
|
930
983
|
open() {
|
|
984
|
+
if (this.open_) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
this.#lastFocused = this.#activeElement();
|
|
931
988
|
this.element.hidden = false;
|
|
989
|
+
(this.#focusables()[0] ?? this.element).focus();
|
|
932
990
|
}
|
|
933
991
|
close() {
|
|
992
|
+
if (!this.open_) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
934
995
|
this.element.hidden = true;
|
|
996
|
+
this.#lastFocused?.focus();
|
|
997
|
+
this.#lastFocused = null;
|
|
998
|
+
}
|
|
999
|
+
/** The focused element within this panel's root (shadow-aware). */
|
|
1000
|
+
#activeElement() {
|
|
1001
|
+
return this.element.getRootNode().activeElement;
|
|
1002
|
+
}
|
|
1003
|
+
/** The panel's tabbable controls, in document order. */
|
|
1004
|
+
#focusables() {
|
|
1005
|
+
return Array.from(this.element.querySelectorAll("button, [tabindex]")).filter(
|
|
1006
|
+
(el) => !el.hidden
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
/** Escape closes; Tab is trapped so focus cannot wander behind the dialog. */
|
|
1010
|
+
#onKeydown(event) {
|
|
1011
|
+
if (event.key === "Escape") {
|
|
1012
|
+
event.stopPropagation();
|
|
1013
|
+
this.close();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (event.key !== "Tab") {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
const focusables = this.#focusables();
|
|
1020
|
+
const first = focusables[0];
|
|
1021
|
+
const last = focusables[focusables.length - 1];
|
|
1022
|
+
const active = this.#activeElement();
|
|
1023
|
+
if (event.shiftKey && active === first) {
|
|
1024
|
+
event.preventDefault();
|
|
1025
|
+
last?.focus();
|
|
1026
|
+
} else if (!event.shiftKey && active === last) {
|
|
1027
|
+
event.preventDefault();
|
|
1028
|
+
first?.focus();
|
|
1029
|
+
}
|
|
935
1030
|
}
|
|
936
1031
|
get open_() {
|
|
937
1032
|
return !this.element.hidden;
|
|
@@ -975,27 +1070,27 @@ var CheckpointMenu = class {
|
|
|
975
1070
|
return row;
|
|
976
1071
|
}
|
|
977
1072
|
#action(runId, verb, label) {
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1073
|
+
const button2 = document.createElement("button");
|
|
1074
|
+
button2.type = "button";
|
|
1075
|
+
button2.className = `checkpoint-action checkpoint-${verb}`;
|
|
1076
|
+
button2.setAttribute("part", `checkpoint-action checkpoint-${verb}`);
|
|
1077
|
+
button2.textContent = label;
|
|
1078
|
+
button2.addEventListener("click", () => {
|
|
984
1079
|
this.close();
|
|
985
1080
|
this.#onPick(runId, verb);
|
|
986
1081
|
});
|
|
987
|
-
return
|
|
1082
|
+
return button2;
|
|
988
1083
|
}
|
|
989
1084
|
};
|
|
990
1085
|
|
|
991
1086
|
// src/ui/confirmation_card.ts
|
|
992
1087
|
function actionButton2(modifier, label) {
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
return
|
|
1088
|
+
const button2 = document.createElement("button");
|
|
1089
|
+
button2.type = "button";
|
|
1090
|
+
button2.className = `confirm-btn confirm-btn--${modifier}`;
|
|
1091
|
+
button2.setAttribute("part", `confirm-button confirm-${modifier}`);
|
|
1092
|
+
button2.textContent = label;
|
|
1093
|
+
return button2;
|
|
999
1094
|
}
|
|
1000
1095
|
function requestConfirmation(host, request, options = {}) {
|
|
1001
1096
|
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
@@ -1014,33 +1109,32 @@ function requestConfirmation(host, request, options = {}) {
|
|
|
1014
1109
|
args.className = "confirm-args";
|
|
1015
1110
|
args.setAttribute("part", "confirm-args");
|
|
1016
1111
|
args.textContent = JSON.stringify(request.args, null, 2);
|
|
1112
|
+
args.hidden = Object.keys(request.args).length === 0;
|
|
1017
1113
|
const actions = document.createElement("div");
|
|
1018
1114
|
actions.className = "confirm-actions";
|
|
1019
1115
|
actions.setAttribute("part", "confirm-actions");
|
|
1020
1116
|
const cancel = actionButton2("cancel", strings.cancel);
|
|
1021
|
-
const
|
|
1117
|
+
const confirm2 = actionButton2("confirm", strings.confirm);
|
|
1022
1118
|
let settled = false;
|
|
1023
1119
|
const close = (accepted) => {
|
|
1024
1120
|
if (settled) {
|
|
1025
1121
|
return;
|
|
1026
1122
|
}
|
|
1027
1123
|
settled = true;
|
|
1028
|
-
|
|
1029
|
-
confirm.disabled = true;
|
|
1030
|
-
card.setAttribute("data-resolved", accepted ? "confirmed" : "declined");
|
|
1124
|
+
card.remove();
|
|
1031
1125
|
resolve(accepted);
|
|
1032
1126
|
};
|
|
1033
1127
|
cancel.addEventListener("click", () => close(false));
|
|
1034
|
-
|
|
1128
|
+
confirm2.addEventListener("click", () => close(true));
|
|
1035
1129
|
options.signal?.addEventListener("abort", () => close(false), { once: true });
|
|
1036
|
-
actions.append(cancel,
|
|
1130
|
+
actions.append(cancel, confirm2);
|
|
1037
1131
|
card.append(body, args, actions);
|
|
1038
1132
|
host.appendChild(card);
|
|
1039
1133
|
if (options.signal?.aborted === true) {
|
|
1040
1134
|
close(false);
|
|
1041
1135
|
return;
|
|
1042
1136
|
}
|
|
1043
|
-
|
|
1137
|
+
confirm2.focus();
|
|
1044
1138
|
});
|
|
1045
1139
|
}
|
|
1046
1140
|
|
|
@@ -4193,13 +4287,13 @@ var SkillsMenu = class {
|
|
|
4193
4287
|
const chipSkills = this.#chipsEnabled ? this.#skills.filter((skill) => skill.chip === true) : [];
|
|
4194
4288
|
this.chips.hidden = chipSkills.length === 0;
|
|
4195
4289
|
for (const skill of chipSkills) {
|
|
4196
|
-
const
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
this.chips.appendChild(
|
|
4290
|
+
const button2 = document.createElement("button");
|
|
4291
|
+
button2.type = "button";
|
|
4292
|
+
button2.className = "skill-chip";
|
|
4293
|
+
button2.setAttribute("part", "skill-chip");
|
|
4294
|
+
button2.textContent = skill.title;
|
|
4295
|
+
button2.addEventListener("click", () => this.#pick(skill));
|
|
4296
|
+
this.chips.appendChild(button2);
|
|
4203
4297
|
}
|
|
4204
4298
|
}
|
|
4205
4299
|
#renderPalette() {
|
|
@@ -4740,6 +4834,52 @@ var STYLES = `
|
|
|
4740
4834
|
padding: 0;
|
|
4741
4835
|
}
|
|
4742
4836
|
|
|
4837
|
+
/* The copy button sits inside the block, so it scrolls with wide code rather
|
|
4838
|
+
than floating over the bubble. Positioning is on the pre; the button only
|
|
4839
|
+
appears once one has been attached. */
|
|
4840
|
+
.message--assistant pre.has-copy {
|
|
4841
|
+
position: relative;
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4844
|
+
.code-copy {
|
|
4845
|
+
position: absolute;
|
|
4846
|
+
top: 4px;
|
|
4847
|
+
right: 4px;
|
|
4848
|
+
padding: 2px 8px;
|
|
4849
|
+
font: inherit;
|
|
4850
|
+
font-size: 0.75em;
|
|
4851
|
+
line-height: 1.6;
|
|
4852
|
+
color: var(--ag-ui-muted);
|
|
4853
|
+
background: var(--ag-ui-surface);
|
|
4854
|
+
border: 1px solid var(--ag-ui-border);
|
|
4855
|
+
border-radius: 4px;
|
|
4856
|
+
cursor: pointer;
|
|
4857
|
+
opacity: 0;
|
|
4858
|
+
transition: opacity 0.12s ease;
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
/* Revealed on hover or keyboard focus. Focus matters as much as hover here:
|
|
4862
|
+
hidden-until-hover is invisible to a keyboard user otherwise. */
|
|
4863
|
+
.message--assistant pre.has-copy:hover .code-copy,
|
|
4864
|
+
.code-copy:focus-visible {
|
|
4865
|
+
opacity: 1;
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
.code-copy:hover {
|
|
4869
|
+
color: var(--ag-ui-text);
|
|
4870
|
+
background: var(--ag-ui-hover);
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
.code-copy[data-state="copied"] {
|
|
4874
|
+
opacity: 1;
|
|
4875
|
+
color: var(--ag-ui-text);
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
.code-copy[data-state="failed"] {
|
|
4879
|
+
opacity: 1;
|
|
4880
|
+
color: var(--ag-ui-danger, var(--ag-ui-text));
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4743
4883
|
.message--assistant blockquote {
|
|
4744
4884
|
padding-left: 10px;
|
|
4745
4885
|
border-left: 3px solid var(--ag-ui-border);
|
|
@@ -4954,10 +5094,9 @@ var STYLES = `
|
|
|
4954
5094
|
}
|
|
4955
5095
|
}
|
|
4956
5096
|
|
|
4957
|
-
/* Inline display mode: the lightest card
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
.tool-call[data-display="inline"] {
|
|
5097
|
+
/* Inline display mode: the lightest card. Drop the box chrome so the status row
|
|
5098
|
+
reads as one line of the answer; the result toggle still expands below it. */
|
|
5099
|
+
:host([data-tool-display="inline"]) .tool-call {
|
|
4961
5100
|
max-width: 100%;
|
|
4962
5101
|
background: transparent;
|
|
4963
5102
|
border: none;
|
|
@@ -4987,6 +5126,37 @@ var STYLES = `
|
|
|
4987
5126
|
color: var(--ag-ui-muted);
|
|
4988
5127
|
}
|
|
4989
5128
|
|
|
5129
|
+
.tool-call-body {
|
|
5130
|
+
display: flex;
|
|
5131
|
+
flex-direction: column;
|
|
5132
|
+
gap: 6px;
|
|
5133
|
+
}
|
|
5134
|
+
|
|
5135
|
+
.tool-call-section {
|
|
5136
|
+
display: flex;
|
|
5137
|
+
flex-direction: column;
|
|
5138
|
+
gap: 3px;
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
/* The heading that tells the two payloads apart. Without it the arguments and
|
|
5142
|
+
the result were one run of text and a reader had to guess the boundary. */
|
|
5143
|
+
.tool-call-section-label {
|
|
5144
|
+
font-size: 10px;
|
|
5145
|
+
font-weight: 700;
|
|
5146
|
+
letter-spacing: 0.06em;
|
|
5147
|
+
text-transform: uppercase;
|
|
5148
|
+
color: var(--ag-ui-muted);
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
/* The record of a human decision on a gated call. An approved call used to
|
|
5152
|
+
look exactly like one that was never gated. */
|
|
5153
|
+
.tool-call-decision {
|
|
5154
|
+
flex: none;
|
|
5155
|
+
font-size: 11px;
|
|
5156
|
+
font-style: italic;
|
|
5157
|
+
color: var(--ag-ui-muted);
|
|
5158
|
+
}
|
|
5159
|
+
|
|
4990
5160
|
.tool-call-args,
|
|
4991
5161
|
.tool-call-result {
|
|
4992
5162
|
margin: 0;
|
|
@@ -5001,6 +5171,43 @@ var STYLES = `
|
|
|
5001
5171
|
color: var(--ag-ui-fg);
|
|
5002
5172
|
}
|
|
5003
5173
|
|
|
5174
|
+
/* Display modes are pure visibility over one DOM shape, selected from the host
|
|
5175
|
+
attribute rather than a value stamped on the card when it was built. That is
|
|
5176
|
+
what lets a host flip data-tool-display and have every card already on screen
|
|
5177
|
+
re-read it, the way data-answer-well behaves. Baking the structure per mode
|
|
5178
|
+
meant the setting only reached cards created afterwards.
|
|
5179
|
+
|
|
5180
|
+
Default (no attribute) is the full mode: arguments always visible, result
|
|
5181
|
+
behind the toggle. */
|
|
5182
|
+
.tool-call[data-expanded="false"] .tool-call-section--result {
|
|
5183
|
+
display: none;
|
|
5184
|
+
}
|
|
5185
|
+
|
|
5186
|
+
/* Compact: one toggle over both regions, so a settled card is a single line
|
|
5187
|
+
until asked. */
|
|
5188
|
+
:host([data-tool-display="compact"]) .tool-call[data-expanded="false"] .tool-call-section {
|
|
5189
|
+
display: none;
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
/* Inline: the result only; the call's arguments are noise at this density. */
|
|
5193
|
+
:host([data-tool-display="inline"]) .tool-call .tool-call-section--args {
|
|
5194
|
+
display: none;
|
|
5195
|
+
}
|
|
5196
|
+
|
|
5197
|
+
/* Minimal: the status row and nothing else, so there is no toggle to press. */
|
|
5198
|
+
:host([data-tool-display="minimal"]) .tool-call .tool-call-toggle,
|
|
5199
|
+
:host([data-tool-display="minimal"]) .tool-call .tool-call-body {
|
|
5200
|
+
display: none;
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
/* A pending card has no result yet, and in the modes where the arguments are
|
|
5204
|
+
hidden too there is nothing behind the toggle. Hide the control rather than
|
|
5205
|
+
offer one that expands onto nothing. */
|
|
5206
|
+
.tool-call[data-status="pending"] .tool-call-toggle,
|
|
5207
|
+
:host([data-tool-display="inline"]) .tool-call[data-status="pending"] .tool-call-toggle {
|
|
5208
|
+
display: none;
|
|
5209
|
+
}
|
|
5210
|
+
|
|
5004
5211
|
.tool-call-toggle {
|
|
5005
5212
|
align-self: flex-start;
|
|
5006
5213
|
border: none;
|
|
@@ -6053,9 +6260,9 @@ var ThreadDrawer = class {
|
|
|
6053
6260
|
}
|
|
6054
6261
|
/** Swap a row for an inline "Delete? [Delete] [Cancel]" confirm. */
|
|
6055
6262
|
#confirmDelete(row, meta) {
|
|
6056
|
-
const
|
|
6057
|
-
|
|
6058
|
-
|
|
6263
|
+
const confirm2 = document.createElement("div");
|
|
6264
|
+
confirm2.className = "drawer-confirm";
|
|
6265
|
+
confirm2.setAttribute("part", "drawer-confirm");
|
|
6059
6266
|
const label = document.createElement("span");
|
|
6060
6267
|
label.className = "drawer-confirm-label";
|
|
6061
6268
|
label.setAttribute("part", "drawer-confirm-label");
|
|
@@ -6072,8 +6279,8 @@ var ThreadDrawer = class {
|
|
|
6072
6279
|
no.setAttribute("part", "drawer-confirm-no");
|
|
6073
6280
|
no.textContent = this.#strings.cancel;
|
|
6074
6281
|
no.addEventListener("click", () => this.#renderList());
|
|
6075
|
-
|
|
6076
|
-
row.replaceChildren(
|
|
6282
|
+
confirm2.append(label, yes, no);
|
|
6283
|
+
row.replaceChildren(confirm2);
|
|
6077
6284
|
}
|
|
6078
6285
|
};
|
|
6079
6286
|
|
|
@@ -6093,24 +6300,32 @@ function resultLabels(strings) {
|
|
|
6093
6300
|
[TOOL_CALL_STATUS.DECLINED]: strings.declinedLabel
|
|
6094
6301
|
};
|
|
6095
6302
|
}
|
|
6303
|
+
function formatPayload(text2) {
|
|
6304
|
+
try {
|
|
6305
|
+
return JSON.stringify(JSON.parse(text2), null, 2);
|
|
6306
|
+
} catch {
|
|
6307
|
+
return text2;
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6096
6310
|
var ToolCallCard = class {
|
|
6097
6311
|
/** The card's root element; append this into the message list. */
|
|
6098
6312
|
element;
|
|
6099
6313
|
#status;
|
|
6100
|
-
#
|
|
6101
|
-
#
|
|
6314
|
+
#decision;
|
|
6315
|
+
#toggle;
|
|
6316
|
+
#resultSection;
|
|
6317
|
+
#resultLabel;
|
|
6318
|
+
#resultBody;
|
|
6102
6319
|
#strings;
|
|
6103
6320
|
#settled = false;
|
|
6104
|
-
constructor(name, args,
|
|
6105
|
-
this.#mode = mode;
|
|
6106
|
-
this.#args = args;
|
|
6321
|
+
constructor(name, args, summary, strings = DEFAULT_UI_STRINGS) {
|
|
6107
6322
|
this.#strings = strings;
|
|
6108
6323
|
this.element = document.createElement("div");
|
|
6109
6324
|
this.element.className = "tool-call";
|
|
6110
6325
|
this.element.setAttribute("part", "tool-card");
|
|
6111
6326
|
this.element.setAttribute("data-tool-name", name);
|
|
6112
6327
|
this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
|
|
6113
|
-
this.element.setAttribute("data-
|
|
6328
|
+
this.element.setAttribute("data-expanded", "false");
|
|
6114
6329
|
const head = document.createElement("div");
|
|
6115
6330
|
head.className = "tool-call-head";
|
|
6116
6331
|
head.setAttribute("part", "tool-card-head");
|
|
@@ -6126,24 +6341,52 @@ var ToolCallCard = class {
|
|
|
6126
6341
|
this.#status.className = "tool-call-status";
|
|
6127
6342
|
this.#status.setAttribute("part", "tool-card-status");
|
|
6128
6343
|
this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
|
|
6129
|
-
|
|
6130
|
-
this.
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6344
|
+
this.#decision = document.createElement("span");
|
|
6345
|
+
this.#decision.className = "tool-call-decision";
|
|
6346
|
+
this.#decision.setAttribute("part", "tool-card-decision");
|
|
6347
|
+
this.#decision.hidden = true;
|
|
6348
|
+
head.append(icon, label, this.#status, this.#decision);
|
|
6349
|
+
const argsSection = this.#section("args", strings.argumentsLabel);
|
|
6350
|
+
argsSection.body.textContent = JSON.stringify(args, null, 2);
|
|
6351
|
+
argsSection.root.hidden = Object.keys(args).length === 0;
|
|
6352
|
+
const resultSection = this.#section("result", strings.resultLabel);
|
|
6353
|
+
this.#resultSection = resultSection.root;
|
|
6354
|
+
this.#resultLabel = resultSection.label;
|
|
6355
|
+
this.#resultBody = resultSection.body;
|
|
6356
|
+
resultSection.root.hidden = true;
|
|
6357
|
+
this.#toggle = document.createElement("button");
|
|
6358
|
+
this.#toggle.type = "button";
|
|
6359
|
+
this.#toggle.className = "tool-call-toggle";
|
|
6360
|
+
this.#toggle.setAttribute("part", "tool-card-toggle");
|
|
6361
|
+
this.#toggle.setAttribute("aria-expanded", "false");
|
|
6362
|
+
this.#toggle.textContent = strings.details;
|
|
6363
|
+
this.#toggle.addEventListener("click", () => this.#setExpanded(!this.#expanded()));
|
|
6364
|
+
const body = document.createElement("div");
|
|
6365
|
+
body.className = "tool-call-body";
|
|
6366
|
+
body.setAttribute("part", "tool-card-body");
|
|
6367
|
+
body.append(argsSection.root, resultSection.root);
|
|
6368
|
+
this.element.append(head, this.#toggle, body);
|
|
6369
|
+
}
|
|
6370
|
+
/**
|
|
6371
|
+
* Record that a human approved or declined this call, as a line in the card.
|
|
6372
|
+
*
|
|
6373
|
+
* Approval used to leave no trace at all: a declined call became a tool
|
|
6374
|
+
* result saying so, while an approved one simply ran, making the transcript
|
|
6375
|
+
* of a gated call byte-identical to one that was never gated. The prompt is
|
|
6376
|
+
* gone once answered, so this is where the decision lives.
|
|
6377
|
+
*/
|
|
6378
|
+
recordDecision(kind) {
|
|
6379
|
+
this.element.setAttribute("data-decision", kind);
|
|
6380
|
+
this.#decision.textContent = kind === "approved" ? this.#strings.decisionApproved : this.#strings.decisionDeclined;
|
|
6381
|
+
this.#decision.hidden = false;
|
|
6138
6382
|
}
|
|
6139
6383
|
/** Whether {@link settle} has already run (so a terminal sweep can skip it). */
|
|
6140
6384
|
get settled() {
|
|
6141
6385
|
return this.#settled;
|
|
6142
6386
|
}
|
|
6143
6387
|
/**
|
|
6144
|
-
* Flip the status pill to
|
|
6145
|
-
*
|
|
6146
|
-
* `inline`), or the args + result together (`compact`).
|
|
6388
|
+
* Flip the status pill to `status` and fill in the result region, whose
|
|
6389
|
+
* heading names the outcome (result / error / declined).
|
|
6147
6390
|
*/
|
|
6148
6391
|
settle(status, text2) {
|
|
6149
6392
|
if (this.#settled) {
|
|
@@ -6152,33 +6395,31 @@ var ToolCallCard = class {
|
|
|
6152
6395
|
this.#settled = true;
|
|
6153
6396
|
this.element.setAttribute("data-status", status);
|
|
6154
6397
|
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
});
|
|
6181
|
-
this.element.append(toggle, output);
|
|
6398
|
+
this.#resultLabel.textContent = resultLabels(this.#strings)[status];
|
|
6399
|
+
this.#resultBody.textContent = formatPayload(text2);
|
|
6400
|
+
this.#resultSection.hidden = false;
|
|
6401
|
+
}
|
|
6402
|
+
/** Build one labelled region of the body: a heading plus a payload block. */
|
|
6403
|
+
#section(kind, labelText) {
|
|
6404
|
+
const root = document.createElement("div");
|
|
6405
|
+
root.className = `tool-call-section tool-call-section--${kind}`;
|
|
6406
|
+
root.setAttribute("part", `tool-card-section tool-card-${kind}-section`);
|
|
6407
|
+
const label = document.createElement("span");
|
|
6408
|
+
label.className = "tool-call-section-label";
|
|
6409
|
+
label.setAttribute("part", `tool-card-section-label tool-card-${kind}-label`);
|
|
6410
|
+
label.textContent = labelText;
|
|
6411
|
+
const body = document.createElement("pre");
|
|
6412
|
+
body.className = `tool-call-${kind}`;
|
|
6413
|
+
body.setAttribute("part", `tool-card-${kind}`);
|
|
6414
|
+
root.append(label, body);
|
|
6415
|
+
return { root, label, body };
|
|
6416
|
+
}
|
|
6417
|
+
#expanded() {
|
|
6418
|
+
return this.element.getAttribute("data-expanded") === "true";
|
|
6419
|
+
}
|
|
6420
|
+
#setExpanded(expand) {
|
|
6421
|
+
this.element.setAttribute("data-expanded", String(expand));
|
|
6422
|
+
this.#toggle.setAttribute("aria-expanded", String(expand));
|
|
6182
6423
|
}
|
|
6183
6424
|
};
|
|
6184
6425
|
|
|
@@ -7593,7 +7834,11 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7593
7834
|
}
|
|
7594
7835
|
/**
|
|
7595
7836
|
* How much detail tool-call cards show, from the `data-tool-display`
|
|
7596
|
-
* attribute (`minimal` / `compact` / `full`). Defaults to `full`.
|
|
7837
|
+
* attribute (`minimal` / `inline` / `compact` / `full`). Defaults to `full`.
|
|
7838
|
+
*
|
|
7839
|
+
* Applied by the shadow CSS from the attribute itself, so changing it
|
|
7840
|
+
* restyles every card already in the transcript rather than only the ones
|
|
7841
|
+
* built afterwards.
|
|
7597
7842
|
*/
|
|
7598
7843
|
get toolDisplay() {
|
|
7599
7844
|
const attr = this.getAttribute("data-tool-display");
|
|
@@ -7647,6 +7892,22 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7647
7892
|
this.#attachTray?.dispose();
|
|
7648
7893
|
this.#voice?.dispose();
|
|
7649
7894
|
}
|
|
7895
|
+
/**
|
|
7896
|
+
* Read an opt-in flag attribute the way HTML reads a boolean attribute.
|
|
7897
|
+
*
|
|
7898
|
+
* Present means on — bare (`data-prompt-chips`), empty
|
|
7899
|
+
* (`data-prompt-chips=""`) or any value except the literal `"false"`. These
|
|
7900
|
+
* were compared against the string `"true"`, so writing the attribute bare —
|
|
7901
|
+
* the spelling every native boolean attribute uses, and the one a reader
|
|
7902
|
+
* reaches for first — silently *disabled* the feature it names, with nothing
|
|
7903
|
+
* to indicate why the chips never appeared.
|
|
7904
|
+
*
|
|
7905
|
+
* `="false"` still turns it off, so an explicit opt-out keeps working.
|
|
7906
|
+
*/
|
|
7907
|
+
#flag(name) {
|
|
7908
|
+
const value = this.getAttribute(name);
|
|
7909
|
+
return value !== null && value !== "false";
|
|
7910
|
+
}
|
|
7650
7911
|
/** Parse the inline `data-strings` JSON overrides (empty when absent/malformed). */
|
|
7651
7912
|
#readStringOverrides() {
|
|
7652
7913
|
const raw = this.getAttribute("data-strings");
|
|
@@ -7827,8 +8088,8 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
7827
8088
|
}
|
|
7828
8089
|
/** Wire the skill surfaces: opt-in flags, embedded catalog, optional fetch. */
|
|
7829
8090
|
#initSkills() {
|
|
7830
|
-
this.#skillsMenu.enableChips(this
|
|
7831
|
-
this.#skillsMenu.enableSlash(this
|
|
8091
|
+
this.#skillsMenu.enableChips(this.#flag("data-prompt-chips"));
|
|
8092
|
+
this.#skillsMenu.enableSlash(this.#flag("data-slash-commands"));
|
|
7832
8093
|
this.#embedSkills = this.#readEmbeddedSkills();
|
|
7833
8094
|
this.#recomputeSkills();
|
|
7834
8095
|
void this.#fetchSkills();
|
|
@@ -8169,6 +8430,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8169
8430
|
bubble.setAttribute("part", `message message-${role}`);
|
|
8170
8431
|
if (role === MESSAGE_ROLE.ASSISTANT) {
|
|
8171
8432
|
bubble.innerHTML = renderMarkdown(content, { allowImages: this.allowImages });
|
|
8433
|
+
attachCopyButtons(bubble, this.#strings);
|
|
8172
8434
|
this.#ensureGroup().appendChild(bubble);
|
|
8173
8435
|
} else {
|
|
8174
8436
|
this.#currentGroup = null;
|
|
@@ -8323,16 +8585,29 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8323
8585
|
this.#rail.addEventListener("click", () => this.setCollapsed(false));
|
|
8324
8586
|
this.#root.append(style, this.#chat, this.#rail);
|
|
8325
8587
|
}
|
|
8326
|
-
/**
|
|
8588
|
+
/**
|
|
8589
|
+
* Build a header control button: a named slot a host can project markup into,
|
|
8590
|
+
* with the built-in glyph as the slot's fallback.
|
|
8591
|
+
*
|
|
8592
|
+
* The glyph used to be the button's own `textContent`, which left a host able
|
|
8593
|
+
* to restyle the control through its `part` but unable to replace it — a CSS
|
|
8594
|
+
* `content` override could swap one character for another, and nothing could
|
|
8595
|
+
* supply a brand `<img>` or `<svg>`. This is the same slot-with-fallback
|
|
8596
|
+
* idiom the header icon already uses, so existing embeds render exactly as
|
|
8597
|
+
* before.
|
|
8598
|
+
*/
|
|
8327
8599
|
#headerButton(modifier, label, glyph) {
|
|
8328
|
-
const
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8600
|
+
const button2 = document.createElement("button");
|
|
8601
|
+
button2.type = "button";
|
|
8602
|
+
button2.className = `header-btn header-btn--${modifier}`;
|
|
8603
|
+
button2.setAttribute("part", `header-button ${modifier}-button`);
|
|
8604
|
+
button2.title = label;
|
|
8605
|
+
button2.setAttribute("aria-label", label);
|
|
8606
|
+
const slot = document.createElement("slot");
|
|
8607
|
+
slot.name = `icon-${modifier}`;
|
|
8608
|
+
slot.append(document.createTextNode(glyph));
|
|
8609
|
+
button2.append(slot);
|
|
8610
|
+
return button2;
|
|
8336
8611
|
}
|
|
8337
8612
|
/**
|
|
8338
8613
|
* An icon holder wrapping a `<slot>` so a host can project custom markup; with
|
|
@@ -8571,7 +8846,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8571
8846
|
request.message = confirmText;
|
|
8572
8847
|
}
|
|
8573
8848
|
this.#confirmAbort = new AbortController();
|
|
8574
|
-
const decision = requestConfirmation(this.#
|
|
8849
|
+
const decision = requestConfirmation(this.#ensureGroup(), request, {
|
|
8575
8850
|
signal: this.#confirmAbort.signal,
|
|
8576
8851
|
strings: this.#strings
|
|
8577
8852
|
});
|
|
@@ -8579,6 +8854,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8579
8854
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
8580
8855
|
const accepted = await decision;
|
|
8581
8856
|
this.#confirmAbort = null;
|
|
8857
|
+
card.recordDecision(accepted ? "approved" : "declined");
|
|
8582
8858
|
if (!accepted) {
|
|
8583
8859
|
const message = this.#strings.declinedAction;
|
|
8584
8860
|
card.settle(TOOL_CALL_STATUS.DECLINED, message);
|
|
@@ -8676,6 +8952,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8676
8952
|
if (this.#streamDeltas <= 1) {
|
|
8677
8953
|
this.#revealWords(bubble);
|
|
8678
8954
|
}
|
|
8955
|
+
attachCopyButtons(bubble, this.#strings);
|
|
8679
8956
|
this.#streamingBubble = null;
|
|
8680
8957
|
},
|
|
8681
8958
|
onToolCall: (call) => {
|
|
@@ -8843,7 +9120,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8843
9120
|
}
|
|
8844
9121
|
const labelled = this.#resolveTool(call.name)?.parameters[X_SUMMARY_KEY];
|
|
8845
9122
|
const summary = typeof labelled === "string" ? labelled : this.toolSummaries[call.name] ?? this.#toolCatalog[call.name] ?? prettifyToolName(call.name);
|
|
8846
|
-
const card = new ToolCallCard(call.name, call.args,
|
|
9123
|
+
const card = new ToolCallCard(call.name, call.args, summary, this.#strings);
|
|
8847
9124
|
this.#toolCards.set(call.id, card);
|
|
8848
9125
|
this.#ensureGroup().appendChild(card.element);
|
|
8849
9126
|
this.#updateEmptyState();
|
|
@@ -8903,7 +9180,7 @@ function setControlValue(el, value) {
|
|
|
8903
9180
|
}
|
|
8904
9181
|
|
|
8905
9182
|
// src/version.ts
|
|
8906
|
-
var VERSION = "0.
|
|
9183
|
+
var VERSION = "0.19.0";
|
|
8907
9184
|
export {
|
|
8908
9185
|
ATTACHMENT_EVENT,
|
|
8909
9186
|
AgUiChat,
|