@artooi/ag-ui-web-component 0.9.0 → 0.11.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 +104 -7
- package/README.md +89 -7
- package/dist/ag-ui-web-component.bundle.js +168 -47
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/core/ag_ui_chat.d.ts +39 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +28 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/attachment.d.ts +5 -0
- package/dist/core/attachment.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +8 -0
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/upload_attachment.d.ts +8 -2
- package/dist/core/upload_attachment.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +887 -128
- package/dist/index.js.map +4 -4
- package/dist/ui/approval_card.d.ts +51 -0
- package/dist/ui/approval_card.d.ts.map +1 -0
- package/dist/ui/attachment_chips.d.ts.map +1 -1
- package/dist/ui/attachment_tray.d.ts +7 -1
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/question_card.d.ts +52 -0
- package/dist/ui/question_card.d.ts.map +1 -0
- package/dist/ui/relative_time.d.ts +5 -3
- package/dist/ui/relative_time.d.ts.map +1 -1
- package/dist/ui/skills_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/thoughts_block.d.ts +2 -2
- package/dist/ui/thoughts_block.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +16 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts +9 -1
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/ag_ui_chat.ts +251 -14
- package/src/core/agui_client.ts +95 -9
- package/src/core/attachment.ts +21 -1
- package/src/core/conversation_store.ts +84 -18
- package/src/core/remote_conversation_store.ts +24 -3
- package/src/core/upload_attachment.ts +8 -1
- package/src/index.ts +14 -0
- package/src/ui/approval_card.ts +119 -0
- package/src/ui/attachment_chips.ts +5 -0
- package/src/ui/attachment_tray.ts +50 -5
- package/src/ui/question_card.ts +216 -0
- package/src/ui/relative_time.ts +8 -3
- package/src/ui/skills_menu.ts +6 -0
- package/src/ui/styles.ts +130 -9
- package/src/ui/thoughts_block.ts +3 -2
- package/src/ui/thread_drawer.ts +94 -9
- package/src/ui/tool_call_card.ts +6 -0
- package/src/ui/ui_strings.ts +30 -0
- package/src/ui/voice_input.ts +21 -1
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -443,62 +443,6 @@ function createStateHookTools(hook) {
|
|
|
443
443
|
return tools;
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
// src/ui/attachment_chips.ts
|
|
447
|
-
function renderAttachmentChips(refs) {
|
|
448
|
-
const list = document.createElement("div");
|
|
449
|
-
list.className = "attachment-chips";
|
|
450
|
-
for (const ref of refs) {
|
|
451
|
-
list.appendChild(renderChip(ref));
|
|
452
|
-
}
|
|
453
|
-
return list;
|
|
454
|
-
}
|
|
455
|
-
function renderChip(ref) {
|
|
456
|
-
const chip = document.createElement("div");
|
|
457
|
-
chip.className = "attachment-chip attachment-chip--ready";
|
|
458
|
-
const icon = document.createElement("span");
|
|
459
|
-
icon.className = "attachment-chip-icon";
|
|
460
|
-
icon.textContent = iconFor(ref.mime);
|
|
461
|
-
icon.setAttribute("aria-hidden", "true");
|
|
462
|
-
const name = document.createElement("span");
|
|
463
|
-
name.className = "attachment-chip-name";
|
|
464
|
-
name.textContent = ref.name;
|
|
465
|
-
name.title = ref.name;
|
|
466
|
-
const size = document.createElement("span");
|
|
467
|
-
size.className = "attachment-chip-size";
|
|
468
|
-
size.textContent = formatBytes(ref.size);
|
|
469
|
-
chip.append(icon, name, size);
|
|
470
|
-
return chip;
|
|
471
|
-
}
|
|
472
|
-
function iconFor(mime) {
|
|
473
|
-
if (mime.startsWith("image/")) {
|
|
474
|
-
return "\u{1F5BC}";
|
|
475
|
-
}
|
|
476
|
-
if (mime === "application/pdf") {
|
|
477
|
-
return "\u{1F4D5}";
|
|
478
|
-
}
|
|
479
|
-
if (mime.startsWith("text/")) {
|
|
480
|
-
return "\u{1F4C4}";
|
|
481
|
-
}
|
|
482
|
-
return "\u{1F4CE}";
|
|
483
|
-
}
|
|
484
|
-
function formatBytes(bytes) {
|
|
485
|
-
if (bytes < 1024) {
|
|
486
|
-
return `${bytes} B`;
|
|
487
|
-
}
|
|
488
|
-
const units = ["KB", "MB", "GB"];
|
|
489
|
-
let value = bytes / 1024;
|
|
490
|
-
let unit = 0;
|
|
491
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
492
|
-
value /= 1024;
|
|
493
|
-
unit += 1;
|
|
494
|
-
}
|
|
495
|
-
const rounded = value < 10 ? Math.round(value * 10) / 10 : Math.round(value);
|
|
496
|
-
return `${rounded} ${units[unit]}`;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// src/ui/attachment_tray.ts
|
|
500
|
-
import { randomUUID } from "@ag-ui/client";
|
|
501
|
-
|
|
502
446
|
// src/ui/ui_strings.ts
|
|
503
447
|
var DEFAULT_UI_STRINGS = {
|
|
504
448
|
title: "Assistant",
|
|
@@ -537,6 +481,14 @@ var DEFAULT_UI_STRINGS = {
|
|
|
537
481
|
confirmRun: "Run \u201C{tool}\u201D?",
|
|
538
482
|
confirm: "Confirm",
|
|
539
483
|
cancel: "Cancel",
|
|
484
|
+
approveAction: "Approve action",
|
|
485
|
+
approvalPrompt: "Approve this action?",
|
|
486
|
+
approve: "Approve",
|
|
487
|
+
deny: "Deny",
|
|
488
|
+
askUserAction: "Question",
|
|
489
|
+
otherOption: "Other\u2026",
|
|
490
|
+
answerPlaceholder: "Type your answer\u2026",
|
|
491
|
+
submit: "Submit",
|
|
540
492
|
chats: "Chats",
|
|
541
493
|
noConversations: "No conversations yet.",
|
|
542
494
|
rename: "Rename",
|
|
@@ -568,7 +520,120 @@ function mergeUiStrings(overrides) {
|
|
|
568
520
|
return merged;
|
|
569
521
|
}
|
|
570
522
|
|
|
523
|
+
// src/ui/approval_card.ts
|
|
524
|
+
function actionButton(modifier, label) {
|
|
525
|
+
const button = document.createElement("button");
|
|
526
|
+
button.type = "button";
|
|
527
|
+
button.className = `approval-btn approval-btn--${modifier}`;
|
|
528
|
+
button.setAttribute("part", `approval-button approval-${modifier}`);
|
|
529
|
+
button.textContent = label;
|
|
530
|
+
return button;
|
|
531
|
+
}
|
|
532
|
+
function requestApproval(host, request, options = {}) {
|
|
533
|
+
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
534
|
+
return new Promise((resolve) => {
|
|
535
|
+
const card = document.createElement("div");
|
|
536
|
+
card.className = "approval";
|
|
537
|
+
card.setAttribute("part", "approval");
|
|
538
|
+
if (request.toolName !== void 0) {
|
|
539
|
+
card.setAttribute("data-tool-name", request.toolName);
|
|
540
|
+
}
|
|
541
|
+
card.setAttribute("role", "group");
|
|
542
|
+
card.setAttribute("aria-label", strings.approveAction);
|
|
543
|
+
const body = document.createElement("div");
|
|
544
|
+
body.className = "approval-body";
|
|
545
|
+
body.setAttribute("part", "approval-body");
|
|
546
|
+
body.textContent = request.message ?? strings.approvalPrompt;
|
|
547
|
+
const actions = document.createElement("div");
|
|
548
|
+
actions.className = "approval-actions";
|
|
549
|
+
actions.setAttribute("part", "approval-actions");
|
|
550
|
+
const deny = actionButton("deny", strings.deny);
|
|
551
|
+
const approve = actionButton("approve", strings.approve);
|
|
552
|
+
let settled = false;
|
|
553
|
+
const close = (approved) => {
|
|
554
|
+
if (settled) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
settled = true;
|
|
558
|
+
deny.disabled = true;
|
|
559
|
+
approve.disabled = true;
|
|
560
|
+
card.setAttribute("data-resolved", approved ? "approved" : "denied");
|
|
561
|
+
resolve(approved);
|
|
562
|
+
};
|
|
563
|
+
deny.addEventListener("click", () => close(false));
|
|
564
|
+
approve.addEventListener("click", () => close(true));
|
|
565
|
+
options.signal?.addEventListener("abort", () => close(false), { once: true });
|
|
566
|
+
actions.append(deny, approve);
|
|
567
|
+
card.append(body, actions);
|
|
568
|
+
host.appendChild(card);
|
|
569
|
+
if (options.signal?.aborted === true) {
|
|
570
|
+
close(false);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
approve.focus();
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/ui/attachment_chips.ts
|
|
578
|
+
function renderAttachmentChips(refs) {
|
|
579
|
+
const list = document.createElement("div");
|
|
580
|
+
list.className = "attachment-chips";
|
|
581
|
+
list.setAttribute("part", "attachment-chips");
|
|
582
|
+
for (const ref of refs) {
|
|
583
|
+
list.appendChild(renderChip(ref));
|
|
584
|
+
}
|
|
585
|
+
return list;
|
|
586
|
+
}
|
|
587
|
+
function renderChip(ref) {
|
|
588
|
+
const chip = document.createElement("div");
|
|
589
|
+
chip.className = "attachment-chip attachment-chip--ready";
|
|
590
|
+
chip.setAttribute("part", "attachment-chip");
|
|
591
|
+
const icon = document.createElement("span");
|
|
592
|
+
icon.className = "attachment-chip-icon";
|
|
593
|
+
icon.setAttribute("part", "attachment-chip-icon");
|
|
594
|
+
icon.textContent = iconFor(ref.mime);
|
|
595
|
+
icon.setAttribute("aria-hidden", "true");
|
|
596
|
+
const name = document.createElement("span");
|
|
597
|
+
name.className = "attachment-chip-name";
|
|
598
|
+
name.setAttribute("part", "attachment-chip-name");
|
|
599
|
+
name.textContent = ref.name;
|
|
600
|
+
name.title = ref.name;
|
|
601
|
+
const size = document.createElement("span");
|
|
602
|
+
size.className = "attachment-chip-size";
|
|
603
|
+
size.setAttribute("part", "attachment-chip-size");
|
|
604
|
+
size.textContent = formatBytes(ref.size);
|
|
605
|
+
chip.append(icon, name, size);
|
|
606
|
+
return chip;
|
|
607
|
+
}
|
|
608
|
+
function iconFor(mime) {
|
|
609
|
+
if (mime.startsWith("image/")) {
|
|
610
|
+
return "\u{1F5BC}";
|
|
611
|
+
}
|
|
612
|
+
if (mime === "application/pdf") {
|
|
613
|
+
return "\u{1F4D5}";
|
|
614
|
+
}
|
|
615
|
+
if (mime.startsWith("text/")) {
|
|
616
|
+
return "\u{1F4C4}";
|
|
617
|
+
}
|
|
618
|
+
return "\u{1F4CE}";
|
|
619
|
+
}
|
|
620
|
+
function formatBytes(bytes) {
|
|
621
|
+
if (bytes < 1024) {
|
|
622
|
+
return `${bytes} B`;
|
|
623
|
+
}
|
|
624
|
+
const units = ["KB", "MB", "GB"];
|
|
625
|
+
let value = bytes / 1024;
|
|
626
|
+
let unit = 0;
|
|
627
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
628
|
+
value /= 1024;
|
|
629
|
+
unit += 1;
|
|
630
|
+
}
|
|
631
|
+
const rounded = value < 10 ? Math.round(value * 10) / 10 : Math.round(value);
|
|
632
|
+
return `${rounded} ${units[unit]}`;
|
|
633
|
+
}
|
|
634
|
+
|
|
571
635
|
// src/ui/attachment_tray.ts
|
|
636
|
+
import { randomUUID } from "@ag-ui/client";
|
|
572
637
|
var AttachmentTray = class {
|
|
573
638
|
/** The tray root; append above the input row. Hidden while empty. */
|
|
574
639
|
element;
|
|
@@ -591,7 +656,8 @@ var AttachmentTray = class {
|
|
|
591
656
|
status: ATTACHMENT_STATUS.UPLOADING,
|
|
592
657
|
progress: 0,
|
|
593
658
|
ref: null,
|
|
594
|
-
error: ""
|
|
659
|
+
error: "",
|
|
660
|
+
controller: null
|
|
595
661
|
};
|
|
596
662
|
this.#items.push(item);
|
|
597
663
|
const rejection = this.#reject(file);
|
|
@@ -629,11 +695,24 @@ var AttachmentTray = class {
|
|
|
629
695
|
this.#items = this.#items.filter((item) => item.status === ATTACHMENT_STATUS.UPLOADING);
|
|
630
696
|
this.#render();
|
|
631
697
|
}
|
|
632
|
-
/** Drop every chip (a reset / new-chat). */
|
|
698
|
+
/** Drop every chip (a reset / new-chat), aborting any in-flight upload. */
|
|
633
699
|
clear() {
|
|
700
|
+
for (const item of this.#items) {
|
|
701
|
+
item.controller?.abort();
|
|
702
|
+
}
|
|
634
703
|
this.#items = [];
|
|
635
704
|
this.#render();
|
|
636
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Abort every in-flight upload without touching the rendered chips — the
|
|
708
|
+
* teardown path when the host element is removed mid-upload, so a cancelled
|
|
709
|
+
* transfer doesn't orphan a server-side file.
|
|
710
|
+
*/
|
|
711
|
+
dispose() {
|
|
712
|
+
for (const item of this.#items) {
|
|
713
|
+
item.controller?.abort();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
637
716
|
/** The size/type rejection reason for a file, or `null` when accepted. */
|
|
638
717
|
#reject(file) {
|
|
639
718
|
if (this.#config.maxBytes > 0 && file.size > this.#config.maxBytes) {
|
|
@@ -645,25 +724,41 @@ var AttachmentTray = class {
|
|
|
645
724
|
return null;
|
|
646
725
|
}
|
|
647
726
|
#upload(item) {
|
|
727
|
+
const rejection = this.#reject(item.file);
|
|
728
|
+
if (rejection !== null) {
|
|
729
|
+
item.status = ATTACHMENT_STATUS.ERROR;
|
|
730
|
+
item.error = rejection;
|
|
731
|
+
this.#render();
|
|
732
|
+
this.#config.onChange?.();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
648
735
|
item.status = ATTACHMENT_STATUS.UPLOADING;
|
|
649
736
|
item.progress = 0;
|
|
650
737
|
item.error = "";
|
|
738
|
+
const controller = new AbortController();
|
|
739
|
+
item.controller = controller;
|
|
651
740
|
this.#render();
|
|
652
|
-
this.#config.upload(
|
|
653
|
-
item.
|
|
654
|
-
|
|
655
|
-
|
|
741
|
+
this.#config.upload(
|
|
742
|
+
item.file,
|
|
743
|
+
(fraction) => {
|
|
744
|
+
item.progress = fraction;
|
|
745
|
+
this.#render();
|
|
746
|
+
},
|
|
747
|
+
controller.signal
|
|
748
|
+
).then((ref) => {
|
|
656
749
|
item.status = ATTACHMENT_STATUS.READY;
|
|
657
750
|
item.ref = ref;
|
|
658
751
|
}).catch((error) => {
|
|
659
752
|
item.status = ATTACHMENT_STATUS.ERROR;
|
|
660
753
|
item.error = error instanceof Error ? error.message : this.#strings.uploadFailed;
|
|
661
754
|
}).finally(() => {
|
|
755
|
+
item.controller = null;
|
|
662
756
|
this.#render();
|
|
663
757
|
this.#config.onChange?.();
|
|
664
758
|
});
|
|
665
759
|
}
|
|
666
760
|
#remove(item) {
|
|
761
|
+
item.controller?.abort();
|
|
667
762
|
this.#items = this.#items.filter((other) => other !== item);
|
|
668
763
|
this.#render();
|
|
669
764
|
this.#config.onChange?.();
|
|
@@ -678,23 +773,29 @@ var AttachmentTray = class {
|
|
|
678
773
|
#renderChip(item) {
|
|
679
774
|
const chip = document.createElement("div");
|
|
680
775
|
chip.className = `attachment-chip attachment-chip--${item.status}`;
|
|
776
|
+
chip.setAttribute("part", "attachment-chip");
|
|
681
777
|
const icon = document.createElement("span");
|
|
682
778
|
icon.className = "attachment-chip-icon";
|
|
779
|
+
icon.setAttribute("part", "attachment-chip-icon");
|
|
683
780
|
icon.textContent = iconFor(item.file.type);
|
|
684
781
|
icon.setAttribute("aria-hidden", "true");
|
|
685
782
|
const name = document.createElement("span");
|
|
686
783
|
name.className = "attachment-chip-name";
|
|
784
|
+
name.setAttribute("part", "attachment-chip-name");
|
|
687
785
|
name.textContent = item.file.name;
|
|
688
786
|
name.title = item.file.name;
|
|
689
787
|
const meta = document.createElement("span");
|
|
690
788
|
meta.className = "attachment-chip-size";
|
|
789
|
+
meta.setAttribute("part", "attachment-chip-size");
|
|
691
790
|
meta.textContent = item.status === ATTACHMENT_STATUS.ERROR ? item.error : formatBytes(item.file.size);
|
|
692
791
|
chip.append(icon, name, meta);
|
|
693
792
|
if (item.status === ATTACHMENT_STATUS.UPLOADING) {
|
|
694
793
|
const bar = document.createElement("div");
|
|
695
794
|
bar.className = "attachment-chip-bar";
|
|
795
|
+
bar.setAttribute("part", "attachment-chip-bar");
|
|
696
796
|
const fill = document.createElement("div");
|
|
697
797
|
fill.className = "attachment-chip-bar-fill";
|
|
798
|
+
fill.setAttribute("part", "attachment-chip-bar-fill");
|
|
698
799
|
fill.style.width = `${Math.round(item.progress * 100)}%`;
|
|
699
800
|
bar.appendChild(fill);
|
|
700
801
|
chip.appendChild(bar);
|
|
@@ -703,6 +804,7 @@ var AttachmentTray = class {
|
|
|
703
804
|
const retry = document.createElement("button");
|
|
704
805
|
retry.type = "button";
|
|
705
806
|
retry.className = "attachment-chip-retry";
|
|
807
|
+
retry.setAttribute("part", "attachment-chip-retry");
|
|
706
808
|
retry.title = this.#strings.retry;
|
|
707
809
|
retry.setAttribute("aria-label", this.#strings.retryUpload);
|
|
708
810
|
retry.textContent = "\u21BB";
|
|
@@ -712,6 +814,7 @@ var AttachmentTray = class {
|
|
|
712
814
|
const remove = document.createElement("button");
|
|
713
815
|
remove.type = "button";
|
|
714
816
|
remove.className = "attachment-chip-remove";
|
|
817
|
+
remove.setAttribute("part", "attachment-chip-remove");
|
|
715
818
|
remove.title = this.#strings.remove;
|
|
716
819
|
remove.setAttribute("aria-label", this.#strings.removeAttachment);
|
|
717
820
|
remove.textContent = "\u2715";
|
|
@@ -739,7 +842,7 @@ function accepts(accept, file) {
|
|
|
739
842
|
}
|
|
740
843
|
|
|
741
844
|
// src/ui/confirmation_card.ts
|
|
742
|
-
function
|
|
845
|
+
function actionButton2(modifier, label) {
|
|
743
846
|
const button = document.createElement("button");
|
|
744
847
|
button.type = "button";
|
|
745
848
|
button.className = `confirm-btn confirm-btn--${modifier}`;
|
|
@@ -767,8 +870,8 @@ function requestConfirmation(host, request, options = {}) {
|
|
|
767
870
|
const actions = document.createElement("div");
|
|
768
871
|
actions.className = "confirm-actions";
|
|
769
872
|
actions.setAttribute("part", "confirm-actions");
|
|
770
|
-
const cancel =
|
|
771
|
-
const confirm =
|
|
873
|
+
const cancel = actionButton2("cancel", strings.cancel);
|
|
874
|
+
const confirm = actionButton2("confirm", strings.confirm);
|
|
772
875
|
let settled = false;
|
|
773
876
|
const close = (accepted) => {
|
|
774
877
|
if (settled) {
|
|
@@ -803,6 +906,149 @@ function prettifyToolName(name) {
|
|
|
803
906
|
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
804
907
|
}
|
|
805
908
|
|
|
909
|
+
// src/ui/question_card.ts
|
|
910
|
+
function answerInput(placeholder) {
|
|
911
|
+
const input = document.createElement("input");
|
|
912
|
+
input.type = "text";
|
|
913
|
+
input.className = "question-input";
|
|
914
|
+
input.setAttribute("part", "question-input");
|
|
915
|
+
input.placeholder = placeholder;
|
|
916
|
+
return input;
|
|
917
|
+
}
|
|
918
|
+
function requestQuestion(host, request, options = {}) {
|
|
919
|
+
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
920
|
+
const choices = request.options ?? [];
|
|
921
|
+
const hasChoices = choices.length > 0;
|
|
922
|
+
const allowsText = !hasChoices || request.allowCustom === true;
|
|
923
|
+
return new Promise((resolve) => {
|
|
924
|
+
const card = document.createElement("div");
|
|
925
|
+
card.className = "question";
|
|
926
|
+
card.setAttribute("part", "question");
|
|
927
|
+
card.setAttribute("role", "group");
|
|
928
|
+
card.setAttribute("aria-label", strings.askUserAction);
|
|
929
|
+
const body = document.createElement("div");
|
|
930
|
+
body.className = "question-body";
|
|
931
|
+
body.setAttribute("part", "question-body");
|
|
932
|
+
body.textContent = request.question;
|
|
933
|
+
const form = document.createElement("div");
|
|
934
|
+
form.className = "question-options";
|
|
935
|
+
form.setAttribute("part", "question-options");
|
|
936
|
+
const group = `q-${choices.length}-${request.question.length}`;
|
|
937
|
+
const radios = [];
|
|
938
|
+
for (const choice of choices) {
|
|
939
|
+
const label = document.createElement("label");
|
|
940
|
+
label.className = "question-choice";
|
|
941
|
+
label.setAttribute("part", "question-choice");
|
|
942
|
+
const radio = document.createElement("input");
|
|
943
|
+
radio.type = "radio";
|
|
944
|
+
radio.name = group;
|
|
945
|
+
radio.value = choice;
|
|
946
|
+
radio.setAttribute("part", "question-radio");
|
|
947
|
+
const text2 = document.createElement("span");
|
|
948
|
+
text2.setAttribute("part", "question-choice-text");
|
|
949
|
+
text2.textContent = choice;
|
|
950
|
+
label.append(radio, text2);
|
|
951
|
+
form.appendChild(label);
|
|
952
|
+
radios.push(radio);
|
|
953
|
+
}
|
|
954
|
+
let otherRadio = null;
|
|
955
|
+
let input = null;
|
|
956
|
+
if (allowsText) {
|
|
957
|
+
input = answerInput(strings.answerPlaceholder);
|
|
958
|
+
if (hasChoices) {
|
|
959
|
+
const label = document.createElement("label");
|
|
960
|
+
label.className = "question-choice";
|
|
961
|
+
label.setAttribute("part", "question-choice");
|
|
962
|
+
otherRadio = document.createElement("input");
|
|
963
|
+
otherRadio.type = "radio";
|
|
964
|
+
otherRadio.name = group;
|
|
965
|
+
otherRadio.value = "";
|
|
966
|
+
otherRadio.setAttribute("part", "question-radio");
|
|
967
|
+
const text2 = document.createElement("span");
|
|
968
|
+
text2.setAttribute("part", "question-choice-text");
|
|
969
|
+
text2.textContent = strings.otherOption;
|
|
970
|
+
label.append(otherRadio, text2);
|
|
971
|
+
form.appendChild(label);
|
|
972
|
+
input.disabled = true;
|
|
973
|
+
}
|
|
974
|
+
form.appendChild(input);
|
|
975
|
+
}
|
|
976
|
+
const actions = document.createElement("div");
|
|
977
|
+
actions.className = "question-actions";
|
|
978
|
+
actions.setAttribute("part", "question-actions");
|
|
979
|
+
const submit = document.createElement("button");
|
|
980
|
+
submit.type = "button";
|
|
981
|
+
submit.className = "question-btn";
|
|
982
|
+
submit.setAttribute("part", "question-button");
|
|
983
|
+
submit.textContent = strings.submit;
|
|
984
|
+
actions.appendChild(submit);
|
|
985
|
+
let settled = false;
|
|
986
|
+
const answerFor = () => {
|
|
987
|
+
const picked = radios.find((r) => r.checked);
|
|
988
|
+
if (picked !== void 0) {
|
|
989
|
+
return picked.value;
|
|
990
|
+
}
|
|
991
|
+
if (input !== null && (otherRadio === null || otherRadio.checked)) {
|
|
992
|
+
const typed = input.value.trim();
|
|
993
|
+
return typed === "" ? null : typed;
|
|
994
|
+
}
|
|
995
|
+
return null;
|
|
996
|
+
};
|
|
997
|
+
const refresh = () => {
|
|
998
|
+
if (input !== null && otherRadio !== null) {
|
|
999
|
+
input.disabled = !otherRadio.checked;
|
|
1000
|
+
}
|
|
1001
|
+
submit.disabled = answerFor() === null;
|
|
1002
|
+
};
|
|
1003
|
+
const close = (answer) => {
|
|
1004
|
+
if (settled) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
settled = true;
|
|
1008
|
+
submit.disabled = true;
|
|
1009
|
+
for (const radio of radios) {
|
|
1010
|
+
radio.disabled = true;
|
|
1011
|
+
}
|
|
1012
|
+
if (otherRadio !== null) {
|
|
1013
|
+
otherRadio.disabled = true;
|
|
1014
|
+
}
|
|
1015
|
+
if (input !== null) {
|
|
1016
|
+
input.disabled = true;
|
|
1017
|
+
}
|
|
1018
|
+
card.setAttribute("data-resolved", answer === "" ? "cancelled" : "answered");
|
|
1019
|
+
resolve(answer);
|
|
1020
|
+
};
|
|
1021
|
+
for (const radio of [...radios, ...otherRadio !== null ? [otherRadio] : []]) {
|
|
1022
|
+
radio.addEventListener("change", refresh);
|
|
1023
|
+
}
|
|
1024
|
+
input?.addEventListener("input", refresh);
|
|
1025
|
+
input?.addEventListener("keydown", (event) => {
|
|
1026
|
+
if (event.key === "Enter") {
|
|
1027
|
+
event.preventDefault();
|
|
1028
|
+
const answer = answerFor();
|
|
1029
|
+
if (answer !== null) {
|
|
1030
|
+
close(answer);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
submit.addEventListener("click", () => {
|
|
1035
|
+
const answer = answerFor();
|
|
1036
|
+
if (answer !== null) {
|
|
1037
|
+
close(answer);
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
options.signal?.addEventListener("abort", () => close(""), { once: true });
|
|
1041
|
+
card.append(body, form, actions);
|
|
1042
|
+
host.appendChild(card);
|
|
1043
|
+
if (options.signal?.aborted === true) {
|
|
1044
|
+
close("");
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
refresh();
|
|
1048
|
+
(hasChoices ? radios[0] : input)?.focus();
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
806
1052
|
// node_modules/.pnpm/dompurify@3.4.7/node_modules/dompurify/dist/purify.es.mjs
|
|
807
1053
|
function _arrayLikeToArray(r, a) {
|
|
808
1054
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -2003,7 +2249,7 @@ function createDOMPurify() {
|
|
|
2003
2249
|
}
|
|
2004
2250
|
var purify = createDOMPurify();
|
|
2005
2251
|
|
|
2006
|
-
// node_modules/.pnpm/marked@18.0.
|
|
2252
|
+
// node_modules/.pnpm/marked@18.0.5/node_modules/marked/lib/marked.esm.js
|
|
2007
2253
|
function M() {
|
|
2008
2254
|
return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
|
|
2009
2255
|
}
|
|
@@ -2047,15 +2293,15 @@ var F = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|
|
|
|
2047
2293
|
var $e = /^[^\n]+/;
|
|
2048
2294
|
var U = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
|
|
2049
2295
|
var Le = d(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", U).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
|
|
2050
|
-
var _e = d(/^(bull)([ \t][^\n]
|
|
2296
|
+
var _e = d(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, j).getRegex();
|
|
2051
2297
|
var H = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
|
|
2052
2298
|
var K = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
|
2053
2299
|
var ze = d("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", K).replace("tag", H).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
|
|
2054
|
-
var le = d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
|
|
2300
|
+
var le = d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
|
|
2055
2301
|
var Me = d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", le).getRegex();
|
|
2056
2302
|
var W = { blockquote: Me, code: we, def: Le, fences: ye, heading: Pe, hr: B, html: ze, lheading: ae, list: _e, newline: Oe, paragraph: le, table: _, text: $e };
|
|
2057
2303
|
var se = d("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
|
|
2058
|
-
var Ee = { ...W, lheading: Se, table: se, paragraph: d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex() };
|
|
2304
|
+
var Ee = { ...W, lheading: Se, table: se, paragraph: d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex() };
|
|
2059
2305
|
var Ie = { ...W, html: d(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", K).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: _, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: d(F).replace("hr", B).replace("heading", ` *#{1,6} *[^
|
|
2060
2306
|
]`).replace("lheading", ae).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() };
|
|
2061
2307
|
var Ae = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
|
|
@@ -3346,9 +3592,11 @@ var SkillsMenu = class {
|
|
|
3346
3592
|
this.#onPick = onPick;
|
|
3347
3593
|
this.chips = document.createElement("div");
|
|
3348
3594
|
this.chips.className = "skill-chips";
|
|
3595
|
+
this.chips.setAttribute("part", "skill-chips");
|
|
3349
3596
|
this.chips.hidden = true;
|
|
3350
3597
|
this.palette = document.createElement("div");
|
|
3351
3598
|
this.palette.className = "skill-palette";
|
|
3599
|
+
this.palette.setAttribute("part", "skill-palette");
|
|
3352
3600
|
this.palette.setAttribute("role", "listbox");
|
|
3353
3601
|
this.palette.hidden = true;
|
|
3354
3602
|
}
|
|
@@ -3443,6 +3691,7 @@ var SkillsMenu = class {
|
|
|
3443
3691
|
const button = document.createElement("button");
|
|
3444
3692
|
button.type = "button";
|
|
3445
3693
|
button.className = "skill-chip";
|
|
3694
|
+
button.setAttribute("part", "skill-chip");
|
|
3446
3695
|
button.textContent = skill.title;
|
|
3447
3696
|
button.addEventListener("click", () => this.#pick(skill));
|
|
3448
3697
|
this.chips.appendChild(button);
|
|
@@ -3454,15 +3703,18 @@ var SkillsMenu = class {
|
|
|
3454
3703
|
const item = document.createElement("button");
|
|
3455
3704
|
item.type = "button";
|
|
3456
3705
|
item.className = "skill-item";
|
|
3706
|
+
item.setAttribute("part", "skill-item");
|
|
3457
3707
|
item.setAttribute("role", "option");
|
|
3458
3708
|
item.setAttribute("aria-selected", index === this.#activeIndex ? "true" : "false");
|
|
3459
3709
|
const title = document.createElement("span");
|
|
3460
3710
|
title.className = "skill-item-title";
|
|
3711
|
+
title.setAttribute("part", "skill-item-title");
|
|
3461
3712
|
title.textContent = skill.title;
|
|
3462
3713
|
item.appendChild(title);
|
|
3463
3714
|
if (skill.description !== void 0) {
|
|
3464
3715
|
const desc = document.createElement("span");
|
|
3465
3716
|
desc.className = "skill-item-desc";
|
|
3717
|
+
desc.setAttribute("part", "skill-item-desc");
|
|
3466
3718
|
desc.textContent = skill.description;
|
|
3467
3719
|
item.appendChild(desc);
|
|
3468
3720
|
}
|
|
@@ -3617,7 +3869,7 @@ var STYLES = `
|
|
|
3617
3869
|
--ag-ui-radius: 0;
|
|
3618
3870
|
}
|
|
3619
3871
|
|
|
3620
|
-
/* Page
|
|
3872
|
+
/* Page: full-bleed background with a centred reading column. Unlike
|
|
3621
3873
|
"full" (edge-to-edge, left-aligned messages) the content sits in a column
|
|
3622
3874
|
capped at --ag-ui-content-max-width. The column is produced by symmetric auto
|
|
3623
3875
|
padding on the scroll area + composer (no per-row wrapper), so user pills
|
|
@@ -3659,7 +3911,7 @@ var STYLES = `
|
|
|
3659
3911
|
max-width: 100%;
|
|
3660
3912
|
}
|
|
3661
3913
|
|
|
3662
|
-
/* Sidebar
|
|
3914
|
+
/* Sidebar: a full-height docked panel that slides open/closed and
|
|
3663
3915
|
collapses to a slim icon rail (not the floating launcher). Docked right by
|
|
3664
3916
|
default; data-side="left" docks it left. Overlay by default \u2014 set
|
|
3665
3917
|
--ag-ui-position: static (and place this element in your own layout) for a
|
|
@@ -3769,7 +4021,7 @@ var STYLES = `
|
|
|
3769
4021
|
white-space: nowrap;
|
|
3770
4022
|
}
|
|
3771
4023
|
|
|
3772
|
-
/* Header / launcher icon holder
|
|
4024
|
+
/* Header / launcher icon holder: a slot, with a data-icon-url <img>
|
|
3773
4025
|
fallback, sized via --ag-ui-icon-size. */
|
|
3774
4026
|
.icon-holder {
|
|
3775
4027
|
display: inline-flex;
|
|
@@ -3843,7 +4095,7 @@ var STYLES = `
|
|
|
3843
4095
|
gap: var(--ag-ui-space);
|
|
3844
4096
|
}
|
|
3845
4097
|
|
|
3846
|
-
/* Empty-state region (
|
|
4098
|
+
/* Empty-state region (slot): centred while it's the only thing in the
|
|
3847
4099
|
list, hidden as soon as a message, card, or pending indicator renders. */
|
|
3848
4100
|
.empty {
|
|
3849
4101
|
margin: auto;
|
|
@@ -3855,7 +4107,7 @@ var STYLES = `
|
|
|
3855
4107
|
display: none;
|
|
3856
4108
|
}
|
|
3857
4109
|
|
|
3858
|
-
/* \u2500\u2500 Answer group / well
|
|
4110
|
+
/* \u2500\u2500 Answer group / well \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3859
4111
|
One .answer per assistant turn wraps the streamed text, its tool cards,
|
|
3860
4112
|
and the pending indicator so a whole answer reads (and can be boxed) as one
|
|
3861
4113
|
unit. A flex column on the message-list gap, stretched to the list width so
|
|
@@ -4024,7 +4276,7 @@ var STYLES = `
|
|
|
4024
4276
|
}
|
|
4025
4277
|
}
|
|
4026
4278
|
|
|
4027
|
-
/* \u2500\u2500 Thoughts region
|
|
4279
|
+
/* \u2500\u2500 Thoughts region \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4028
4280
|
A muted, collapsible chain-of-thought at the top of the answer group: open
|
|
4029
4281
|
while the model reasons, folded once the answer text starts. */
|
|
4030
4282
|
.thoughts {
|
|
@@ -4117,7 +4369,7 @@ var STYLES = `
|
|
|
4117
4369
|
word-break: break-word;
|
|
4118
4370
|
}
|
|
4119
4371
|
|
|
4120
|
-
/* Leading status icon
|
|
4372
|
+
/* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn
|
|
4121
4373
|
here from the card's data-status, so it stays themeable. */
|
|
4122
4374
|
.tool-call-icon {
|
|
4123
4375
|
flex: none;
|
|
@@ -4165,7 +4417,7 @@ var STYLES = `
|
|
|
4165
4417
|
}
|
|
4166
4418
|
}
|
|
4167
4419
|
|
|
4168
|
-
/* Inline display mode
|
|
4420
|
+
/* Inline display mode: the lightest card \u2014 drop the box chrome so the
|
|
4169
4421
|
status row reads as one line of the answer; the result toggle still expands
|
|
4170
4422
|
below it. */
|
|
4171
4423
|
.tool-call[data-display="inline"] {
|
|
@@ -4295,7 +4547,7 @@ var STYLES = `
|
|
|
4295
4547
|
display: none;
|
|
4296
4548
|
}
|
|
4297
4549
|
|
|
4298
|
-
/* The \u{1F3A4} mic button
|
|
4550
|
+
/* The \u{1F3A4} mic button; shown only once #wireVoice mounts it. */
|
|
4299
4551
|
.voice-slot {
|
|
4300
4552
|
display: contents;
|
|
4301
4553
|
}
|
|
@@ -4499,6 +4751,127 @@ var STYLES = `
|
|
|
4499
4751
|
color: #ffffff;
|
|
4500
4752
|
}
|
|
4501
4753
|
|
|
4754
|
+
/* Approval card \u2014 the server-side-tool gate (approve/deny an interrupt). */
|
|
4755
|
+
.approval {
|
|
4756
|
+
align-self: stretch;
|
|
4757
|
+
box-sizing: border-box;
|
|
4758
|
+
display: flex;
|
|
4759
|
+
flex-direction: column;
|
|
4760
|
+
gap: 8px;
|
|
4761
|
+
padding: 12px;
|
|
4762
|
+
background: var(--ag-ui-bg);
|
|
4763
|
+
border: 1px solid var(--ag-ui-accent);
|
|
4764
|
+
border-radius: 10px;
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
.approval[data-resolved] {
|
|
4768
|
+
opacity: 0.7;
|
|
4769
|
+
border-color: var(--ag-ui-border);
|
|
4770
|
+
}
|
|
4771
|
+
|
|
4772
|
+
.approval-body {
|
|
4773
|
+
font-weight: 600;
|
|
4774
|
+
}
|
|
4775
|
+
|
|
4776
|
+
.approval-actions {
|
|
4777
|
+
display: flex;
|
|
4778
|
+
gap: 8px;
|
|
4779
|
+
justify-content: flex-end;
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4782
|
+
.approval-btn {
|
|
4783
|
+
border: 1px solid var(--ag-ui-border);
|
|
4784
|
+
border-radius: 8px;
|
|
4785
|
+
padding: 8px 14px;
|
|
4786
|
+
font: inherit;
|
|
4787
|
+
font-weight: 600;
|
|
4788
|
+
cursor: pointer;
|
|
4789
|
+
background: var(--ag-ui-bg);
|
|
4790
|
+
color: var(--ag-ui-fg);
|
|
4791
|
+
}
|
|
4792
|
+
|
|
4793
|
+
.approval-btn:disabled {
|
|
4794
|
+
cursor: default;
|
|
4795
|
+
opacity: 0.6;
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
.approval-btn--approve {
|
|
4799
|
+
border-color: var(--ag-ui-accent);
|
|
4800
|
+
background: var(--ag-ui-accent);
|
|
4801
|
+
color: #ffffff;
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
/* Question card \u2014 the built-in ask_user prompt (radios and/or free text). */
|
|
4805
|
+
.question {
|
|
4806
|
+
align-self: stretch;
|
|
4807
|
+
box-sizing: border-box;
|
|
4808
|
+
display: flex;
|
|
4809
|
+
flex-direction: column;
|
|
4810
|
+
gap: 8px;
|
|
4811
|
+
padding: 12px;
|
|
4812
|
+
background: var(--ag-ui-bg);
|
|
4813
|
+
border: 1px solid var(--ag-ui-accent);
|
|
4814
|
+
border-radius: 10px;
|
|
4815
|
+
}
|
|
4816
|
+
|
|
4817
|
+
.question[data-resolved] {
|
|
4818
|
+
opacity: 0.7;
|
|
4819
|
+
border-color: var(--ag-ui-border);
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4822
|
+
.question-body {
|
|
4823
|
+
font-weight: 600;
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
.question-options {
|
|
4827
|
+
display: flex;
|
|
4828
|
+
flex-direction: column;
|
|
4829
|
+
gap: 6px;
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
.question-choice {
|
|
4833
|
+
display: flex;
|
|
4834
|
+
align-items: center;
|
|
4835
|
+
gap: 8px;
|
|
4836
|
+
cursor: pointer;
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
.question-input {
|
|
4840
|
+
box-sizing: border-box;
|
|
4841
|
+
width: 100%;
|
|
4842
|
+
padding: 8px 10px;
|
|
4843
|
+
font: inherit;
|
|
4844
|
+
color: var(--ag-ui-fg);
|
|
4845
|
+
background: var(--ag-ui-bg);
|
|
4846
|
+
border: 1px solid var(--ag-ui-border);
|
|
4847
|
+
border-radius: 8px;
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
.question-input:disabled {
|
|
4851
|
+
opacity: 0.6;
|
|
4852
|
+
}
|
|
4853
|
+
|
|
4854
|
+
.question-actions {
|
|
4855
|
+
display: flex;
|
|
4856
|
+
justify-content: flex-end;
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4859
|
+
.question-btn {
|
|
4860
|
+
border: 1px solid var(--ag-ui-accent);
|
|
4861
|
+
border-radius: 8px;
|
|
4862
|
+
padding: 8px 14px;
|
|
4863
|
+
font: inherit;
|
|
4864
|
+
font-weight: 600;
|
|
4865
|
+
cursor: pointer;
|
|
4866
|
+
background: var(--ag-ui-accent);
|
|
4867
|
+
color: #ffffff;
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
.question-btn:disabled {
|
|
4871
|
+
cursor: default;
|
|
4872
|
+
opacity: 0.6;
|
|
4873
|
+
}
|
|
4874
|
+
|
|
4502
4875
|
/* Skills \u2014 chips row + the /-command palette, above the input. */
|
|
4503
4876
|
.skill-chips {
|
|
4504
4877
|
display: flex;
|
|
@@ -4774,6 +5147,7 @@ var ThoughtsBlock = class {
|
|
|
4774
5147
|
this.#toggle.setAttribute("aria-expanded", "true");
|
|
4775
5148
|
this.#label = document.createElement("span");
|
|
4776
5149
|
this.#label.className = "thoughts-label";
|
|
5150
|
+
this.#label.setAttribute("part", "thoughts-label");
|
|
4777
5151
|
this.#label.textContent = strings.thinking;
|
|
4778
5152
|
this.#toggle.append(this.#label);
|
|
4779
5153
|
this.#body = document.createElement("pre");
|
|
@@ -4810,6 +5184,9 @@ var ThoughtsBlock = class {
|
|
|
4810
5184
|
|
|
4811
5185
|
// src/ui/relative_time.ts
|
|
4812
5186
|
function relativeTime(timestamp, now = Date.now(), strings = DEFAULT_UI_STRINGS) {
|
|
5187
|
+
if (!Number.isFinite(timestamp)) {
|
|
5188
|
+
return strings.justNow;
|
|
5189
|
+
}
|
|
4813
5190
|
const seconds = Math.round((now - timestamp) / 1e3);
|
|
4814
5191
|
if (seconds < 60) {
|
|
4815
5192
|
return strings.justNow;
|
|
@@ -4841,6 +5218,8 @@ var ThreadDrawer = class {
|
|
|
4841
5218
|
#strings;
|
|
4842
5219
|
#threads = [];
|
|
4843
5220
|
#activeId = "";
|
|
5221
|
+
/** The element focused before the drawer opened, restored on close. */
|
|
5222
|
+
#lastFocused = null;
|
|
4844
5223
|
constructor(callbacks, strings = DEFAULT_UI_STRINGS) {
|
|
4845
5224
|
this.#callbacks = callbacks;
|
|
4846
5225
|
this.#strings = strings;
|
|
@@ -4856,7 +5235,9 @@ var ThreadDrawer = class {
|
|
|
4856
5235
|
this.#panel.className = "drawer-panel";
|
|
4857
5236
|
this.#panel.setAttribute("part", "drawer-panel");
|
|
4858
5237
|
this.#panel.setAttribute("role", "dialog");
|
|
5238
|
+
this.#panel.setAttribute("aria-modal", "true");
|
|
4859
5239
|
this.#panel.setAttribute("aria-label", strings.chatHistory);
|
|
5240
|
+
this.#panel.addEventListener("keydown", (event) => this.#onPanelKeydown(event));
|
|
4860
5241
|
const header = document.createElement("div");
|
|
4861
5242
|
header.className = "drawer-header";
|
|
4862
5243
|
header.setAttribute("part", "drawer-header");
|
|
@@ -4892,13 +5273,55 @@ var ThreadDrawer = class {
|
|
|
4892
5273
|
return !this.element.hidden;
|
|
4893
5274
|
}
|
|
4894
5275
|
open() {
|
|
5276
|
+
if (this.isOpen()) {
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5279
|
+
this.#lastFocused = this.#activeElement();
|
|
4895
5280
|
this.element.hidden = false;
|
|
5281
|
+
this.#newButton.focus();
|
|
4896
5282
|
}
|
|
4897
5283
|
close() {
|
|
5284
|
+
if (!this.isOpen()) {
|
|
5285
|
+
return;
|
|
5286
|
+
}
|
|
4898
5287
|
this.element.hidden = true;
|
|
5288
|
+
this.#lastFocused?.focus();
|
|
5289
|
+
this.#lastFocused = null;
|
|
4899
5290
|
}
|
|
4900
5291
|
toggle() {
|
|
4901
|
-
this.
|
|
5292
|
+
if (this.isOpen()) {
|
|
5293
|
+
this.close();
|
|
5294
|
+
} else {
|
|
5295
|
+
this.open();
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
/** The currently-focused element within the drawer's root (shadow-aware). */
|
|
5299
|
+
#activeElement() {
|
|
5300
|
+
return this.element.getRootNode().activeElement;
|
|
5301
|
+
}
|
|
5302
|
+
/** Escape-to-close and a Tab focus trap while the dialog is open. */
|
|
5303
|
+
#onPanelKeydown(event) {
|
|
5304
|
+
if (event.key === "Escape") {
|
|
5305
|
+
event.preventDefault();
|
|
5306
|
+
this.close();
|
|
5307
|
+
return;
|
|
5308
|
+
}
|
|
5309
|
+
if (event.key !== "Tab") {
|
|
5310
|
+
return;
|
|
5311
|
+
}
|
|
5312
|
+
const focusables = Array.from(
|
|
5313
|
+
this.#panel.querySelectorAll("button, input, [tabindex]")
|
|
5314
|
+
).filter((el) => !el.hidden);
|
|
5315
|
+
const first = focusables[0];
|
|
5316
|
+
const last = focusables[focusables.length - 1];
|
|
5317
|
+
const active = this.#activeElement();
|
|
5318
|
+
if (event.shiftKey && active === first) {
|
|
5319
|
+
event.preventDefault();
|
|
5320
|
+
last?.focus();
|
|
5321
|
+
} else if (!event.shiftKey && active === last) {
|
|
5322
|
+
event.preventDefault();
|
|
5323
|
+
first?.focus();
|
|
5324
|
+
}
|
|
4902
5325
|
}
|
|
4903
5326
|
/** Render the rows (or the empty state), highlighting the active thread. */
|
|
4904
5327
|
setThreads(threads, activeId) {
|
|
@@ -4933,12 +5356,15 @@ var ThreadDrawer = class {
|
|
|
4933
5356
|
select.setAttribute("part", "drawer-row-select");
|
|
4934
5357
|
const title = document.createElement("span");
|
|
4935
5358
|
title.className = "drawer-row-title";
|
|
5359
|
+
title.setAttribute("part", "drawer-row-title");
|
|
4936
5360
|
title.textContent = meta.title;
|
|
4937
5361
|
const time = document.createElement("span");
|
|
4938
5362
|
time.className = "drawer-row-time";
|
|
5363
|
+
time.setAttribute("part", "drawer-row-time");
|
|
4939
5364
|
time.textContent = relativeTime(meta.updatedAt, void 0, this.#strings);
|
|
4940
5365
|
const preview = document.createElement("span");
|
|
4941
5366
|
preview.className = "drawer-row-preview";
|
|
5367
|
+
preview.setAttribute("part", "drawer-row-preview");
|
|
4942
5368
|
preview.textContent = meta.preview;
|
|
4943
5369
|
select.append(title, time, preview);
|
|
4944
5370
|
select.addEventListener("click", () => {
|
|
@@ -4948,6 +5374,7 @@ var ThreadDrawer = class {
|
|
|
4948
5374
|
const rename = document.createElement("button");
|
|
4949
5375
|
rename.type = "button";
|
|
4950
5376
|
rename.className = "drawer-row-rename";
|
|
5377
|
+
rename.setAttribute("part", "drawer-row-rename");
|
|
4951
5378
|
rename.title = this.#strings.rename;
|
|
4952
5379
|
rename.setAttribute("aria-label", this.#strings.renameConversation);
|
|
4953
5380
|
rename.textContent = "\u270E";
|
|
@@ -4955,34 +5382,56 @@ var ThreadDrawer = class {
|
|
|
4955
5382
|
const remove = document.createElement("button");
|
|
4956
5383
|
remove.type = "button";
|
|
4957
5384
|
remove.className = "drawer-row-delete";
|
|
5385
|
+
remove.setAttribute("part", "drawer-row-delete");
|
|
4958
5386
|
remove.title = this.#strings.delete;
|
|
4959
5387
|
remove.setAttribute("aria-label", this.#strings.deleteConversation);
|
|
4960
5388
|
remove.textContent = "\u{1F5D1}";
|
|
4961
5389
|
remove.addEventListener("click", () => this.#confirmDelete(row, meta));
|
|
4962
5390
|
const actions = document.createElement("div");
|
|
4963
5391
|
actions.className = "drawer-row-actions";
|
|
5392
|
+
actions.setAttribute("part", "drawer-row-actions");
|
|
4964
5393
|
actions.append(rename, remove);
|
|
4965
5394
|
row.append(select, actions);
|
|
4966
5395
|
return row;
|
|
4967
5396
|
}
|
|
4968
|
-
/** Swap a row for an inline rename input; Enter commits, Escape cancels. */
|
|
5397
|
+
/** Swap a row for an inline rename input; Enter/blur commits, Escape cancels. */
|
|
4969
5398
|
#startRename(row, meta) {
|
|
4970
5399
|
const input = document.createElement("input");
|
|
4971
5400
|
input.type = "text";
|
|
4972
5401
|
input.className = "drawer-rename-input";
|
|
5402
|
+
input.setAttribute("part", "drawer-rename-input");
|
|
4973
5403
|
input.value = meta.title;
|
|
5404
|
+
let done = false;
|
|
5405
|
+
const commit = () => {
|
|
5406
|
+
if (done) {
|
|
5407
|
+
return;
|
|
5408
|
+
}
|
|
5409
|
+
done = true;
|
|
5410
|
+
const value = input.value.trim();
|
|
5411
|
+
if (value === "" || value === meta.title) {
|
|
5412
|
+
this.#renderList();
|
|
5413
|
+
} else {
|
|
5414
|
+
this.#callbacks.onRename(meta.threadId, value);
|
|
5415
|
+
}
|
|
5416
|
+
};
|
|
5417
|
+
const cancel = () => {
|
|
5418
|
+
if (done) {
|
|
5419
|
+
return;
|
|
5420
|
+
}
|
|
5421
|
+
done = true;
|
|
5422
|
+
this.#renderList();
|
|
5423
|
+
};
|
|
4974
5424
|
input.addEventListener("keydown", (event) => {
|
|
4975
5425
|
if (event.key === "Enter") {
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
this.#renderList();
|
|
4979
|
-
} else {
|
|
4980
|
-
this.#callbacks.onRename(meta.threadId, value);
|
|
4981
|
-
}
|
|
5426
|
+
event.preventDefault();
|
|
5427
|
+
commit();
|
|
4982
5428
|
} else if (event.key === "Escape") {
|
|
4983
|
-
|
|
5429
|
+
event.preventDefault();
|
|
5430
|
+
event.stopPropagation();
|
|
5431
|
+
cancel();
|
|
4984
5432
|
}
|
|
4985
5433
|
});
|
|
5434
|
+
input.addEventListener("blur", () => commit());
|
|
4986
5435
|
row.replaceChildren(input);
|
|
4987
5436
|
input.focus();
|
|
4988
5437
|
input.select();
|
|
@@ -4991,17 +5440,21 @@ var ThreadDrawer = class {
|
|
|
4991
5440
|
#confirmDelete(row, meta) {
|
|
4992
5441
|
const confirm = document.createElement("div");
|
|
4993
5442
|
confirm.className = "drawer-confirm";
|
|
5443
|
+
confirm.setAttribute("part", "drawer-confirm");
|
|
4994
5444
|
const label = document.createElement("span");
|
|
4995
5445
|
label.className = "drawer-confirm-label";
|
|
5446
|
+
label.setAttribute("part", "drawer-confirm-label");
|
|
4996
5447
|
label.textContent = this.#strings.deletePrompt;
|
|
4997
5448
|
const yes = document.createElement("button");
|
|
4998
5449
|
yes.type = "button";
|
|
4999
5450
|
yes.className = "drawer-confirm-yes";
|
|
5451
|
+
yes.setAttribute("part", "drawer-confirm-yes");
|
|
5000
5452
|
yes.textContent = this.#strings.delete;
|
|
5001
5453
|
yes.addEventListener("click", () => this.#callbacks.onDelete(meta.threadId));
|
|
5002
5454
|
const no = document.createElement("button");
|
|
5003
5455
|
no.type = "button";
|
|
5004
5456
|
no.className = "drawer-confirm-no";
|
|
5457
|
+
no.setAttribute("part", "drawer-confirm-no");
|
|
5005
5458
|
no.textContent = this.#strings.cancel;
|
|
5006
5459
|
no.addEventListener("click", () => this.#renderList());
|
|
5007
5460
|
confirm.append(label, yes, no);
|
|
@@ -5078,6 +5531,9 @@ var ToolCallCard = class {
|
|
|
5078
5531
|
* `inline`), or the args + result together (`compact`).
|
|
5079
5532
|
*/
|
|
5080
5533
|
settle(status, text2) {
|
|
5534
|
+
if (this.#settled) {
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5081
5537
|
this.#settled = true;
|
|
5082
5538
|
this.element.setAttribute("data-status", status);
|
|
5083
5539
|
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
@@ -5122,6 +5578,7 @@ var VoiceInput = class {
|
|
|
5122
5578
|
#recorder = null;
|
|
5123
5579
|
#stream = null;
|
|
5124
5580
|
#chunks = [];
|
|
5581
|
+
#disposed = false;
|
|
5125
5582
|
constructor(options) {
|
|
5126
5583
|
this.#transcribe = options.transcribe;
|
|
5127
5584
|
this.#onText = options.onText;
|
|
@@ -5171,7 +5628,25 @@ var VoiceInput = class {
|
|
|
5171
5628
|
#stop() {
|
|
5172
5629
|
this.#recorder?.stop();
|
|
5173
5630
|
}
|
|
5631
|
+
/**
|
|
5632
|
+
* Tear the control down — the teardown path when the host element is removed
|
|
5633
|
+
* mid-recording. Stops any live `MediaRecorder`, releases the mic tracks (so
|
|
5634
|
+
* the browser's recording indicator clears), and suppresses the pending
|
|
5635
|
+
* transcription: a disconnected control must not fire `onText` back into a
|
|
5636
|
+
* detached element.
|
|
5637
|
+
*/
|
|
5638
|
+
dispose() {
|
|
5639
|
+
this.#disposed = true;
|
|
5640
|
+
if (this.#recorder !== null && this.#recorder.state !== "inactive") {
|
|
5641
|
+
this.#recorder.stop();
|
|
5642
|
+
}
|
|
5643
|
+
this.#recorder = null;
|
|
5644
|
+
this.#releaseStream();
|
|
5645
|
+
}
|
|
5174
5646
|
async #finish(mimeType) {
|
|
5647
|
+
if (this.#disposed) {
|
|
5648
|
+
return;
|
|
5649
|
+
}
|
|
5175
5650
|
this.#releaseStream();
|
|
5176
5651
|
this.#setState("transcribing");
|
|
5177
5652
|
const audio = new Blob(this.#chunks, { type: mimeType || "audio/webm" });
|
|
@@ -5221,7 +5696,10 @@ var VoiceInput = class {
|
|
|
5221
5696
|
};
|
|
5222
5697
|
|
|
5223
5698
|
// src/core/agui_client.ts
|
|
5224
|
-
import {
|
|
5699
|
+
import {
|
|
5700
|
+
buildResumeArray,
|
|
5701
|
+
randomUUID as randomUUID2
|
|
5702
|
+
} from "@ag-ui/client";
|
|
5225
5703
|
var ConnectionLostError = class extends Error {
|
|
5226
5704
|
constructor(message) {
|
|
5227
5705
|
super(message);
|
|
@@ -5234,6 +5712,7 @@ var AgUiClient = class {
|
|
|
5234
5712
|
#getTools;
|
|
5235
5713
|
#getContext;
|
|
5236
5714
|
#executeTool;
|
|
5715
|
+
#resolveInterrupts;
|
|
5237
5716
|
#onPersist;
|
|
5238
5717
|
#connectionLostMessage;
|
|
5239
5718
|
// Set by cancel(); reset at the top of each #run(). Checked by the loop so
|
|
@@ -5245,6 +5724,7 @@ var AgUiClient = class {
|
|
|
5245
5724
|
this.#getTools = config.getTools ?? (() => []);
|
|
5246
5725
|
this.#getContext = config.getContext ?? (() => []);
|
|
5247
5726
|
this.#executeTool = config.executeTool ?? null;
|
|
5727
|
+
this.#resolveInterrupts = config.resolveInterrupts ?? null;
|
|
5248
5728
|
this.#onPersist = config.onPersist ?? (() => {
|
|
5249
5729
|
});
|
|
5250
5730
|
this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
|
|
@@ -5324,16 +5804,22 @@ var AgUiClient = class {
|
|
|
5324
5804
|
this.#handlers.onCancelled();
|
|
5325
5805
|
}
|
|
5326
5806
|
async #runLoop() {
|
|
5807
|
+
let resume;
|
|
5327
5808
|
for (let round = 0; round < MAX_TOOL_ROUNDS; round += 1) {
|
|
5328
5809
|
if (this.#cancelled) {
|
|
5329
5810
|
return;
|
|
5330
5811
|
}
|
|
5331
5812
|
const pending = [];
|
|
5332
|
-
const runState = { terminal: false };
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
this.#
|
|
5336
|
-
|
|
5813
|
+
const runState = { terminal: false, errored: false, interrupts: [] };
|
|
5814
|
+
const params = {
|
|
5815
|
+
tools: this.#getTools(),
|
|
5816
|
+
context: this.#getContext()
|
|
5817
|
+
};
|
|
5818
|
+
if (resume !== void 0) {
|
|
5819
|
+
params.resume = resume;
|
|
5820
|
+
}
|
|
5821
|
+
await this.#agent.runAgent(params, this.#buildSubscriber(pending, runState));
|
|
5822
|
+
resume = void 0;
|
|
5337
5823
|
this.#onPersist(this.#agent.messages);
|
|
5338
5824
|
if (this.#cancelled) {
|
|
5339
5825
|
return;
|
|
@@ -5341,6 +5827,20 @@ var AgUiClient = class {
|
|
|
5341
5827
|
if (!runState.terminal) {
|
|
5342
5828
|
throw new ConnectionLostError(this.#connectionLostMessage);
|
|
5343
5829
|
}
|
|
5830
|
+
if (runState.errored) {
|
|
5831
|
+
return;
|
|
5832
|
+
}
|
|
5833
|
+
if (runState.interrupts.length > 0) {
|
|
5834
|
+
if (this.#resolveInterrupts === null) {
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
const responses = await this.#resolveInterrupts(runState.interrupts);
|
|
5838
|
+
if (this.#cancelled) {
|
|
5839
|
+
return;
|
|
5840
|
+
}
|
|
5841
|
+
resume = buildResumeArray(runState.interrupts, responses);
|
|
5842
|
+
continue;
|
|
5843
|
+
}
|
|
5344
5844
|
if (this.#executeTool === null || pending.length === 0) {
|
|
5345
5845
|
return;
|
|
5346
5846
|
}
|
|
@@ -5391,7 +5891,7 @@ var AgUiClient = class {
|
|
|
5391
5891
|
onToolCallResultEvent({ event }) {
|
|
5392
5892
|
h.onToolResult(event.toolCallId, event.content);
|
|
5393
5893
|
},
|
|
5394
|
-
// Reasoning
|
|
5894
|
+
// Reasoning. `@ag-ui/client` already maps the deprecated
|
|
5395
5895
|
// THINKING_* events onto these REASONING_* callbacks, so handling the
|
|
5396
5896
|
// reasoning family alone covers both protocol versions.
|
|
5397
5897
|
onReasoningStartEvent() {
|
|
@@ -5403,8 +5903,15 @@ var AgUiClient = class {
|
|
|
5403
5903
|
onReasoningEndEvent() {
|
|
5404
5904
|
h.onReasoningEnd();
|
|
5405
5905
|
},
|
|
5906
|
+
onRunFinishedEvent(params) {
|
|
5907
|
+
runState.terminal = true;
|
|
5908
|
+
if (params.outcome === "interrupt") {
|
|
5909
|
+
runState.interrupts = params.interrupts;
|
|
5910
|
+
}
|
|
5911
|
+
},
|
|
5406
5912
|
onRunErrorEvent({ event }) {
|
|
5407
5913
|
runState.terminal = true;
|
|
5914
|
+
runState.errored = true;
|
|
5408
5915
|
h.onError(event.message);
|
|
5409
5916
|
},
|
|
5410
5917
|
onRunFinalized() {
|
|
@@ -5421,40 +5928,56 @@ function isAbortError(error) {
|
|
|
5421
5928
|
// src/core/attachment.ts
|
|
5422
5929
|
function messageAttachments(message) {
|
|
5423
5930
|
const refs = message.attachments;
|
|
5424
|
-
return Array.isArray(refs) ? refs : [];
|
|
5931
|
+
return Array.isArray(refs) ? refs.filter(isAttachmentRef) : [];
|
|
5932
|
+
}
|
|
5933
|
+
function isAttachmentRef(value) {
|
|
5934
|
+
if (typeof value !== "object" || value === null) {
|
|
5935
|
+
return false;
|
|
5936
|
+
}
|
|
5937
|
+
const ref = value;
|
|
5938
|
+
return typeof ref["id"] === "string" && typeof ref["name"] === "string" && typeof ref["mime"] === "string" && typeof ref["size"] === "number" && (ref["url"] === void 0 || typeof ref["url"] === "string");
|
|
5425
5939
|
}
|
|
5426
5940
|
|
|
5427
5941
|
// src/core/conversation_store.ts
|
|
5428
5942
|
import { randomUUID as randomUUID3 } from "@ag-ui/client";
|
|
5429
|
-
var
|
|
5430
|
-
var
|
|
5431
|
-
var
|
|
5432
|
-
var
|
|
5943
|
+
var KEY_ROOT = "ag-ui-chat";
|
|
5944
|
+
var THREAD_SUFFIX = "thread";
|
|
5945
|
+
var THREADS_SUFFIX = "threads";
|
|
5946
|
+
var MESSAGES_SUFFIX = "messages:";
|
|
5947
|
+
var CHECKPOINT_SUFFIX = "checkpoint:";
|
|
5433
5948
|
var TITLE_LIMIT = 60;
|
|
5434
5949
|
var PREVIEW_LIMIT = 100;
|
|
5435
5950
|
var DEFAULT_TITLE = "New conversation";
|
|
5436
5951
|
var SessionStorageStore = class {
|
|
5952
|
+
#root;
|
|
5953
|
+
constructor(namespace = "") {
|
|
5954
|
+
this.#root = namespace === "" ? KEY_ROOT : `${KEY_ROOT}@${namespace}`;
|
|
5955
|
+
if (namespace !== "") {
|
|
5956
|
+
this.#migrateLegacyKeys();
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5437
5959
|
threadId() {
|
|
5438
|
-
const
|
|
5960
|
+
const key = this.#key(THREAD_SUFFIX);
|
|
5961
|
+
const existing = sessionStorage.getItem(key);
|
|
5439
5962
|
if (existing !== null) {
|
|
5440
5963
|
return existing;
|
|
5441
5964
|
}
|
|
5442
5965
|
const id = randomUUID3();
|
|
5443
|
-
sessionStorage.setItem(
|
|
5966
|
+
sessionStorage.setItem(key, id);
|
|
5444
5967
|
return id;
|
|
5445
5968
|
}
|
|
5446
5969
|
loadMessages(threadId) {
|
|
5447
|
-
return Promise.resolve(this.#readJson(
|
|
5970
|
+
return Promise.resolve(this.#readJson(this.#key(MESSAGES_SUFFIX + threadId)));
|
|
5448
5971
|
}
|
|
5449
5972
|
saveMessages(threadId, messages) {
|
|
5450
|
-
sessionStorage.setItem(
|
|
5973
|
+
sessionStorage.setItem(this.#key(MESSAGES_SUFFIX + threadId), JSON.stringify(messages));
|
|
5451
5974
|
this.#touchThread(threadId, messages);
|
|
5452
5975
|
}
|
|
5453
5976
|
loadCheckpoint(threadId) {
|
|
5454
|
-
return this.#readJson(
|
|
5977
|
+
return this.#readJson(this.#key(CHECKPOINT_SUFFIX + threadId));
|
|
5455
5978
|
}
|
|
5456
5979
|
saveCheckpoint(threadId, checkpoint) {
|
|
5457
|
-
const key =
|
|
5980
|
+
const key = this.#key(CHECKPOINT_SUFFIX + threadId);
|
|
5458
5981
|
if (checkpoint === null) {
|
|
5459
5982
|
sessionStorage.removeItem(key);
|
|
5460
5983
|
return;
|
|
@@ -5462,11 +5985,11 @@ var SessionStorageStore = class {
|
|
|
5462
5985
|
sessionStorage.setItem(key, JSON.stringify(checkpoint));
|
|
5463
5986
|
}
|
|
5464
5987
|
clear(threadId) {
|
|
5465
|
-
sessionStorage.removeItem(
|
|
5466
|
-
sessionStorage.removeItem(
|
|
5988
|
+
sessionStorage.removeItem(this.#key(MESSAGES_SUFFIX + threadId));
|
|
5989
|
+
sessionStorage.removeItem(this.#key(CHECKPOINT_SUFFIX + threadId));
|
|
5467
5990
|
this.#writeThreads(this.#readThreads().filter((thread) => thread.threadId !== threadId));
|
|
5468
|
-
if (sessionStorage.getItem(
|
|
5469
|
-
sessionStorage.removeItem(
|
|
5991
|
+
if (sessionStorage.getItem(this.#key(THREAD_SUFFIX)) === threadId) {
|
|
5992
|
+
sessionStorage.removeItem(this.#key(THREAD_SUFFIX));
|
|
5470
5993
|
}
|
|
5471
5994
|
}
|
|
5472
5995
|
listThreads() {
|
|
@@ -5474,7 +5997,7 @@ var SessionStorageStore = class {
|
|
|
5474
5997
|
return Promise.resolve(metas);
|
|
5475
5998
|
}
|
|
5476
5999
|
setActiveThread(threadId) {
|
|
5477
|
-
sessionStorage.setItem(
|
|
6000
|
+
sessionStorage.setItem(this.#key(THREAD_SUFFIX), threadId);
|
|
5478
6001
|
}
|
|
5479
6002
|
renameThread(threadId, title) {
|
|
5480
6003
|
const threads = this.#readThreads();
|
|
@@ -5510,14 +6033,48 @@ var SessionStorageStore = class {
|
|
|
5510
6033
|
this.#writeThreads(threads);
|
|
5511
6034
|
}
|
|
5512
6035
|
#readThreads() {
|
|
5513
|
-
return this.#readJson(
|
|
6036
|
+
return this.#readJson(this.#key(THREADS_SUFFIX)) ?? [];
|
|
5514
6037
|
}
|
|
5515
6038
|
#writeThreads(threads) {
|
|
6039
|
+
const key = this.#key(THREADS_SUFFIX);
|
|
5516
6040
|
if (threads.length === 0) {
|
|
5517
|
-
sessionStorage.removeItem(
|
|
6041
|
+
sessionStorage.removeItem(key);
|
|
5518
6042
|
return;
|
|
5519
6043
|
}
|
|
5520
|
-
sessionStorage.setItem(
|
|
6044
|
+
sessionStorage.setItem(key, JSON.stringify(threads));
|
|
6045
|
+
}
|
|
6046
|
+
/** This store's fully-qualified key for a suffix (namespaced when set). */
|
|
6047
|
+
#key(suffix) {
|
|
6048
|
+
return `${this.#root}:${suffix}`;
|
|
6049
|
+
}
|
|
6050
|
+
/**
|
|
6051
|
+
* One-time move of pre-namespacing (`ag-ui-chat:*`) keys into this instance's
|
|
6052
|
+
* namespace, so an existing conversation isn't orphaned by the upgrade. Only
|
|
6053
|
+
* this store's own keys move (thread pointer, drawer index, per-thread
|
|
6054
|
+
* messages/checkpoints) — the element's `collapsed`/`theme` keys are left
|
|
6055
|
+
* alone. The first namespaced instance to mount adopts the legacy data; a
|
|
6056
|
+
* second namespace finds it gone and starts fresh.
|
|
6057
|
+
*/
|
|
6058
|
+
#migrateLegacyKeys() {
|
|
6059
|
+
const legacyRoot = `${KEY_ROOT}:`;
|
|
6060
|
+
const moves = [];
|
|
6061
|
+
for (let i = 0; i < sessionStorage.length; i += 1) {
|
|
6062
|
+
const key = sessionStorage.key(i);
|
|
6063
|
+
if (key === null || !key.startsWith(legacyRoot)) {
|
|
6064
|
+
continue;
|
|
6065
|
+
}
|
|
6066
|
+
const suffix = key.slice(legacyRoot.length);
|
|
6067
|
+
if (isOwnedSuffix(suffix)) {
|
|
6068
|
+
moves.push([key, this.#key(suffix)]);
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
6071
|
+
for (const [from, to] of moves) {
|
|
6072
|
+
const value = sessionStorage.getItem(from);
|
|
6073
|
+
if (value !== null && sessionStorage.getItem(to) === null) {
|
|
6074
|
+
sessionStorage.setItem(to, value);
|
|
6075
|
+
}
|
|
6076
|
+
sessionStorage.removeItem(from);
|
|
6077
|
+
}
|
|
5521
6078
|
}
|
|
5522
6079
|
/** Parse a stored JSON value, returning `null` when absent or corrupt. */
|
|
5523
6080
|
#readJson(key) {
|
|
@@ -5532,6 +6089,9 @@ var SessionStorageStore = class {
|
|
|
5532
6089
|
}
|
|
5533
6090
|
}
|
|
5534
6091
|
};
|
|
6092
|
+
function isOwnedSuffix(suffix) {
|
|
6093
|
+
return suffix === THREAD_SUFFIX || suffix === THREADS_SUFFIX || suffix.startsWith(MESSAGES_SUFFIX) || suffix.startsWith(CHECKPOINT_SUFFIX);
|
|
6094
|
+
}
|
|
5535
6095
|
function deriveTitle(messages) {
|
|
5536
6096
|
for (const message of messages) {
|
|
5537
6097
|
if (message.role === "user") {
|
|
@@ -5638,7 +6198,10 @@ var RemoteConversationStore = class {
|
|
|
5638
6198
|
if (response === null || !response.ok) {
|
|
5639
6199
|
return this.#local.loadMessages(threadId);
|
|
5640
6200
|
}
|
|
5641
|
-
const body = await response
|
|
6201
|
+
const body = await this.#readJson(response);
|
|
6202
|
+
if (body === null) {
|
|
6203
|
+
return this.#local.loadMessages(threadId);
|
|
6204
|
+
}
|
|
5642
6205
|
return body.messages ?? null;
|
|
5643
6206
|
}
|
|
5644
6207
|
async #fetchThreads() {
|
|
@@ -5646,14 +6209,28 @@ var RemoteConversationStore = class {
|
|
|
5646
6209
|
if (response === null || !response.ok) {
|
|
5647
6210
|
return null;
|
|
5648
6211
|
}
|
|
5649
|
-
const body = await response
|
|
6212
|
+
const body = await this.#readJson(response);
|
|
6213
|
+
if (body === null) {
|
|
6214
|
+
return null;
|
|
6215
|
+
}
|
|
5650
6216
|
return body.threads ?? [];
|
|
5651
6217
|
}
|
|
6218
|
+
/** Parse a `Response` body as JSON, or `null` when it isn't valid JSON. */
|
|
6219
|
+
async #readJson(response) {
|
|
6220
|
+
try {
|
|
6221
|
+
return await response.json();
|
|
6222
|
+
} catch {
|
|
6223
|
+
return null;
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
5652
6226
|
#toMeta(row) {
|
|
5653
6227
|
return {
|
|
5654
6228
|
threadId: row.thread_id,
|
|
5655
6229
|
title: this.#renamed.get(row.thread_id) ?? row.title,
|
|
5656
|
-
|
|
6230
|
+
// `null` or an unparseable date both become `NaN` (Date.parse's own
|
|
6231
|
+
// signal), which `relativeTime` renders as a neutral label rather than
|
|
6232
|
+
// "~2950w ago" (epoch 0) or "NaNw ago".
|
|
6233
|
+
updatedAt: row.updated_at === null ? Number.NaN : Date.parse(row.updated_at),
|
|
5657
6234
|
preview: row.preview
|
|
5658
6235
|
};
|
|
5659
6236
|
}
|
|
@@ -5789,6 +6366,33 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5789
6366
|
allowImages = false;
|
|
5790
6367
|
/** When true, destructive tools execute without a confirmation modal. */
|
|
5791
6368
|
autoConfirm = false;
|
|
6369
|
+
/**
|
|
6370
|
+
* When true, the built-in `ask_user` frontend tool is offered to the agent:
|
|
6371
|
+
* calling it renders an inline question card (radio choices and/or a free-text
|
|
6372
|
+
* field) and returns the user's answer. Off by default — like the other
|
|
6373
|
+
* built-in tool groups (route / page-action), it is opt-in so it doesn't
|
|
6374
|
+
* change the advertised catalog until a host asks for it.
|
|
6375
|
+
*/
|
|
6376
|
+
askUser = false;
|
|
6377
|
+
/**
|
|
6378
|
+
* Optional full replacement for the `ask_user` question UI. When set, calling
|
|
6379
|
+
* `ask_user` invokes this instead of the built-in inline card: the host
|
|
6380
|
+
* renders whatever it likes (a native modal, a framework component, …) and
|
|
6381
|
+
* resolves with the answer. Unset (default) uses the built-in
|
|
6382
|
+
* {@link requestQuestion} card — style that via the `strings` override and the
|
|
6383
|
+
* `question*` CSS `::part()`s. Requires {@link askUser} to be enabled.
|
|
6384
|
+
*/
|
|
6385
|
+
askUserRenderer = null;
|
|
6386
|
+
/**
|
|
6387
|
+
* Optional full replacement for the server-side-tool approval UI. When set, an
|
|
6388
|
+
* approval interrupt invokes this instead of the built-in inline approval
|
|
6389
|
+
* card: the host renders whatever it likes and resolves `true` to approve /
|
|
6390
|
+
* `false` to deny. Unset (default) uses the built-in {@link requestApproval}
|
|
6391
|
+
* card — style that via the `strings` override and the `approval*` CSS
|
|
6392
|
+
* `::part()`s. The gate itself is enabled server-side; this only changes how
|
|
6393
|
+
* the decision is collected.
|
|
6394
|
+
*/
|
|
6395
|
+
approvalRenderer = null;
|
|
5792
6396
|
/**
|
|
5793
6397
|
* Optional per-call confirmation predicate. When set, it is authoritative:
|
|
5794
6398
|
* given a tool name + args it decides whether *this* call needs confirmation
|
|
@@ -5929,7 +6533,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5929
6533
|
#attachButton;
|
|
5930
6534
|
#fileInput;
|
|
5931
6535
|
#attachSlot;
|
|
5932
|
-
/** Optional built-in header theme toggle
|
|
6536
|
+
/** Optional built-in header theme toggle; shown only with `data-theme-toggle`. */
|
|
5933
6537
|
#themeToggle;
|
|
5934
6538
|
/** The collapsed-sidebar rail (an expand affordance; shown only for `placement="sidebar"`). */
|
|
5935
6539
|
#rail;
|
|
@@ -5957,18 +6561,25 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5957
6561
|
// it; ≤1 ⇒ it arrived at once and the word reveal is appropriate.
|
|
5958
6562
|
#streamDeltas = 0;
|
|
5959
6563
|
#pending = null;
|
|
5960
|
-
// The current assistant turn's grouping container
|
|
6564
|
+
// The current assistant turn's grouping container. One `.answer`
|
|
5961
6565
|
// wraps everything a single answer produces — streamed text, tool cards, the
|
|
5962
6566
|
// pending indicator — so it can be boxed as one "well" by CSS. Opened on the
|
|
5963
6567
|
// turn's first run start, closed at settle, so it spans the whole multi-round
|
|
5964
6568
|
// frontend-tool loop (which is several AG-UI runs), not one run. `null`
|
|
5965
6569
|
// between turns; user bubbles never enter it.
|
|
5966
6570
|
#currentGroup = null;
|
|
5967
|
-
// The current turn's streamed-reasoning region
|
|
6571
|
+
// The current turn's streamed-reasoning region, shown at the top of
|
|
5968
6572
|
// the answer group while a reasoning model thinks and collapsed once the
|
|
5969
6573
|
// answer's first text token arrives. `null` outside a reasoning turn.
|
|
5970
6574
|
#thoughts = null;
|
|
5971
6575
|
#threadId = "";
|
|
6576
|
+
// Per-instance suffix for the origin-scoped storage keys (collapsed / theme /
|
|
6577
|
+
// active thread), so two instances on one origin don't clobber each other.
|
|
6578
|
+
// Empty ⇒ the pre-namespacing global keys (back-compat). Resolved on connect.
|
|
6579
|
+
#storageNs = "";
|
|
6580
|
+
// Bumped on every #rehydrate; a replay whose generation is stale (a newer
|
|
6581
|
+
// thread switch started while it awaited a slow store) drops its result.
|
|
6582
|
+
#rehydrateGeneration = 0;
|
|
5972
6583
|
#initialMessages = [];
|
|
5973
6584
|
// Skill catalog by source; merged backend → embed → client (later wins).
|
|
5974
6585
|
#backendSkills = [];
|
|
@@ -6075,9 +6686,73 @@ var AgUiChat = class extends HTMLElement {
|
|
|
6075
6686
|
);
|
|
6076
6687
|
return createPageActionTools(enabled, (target) => this.resolvePageTarget(target));
|
|
6077
6688
|
}
|
|
6078
|
-
/** All built-in (route + page + page-action) frontend tools. */
|
|
6689
|
+
/** All built-in (route + page + page-action + ask_user) frontend tools. */
|
|
6079
6690
|
#builtinTools() {
|
|
6080
|
-
return [
|
|
6691
|
+
return [
|
|
6692
|
+
...this.#routeTools(),
|
|
6693
|
+
...this.#pageTools(),
|
|
6694
|
+
...this.#pageActionTools(),
|
|
6695
|
+
...this.#askUserTool()
|
|
6696
|
+
];
|
|
6697
|
+
}
|
|
6698
|
+
/**
|
|
6699
|
+
* The built-in `ask_user` frontend tool, or `[]` when {@link askUser} is off.
|
|
6700
|
+
*
|
|
6701
|
+
* A generic "ask the user a typed question" primitive: the agent calls it, the
|
|
6702
|
+
* client executes it locally via the normal frontend-tool path (rendering a
|
|
6703
|
+
* {@link requestQuestion} card), and the chosen/typed answer flows back as the
|
|
6704
|
+
* tool result — no new protocol, reusing the machinery already in place.
|
|
6705
|
+
*/
|
|
6706
|
+
#askUserTool() {
|
|
6707
|
+
if (!this.askUser) {
|
|
6708
|
+
return [];
|
|
6709
|
+
}
|
|
6710
|
+
return [
|
|
6711
|
+
{
|
|
6712
|
+
name: "ask_user",
|
|
6713
|
+
description: "Ask the user a question and wait for their answer. Provide `options` for a multiple-choice prompt; set `allow_custom` to also accept a free-text answer.",
|
|
6714
|
+
parameters: {
|
|
6715
|
+
type: "object",
|
|
6716
|
+
properties: {
|
|
6717
|
+
question: { type: "string", description: "The question to ask the user." },
|
|
6718
|
+
options: {
|
|
6719
|
+
type: "array",
|
|
6720
|
+
items: { type: "string" },
|
|
6721
|
+
description: "Preset choices offered as radio buttons."
|
|
6722
|
+
},
|
|
6723
|
+
allow_custom: {
|
|
6724
|
+
type: "boolean",
|
|
6725
|
+
description: "Allow a free-text answer in addition to any options."
|
|
6726
|
+
}
|
|
6727
|
+
},
|
|
6728
|
+
required: ["question"]
|
|
6729
|
+
},
|
|
6730
|
+
handler: (args) => this.#askUser(args)
|
|
6731
|
+
}
|
|
6732
|
+
];
|
|
6733
|
+
}
|
|
6734
|
+
/** Render the `ask_user` question card and resolve with the user's answer. */
|
|
6735
|
+
async #askUser(args) {
|
|
6736
|
+
const question = typeof args["question"] === "string" ? args["question"] : "";
|
|
6737
|
+
const request = { question };
|
|
6738
|
+
const rawOptions = args["options"];
|
|
6739
|
+
if (Array.isArray(rawOptions)) {
|
|
6740
|
+
request.options = rawOptions.filter((option) => typeof option === "string");
|
|
6741
|
+
}
|
|
6742
|
+
if (args["allow_custom"] === true) {
|
|
6743
|
+
request.allowCustom = true;
|
|
6744
|
+
}
|
|
6745
|
+
this.#confirmAbort = new AbortController();
|
|
6746
|
+
const signal = this.#confirmAbort.signal;
|
|
6747
|
+
this.#hidePending();
|
|
6748
|
+
const answer = this.askUserRenderer !== null ? await this.askUserRenderer(request, { signal }) : await requestQuestion(this.#ensureGroup(), request, {
|
|
6749
|
+
signal,
|
|
6750
|
+
strings: this.#strings
|
|
6751
|
+
});
|
|
6752
|
+
this.#confirmAbort = null;
|
|
6753
|
+
this.#updateEmptyState();
|
|
6754
|
+
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
6755
|
+
return answer;
|
|
6081
6756
|
}
|
|
6082
6757
|
/** Resolve a tool by name: built-in tools first, then the registry. */
|
|
6083
6758
|
#resolveTool(name) {
|
|
@@ -6112,27 +6787,44 @@ var AgUiChat = class extends HTMLElement {
|
|
|
6112
6787
|
this.setAttribute("data-tool-display", value);
|
|
6113
6788
|
}
|
|
6114
6789
|
connectedCallback() {
|
|
6790
|
+
this.#storageNs = this.id !== "" ? this.id : this.endpoint;
|
|
6115
6791
|
this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
|
|
6116
6792
|
if (this.getAttribute("data-theme-toggle") !== null) {
|
|
6117
|
-
const saved =
|
|
6793
|
+
const saved = this.#readScopedItem(THEME_KEY);
|
|
6118
6794
|
if (saved !== null) {
|
|
6119
6795
|
this.setAttribute("theme", saved);
|
|
6120
6796
|
}
|
|
6121
6797
|
}
|
|
6122
6798
|
this.#render();
|
|
6123
6799
|
this.#drawer.setStrings(this.#strings);
|
|
6124
|
-
if (
|
|
6800
|
+
if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
|
|
6125
6801
|
this.setAttribute("collapsed", "");
|
|
6126
6802
|
}
|
|
6127
6803
|
this.#syncRail();
|
|
6128
6804
|
this.#initSkills();
|
|
6129
6805
|
void this.#fetchToolCatalog();
|
|
6806
|
+
if (this.#storageNs !== "" && this.conversationStore instanceof SessionStorageStore) {
|
|
6807
|
+
this.conversationStore = new SessionStorageStore(this.#storageNs);
|
|
6808
|
+
}
|
|
6130
6809
|
this.#wireThreadStore();
|
|
6131
6810
|
this.#wireAttachments();
|
|
6132
6811
|
this.#wireVoice();
|
|
6133
6812
|
this.#threadId = this.conversationStore.threadId();
|
|
6134
6813
|
void this.#rehydrate();
|
|
6135
6814
|
}
|
|
6815
|
+
/**
|
|
6816
|
+
* Tear down live resources when the element leaves the DOM (a removed node, a
|
|
6817
|
+
* client-side route swap): cancel the in-flight run so its SSE stream closes,
|
|
6818
|
+
* abort any in-flight uploads so they don't orphan server-side files, and
|
|
6819
|
+
* release the mic so the browser's recording indicator clears. Without this a
|
|
6820
|
+
* removed `<ag-ui-chat>` leaks a streaming request, uploads, and a live
|
|
6821
|
+
* `MediaRecorder`.
|
|
6822
|
+
*/
|
|
6823
|
+
disconnectedCallback() {
|
|
6824
|
+
this.#cancelRun();
|
|
6825
|
+
this.#attachTray?.dispose();
|
|
6826
|
+
this.#voice?.dispose();
|
|
6827
|
+
}
|
|
6136
6828
|
/** Parse the inline `data-strings` JSON overrides (empty when absent/malformed). */
|
|
6137
6829
|
#readStringOverrides() {
|
|
6138
6830
|
const raw = this.getAttribute("data-strings");
|
|
@@ -6178,7 +6870,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
6178
6870
|
if (url === null) {
|
|
6179
6871
|
return null;
|
|
6180
6872
|
}
|
|
6181
|
-
return (file, onProgress) => uploadAttachment(file, { url, headers: this.headers, onProgress });
|
|
6873
|
+
return (file, onProgress, signal) => uploadAttachment(file, { url, headers: this.headers, onProgress, signal });
|
|
6182
6874
|
}
|
|
6183
6875
|
/**
|
|
6184
6876
|
* Reveal the composer's 🎤 mic button when transcription is possible — either
|
|
@@ -6382,7 +7074,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6382
7074
|
} else {
|
|
6383
7075
|
this.removeAttribute("collapsed");
|
|
6384
7076
|
}
|
|
6385
|
-
sessionStorage.setItem(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
7077
|
+
sessionStorage.setItem(this.#storageKey(COLLAPSED_KEY), collapsed ? "1" : "0");
|
|
6386
7078
|
this.#syncRail();
|
|
6387
7079
|
this.dispatchEvent(
|
|
6388
7080
|
new CustomEvent(TOGGLE_EVENT, {
|
|
@@ -6405,9 +7097,25 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6405
7097
|
toggleTheme() {
|
|
6406
7098
|
const next = this.getAttribute("theme") === "dark" ? "light" : "dark";
|
|
6407
7099
|
this.setAttribute("theme", next);
|
|
6408
|
-
sessionStorage.setItem(THEME_KEY, next);
|
|
7100
|
+
sessionStorage.setItem(this.#storageKey(THEME_KEY), next);
|
|
6409
7101
|
this.#syncThemeGlyph();
|
|
6410
7102
|
}
|
|
7103
|
+
/** This instance's namespaced form of an origin-scoped storage key. */
|
|
7104
|
+
#storageKey(base) {
|
|
7105
|
+
return this.#storageNs === "" ? base : `${base}:${this.#storageNs}`;
|
|
7106
|
+
}
|
|
7107
|
+
/**
|
|
7108
|
+
* Read a namespaced origin-scoped value, falling back once to the legacy
|
|
7109
|
+
* pre-namespacing global key (left in place) so an existing collapsed/theme
|
|
7110
|
+
* preference survives the upgrade.
|
|
7111
|
+
*/
|
|
7112
|
+
#readScopedItem(base) {
|
|
7113
|
+
const scoped = sessionStorage.getItem(this.#storageKey(base));
|
|
7114
|
+
if (scoped !== null || this.#storageNs === "") {
|
|
7115
|
+
return scoped;
|
|
7116
|
+
}
|
|
7117
|
+
return sessionStorage.getItem(base);
|
|
7118
|
+
}
|
|
6411
7119
|
/** Reflect the current theme on the toggle: show the destination's glyph. */
|
|
6412
7120
|
#syncThemeGlyph() {
|
|
6413
7121
|
const dark = this.getAttribute("theme") === "dark";
|
|
@@ -6475,7 +7183,12 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6475
7183
|
* result from the page we landed on.
|
|
6476
7184
|
*/
|
|
6477
7185
|
async #rehydrate() {
|
|
7186
|
+
this.#rehydrateGeneration += 1;
|
|
7187
|
+
const generation = this.#rehydrateGeneration;
|
|
6478
7188
|
const messages = await this.conversationStore.loadMessages(this.#threadId);
|
|
7189
|
+
if (generation !== this.#rehydrateGeneration) {
|
|
7190
|
+
return;
|
|
7191
|
+
}
|
|
6479
7192
|
if (messages !== null) {
|
|
6480
7193
|
this.#initialMessages = messages;
|
|
6481
7194
|
for (const message of messages) {
|
|
@@ -6562,7 +7275,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6562
7275
|
* stays literal text (no need to parse what the user typed, and it avoids
|
|
6563
7276
|
* rendering user-authored markup).
|
|
6564
7277
|
*
|
|
6565
|
-
* Assistant bubbles land in the current answer group
|
|
7278
|
+
* Assistant bubbles land in the current answer group, opening one if
|
|
6566
7279
|
* needed; a user bubble closes the prior group and sits directly in the list
|
|
6567
7280
|
* (the well wraps the *assistant* turn, the user message precedes it).
|
|
6568
7281
|
*/
|
|
@@ -6677,6 +7390,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6677
7390
|
void this.#submit();
|
|
6678
7391
|
});
|
|
6679
7392
|
this.#skillHint.className = "skill-hint";
|
|
7393
|
+
this.#skillHint.setAttribute("part", "skill-hint");
|
|
6680
7394
|
this.#skillHint.hidden = true;
|
|
6681
7395
|
this.#attachButton.className = "attach-btn";
|
|
6682
7396
|
this.#attachButton.type = "button";
|
|
@@ -6796,6 +7510,9 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6796
7510
|
this.#send.dataset["state"] = running ? "running" : "idle";
|
|
6797
7511
|
}
|
|
6798
7512
|
async #submit() {
|
|
7513
|
+
if (this.#running) {
|
|
7514
|
+
return;
|
|
7515
|
+
}
|
|
6799
7516
|
const content = this.#input.value.trim();
|
|
6800
7517
|
const attachments = this.#attachTray?.readyRefs() ?? [];
|
|
6801
7518
|
if (content === "" && attachments.length === 0) {
|
|
@@ -6841,6 +7558,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6841
7558
|
getTools: () => this.getTools(),
|
|
6842
7559
|
getContext: () => this.getContext(),
|
|
6843
7560
|
executeTool: (call) => this.#executeTool(call),
|
|
7561
|
+
resolveInterrupts: (interrupts) => this.#resolveInterrupts(interrupts),
|
|
6844
7562
|
onPersist: (messages) => this.conversationStore.saveMessages(this.#threadId, messages),
|
|
6845
7563
|
connectionLostMessage: this.#strings.connectionLost
|
|
6846
7564
|
});
|
|
@@ -6913,6 +7631,45 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6913
7631
|
return { content: `Error: ${message}`, error: message };
|
|
6914
7632
|
}
|
|
6915
7633
|
}
|
|
7634
|
+
/**
|
|
7635
|
+
* Render an approval card per server-side-tool interrupt and collect the
|
|
7636
|
+
* user's decisions (approve → run it, deny → decline it).
|
|
7637
|
+
*
|
|
7638
|
+
* The run is suspended on these cards; a Stop while any is open aborts the
|
|
7639
|
+
* shared {@link #confirmAbort} controller, resolving every still-open card as
|
|
7640
|
+
* denied (and the client loop then sees the cancellation and stops). An
|
|
7641
|
+
* approved tool runs on the follow-up (resume) run and streams its result
|
|
7642
|
+
* back into the same pending card; a denied one is settled here, since no
|
|
7643
|
+
* result will ever arrive for it.
|
|
7644
|
+
*/
|
|
7645
|
+
async #resolveInterrupts(interrupts) {
|
|
7646
|
+
const responses = {};
|
|
7647
|
+
this.#confirmAbort = new AbortController();
|
|
7648
|
+
this.#hidePending();
|
|
7649
|
+
for (const interrupt of interrupts) {
|
|
7650
|
+
const request = {};
|
|
7651
|
+
if (interrupt.message !== void 0) {
|
|
7652
|
+
request.message = interrupt.message;
|
|
7653
|
+
}
|
|
7654
|
+
const card = interrupt.toolCallId !== void 0 ? this.#toolCards.get(interrupt.toolCallId) : void 0;
|
|
7655
|
+
const toolName = card?.element.getAttribute("data-tool-name");
|
|
7656
|
+
if (toolName !== null && toolName !== void 0) {
|
|
7657
|
+
request.toolName = toolName;
|
|
7658
|
+
}
|
|
7659
|
+
const signal = this.#confirmAbort.signal;
|
|
7660
|
+
const approved = this.approvalRenderer !== null ? await this.approvalRenderer(request, { signal }) : await requestApproval(this.#ensureGroup(), request, { signal, strings: this.#strings });
|
|
7661
|
+
this.#updateEmptyState();
|
|
7662
|
+
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
7663
|
+
if (approved) {
|
|
7664
|
+
responses[interrupt.id] = { status: "resolved", payload: { approved: true } };
|
|
7665
|
+
} else {
|
|
7666
|
+
responses[interrupt.id] = { status: "cancelled" };
|
|
7667
|
+
card?.settle(TOOL_CALL_STATUS.DECLINED, this.#strings.declinedAction);
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
this.#confirmAbort = null;
|
|
7671
|
+
return responses;
|
|
7672
|
+
}
|
|
6916
7673
|
#handlers() {
|
|
6917
7674
|
return {
|
|
6918
7675
|
onRunStart: () => {
|
|
@@ -7113,7 +7870,7 @@ function setControlValue(el, value) {
|
|
|
7113
7870
|
}
|
|
7114
7871
|
|
|
7115
7872
|
// src/version.ts
|
|
7116
|
-
var VERSION = "0.
|
|
7873
|
+
var VERSION = "0.11.0";
|
|
7117
7874
|
export {
|
|
7118
7875
|
AgUiChat,
|
|
7119
7876
|
AgUiClient,
|
|
@@ -7156,7 +7913,9 @@ export {
|
|
|
7156
7913
|
pressThenClick,
|
|
7157
7914
|
prettifyToolName,
|
|
7158
7915
|
renderMarkdown,
|
|
7916
|
+
requestApproval,
|
|
7159
7917
|
requestConfirmation,
|
|
7918
|
+
requestQuestion,
|
|
7160
7919
|
scrollIntoCenterView,
|
|
7161
7920
|
selectControl,
|
|
7162
7921
|
selectOption,
|