@runtypelabs/persona 1.48.0 → 2.0.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/README.md +140 -8
- package/dist/index.cjs +90 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1055 -24
- package/dist/index.d.ts +1055 -24
- package/dist/index.global.js +111 -60
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +90 -39
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/widget.css +836 -513
- package/package.json +1 -1
- package/src/artifacts-session.test.ts +80 -0
- package/src/client.test.ts +20 -21
- package/src/client.ts +153 -4
- package/src/components/approval-bubble.ts +45 -42
- package/src/components/artifact-card.ts +91 -0
- package/src/components/artifact-pane.ts +501 -0
- package/src/components/composer-builder.ts +32 -27
- package/src/components/event-stream-view.ts +40 -40
- package/src/components/feedback.ts +36 -36
- package/src/components/forms.ts +11 -11
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +55 -36
- package/src/components/header-layouts.ts +58 -125
- package/src/components/launcher.ts +36 -21
- package/src/components/message-bubble.ts +92 -65
- package/src/components/messages.ts +2 -2
- package/src/components/panel.ts +42 -11
- package/src/components/reasoning-bubble.ts +23 -23
- package/src/components/registry.ts +4 -0
- package/src/components/suggestions.ts +1 -1
- package/src/components/tool-bubble.ts +32 -32
- package/src/defaults.ts +30 -4
- package/src/index.ts +80 -2
- package/src/install.ts +22 -0
- package/src/plugins/types.ts +23 -0
- package/src/postprocessors.ts +2 -2
- package/src/runtime/host-layout.ts +174 -0
- package/src/runtime/init.test.ts +236 -0
- package/src/runtime/init.ts +114 -55
- package/src/session.ts +135 -2
- package/src/styles/tailwind.css +1 -1
- package/src/styles/widget.css +836 -513
- package/src/types/theme.ts +354 -0
- package/src/types.ts +314 -15
- package/src/ui.docked.test.ts +104 -0
- package/src/ui.ts +940 -227
- package/src/utils/artifact-gate.test.ts +255 -0
- package/src/utils/artifact-gate.ts +142 -0
- package/src/utils/artifact-resize.test.ts +64 -0
- package/src/utils/artifact-resize.ts +67 -0
- package/src/utils/attachment-manager.ts +10 -10
- package/src/utils/code-generators.test.ts +52 -0
- package/src/utils/code-generators.ts +40 -36
- package/src/utils/dock.ts +17 -0
- package/src/utils/dom-context.test.ts +504 -0
- package/src/utils/dom-context.ts +896 -0
- package/src/utils/dom.ts +12 -1
- package/src/utils/message-fingerprint.test.ts +187 -0
- package/src/utils/message-fingerprint.ts +105 -0
- package/src/utils/migration.ts +179 -0
- package/src/utils/morph.ts +1 -1
- package/src/utils/plugins.ts +175 -0
- package/src/utils/positioning.ts +4 -4
- package/src/utils/theme.test.ts +125 -0
- package/src/utils/theme.ts +216 -60
- package/src/utils/tokens.ts +682 -0
package/src/ui.ts
CHANGED
|
@@ -20,15 +20,19 @@ import {
|
|
|
20
20
|
InjectSystemMessageOptions,
|
|
21
21
|
LoadingIndicatorRenderContext,
|
|
22
22
|
IdleIndicatorRenderContext,
|
|
23
|
-
VoiceStatus
|
|
23
|
+
VoiceStatus,
|
|
24
|
+
PersonaArtifactRecord,
|
|
25
|
+
PersonaArtifactManualUpsert
|
|
24
26
|
} from "./types";
|
|
25
27
|
import { AttachmentManager } from "./utils/attachment-manager";
|
|
26
28
|
import { createTextPart, ALL_SUPPORTED_MIME_TYPES } from "./utils/content";
|
|
27
29
|
import { applyThemeVariables, createThemeObserver } from "./utils/theme";
|
|
28
30
|
import { renderLucideIcon } from "./utils/icons";
|
|
29
|
-
import { createElement } from "./utils/dom";
|
|
31
|
+
import { createElement, createElementInDocument } from "./utils/dom";
|
|
30
32
|
import { morphMessages } from "./utils/morph";
|
|
33
|
+
import { computeMessageFingerprint, createMessageCache, getCachedWrapper, setCachedWrapper, pruneCache } from "./utils/message-fingerprint";
|
|
31
34
|
import { statusCopy } from "./utils/constants";
|
|
35
|
+
import { isDockedMountMode } from "./utils/dock";
|
|
32
36
|
import { createLauncherButton } from "./components/launcher";
|
|
33
37
|
import { createWrapper, buildPanel, buildHeader, buildComposer, attachHeaderToContainer } from "./components/panel";
|
|
34
38
|
import { buildHeaderWithLayout } from "./components/header-layouts";
|
|
@@ -43,6 +47,14 @@ import { createSuggestions } from "./components/suggestions";
|
|
|
43
47
|
import { EventStreamBuffer } from "./utils/event-stream-buffer";
|
|
44
48
|
import { EventStreamStore } from "./utils/event-stream-store";
|
|
45
49
|
import { createEventStreamView } from "./components/event-stream-view";
|
|
50
|
+
import { createArtifactPane, type ArtifactPaneApi } from "./components/artifact-pane";
|
|
51
|
+
import {
|
|
52
|
+
artifactsSidebarEnabled,
|
|
53
|
+
applyArtifactLayoutCssVars,
|
|
54
|
+
applyArtifactPaneAppearance,
|
|
55
|
+
shouldExpandLauncherForArtifacts
|
|
56
|
+
} from "./utils/artifact-gate";
|
|
57
|
+
import { readFlexGapPx, resolveArtifactPaneWidthPx } from "./utils/artifact-resize";
|
|
46
58
|
import { enhanceWithForms } from "./components/forms";
|
|
47
59
|
import { pluginRegistry } from "./plugins/registry";
|
|
48
60
|
import { mergeWithDefaults } from "./defaults";
|
|
@@ -277,6 +289,14 @@ type Controller = {
|
|
|
277
289
|
hideEventStream: () => void;
|
|
278
290
|
/** Returns current visibility state of the event stream panel */
|
|
279
291
|
isEventStreamVisible: () => boolean;
|
|
292
|
+
/** Show artifact sidebar (no-op if features.artifacts.enabled is false) */
|
|
293
|
+
showArtifacts: () => void;
|
|
294
|
+
/** Hide artifact sidebar */
|
|
295
|
+
hideArtifacts: () => void;
|
|
296
|
+
/** Upsert an artifact programmatically */
|
|
297
|
+
upsertArtifact: (manual: PersonaArtifactManualUpsert) => PersonaArtifactRecord | null;
|
|
298
|
+
selectArtifact: (id: string) => void;
|
|
299
|
+
clearArtifacts: () => void;
|
|
280
300
|
/**
|
|
281
301
|
* Focus the chat input. Returns true if focus succeeded, false if panel is closed
|
|
282
302
|
* (launcher mode) or textarea is unavailable.
|
|
@@ -467,6 +487,7 @@ export const createAgentExperience = (
|
|
|
467
487
|
let prevAutoExpand = autoExpand;
|
|
468
488
|
let prevLauncherEnabled = launcherEnabled;
|
|
469
489
|
let prevHeaderLayout = config.layout?.header?.layout;
|
|
490
|
+
let wasMobileFullscreen = false;
|
|
470
491
|
let open = launcherEnabled ? autoExpand : true;
|
|
471
492
|
|
|
472
493
|
// Track pending resubmit state for injection-triggered resubmit
|
|
@@ -591,20 +612,11 @@ export const createAgentExperience = (
|
|
|
591
612
|
let attachmentInput: HTMLInputElement | null = panelElements.attachmentInput;
|
|
592
613
|
let attachmentPreviewsContainer: HTMLElement | null = panelElements.attachmentPreviewsContainer;
|
|
593
614
|
|
|
594
|
-
//
|
|
615
|
+
// Initialized after composer plugins rebind footer DOM (see `bindComposerRefsFromFooter`)
|
|
595
616
|
let attachmentManager: AttachmentManager | null = null;
|
|
596
|
-
if (config.attachments?.enabled && attachmentInput && attachmentPreviewsContainer) {
|
|
597
|
-
attachmentManager = AttachmentManager.fromConfig(config.attachments);
|
|
598
|
-
attachmentManager.setPreviewsContainer(attachmentPreviewsContainer);
|
|
599
617
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const target = e.target as HTMLInputElement;
|
|
603
|
-
attachmentManager?.handleFileSelect(target.files);
|
|
604
|
-
// Reset input so same file can be selected again
|
|
605
|
-
target.value = "";
|
|
606
|
-
});
|
|
607
|
-
}
|
|
618
|
+
/** Wired after `handleMicButtonClick` is defined; used by `renderComposer` `onVoiceToggle`. */
|
|
619
|
+
let composerVoiceBridge: (() => void) | null = null;
|
|
608
620
|
|
|
609
621
|
// Plugin hook: renderHeader - allow plugins to provide custom header
|
|
610
622
|
const headerPlugin = plugins.find(p => p.renderHeader);
|
|
@@ -620,7 +632,7 @@ export const createAgentExperience = (
|
|
|
620
632
|
});
|
|
621
633
|
if (customHeader) {
|
|
622
634
|
// Replace the default header with custom header
|
|
623
|
-
const existingHeader = container.querySelector('.
|
|
635
|
+
const existingHeader = container.querySelector('.persona-border-b-persona-divider');
|
|
624
636
|
if (existingHeader) {
|
|
625
637
|
existingHeader.replaceWith(customHeader);
|
|
626
638
|
header = customHeader;
|
|
@@ -647,9 +659,9 @@ export const createAgentExperience = (
|
|
|
647
659
|
eventStreamView.update();
|
|
648
660
|
}
|
|
649
661
|
if (eventStreamToggleBtn) {
|
|
650
|
-
eventStreamToggleBtn.classList.remove("
|
|
651
|
-
eventStreamToggleBtn.classList.add("
|
|
652
|
-
eventStreamToggleBtn.style.boxShadow = "inset 0 0 0 1.5px var(--
|
|
662
|
+
eventStreamToggleBtn.classList.remove("persona-text-persona-muted");
|
|
663
|
+
eventStreamToggleBtn.classList.add("persona-text-persona-accent");
|
|
664
|
+
eventStreamToggleBtn.style.boxShadow = "inset 0 0 0 1.5px var(--persona-accent, #3b82f6)";
|
|
653
665
|
const activeClasses = config.features?.eventStream?.classNames?.toggleButtonActive;
|
|
654
666
|
if (activeClasses) activeClasses.split(/\s+/).forEach(c => c && eventStreamToggleBtn!.classList.add(c));
|
|
655
667
|
}
|
|
@@ -676,8 +688,8 @@ export const createAgentExperience = (
|
|
|
676
688
|
}
|
|
677
689
|
body.style.display = "";
|
|
678
690
|
if (eventStreamToggleBtn) {
|
|
679
|
-
eventStreamToggleBtn.classList.remove("
|
|
680
|
-
eventStreamToggleBtn.classList.add("
|
|
691
|
+
eventStreamToggleBtn.classList.remove("persona-text-persona-accent");
|
|
692
|
+
eventStreamToggleBtn.classList.add("persona-text-persona-muted");
|
|
681
693
|
eventStreamToggleBtn.style.boxShadow = "";
|
|
682
694
|
const activeClasses = config.features?.eventStream?.classNames?.toggleButtonActive;
|
|
683
695
|
if (activeClasses) activeClasses.split(/\s+/).forEach(c => c && eventStreamToggleBtn!.classList.remove(c));
|
|
@@ -694,7 +706,7 @@ export const createAgentExperience = (
|
|
|
694
706
|
let eventStreamToggleBtn: HTMLButtonElement | null = null;
|
|
695
707
|
if (showEventStreamToggle) {
|
|
696
708
|
const esClassNames = config.features?.eventStream?.classNames;
|
|
697
|
-
const toggleBtnClasses = "
|
|
709
|
+
const toggleBtnClasses = "persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-text-persona-muted hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none persona-bg-transparent persona-p-1" + (esClassNames?.toggleButton ? " " + esClassNames.toggleButton : "");
|
|
698
710
|
eventStreamToggleBtn = createElement("button", toggleBtnClasses) as HTMLButtonElement;
|
|
699
711
|
eventStreamToggleBtn.style.width = "28px";
|
|
700
712
|
eventStreamToggleBtn.style.height = "28px";
|
|
@@ -723,9 +735,38 @@ export const createAgentExperience = (
|
|
|
723
735
|
});
|
|
724
736
|
}
|
|
725
737
|
|
|
738
|
+
const ensureComposerAttachmentSurface = (rootFooter: HTMLElement) => {
|
|
739
|
+
const att = config.attachments;
|
|
740
|
+
if (!att?.enabled) return;
|
|
741
|
+
let previews = rootFooter.querySelector<HTMLElement>(".persona-attachment-previews");
|
|
742
|
+
if (!previews) {
|
|
743
|
+
previews = createElement(
|
|
744
|
+
"div",
|
|
745
|
+
"persona-attachment-previews persona-flex persona-flex-wrap persona-gap-2 persona-mb-2"
|
|
746
|
+
);
|
|
747
|
+
previews.style.display = "none";
|
|
748
|
+
const form = rootFooter.querySelector("[data-persona-composer-form]");
|
|
749
|
+
if (form?.parentNode) {
|
|
750
|
+
form.parentNode.insertBefore(previews, form);
|
|
751
|
+
} else {
|
|
752
|
+
rootFooter.insertBefore(previews, rootFooter.firstChild);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (!rootFooter.querySelector<HTMLInputElement>('input[type="file"]')) {
|
|
756
|
+
const fileIn = createElement("input") as HTMLInputElement;
|
|
757
|
+
fileIn.type = "file";
|
|
758
|
+
fileIn.accept = (att.allowedTypes ?? ALL_SUPPORTED_MIME_TYPES).join(",");
|
|
759
|
+
fileIn.multiple = (att.maxFiles ?? 4) > 1;
|
|
760
|
+
fileIn.style.display = "none";
|
|
761
|
+
fileIn.setAttribute("aria-label", att.buttonTooltipText ?? "Attach files");
|
|
762
|
+
rootFooter.appendChild(fileIn);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
|
|
726
766
|
// Plugin hook: renderComposer - allow plugins to provide custom composer
|
|
727
767
|
const composerPlugin = plugins.find(p => p.renderComposer);
|
|
728
768
|
if (composerPlugin?.renderComposer) {
|
|
769
|
+
const composerCfg = config.composer;
|
|
729
770
|
const customComposer = composerPlugin.renderComposer({
|
|
730
771
|
config,
|
|
731
772
|
defaultRenderer: () => {
|
|
@@ -737,17 +778,79 @@ export const createAgentExperience = (
|
|
|
737
778
|
session.sendMessage(text);
|
|
738
779
|
}
|
|
739
780
|
},
|
|
740
|
-
|
|
781
|
+
streaming: false,
|
|
782
|
+
disabled: false,
|
|
783
|
+
openAttachmentPicker: () => {
|
|
784
|
+
attachmentInput?.click();
|
|
785
|
+
},
|
|
786
|
+
models: composerCfg?.models,
|
|
787
|
+
selectedModelId: composerCfg?.selectedModelId,
|
|
788
|
+
onModelChange: (modelId: string) => {
|
|
789
|
+
config.composer = { ...config.composer, selectedModelId: modelId };
|
|
790
|
+
},
|
|
791
|
+
onVoiceToggle:
|
|
792
|
+
config.voiceRecognition?.enabled === true
|
|
793
|
+
? () => {
|
|
794
|
+
composerVoiceBridge?.();
|
|
795
|
+
}
|
|
796
|
+
: undefined
|
|
741
797
|
});
|
|
742
798
|
if (customComposer) {
|
|
743
799
|
// Replace the default footer with custom composer
|
|
744
800
|
footer.replaceWith(customComposer);
|
|
745
801
|
footer = customComposer;
|
|
746
|
-
// Note: When using custom composer, textarea/sendButton/etc may not exist
|
|
747
|
-
// The plugin is responsible for providing its own submit handling
|
|
748
802
|
}
|
|
749
803
|
}
|
|
750
804
|
|
|
805
|
+
const bindComposerRefsFromFooter = (rootFooter: HTMLElement) => {
|
|
806
|
+
const form = rootFooter.querySelector<HTMLFormElement>("[data-persona-composer-form]");
|
|
807
|
+
const ta = rootFooter.querySelector<HTMLTextAreaElement>("[data-persona-composer-input]");
|
|
808
|
+
const sb = rootFooter.querySelector<HTMLButtonElement>("[data-persona-composer-submit]");
|
|
809
|
+
const mic = rootFooter.querySelector<HTMLButtonElement>("[data-persona-composer-mic]");
|
|
810
|
+
const st = rootFooter.querySelector<HTMLElement>("[data-persona-composer-status]");
|
|
811
|
+
if (form) composerForm = form;
|
|
812
|
+
if (ta) textarea = ta;
|
|
813
|
+
if (sb) sendButton = sb;
|
|
814
|
+
if (mic) {
|
|
815
|
+
micButton = mic;
|
|
816
|
+
micButtonWrapper = mic.parentElement as HTMLElement | null;
|
|
817
|
+
}
|
|
818
|
+
if (st) statusText = st;
|
|
819
|
+
const sug = rootFooter.querySelector<HTMLElement>(
|
|
820
|
+
".persona-mb-3.persona-flex.persona-flex-wrap.persona-gap-2"
|
|
821
|
+
);
|
|
822
|
+
if (sug) suggestions = sug;
|
|
823
|
+
const attBtn = rootFooter.querySelector<HTMLButtonElement>(".persona-attachment-button");
|
|
824
|
+
if (attBtn) {
|
|
825
|
+
attachmentButton = attBtn;
|
|
826
|
+
attachmentButtonWrapper = attBtn.parentElement as HTMLElement | null;
|
|
827
|
+
}
|
|
828
|
+
attachmentInput = rootFooter.querySelector<HTMLInputElement>('input[type="file"]');
|
|
829
|
+
attachmentPreviewsContainer = rootFooter.querySelector<HTMLElement>(".persona-attachment-previews");
|
|
830
|
+
const ar = rootFooter.querySelector<HTMLElement>(".persona-widget-composer .persona-flex.persona-items-center.persona-justify-between");
|
|
831
|
+
if (ar) _actionsRow = ar;
|
|
832
|
+
};
|
|
833
|
+
ensureComposerAttachmentSurface(footer);
|
|
834
|
+
bindComposerRefsFromFooter(footer);
|
|
835
|
+
|
|
836
|
+
// Apply contentMaxWidth to composer form if configured
|
|
837
|
+
const contentMaxWidth = config.layout?.contentMaxWidth;
|
|
838
|
+
if (contentMaxWidth && composerForm) {
|
|
839
|
+
composerForm.style.maxWidth = contentMaxWidth;
|
|
840
|
+
composerForm.style.marginLeft = "auto";
|
|
841
|
+
composerForm.style.marginRight = "auto";
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (config.attachments?.enabled && attachmentInput && attachmentPreviewsContainer) {
|
|
845
|
+
attachmentManager = AttachmentManager.fromConfig(config.attachments);
|
|
846
|
+
attachmentManager.setPreviewsContainer(attachmentPreviewsContainer);
|
|
847
|
+
attachmentInput.addEventListener("change", (e) => {
|
|
848
|
+
const target = e.target as HTMLInputElement;
|
|
849
|
+
attachmentManager?.handleFileSelect(target.files);
|
|
850
|
+
target.value = "";
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
751
854
|
// Slot system: allow custom content injection into specific regions
|
|
752
855
|
const renderSlots = () => {
|
|
753
856
|
const slots = config.layout?.slots ?? {};
|
|
@@ -757,7 +860,7 @@ export const createAgentExperience = (
|
|
|
757
860
|
switch (slot) {
|
|
758
861
|
case "body-top":
|
|
759
862
|
// Default: the intro card
|
|
760
|
-
return container.querySelector(".
|
|
863
|
+
return container.querySelector(".persona-rounded-2xl.persona-bg-persona-surface.persona-p-6") as HTMLElement || null;
|
|
761
864
|
case "messages":
|
|
762
865
|
return messagesWrapper;
|
|
763
866
|
case "footer-top":
|
|
@@ -784,7 +887,7 @@ export const createAgentExperience = (
|
|
|
784
887
|
header.appendChild(element);
|
|
785
888
|
} else {
|
|
786
889
|
// header-center: insert after icon/title
|
|
787
|
-
const titleSection = header.querySelector(".
|
|
890
|
+
const titleSection = header.querySelector(".persona-flex-col");
|
|
788
891
|
if (titleSection) {
|
|
789
892
|
titleSection.parentNode?.insertBefore(element, titleSection.nextSibling);
|
|
790
893
|
} else {
|
|
@@ -794,7 +897,7 @@ export const createAgentExperience = (
|
|
|
794
897
|
break;
|
|
795
898
|
case "body-top": {
|
|
796
899
|
// Replace or prepend to body
|
|
797
|
-
const introCard = body.querySelector(".
|
|
900
|
+
const introCard = body.querySelector(".persona-rounded-2xl.persona-bg-persona-surface.persona-p-6");
|
|
798
901
|
if (introCard) {
|
|
799
902
|
introCard.replaceWith(element);
|
|
800
903
|
} else {
|
|
@@ -904,7 +1007,7 @@ export const createAgentExperience = (
|
|
|
904
1007
|
|
|
905
1008
|
messagesWrapper.addEventListener('click', (event) => {
|
|
906
1009
|
const target = event.target as HTMLElement;
|
|
907
|
-
const actionBtn = target.closest('.
|
|
1010
|
+
const actionBtn = target.closest('.persona-message-action-btn[data-action]') as HTMLElement;
|
|
908
1011
|
if (!actionBtn) return;
|
|
909
1012
|
|
|
910
1013
|
event.preventDefault();
|
|
@@ -926,14 +1029,14 @@ export const createAgentExperience = (
|
|
|
926
1029
|
const textToCopy = message.content || "";
|
|
927
1030
|
navigator.clipboard.writeText(textToCopy).then(() => {
|
|
928
1031
|
// Show success feedback - swap icon temporarily
|
|
929
|
-
actionBtn.classList.add("
|
|
1032
|
+
actionBtn.classList.add("persona-message-action-success");
|
|
930
1033
|
const checkIcon = renderLucideIcon("check", 14, "currentColor", 2);
|
|
931
1034
|
if (checkIcon) {
|
|
932
1035
|
actionBtn.innerHTML = "";
|
|
933
1036
|
actionBtn.appendChild(checkIcon);
|
|
934
1037
|
}
|
|
935
1038
|
setTimeout(() => {
|
|
936
|
-
actionBtn.classList.remove("
|
|
1039
|
+
actionBtn.classList.remove("persona-message-action-success");
|
|
937
1040
|
const originalIcon = renderLucideIcon("copy", 14, "currentColor", 2);
|
|
938
1041
|
if (originalIcon) {
|
|
939
1042
|
actionBtn.innerHTML = "";
|
|
@@ -955,17 +1058,17 @@ export const createAgentExperience = (
|
|
|
955
1058
|
if (wasActive) {
|
|
956
1059
|
// Toggle off
|
|
957
1060
|
messageVoteState.delete(messageId);
|
|
958
|
-
actionBtn.classList.remove("
|
|
1061
|
+
actionBtn.classList.remove("persona-message-action-active");
|
|
959
1062
|
} else {
|
|
960
1063
|
// Clear opposite vote button
|
|
961
1064
|
const oppositeAction = action === 'upvote' ? 'downvote' : 'upvote';
|
|
962
1065
|
const oppositeBtn = actionsContainer.querySelector(`[data-action="${oppositeAction}"]`);
|
|
963
1066
|
if (oppositeBtn) {
|
|
964
|
-
oppositeBtn.classList.remove("
|
|
1067
|
+
oppositeBtn.classList.remove("persona-message-action-active");
|
|
965
1068
|
}
|
|
966
1069
|
|
|
967
1070
|
messageVoteState.set(messageId, action);
|
|
968
|
-
actionBtn.classList.add("
|
|
1071
|
+
actionBtn.classList.add("persona-message-action-active");
|
|
969
1072
|
|
|
970
1073
|
// Trigger feedback
|
|
971
1074
|
const messages = session.getMessages();
|
|
@@ -1021,32 +1124,334 @@ export const createAgentExperience = (
|
|
|
1021
1124
|
session.resolveApproval(approvalMessage.approval, decision);
|
|
1022
1125
|
});
|
|
1023
1126
|
|
|
1024
|
-
|
|
1127
|
+
let artifactPaneApi: ArtifactPaneApi | null = null;
|
|
1128
|
+
let artifactPanelResizeObs: ResizeObserver | null = null;
|
|
1129
|
+
let lastArtifactsState: {
|
|
1130
|
+
artifacts: PersonaArtifactRecord[];
|
|
1131
|
+
selectedId: string | null;
|
|
1132
|
+
} = { artifacts: [], selectedId: null };
|
|
1133
|
+
let artifactsPaneUserHidden = false;
|
|
1134
|
+
const sessionRef: { current: AgentWidgetSession | null } = { current: null };
|
|
1135
|
+
|
|
1136
|
+
// Click delegation for artifact download buttons
|
|
1137
|
+
messagesWrapper.addEventListener('click', (event) => {
|
|
1138
|
+
const target = event.target as HTMLElement;
|
|
1139
|
+
const dlBtn = target.closest('[data-download-artifact]') as HTMLElement;
|
|
1140
|
+
if (!dlBtn) return;
|
|
1141
|
+
event.preventDefault();
|
|
1142
|
+
event.stopPropagation();
|
|
1143
|
+
const artifactId = dlBtn.getAttribute('data-download-artifact');
|
|
1144
|
+
if (!artifactId) return;
|
|
1145
|
+
// Try session state first, fall back to content stored in the card's rawContent props
|
|
1146
|
+
const artifact = session.getArtifactById(artifactId);
|
|
1147
|
+
let markdown = artifact?.markdown;
|
|
1148
|
+
let title = artifact?.title || 'artifact';
|
|
1149
|
+
if (!markdown) {
|
|
1150
|
+
// After page refresh, session state is gone — read from the persisted card message
|
|
1151
|
+
const cardEl = dlBtn.closest('[data-open-artifact]');
|
|
1152
|
+
const msgEl = cardEl?.closest('[data-message-id]');
|
|
1153
|
+
const msgId = msgEl?.getAttribute('data-message-id');
|
|
1154
|
+
if (msgId) {
|
|
1155
|
+
const msgs = session.getMessages();
|
|
1156
|
+
const msg = msgs.find(m => m.id === msgId);
|
|
1157
|
+
if (msg?.rawContent) {
|
|
1158
|
+
try {
|
|
1159
|
+
const parsed = JSON.parse(msg.rawContent);
|
|
1160
|
+
markdown = parsed?.props?.markdown;
|
|
1161
|
+
title = parsed?.props?.title || title;
|
|
1162
|
+
} catch { /* ignore */ }
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (!markdown) return;
|
|
1167
|
+
const blob = new Blob([markdown], { type: 'text/markdown' });
|
|
1168
|
+
const url = URL.createObjectURL(blob);
|
|
1169
|
+
const a = document.createElement('a');
|
|
1170
|
+
a.href = url;
|
|
1171
|
+
a.download = `${title}.md`;
|
|
1172
|
+
a.click();
|
|
1173
|
+
URL.revokeObjectURL(url);
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// Click delegation for artifact reference cards
|
|
1177
|
+
messagesWrapper.addEventListener('click', (event) => {
|
|
1178
|
+
const target = event.target as HTMLElement;
|
|
1179
|
+
const card = target.closest('[data-open-artifact]') as HTMLElement;
|
|
1180
|
+
if (!card) return;
|
|
1181
|
+
const artifactId = card.getAttribute('data-open-artifact');
|
|
1182
|
+
if (!artifactId) return;
|
|
1183
|
+
event.preventDefault();
|
|
1184
|
+
event.stopPropagation();
|
|
1185
|
+
session.selectArtifact(artifactId);
|
|
1186
|
+
syncArtifactPane();
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
// Keyboard support for artifact cards
|
|
1190
|
+
messagesWrapper.addEventListener('keydown', (event) => {
|
|
1191
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
1192
|
+
const target = event.target as HTMLElement;
|
|
1193
|
+
if (!target.hasAttribute('data-open-artifact')) return;
|
|
1194
|
+
event.preventDefault();
|
|
1195
|
+
target.click();
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
let artifactSplitRoot: HTMLElement | null = null;
|
|
1199
|
+
let artifactResizeHandle: HTMLElement | null = null;
|
|
1200
|
+
let artifactResizeUnbind: (() => void) | null = null;
|
|
1201
|
+
let artifactResizeDocEnd: (() => void) | null = null;
|
|
1202
|
+
let reconcileArtifactResize: () => void = () => {};
|
|
1203
|
+
|
|
1204
|
+
function stopArtifactResizePointer() {
|
|
1205
|
+
artifactResizeDocEnd?.();
|
|
1206
|
+
artifactResizeDocEnd = null;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/** Flush split: overlay handle on the seam so it does not consume flex gap (extension + resizable). */
|
|
1210
|
+
const positionExtensionArtifactResizeHandle = () => {
|
|
1211
|
+
if (!artifactSplitRoot || !artifactResizeHandle) return;
|
|
1212
|
+
const ext = mount.classList.contains("persona-artifact-appearance-seamless");
|
|
1213
|
+
const ownerWin = mount.ownerDocument.defaultView ?? window;
|
|
1214
|
+
const mobile = ownerWin.innerWidth <= 640;
|
|
1215
|
+
if (!ext || mount.classList.contains("persona-artifact-narrow-host") || mobile) {
|
|
1216
|
+
artifactResizeHandle.style.removeProperty("position");
|
|
1217
|
+
artifactResizeHandle.style.removeProperty("left");
|
|
1218
|
+
artifactResizeHandle.style.removeProperty("top");
|
|
1219
|
+
artifactResizeHandle.style.removeProperty("bottom");
|
|
1220
|
+
artifactResizeHandle.style.removeProperty("width");
|
|
1221
|
+
artifactResizeHandle.style.removeProperty("z-index");
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const chat = artifactSplitRoot.firstElementChild as HTMLElement | null;
|
|
1225
|
+
if (!chat || chat === artifactResizeHandle) return;
|
|
1226
|
+
const hitW = 10;
|
|
1227
|
+
artifactResizeHandle.style.position = "absolute";
|
|
1228
|
+
artifactResizeHandle.style.top = "0";
|
|
1229
|
+
artifactResizeHandle.style.bottom = "0";
|
|
1230
|
+
artifactResizeHandle.style.width = `${hitW}px`;
|
|
1231
|
+
artifactResizeHandle.style.zIndex = "5";
|
|
1232
|
+
const left = chat.offsetWidth - hitW / 2;
|
|
1233
|
+
artifactResizeHandle.style.left = `${Math.max(0, left)}px`;
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
/** No-op until artifact pane is created; replaced below when artifacts are enabled. */
|
|
1237
|
+
let applyLauncherArtifactPanelWidth: () => void = () => {};
|
|
1238
|
+
|
|
1239
|
+
const syncArtifactPane = () => {
|
|
1240
|
+
if (!artifactPaneApi || !artifactsSidebarEnabled(config)) return;
|
|
1241
|
+
applyArtifactLayoutCssVars(mount, config);
|
|
1242
|
+
applyArtifactPaneAppearance(mount, config);
|
|
1243
|
+
applyLauncherArtifactPanelWidth();
|
|
1244
|
+
const threshold = config.features?.artifacts?.layout?.narrowHostMaxWidth ?? 520;
|
|
1245
|
+
const w = panel.getBoundingClientRect().width || 0;
|
|
1246
|
+
mount.classList.toggle("persona-artifact-narrow-host", w > 0 && w <= threshold);
|
|
1247
|
+
artifactPaneApi.update(lastArtifactsState);
|
|
1248
|
+
if (artifactsPaneUserHidden) {
|
|
1249
|
+
artifactPaneApi.setMobileOpen(false);
|
|
1250
|
+
artifactPaneApi.element.classList.add("persona-hidden");
|
|
1251
|
+
artifactPaneApi.backdrop?.classList.add("persona-hidden");
|
|
1252
|
+
} else if (lastArtifactsState.artifacts.length > 0) {
|
|
1253
|
+
// User chose “show” again (e.g. programmatic showArtifacts): clear dismiss chrome
|
|
1254
|
+
// and force drawer open so narrow-host / mobile slide-out is not stuck off-screen.
|
|
1255
|
+
artifactPaneApi.element.classList.remove("persona-hidden");
|
|
1256
|
+
artifactPaneApi.setMobileOpen(true);
|
|
1257
|
+
}
|
|
1258
|
+
reconcileArtifactResize();
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
if (artifactsSidebarEnabled(config)) {
|
|
1262
|
+
panel.style.position = "relative";
|
|
1263
|
+
const chatColumn = createElement(
|
|
1264
|
+
"div",
|
|
1265
|
+
"persona-flex persona-flex-1 persona-flex-col persona-min-w-0 persona-min-h-0"
|
|
1266
|
+
);
|
|
1267
|
+
const splitRoot = createElement(
|
|
1268
|
+
"div",
|
|
1269
|
+
"persona-flex persona-h-full persona-w-full persona-min-h-0 persona-artifact-split-root"
|
|
1270
|
+
);
|
|
1271
|
+
chatColumn.appendChild(container);
|
|
1272
|
+
artifactPaneApi = createArtifactPane(config, {
|
|
1273
|
+
onSelect: (id) => sessionRef.current?.selectArtifact(id),
|
|
1274
|
+
onDismiss: () => {
|
|
1275
|
+
artifactsPaneUserHidden = true;
|
|
1276
|
+
syncArtifactPane();
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
artifactPaneApi.element.classList.add("persona-hidden");
|
|
1280
|
+
artifactSplitRoot = splitRoot;
|
|
1281
|
+
splitRoot.appendChild(chatColumn);
|
|
1282
|
+
splitRoot.appendChild(artifactPaneApi.element);
|
|
1283
|
+
if (artifactPaneApi.backdrop) {
|
|
1284
|
+
panel.appendChild(artifactPaneApi.backdrop);
|
|
1285
|
+
}
|
|
1286
|
+
panel.appendChild(splitRoot);
|
|
1287
|
+
|
|
1288
|
+
reconcileArtifactResize = () => {
|
|
1289
|
+
if (!artifactSplitRoot || !artifactPaneApi) return;
|
|
1290
|
+
const want = config.features?.artifacts?.layout?.resizable === true;
|
|
1291
|
+
if (!want) {
|
|
1292
|
+
artifactResizeUnbind?.();
|
|
1293
|
+
artifactResizeUnbind = null;
|
|
1294
|
+
stopArtifactResizePointer();
|
|
1295
|
+
if (artifactResizeHandle) {
|
|
1296
|
+
artifactResizeHandle.remove();
|
|
1297
|
+
artifactResizeHandle = null;
|
|
1298
|
+
}
|
|
1299
|
+
artifactPaneApi.element.style.removeProperty("width");
|
|
1300
|
+
artifactPaneApi.element.style.removeProperty("maxWidth");
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
if (!artifactResizeHandle) {
|
|
1304
|
+
const handle = createElement(
|
|
1305
|
+
"div",
|
|
1306
|
+
"persona-artifact-split-handle persona-shrink-0 persona-h-full"
|
|
1307
|
+
);
|
|
1308
|
+
handle.setAttribute("role", "separator");
|
|
1309
|
+
handle.setAttribute("aria-orientation", "vertical");
|
|
1310
|
+
handle.setAttribute("aria-label", "Resize artifacts panel");
|
|
1311
|
+
handle.tabIndex = 0;
|
|
1312
|
+
|
|
1313
|
+
const doc = mount.ownerDocument;
|
|
1314
|
+
const win = doc.defaultView ?? window;
|
|
1315
|
+
|
|
1316
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
1317
|
+
if (!artifactPaneApi || e.button !== 0) return;
|
|
1318
|
+
if (mount.classList.contains("persona-artifact-narrow-host")) return;
|
|
1319
|
+
if (win.innerWidth <= 640) return;
|
|
1320
|
+
e.preventDefault();
|
|
1321
|
+
stopArtifactResizePointer();
|
|
1322
|
+
const startX = e.clientX;
|
|
1323
|
+
const startW = artifactPaneApi.element.getBoundingClientRect().width;
|
|
1324
|
+
const layout = config.features?.artifacts?.layout;
|
|
1325
|
+
const onMove = (ev: PointerEvent) => {
|
|
1326
|
+
const splitW = artifactSplitRoot!.getBoundingClientRect().width;
|
|
1327
|
+
const extensionChrome = mount.classList.contains("persona-artifact-appearance-seamless");
|
|
1328
|
+
const gapPx = extensionChrome ? 0 : readFlexGapPx(artifactSplitRoot!, win);
|
|
1329
|
+
const handleW = extensionChrome ? 0 : handle.getBoundingClientRect().width || 6;
|
|
1330
|
+
// Handle is left of the artifact: drag left widens artifact, drag right narrows it.
|
|
1331
|
+
const next = startW - (ev.clientX - startX);
|
|
1332
|
+
const clamped = resolveArtifactPaneWidthPx(
|
|
1333
|
+
next,
|
|
1334
|
+
splitW,
|
|
1335
|
+
gapPx,
|
|
1336
|
+
handleW,
|
|
1337
|
+
layout?.resizableMinWidth,
|
|
1338
|
+
layout?.resizableMaxWidth
|
|
1339
|
+
);
|
|
1340
|
+
artifactPaneApi!.element.style.width = `${clamped}px`;
|
|
1341
|
+
artifactPaneApi!.element.style.maxWidth = "none";
|
|
1342
|
+
positionExtensionArtifactResizeHandle();
|
|
1343
|
+
};
|
|
1344
|
+
const onUp = () => {
|
|
1345
|
+
doc.removeEventListener("pointermove", onMove);
|
|
1346
|
+
doc.removeEventListener("pointerup", onUp);
|
|
1347
|
+
doc.removeEventListener("pointercancel", onUp);
|
|
1348
|
+
artifactResizeDocEnd = null;
|
|
1349
|
+
try {
|
|
1350
|
+
handle.releasePointerCapture(e.pointerId);
|
|
1351
|
+
} catch {
|
|
1352
|
+
/* ignore */
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
artifactResizeDocEnd = onUp;
|
|
1356
|
+
doc.addEventListener("pointermove", onMove);
|
|
1357
|
+
doc.addEventListener("pointerup", onUp);
|
|
1358
|
+
doc.addEventListener("pointercancel", onUp);
|
|
1359
|
+
try {
|
|
1360
|
+
handle.setPointerCapture(e.pointerId);
|
|
1361
|
+
} catch {
|
|
1362
|
+
/* ignore */
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
handle.addEventListener("pointerdown", onPointerDown);
|
|
1367
|
+
artifactResizeHandle = handle;
|
|
1368
|
+
artifactSplitRoot.insertBefore(handle, artifactPaneApi.element);
|
|
1369
|
+
artifactResizeUnbind = () => {
|
|
1370
|
+
handle.removeEventListener("pointerdown", onPointerDown);
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
if (artifactResizeHandle) {
|
|
1374
|
+
const has =
|
|
1375
|
+
lastArtifactsState.artifacts.length > 0 && !artifactsPaneUserHidden;
|
|
1376
|
+
artifactResizeHandle.classList.toggle("persona-hidden", !has);
|
|
1377
|
+
positionExtensionArtifactResizeHandle();
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
applyLauncherArtifactPanelWidth = () => {
|
|
1382
|
+
if (!launcherEnabled || !artifactPaneApi) return;
|
|
1383
|
+
const sidebarMode = config.launcher?.sidebarMode ?? false;
|
|
1384
|
+
if (sidebarMode) return;
|
|
1385
|
+
const ownerWindow = mount.ownerDocument.defaultView ?? window;
|
|
1386
|
+
const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;
|
|
1387
|
+
const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;
|
|
1388
|
+
if (mobileFullscreen && ownerWindow.innerWidth <= mobileBreakpoint) return;
|
|
1389
|
+
if (!shouldExpandLauncherForArtifacts(config, launcherEnabled)) return;
|
|
1390
|
+
|
|
1391
|
+
const base = config.launcher?.width ?? config.launcherWidth ?? "min(400px, calc(100vw - 24px))";
|
|
1392
|
+
const expanded =
|
|
1393
|
+
config.features?.artifacts?.layout?.expandedPanelWidth ??
|
|
1394
|
+
"min(720px, calc(100vw - 24px))";
|
|
1395
|
+
const hasVisible =
|
|
1396
|
+
lastArtifactsState.artifacts.length > 0 && !artifactsPaneUserHidden;
|
|
1397
|
+
if (hasVisible) {
|
|
1398
|
+
panel.style.width = expanded;
|
|
1399
|
+
panel.style.maxWidth = expanded;
|
|
1400
|
+
} else {
|
|
1401
|
+
panel.style.width = base;
|
|
1402
|
+
panel.style.maxWidth = base;
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1407
|
+
artifactPanelResizeObs = new ResizeObserver(() => {
|
|
1408
|
+
syncArtifactPane();
|
|
1409
|
+
});
|
|
1410
|
+
artifactPanelResizeObs.observe(panel);
|
|
1411
|
+
}
|
|
1412
|
+
} else {
|
|
1413
|
+
panel.appendChild(container);
|
|
1414
|
+
}
|
|
1025
1415
|
mount.appendChild(wrapper);
|
|
1026
1416
|
|
|
1027
1417
|
// Apply full-height and sidebar styles if enabled
|
|
1028
1418
|
// This ensures the widget fills its container height with proper flex layout
|
|
1029
1419
|
const applyFullHeightStyles = () => {
|
|
1420
|
+
const dockedMode = isDockedMountMode(config);
|
|
1030
1421
|
const sidebarMode = config.launcher?.sidebarMode ?? false;
|
|
1031
|
-
const fullHeight = sidebarMode || (config.launcher?.fullHeight ?? false);
|
|
1422
|
+
const fullHeight = dockedMode || sidebarMode || (config.launcher?.fullHeight ?? false);
|
|
1423
|
+
/** Script-tag / div embed: launcher off, host supplies a sized mount. */
|
|
1424
|
+
const isInlineEmbed = config.launcher?.enabled === false;
|
|
1032
1425
|
const theme = config.theme ?? {};
|
|
1033
|
-
|
|
1426
|
+
|
|
1427
|
+
// Mobile fullscreen detection
|
|
1428
|
+
// Use mount's ownerDocument window to get correct viewport width when widget is inside an iframe
|
|
1429
|
+
const ownerWindow = mount.ownerDocument.defaultView ?? window;
|
|
1430
|
+
const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;
|
|
1431
|
+
const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;
|
|
1432
|
+
const isMobileViewport = ownerWindow.innerWidth <= mobileBreakpoint;
|
|
1433
|
+
const shouldGoFullscreen = mobileFullscreen && isMobileViewport && launcherEnabled;
|
|
1434
|
+
|
|
1034
1435
|
// Determine panel styling based on mode, with theme overrides
|
|
1035
1436
|
const position = config.launcher?.position ?? 'bottom-left';
|
|
1036
1437
|
const isLeftSidebar = position === 'bottom-left' || position === 'top-left';
|
|
1037
|
-
|
|
1438
|
+
|
|
1038
1439
|
// Default values based on mode
|
|
1039
|
-
const defaultPanelBorder = sidebarMode ? 'none' : '1px solid var(--
|
|
1040
|
-
const defaultPanelShadow =
|
|
1041
|
-
?
|
|
1042
|
-
:
|
|
1043
|
-
|
|
1044
|
-
|
|
1440
|
+
const defaultPanelBorder = (sidebarMode || shouldGoFullscreen) ? 'none' : '1px solid var(--persona-persona-border)';
|
|
1441
|
+
const defaultPanelShadow = shouldGoFullscreen
|
|
1442
|
+
? 'none'
|
|
1443
|
+
: sidebarMode
|
|
1444
|
+
? (isLeftSidebar ? 'var(--persona-palette-shadows-sidebar-left, 2px 0 12px rgba(0, 0, 0, 0.08))' : 'var(--persona-palette-shadows-sidebar-right, -2px 0 12px rgba(0, 0, 0, 0.08))')
|
|
1445
|
+
: 'var(--persona-palette-shadows-xl, 0 25px 50px -12px rgba(0, 0, 0, 0.25))';
|
|
1446
|
+
const defaultPanelBorderRadius = (sidebarMode || shouldGoFullscreen)
|
|
1447
|
+
? '0'
|
|
1448
|
+
: 'var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))';
|
|
1449
|
+
|
|
1045
1450
|
// Apply theme overrides or defaults
|
|
1046
1451
|
const panelBorder = theme.panelBorder ?? defaultPanelBorder;
|
|
1047
1452
|
const panelShadow = theme.panelShadow ?? defaultPanelShadow;
|
|
1048
1453
|
const panelBorderRadius = theme.panelBorderRadius ?? defaultPanelBorderRadius;
|
|
1049
|
-
|
|
1454
|
+
|
|
1050
1455
|
// Reset all inline styles first to handle mode toggling
|
|
1051
1456
|
// This ensures styles don't persist when switching between modes
|
|
1052
1457
|
mount.style.cssText = '';
|
|
@@ -1056,14 +1461,87 @@ export const createAgentExperience = (
|
|
|
1056
1461
|
body.style.cssText = '';
|
|
1057
1462
|
footer.style.cssText = '';
|
|
1058
1463
|
|
|
1464
|
+
// Mobile fullscreen: fill entire viewport with no radius/shadow/margins
|
|
1465
|
+
if (shouldGoFullscreen) {
|
|
1466
|
+
// Remove position offset classes
|
|
1467
|
+
wrapper.classList.remove(
|
|
1468
|
+
'persona-bottom-6', 'persona-right-6', 'persona-left-6', 'persona-top-6',
|
|
1469
|
+
'persona-bottom-4', 'persona-right-4', 'persona-left-4', 'persona-top-4'
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
// Wrapper — fill entire viewport
|
|
1473
|
+
wrapper.style.cssText = `
|
|
1474
|
+
position: fixed !important;
|
|
1475
|
+
inset: 0 !important;
|
|
1476
|
+
width: 100% !important;
|
|
1477
|
+
height: 100% !important;
|
|
1478
|
+
max-height: 100% !important;
|
|
1479
|
+
margin: 0 !important;
|
|
1480
|
+
padding: 0 !important;
|
|
1481
|
+
display: flex !important;
|
|
1482
|
+
flex-direction: column !important;
|
|
1483
|
+
z-index: inherit !important;
|
|
1484
|
+
`;
|
|
1485
|
+
|
|
1486
|
+
// Panel — fill wrapper, no radius/shadow
|
|
1487
|
+
panel.style.cssText = `
|
|
1488
|
+
position: relative !important;
|
|
1489
|
+
display: flex !important;
|
|
1490
|
+
flex-direction: column !important;
|
|
1491
|
+
flex: 1 1 0% !important;
|
|
1492
|
+
width: 100% !important;
|
|
1493
|
+
max-width: 100% !important;
|
|
1494
|
+
height: 100% !important;
|
|
1495
|
+
min-height: 0 !important;
|
|
1496
|
+
margin: 0 !important;
|
|
1497
|
+
padding: 0 !important;
|
|
1498
|
+
box-shadow: none !important;
|
|
1499
|
+
border-radius: 0 !important;
|
|
1500
|
+
`;
|
|
1501
|
+
|
|
1502
|
+
// Container — fill panel, no radius/border
|
|
1503
|
+
container.style.cssText = `
|
|
1504
|
+
display: flex !important;
|
|
1505
|
+
flex-direction: column !important;
|
|
1506
|
+
flex: 1 1 0% !important;
|
|
1507
|
+
width: 100% !important;
|
|
1508
|
+
height: 100% !important;
|
|
1509
|
+
min-height: 0 !important;
|
|
1510
|
+
max-height: 100% !important;
|
|
1511
|
+
overflow: hidden !important;
|
|
1512
|
+
border-radius: 0 !important;
|
|
1513
|
+
border: none !important;
|
|
1514
|
+
`;
|
|
1515
|
+
|
|
1516
|
+
// Body — scrollable messages
|
|
1517
|
+
body.style.flex = '1 1 0%';
|
|
1518
|
+
body.style.minHeight = '0';
|
|
1519
|
+
body.style.overflowY = 'auto';
|
|
1520
|
+
|
|
1521
|
+
// Footer — pinned at bottom
|
|
1522
|
+
footer.style.flexShrink = '0';
|
|
1523
|
+
|
|
1524
|
+
wasMobileFullscreen = true;
|
|
1525
|
+
return; // Skip remaining mode logic
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1059
1528
|
// Re-apply panel width/maxWidth from initial setup
|
|
1060
1529
|
const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
|
|
1061
1530
|
const width = launcherWidth ?? "min(400px, calc(100vw - 24px))";
|
|
1062
|
-
if (!sidebarMode) {
|
|
1063
|
-
|
|
1064
|
-
|
|
1531
|
+
if (!sidebarMode && !dockedMode) {
|
|
1532
|
+
if (isInlineEmbed && fullHeight) {
|
|
1533
|
+
panel.style.width = "100%";
|
|
1534
|
+
panel.style.maxWidth = "100%";
|
|
1535
|
+
} else {
|
|
1536
|
+
panel.style.width = width;
|
|
1537
|
+
panel.style.maxWidth = width;
|
|
1538
|
+
}
|
|
1539
|
+
} else if (dockedMode) {
|
|
1540
|
+
panel.style.width = "100%";
|
|
1541
|
+
panel.style.maxWidth = "100%";
|
|
1065
1542
|
}
|
|
1066
|
-
|
|
1543
|
+
applyLauncherArtifactPanelWidth();
|
|
1544
|
+
|
|
1067
1545
|
// Apply panel styling
|
|
1068
1546
|
// Box-shadow is applied to panel (parent) instead of container to avoid
|
|
1069
1547
|
// rendering artifacts when container has overflow:hidden + border-radius
|
|
@@ -1072,16 +1550,16 @@ export const createAgentExperience = (
|
|
|
1072
1550
|
panel.style.borderRadius = panelBorderRadius;
|
|
1073
1551
|
container.style.border = panelBorder;
|
|
1074
1552
|
container.style.borderRadius = panelBorderRadius;
|
|
1075
|
-
|
|
1076
|
-
// Check if this is inline embed mode (launcher disabled) vs launcher mode
|
|
1077
|
-
const isInlineEmbed = config.launcher?.enabled === false;
|
|
1078
|
-
|
|
1553
|
+
|
|
1079
1554
|
if (fullHeight) {
|
|
1080
1555
|
// Mount container
|
|
1081
1556
|
mount.style.display = 'flex';
|
|
1082
1557
|
mount.style.flexDirection = 'column';
|
|
1083
1558
|
mount.style.height = '100%';
|
|
1084
1559
|
mount.style.minHeight = '0';
|
|
1560
|
+
if (isInlineEmbed) {
|
|
1561
|
+
mount.style.width = '100%';
|
|
1562
|
+
}
|
|
1085
1563
|
|
|
1086
1564
|
// Wrapper
|
|
1087
1565
|
// - Inline embed: needs overflow:hidden to contain the flex layout
|
|
@@ -1125,11 +1603,11 @@ export const createAgentExperience = (
|
|
|
1125
1603
|
// Handle positioning classes based on mode
|
|
1126
1604
|
// First remove all position classes to reset state
|
|
1127
1605
|
wrapper.classList.remove(
|
|
1128
|
-
'
|
|
1129
|
-
'
|
|
1606
|
+
'persona-bottom-6', 'persona-right-6', 'persona-left-6', 'persona-top-6',
|
|
1607
|
+
'persona-bottom-4', 'persona-right-4', 'persona-left-4', 'persona-top-4'
|
|
1130
1608
|
);
|
|
1131
1609
|
|
|
1132
|
-
if (!sidebarMode && !isInlineEmbed) {
|
|
1610
|
+
if (!sidebarMode && !isInlineEmbed && !dockedMode) {
|
|
1133
1611
|
// Restore positioning classes when not in sidebar mode (launcher mode only)
|
|
1134
1612
|
const positionClasses = positionMap[position as keyof typeof positionMap] ?? positionMap['bottom-right'];
|
|
1135
1613
|
positionClasses.split(' ').forEach(cls => wrapper.classList.add(cls));
|
|
@@ -1202,7 +1680,7 @@ export const createAgentExperience = (
|
|
|
1202
1680
|
// Use both -moz-available (Firefox) and stretch (standard) for cross-browser support
|
|
1203
1681
|
// Append to cssText to allow multiple fallback values for the same property
|
|
1204
1682
|
// Only apply to launcher mode (not sidebar or inline embed)
|
|
1205
|
-
if (!isInlineEmbed) {
|
|
1683
|
+
if (!isInlineEmbed && !dockedMode) {
|
|
1206
1684
|
const maxHeightStyles = 'max-height: -moz-available !important; max-height: stretch !important;';
|
|
1207
1685
|
const paddingStyles = sidebarMode ? '' : 'padding-top: 1.25em !important;';
|
|
1208
1686
|
wrapper.style.cssText += maxHeightStyles + paddingStyles;
|
|
@@ -1211,9 +1689,30 @@ export const createAgentExperience = (
|
|
|
1211
1689
|
applyFullHeightStyles();
|
|
1212
1690
|
// Apply theme variables after applyFullHeightStyles since it resets mount.style.cssText
|
|
1213
1691
|
applyThemeVariables(mount, config);
|
|
1692
|
+
applyArtifactLayoutCssVars(mount, config);
|
|
1693
|
+
applyArtifactPaneAppearance(mount, config);
|
|
1214
1694
|
|
|
1215
1695
|
const destroyCallbacks: Array<() => void> = [];
|
|
1216
1696
|
|
|
1697
|
+
if (artifactPanelResizeObs) {
|
|
1698
|
+
destroyCallbacks.push(() => {
|
|
1699
|
+
artifactPanelResizeObs?.disconnect();
|
|
1700
|
+
artifactPanelResizeObs = null;
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
destroyCallbacks.push(() => {
|
|
1705
|
+
artifactResizeUnbind?.();
|
|
1706
|
+
artifactResizeUnbind = null;
|
|
1707
|
+
stopArtifactResizePointer();
|
|
1708
|
+
if (artifactResizeHandle) {
|
|
1709
|
+
artifactResizeHandle.remove();
|
|
1710
|
+
artifactResizeHandle = null;
|
|
1711
|
+
}
|
|
1712
|
+
artifactPaneApi?.element.style.removeProperty("width");
|
|
1713
|
+
artifactPaneApi?.element.style.removeProperty("maxWidth");
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1217
1716
|
// Event stream cleanup
|
|
1218
1717
|
if (showEventStreamToggle) {
|
|
1219
1718
|
destroyCallbacks.push(() => {
|
|
@@ -1257,6 +1756,8 @@ export const createAgentExperience = (
|
|
|
1257
1756
|
let closeHandler: (() => void) | null = null;
|
|
1258
1757
|
let session: AgentWidgetSession;
|
|
1259
1758
|
let isStreaming = false;
|
|
1759
|
+
const messageCache = createMessageCache();
|
|
1760
|
+
let configVersion = 0;
|
|
1260
1761
|
let shouldAutoScroll = true;
|
|
1261
1762
|
let lastScrollTop = 0;
|
|
1262
1763
|
let lastAutoScrollTime = 0;
|
|
@@ -1530,7 +2031,20 @@ export const createAgentExperience = (
|
|
|
1530
2031
|
|
|
1531
2032
|
const inlineLoadingRenderer = getInlineLoadingIndicatorRenderer();
|
|
1532
2033
|
|
|
2034
|
+
// Track active message IDs for cache pruning
|
|
2035
|
+
const activeMessageIds = new Set<string>();
|
|
2036
|
+
|
|
1533
2037
|
messages.forEach((message) => {
|
|
2038
|
+
activeMessageIds.add(message.id);
|
|
2039
|
+
|
|
2040
|
+
// Fingerprint cache: skip re-rendering unchanged messages
|
|
2041
|
+
const fingerprint = computeMessageFingerprint(message, configVersion);
|
|
2042
|
+
const cachedWrapper = getCachedWrapper(messageCache, message.id, fingerprint);
|
|
2043
|
+
if (cachedWrapper) {
|
|
2044
|
+
tempContainer.appendChild(cachedWrapper.cloneNode(true));
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
1534
2048
|
let bubble: HTMLElement | null = null;
|
|
1535
2049
|
|
|
1536
2050
|
// Try plugins first
|
|
@@ -1612,36 +2126,59 @@ export const createAgentExperience = (
|
|
|
1612
2126
|
transform
|
|
1613
2127
|
});
|
|
1614
2128
|
if (componentBubble) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
}
|
|
2129
|
+
const wrapChrome = config.wrapComponentDirectiveInBubble !== false;
|
|
2130
|
+
if (wrapChrome) {
|
|
2131
|
+
const componentWrapper = document.createElement("div");
|
|
2132
|
+
componentWrapper.className = [
|
|
2133
|
+
"vanilla-message-bubble",
|
|
2134
|
+
"persona-max-w-[85%]",
|
|
2135
|
+
"persona-rounded-2xl",
|
|
2136
|
+
"persona-bg-persona-surface",
|
|
2137
|
+
"persona-border",
|
|
2138
|
+
"persona-border-persona-message-border",
|
|
2139
|
+
"persona-p-4"
|
|
2140
|
+
].join(" ");
|
|
2141
|
+
componentWrapper.id = `bubble-${message.id}`;
|
|
2142
|
+
componentWrapper.setAttribute("data-message-id", message.id);
|
|
2143
|
+
|
|
2144
|
+
if (message.content && message.content.trim()) {
|
|
2145
|
+
const textDiv = document.createElement("div");
|
|
2146
|
+
textDiv.className = "persona-mb-3 persona-text-sm persona-leading-relaxed";
|
|
2147
|
+
textDiv.innerHTML = transform({
|
|
2148
|
+
text: message.content,
|
|
2149
|
+
message,
|
|
2150
|
+
streaming: Boolean(message.streaming),
|
|
2151
|
+
raw: message.rawContent
|
|
2152
|
+
});
|
|
2153
|
+
componentWrapper.appendChild(textDiv);
|
|
2154
|
+
}
|
|
1642
2155
|
|
|
1643
|
-
|
|
1644
|
-
|
|
2156
|
+
componentWrapper.appendChild(componentBubble);
|
|
2157
|
+
bubble = componentWrapper;
|
|
2158
|
+
} else {
|
|
2159
|
+
const stack = document.createElement("div");
|
|
2160
|
+
stack.className =
|
|
2161
|
+
"persona-flex persona-flex-col persona-w-full persona-max-w-full persona-gap-3 persona-items-stretch";
|
|
2162
|
+
stack.id = `bubble-${message.id}`;
|
|
2163
|
+
stack.setAttribute("data-message-id", message.id);
|
|
2164
|
+
stack.setAttribute("data-persona-component-directive", "true");
|
|
2165
|
+
|
|
2166
|
+
if (message.content && message.content.trim()) {
|
|
2167
|
+
const textDiv = document.createElement("div");
|
|
2168
|
+
textDiv.className =
|
|
2169
|
+
"persona-text-sm persona-leading-relaxed persona-text-persona-primary persona-w-full";
|
|
2170
|
+
textDiv.innerHTML = transform({
|
|
2171
|
+
text: message.content,
|
|
2172
|
+
message,
|
|
2173
|
+
streaming: Boolean(message.streaming),
|
|
2174
|
+
raw: message.rawContent
|
|
2175
|
+
});
|
|
2176
|
+
stack.appendChild(textDiv);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
stack.appendChild(componentBubble);
|
|
2180
|
+
bubble = stack;
|
|
2181
|
+
}
|
|
1645
2182
|
}
|
|
1646
2183
|
}
|
|
1647
2184
|
}
|
|
@@ -1693,17 +2230,24 @@ export const createAgentExperience = (
|
|
|
1693
2230
|
}
|
|
1694
2231
|
|
|
1695
2232
|
const wrapper = document.createElement("div");
|
|
1696
|
-
wrapper.className = "
|
|
2233
|
+
wrapper.className = "persona-flex";
|
|
1697
2234
|
// Set id for idiomorph matching
|
|
1698
2235
|
wrapper.id = `wrapper-${message.id}`;
|
|
1699
2236
|
wrapper.setAttribute("data-wrapper-id", message.id);
|
|
1700
2237
|
if (message.role === "user") {
|
|
1701
|
-
wrapper.classList.add("
|
|
2238
|
+
wrapper.classList.add("persona-justify-end");
|
|
2239
|
+
}
|
|
2240
|
+
if (bubble?.getAttribute("data-persona-component-directive") === "true") {
|
|
2241
|
+
wrapper.classList.add("persona-w-full");
|
|
1702
2242
|
}
|
|
1703
2243
|
wrapper.appendChild(bubble);
|
|
2244
|
+
setCachedWrapper(messageCache, message.id, fingerprint, wrapper);
|
|
1704
2245
|
tempContainer.appendChild(wrapper);
|
|
1705
2246
|
});
|
|
1706
2247
|
|
|
2248
|
+
// Remove cache entries for messages that no longer exist
|
|
2249
|
+
pruneCache(messageCache, activeMessageIds);
|
|
2250
|
+
|
|
1707
2251
|
// Add standalone typing indicator only if streaming but no assistant message is streaming yet
|
|
1708
2252
|
// (This shows while waiting for the stream to start)
|
|
1709
2253
|
// Check for ANY streaming assistant message, even if empty (to avoid duplicate bubbles)
|
|
@@ -1752,30 +2296,30 @@ export const createAgentExperience = (
|
|
|
1752
2296
|
const showBubble = config.loadingIndicator?.showBubble !== false; // default true
|
|
1753
2297
|
typingBubble.className = showBubble
|
|
1754
2298
|
? [
|
|
1755
|
-
"
|
|
1756
|
-
"
|
|
1757
|
-
"
|
|
1758
|
-
"
|
|
1759
|
-
"
|
|
1760
|
-
"
|
|
1761
|
-
"
|
|
1762
|
-
"
|
|
1763
|
-
"
|
|
1764
|
-
"
|
|
1765
|
-
"
|
|
2299
|
+
"persona-max-w-[85%]",
|
|
2300
|
+
"persona-rounded-2xl",
|
|
2301
|
+
"persona-text-sm",
|
|
2302
|
+
"persona-leading-relaxed",
|
|
2303
|
+
"persona-shadow-sm",
|
|
2304
|
+
"persona-bg-persona-surface",
|
|
2305
|
+
"persona-border",
|
|
2306
|
+
"persona-border-persona-message-border",
|
|
2307
|
+
"persona-text-persona-primary",
|
|
2308
|
+
"persona-px-5",
|
|
2309
|
+
"persona-py-3"
|
|
1766
2310
|
].join(" ")
|
|
1767
2311
|
: [
|
|
1768
|
-
"
|
|
1769
|
-
"
|
|
1770
|
-
"
|
|
1771
|
-
"
|
|
2312
|
+
"persona-max-w-[85%]",
|
|
2313
|
+
"persona-text-sm",
|
|
2314
|
+
"persona-leading-relaxed",
|
|
2315
|
+
"persona-text-persona-primary"
|
|
1772
2316
|
].join(" ");
|
|
1773
2317
|
typingBubble.setAttribute("data-typing-indicator", "true");
|
|
1774
2318
|
|
|
1775
2319
|
typingBubble.appendChild(typingIndicator);
|
|
1776
2320
|
|
|
1777
2321
|
const typingWrapper = document.createElement("div");
|
|
1778
|
-
typingWrapper.className = "
|
|
2322
|
+
typingWrapper.className = "persona-flex";
|
|
1779
2323
|
// Set id for idiomorph matching
|
|
1780
2324
|
typingWrapper.id = "wrapper-typing-indicator";
|
|
1781
2325
|
typingWrapper.setAttribute("data-wrapper-id", "typing-indicator");
|
|
@@ -1816,30 +2360,30 @@ export const createAgentExperience = (
|
|
|
1816
2360
|
const showBubble = config.loadingIndicator?.showBubble !== false; // default true
|
|
1817
2361
|
idleBubble.className = showBubble
|
|
1818
2362
|
? [
|
|
1819
|
-
"
|
|
1820
|
-
"
|
|
1821
|
-
"
|
|
1822
|
-
"
|
|
1823
|
-
"
|
|
1824
|
-
"
|
|
1825
|
-
"
|
|
1826
|
-
"
|
|
1827
|
-
"
|
|
1828
|
-
"
|
|
1829
|
-
"
|
|
2363
|
+
"persona-max-w-[85%]",
|
|
2364
|
+
"persona-rounded-2xl",
|
|
2365
|
+
"persona-text-sm",
|
|
2366
|
+
"persona-leading-relaxed",
|
|
2367
|
+
"persona-shadow-sm",
|
|
2368
|
+
"persona-bg-persona-surface",
|
|
2369
|
+
"persona-border",
|
|
2370
|
+
"persona-border-persona-message-border",
|
|
2371
|
+
"persona-text-persona-primary",
|
|
2372
|
+
"persona-px-5",
|
|
2373
|
+
"persona-py-3"
|
|
1830
2374
|
].join(" ")
|
|
1831
2375
|
: [
|
|
1832
|
-
"
|
|
1833
|
-
"
|
|
1834
|
-
"
|
|
1835
|
-
"
|
|
2376
|
+
"persona-max-w-[85%]",
|
|
2377
|
+
"persona-text-sm",
|
|
2378
|
+
"persona-leading-relaxed",
|
|
2379
|
+
"persona-text-persona-primary"
|
|
1836
2380
|
].join(" ");
|
|
1837
2381
|
idleBubble.setAttribute("data-idle-indicator", "true");
|
|
1838
2382
|
|
|
1839
2383
|
idleBubble.appendChild(idleIndicator);
|
|
1840
2384
|
|
|
1841
2385
|
const idleWrapper = document.createElement("div");
|
|
1842
|
-
idleWrapper.className = "
|
|
2386
|
+
idleWrapper.className = "persona-flex";
|
|
1843
2387
|
// Set id for idiomorph matching
|
|
1844
2388
|
idleWrapper.id = "wrapper-idle-indicator";
|
|
1845
2389
|
idleWrapper.setAttribute("data-wrapper-id", "idle-indicator");
|
|
@@ -1867,10 +2411,12 @@ export const createAgentExperience = (
|
|
|
1867
2411
|
|
|
1868
2412
|
const updateOpenState = () => {
|
|
1869
2413
|
if (!launcherEnabled) return;
|
|
2414
|
+
const dockedMode = isDockedMountMode(config);
|
|
1870
2415
|
if (open) {
|
|
1871
|
-
wrapper.
|
|
1872
|
-
|
|
1873
|
-
panel.classList.
|
|
2416
|
+
wrapper.style.display = dockedMode ? "flex" : "";
|
|
2417
|
+
wrapper.classList.remove("persona-pointer-events-none", "persona-opacity-0");
|
|
2418
|
+
panel.classList.remove("persona-scale-95", "persona-opacity-0");
|
|
2419
|
+
panel.classList.add("persona-scale-100", "persona-opacity-100");
|
|
1874
2420
|
// Hide launcher button when widget is open
|
|
1875
2421
|
if (launcherButtonInstance) {
|
|
1876
2422
|
launcherButtonInstance.element.style.display = "none";
|
|
@@ -1878,9 +2424,16 @@ export const createAgentExperience = (
|
|
|
1878
2424
|
customLauncherElement.style.display = "none";
|
|
1879
2425
|
}
|
|
1880
2426
|
} else {
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2427
|
+
if (dockedMode) {
|
|
2428
|
+
wrapper.style.display = "none";
|
|
2429
|
+
wrapper.classList.remove("persona-pointer-events-none", "persona-opacity-0");
|
|
2430
|
+
panel.classList.remove("persona-scale-100", "persona-opacity-100", "persona-scale-95", "persona-opacity-0");
|
|
2431
|
+
} else {
|
|
2432
|
+
wrapper.style.display = "";
|
|
2433
|
+
wrapper.classList.add("persona-pointer-events-none", "persona-opacity-0");
|
|
2434
|
+
panel.classList.remove("persona-scale-100", "persona-opacity-100");
|
|
2435
|
+
panel.classList.add("persona-scale-95", "persona-opacity-0");
|
|
2436
|
+
}
|
|
1884
2437
|
// Show launcher button when widget is closed
|
|
1885
2438
|
if (launcherButtonInstance) {
|
|
1886
2439
|
launcherButtonInstance.element.style.display = "";
|
|
@@ -1935,6 +2488,17 @@ export const createAgentExperience = (
|
|
|
1935
2488
|
suggestionsManager.buttons.forEach((btn) => {
|
|
1936
2489
|
btn.disabled = disabled;
|
|
1937
2490
|
});
|
|
2491
|
+
footer.dataset.personaComposerStreaming = disabled ? "true" : "false";
|
|
2492
|
+
footer.querySelectorAll<HTMLElement>("[data-persona-composer-disable-when-streaming]").forEach((el) => {
|
|
2493
|
+
if (
|
|
2494
|
+
el instanceof HTMLButtonElement ||
|
|
2495
|
+
el instanceof HTMLInputElement ||
|
|
2496
|
+
el instanceof HTMLTextAreaElement ||
|
|
2497
|
+
el instanceof HTMLSelectElement
|
|
2498
|
+
) {
|
|
2499
|
+
el.disabled = disabled;
|
|
2500
|
+
}
|
|
2501
|
+
});
|
|
1938
2502
|
};
|
|
1939
2503
|
|
|
1940
2504
|
const maybeFocusInput = () => {
|
|
@@ -2082,9 +2646,15 @@ export const createAgentExperience = (
|
|
|
2082
2646
|
}
|
|
2083
2647
|
break;
|
|
2084
2648
|
}
|
|
2649
|
+
},
|
|
2650
|
+
onArtifactsState(state) {
|
|
2651
|
+
lastArtifactsState = state;
|
|
2652
|
+
syncArtifactPane();
|
|
2085
2653
|
}
|
|
2086
2654
|
});
|
|
2087
2655
|
|
|
2656
|
+
sessionRef.current = session;
|
|
2657
|
+
|
|
2088
2658
|
// Setup Runtype voice provider when configured (connects WebSocket for server-side STT)
|
|
2089
2659
|
if (config.voiceRecognition?.provider?.type === 'runtype') {
|
|
2090
2660
|
try {
|
|
@@ -2312,7 +2882,7 @@ export const createAgentExperience = (
|
|
|
2312
2882
|
const recordingIconColor = voiceConfig.recordingIconColor;
|
|
2313
2883
|
const recordingBorderColor = voiceConfig.recordingBorderColor;
|
|
2314
2884
|
|
|
2315
|
-
micButton.classList.add("
|
|
2885
|
+
micButton.classList.add("persona-voice-recording");
|
|
2316
2886
|
micButton.style.backgroundColor = recordingBackgroundColor;
|
|
2317
2887
|
|
|
2318
2888
|
if (recordingIconColor) {
|
|
@@ -2360,7 +2930,24 @@ export const createAgentExperience = (
|
|
|
2360
2930
|
persistVoiceMetadata();
|
|
2361
2931
|
|
|
2362
2932
|
if (micButton) {
|
|
2363
|
-
|
|
2933
|
+
micButton.classList.remove("persona-voice-recording");
|
|
2934
|
+
|
|
2935
|
+
// Restore original styles
|
|
2936
|
+
if (originalMicStyles) {
|
|
2937
|
+
micButton.style.backgroundColor = originalMicStyles.backgroundColor;
|
|
2938
|
+
micButton.style.color = originalMicStyles.color;
|
|
2939
|
+
micButton.style.borderColor = originalMicStyles.borderColor;
|
|
2940
|
+
|
|
2941
|
+
// Restore SVG stroke color if present
|
|
2942
|
+
const svg = micButton.querySelector("svg");
|
|
2943
|
+
if (svg) {
|
|
2944
|
+
svg.setAttribute("stroke", originalMicStyles.color || "currentColor");
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
originalMicStyles = null;
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
micButton.setAttribute("aria-label", "Start voice recognition");
|
|
2364
2951
|
}
|
|
2365
2952
|
};
|
|
2366
2953
|
|
|
@@ -2375,10 +2962,10 @@ export const createAgentExperience = (
|
|
|
2375
2962
|
|
|
2376
2963
|
if (!hasVoiceInput) return null;
|
|
2377
2964
|
|
|
2378
|
-
const micButtonWrapper = createElement("div", "
|
|
2965
|
+
const micButtonWrapper = createElement("div", "persona-send-button-wrapper");
|
|
2379
2966
|
const micButton = createElement(
|
|
2380
2967
|
"button",
|
|
2381
|
-
"
|
|
2968
|
+
"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer"
|
|
2382
2969
|
) as HTMLButtonElement;
|
|
2383
2970
|
|
|
2384
2971
|
micButton.type = "button";
|
|
@@ -2416,14 +3003,14 @@ export const createAgentExperience = (
|
|
|
2416
3003
|
if (backgroundColor) {
|
|
2417
3004
|
micButton.style.backgroundColor = backgroundColor;
|
|
2418
3005
|
} else {
|
|
2419
|
-
micButton.classList.add("
|
|
3006
|
+
micButton.classList.add("persona-bg-persona-primary");
|
|
2420
3007
|
}
|
|
2421
3008
|
|
|
2422
3009
|
// Apply icon/text color
|
|
2423
3010
|
if (iconColor) {
|
|
2424
3011
|
micButton.style.color = iconColor;
|
|
2425
3012
|
} else if (!iconColor && !sendButtonConfig?.textColor) {
|
|
2426
|
-
micButton.classList.add("
|
|
3013
|
+
micButton.classList.add("persona-text-white");
|
|
2427
3014
|
}
|
|
2428
3015
|
|
|
2429
3016
|
// Apply border styling
|
|
@@ -2451,7 +3038,7 @@ export const createAgentExperience = (
|
|
|
2451
3038
|
const tooltipText = voiceConfig?.tooltipText ?? "Start voice recognition";
|
|
2452
3039
|
const showTooltip = voiceConfig?.showTooltip ?? false;
|
|
2453
3040
|
if (showTooltip && tooltipText) {
|
|
2454
|
-
const tooltip = createElement("div", "
|
|
3041
|
+
const tooltip = createElement("div", "persona-send-button-tooltip");
|
|
2455
3042
|
tooltip.textContent = tooltipText;
|
|
2456
3043
|
micButtonWrapper.appendChild(tooltip);
|
|
2457
3044
|
}
|
|
@@ -2486,7 +3073,7 @@ export const createAgentExperience = (
|
|
|
2486
3073
|
/** Remove all voice state CSS classes */
|
|
2487
3074
|
const removeAllVoiceStateClasses = () => {
|
|
2488
3075
|
if (!micButton) return;
|
|
2489
|
-
micButton.classList.remove("
|
|
3076
|
+
micButton.classList.remove("persona-voice-recording", "persona-voice-processing", "persona-voice-speaking");
|
|
2490
3077
|
};
|
|
2491
3078
|
|
|
2492
3079
|
// --- Per-state style application ---
|
|
@@ -2499,7 +3086,7 @@ export const createAgentExperience = (
|
|
|
2499
3086
|
const recordingIconColor = voiceConfig.recordingIconColor;
|
|
2500
3087
|
const recordingBorderColor = voiceConfig.recordingBorderColor;
|
|
2501
3088
|
removeAllVoiceStateClasses();
|
|
2502
|
-
micButton.classList.add("
|
|
3089
|
+
micButton.classList.add("persona-voice-recording");
|
|
2503
3090
|
micButton.style.backgroundColor = recordingBackgroundColor;
|
|
2504
3091
|
if (recordingIconColor) {
|
|
2505
3092
|
micButton.style.color = recordingIconColor;
|
|
@@ -2521,7 +3108,7 @@ export const createAgentExperience = (
|
|
|
2521
3108
|
const borderColor = voiceConfig.processingBorderColor ?? originalMicStyles?.borderColor ?? "";
|
|
2522
3109
|
|
|
2523
3110
|
removeAllVoiceStateClasses();
|
|
2524
|
-
micButton.classList.add("
|
|
3111
|
+
micButton.classList.add("persona-voice-processing");
|
|
2525
3112
|
micButton.style.backgroundColor = bgColor;
|
|
2526
3113
|
micButton.style.borderColor = borderColor;
|
|
2527
3114
|
const resolvedColor = iconColor || "currentColor";
|
|
@@ -2553,7 +3140,7 @@ export const createAgentExperience = (
|
|
|
2553
3140
|
?? (interruptionMode === "barge-in" ? (voiceConfig.recordingBorderColor ?? "") : (originalMicStyles?.borderColor ?? ""));
|
|
2554
3141
|
|
|
2555
3142
|
removeAllVoiceStateClasses();
|
|
2556
|
-
micButton.classList.add("
|
|
3143
|
+
micButton.classList.add("persona-voice-speaking");
|
|
2557
3144
|
micButton.style.backgroundColor = bgColor;
|
|
2558
3145
|
micButton.style.borderColor = borderColor;
|
|
2559
3146
|
const resolvedColor = iconColor || "currentColor";
|
|
@@ -2573,7 +3160,7 @@ export const createAgentExperience = (
|
|
|
2573
3160
|
}
|
|
2574
3161
|
// In "barge-in" mode, add recording class to show mic is hot
|
|
2575
3162
|
if (interruptionMode === "barge-in") {
|
|
2576
|
-
micButton.classList.add("
|
|
3163
|
+
micButton.classList.add("persona-voice-recording");
|
|
2577
3164
|
}
|
|
2578
3165
|
};
|
|
2579
3166
|
|
|
@@ -2660,6 +3247,8 @@ export const createAgentExperience = (
|
|
|
2660
3247
|
}
|
|
2661
3248
|
};
|
|
2662
3249
|
|
|
3250
|
+
composerVoiceBridge = handleMicButtonClick;
|
|
3251
|
+
|
|
2663
3252
|
if (micButton) {
|
|
2664
3253
|
micButton.addEventListener("click", handleMicButtonClick);
|
|
2665
3254
|
|
|
@@ -2763,26 +3352,48 @@ export const createAgentExperience = (
|
|
|
2763
3352
|
}
|
|
2764
3353
|
|
|
2765
3354
|
const recalcPanelHeight = () => {
|
|
3355
|
+
const dockedMode = isDockedMountMode(config);
|
|
2766
3356
|
const sidebarMode = config.launcher?.sidebarMode ?? false;
|
|
2767
|
-
const fullHeight = sidebarMode || (config.launcher?.fullHeight ?? false);
|
|
2768
|
-
|
|
2769
|
-
|
|
3357
|
+
const fullHeight = dockedMode || sidebarMode || (config.launcher?.fullHeight ?? false);
|
|
3358
|
+
|
|
3359
|
+
// Mobile fullscreen: re-apply fullscreen styles on resize (handles orientation changes)
|
|
3360
|
+
const ownerWindow = mount.ownerDocument.defaultView ?? window;
|
|
3361
|
+
const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;
|
|
3362
|
+
const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;
|
|
3363
|
+
const isMobileViewport = ownerWindow.innerWidth <= mobileBreakpoint;
|
|
3364
|
+
const shouldGoFullscreen = mobileFullscreen && isMobileViewport && launcherEnabled;
|
|
3365
|
+
|
|
3366
|
+
if (shouldGoFullscreen) {
|
|
3367
|
+
applyFullHeightStyles();
|
|
3368
|
+
applyThemeVariables(mount, config);
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
// Exiting mobile fullscreen (e.g., orientation change to landscape) — reset all styles
|
|
3373
|
+
if (wasMobileFullscreen) {
|
|
3374
|
+
wasMobileFullscreen = false;
|
|
3375
|
+
applyFullHeightStyles();
|
|
3376
|
+
applyThemeVariables(mount, config);
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
if (!launcherEnabled && !dockedMode) {
|
|
2770
3380
|
panel.style.height = "";
|
|
2771
3381
|
panel.style.width = "";
|
|
2772
3382
|
return;
|
|
2773
3383
|
}
|
|
2774
|
-
|
|
3384
|
+
|
|
2775
3385
|
// In sidebar/fullHeight mode, don't override the width - it's handled by applyFullHeightStyles
|
|
2776
|
-
if (!sidebarMode) {
|
|
3386
|
+
if (!sidebarMode && !dockedMode) {
|
|
2777
3387
|
const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
|
|
2778
3388
|
const width = launcherWidth ?? "min(400px, calc(100vw - 24px))";
|
|
2779
3389
|
panel.style.width = width;
|
|
2780
3390
|
panel.style.maxWidth = width;
|
|
2781
3391
|
}
|
|
2782
|
-
|
|
3392
|
+
applyLauncherArtifactPanelWidth();
|
|
3393
|
+
|
|
2783
3394
|
// In fullHeight mode, don't set a fixed height
|
|
2784
3395
|
if (!fullHeight) {
|
|
2785
|
-
const viewportHeight =
|
|
3396
|
+
const viewportHeight = ownerWindow.innerHeight;
|
|
2786
3397
|
const verticalMargin = 64; // leave space for launcher's offset
|
|
2787
3398
|
const heightOffset = config.launcher?.heightOffset ?? 0;
|
|
2788
3399
|
const available = Math.max(200, viewportHeight - verticalMargin);
|
|
@@ -2793,8 +3404,9 @@ export const createAgentExperience = (
|
|
|
2793
3404
|
};
|
|
2794
3405
|
|
|
2795
3406
|
recalcPanelHeight();
|
|
2796
|
-
|
|
2797
|
-
|
|
3407
|
+
const ownerWindow = mount.ownerDocument.defaultView ?? window;
|
|
3408
|
+
ownerWindow.addEventListener("resize", recalcPanelHeight);
|
|
3409
|
+
destroyCallbacks.push(() => ownerWindow.removeEventListener("resize", recalcPanelHeight));
|
|
2798
3410
|
|
|
2799
3411
|
lastScrollTop = body.scrollTop;
|
|
2800
3412
|
|
|
@@ -2837,8 +3449,7 @@ export const createAgentExperience = (
|
|
|
2837
3449
|
if (launcherEnabled) {
|
|
2838
3450
|
closeButton.style.display = "";
|
|
2839
3451
|
closeHandler = () => {
|
|
2840
|
-
|
|
2841
|
-
updateOpenState();
|
|
3452
|
+
setOpenState(false, "user");
|
|
2842
3453
|
};
|
|
2843
3454
|
closeButton.addEventListener("click", closeHandler);
|
|
2844
3455
|
} else {
|
|
@@ -2856,6 +3467,7 @@ export const createAgentExperience = (
|
|
|
2856
3467
|
clearChatButton.addEventListener("click", () => {
|
|
2857
3468
|
// Clear messages in session (this will trigger onMessagesChanged which re-renders)
|
|
2858
3469
|
session.clearMessages();
|
|
3470
|
+
messageCache.clear();
|
|
2859
3471
|
|
|
2860
3472
|
// Always clear the default localStorage key
|
|
2861
3473
|
try {
|
|
@@ -2914,14 +3526,18 @@ export const createAgentExperience = (
|
|
|
2914
3526
|
|
|
2915
3527
|
setupClearChatButton();
|
|
2916
3528
|
|
|
2917
|
-
composerForm
|
|
2918
|
-
|
|
2919
|
-
|
|
3529
|
+
if (composerForm) {
|
|
3530
|
+
composerForm.addEventListener("submit", handleSubmit);
|
|
3531
|
+
}
|
|
3532
|
+
textarea?.addEventListener("keydown", handleInputEnter);
|
|
3533
|
+
textarea?.addEventListener("paste", handleInputPaste);
|
|
2920
3534
|
|
|
2921
3535
|
destroyCallbacks.push(() => {
|
|
2922
|
-
composerForm
|
|
2923
|
-
|
|
2924
|
-
|
|
3536
|
+
if (composerForm) {
|
|
3537
|
+
composerForm.removeEventListener("submit", handleSubmit);
|
|
3538
|
+
}
|
|
3539
|
+
textarea?.removeEventListener("keydown", handleInputEnter);
|
|
3540
|
+
textarea?.removeEventListener("paste", handleInputPaste);
|
|
2925
3541
|
});
|
|
2926
3542
|
|
|
2927
3543
|
destroyCallbacks.push(() => {
|
|
@@ -2941,11 +3557,16 @@ export const createAgentExperience = (
|
|
|
2941
3557
|
const controller: Controller = {
|
|
2942
3558
|
update(nextConfig: AgentWidgetConfig) {
|
|
2943
3559
|
const previousToolCallConfig = config.toolCall;
|
|
3560
|
+
const previousMessageActions = config.messageActions;
|
|
3561
|
+
const previousLayoutMessages = config.layout?.messages;
|
|
2944
3562
|
const previousColorScheme = config.colorScheme;
|
|
2945
3563
|
config = { ...config, ...nextConfig };
|
|
2946
3564
|
// applyFullHeightStyles resets mount.style.cssText, so call it before applyThemeVariables
|
|
2947
3565
|
applyFullHeightStyles();
|
|
2948
3566
|
applyThemeVariables(mount, config);
|
|
3567
|
+
applyArtifactLayoutCssVars(mount, config);
|
|
3568
|
+
applyArtifactPaneAppearance(mount, config);
|
|
3569
|
+
syncArtifactPane();
|
|
2949
3570
|
|
|
2950
3571
|
// Re-setup theme observer if colorScheme changed
|
|
2951
3572
|
if (config.colorScheme !== previousColorScheme) {
|
|
@@ -2984,7 +3605,7 @@ export const createAgentExperience = (
|
|
|
2984
3605
|
// Add header toggle button if not present
|
|
2985
3606
|
if (!eventStreamToggleBtn && header) {
|
|
2986
3607
|
const dynEsClassNames = config.features?.eventStream?.classNames;
|
|
2987
|
-
const dynToggleBtnClasses = "
|
|
3608
|
+
const dynToggleBtnClasses = "persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-text-persona-muted hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none persona-bg-transparent persona-p-1" + (dynEsClassNames?.toggleButton ? " " + dynEsClassNames.toggleButton : "");
|
|
2988
3609
|
eventStreamToggleBtn = createElement("button", dynToggleBtnClasses) as HTMLButtonElement;
|
|
2989
3610
|
eventStreamToggleBtn.style.width = "28px";
|
|
2990
3611
|
eventStreamToggleBtn.style.height = "28px";
|
|
@@ -3116,11 +3737,11 @@ export const createAgentExperience = (
|
|
|
3116
3737
|
panelElements.clearChatButtonWrapper.style.display = showClearChat ? "" : "none";
|
|
3117
3738
|
// When clear chat is hidden, close button needs ml-auto to stay right-aligned
|
|
3118
3739
|
const { closeButtonWrapper } = panelElements;
|
|
3119
|
-
if (closeButtonWrapper && !closeButtonWrapper.classList.contains("
|
|
3740
|
+
if (closeButtonWrapper && !closeButtonWrapper.classList.contains("persona-absolute")) {
|
|
3120
3741
|
if (showClearChat) {
|
|
3121
|
-
closeButtonWrapper.classList.remove("
|
|
3742
|
+
closeButtonWrapper.classList.remove("persona-ml-auto");
|
|
3122
3743
|
} else {
|
|
3123
|
-
closeButtonWrapper.classList.add("
|
|
3744
|
+
closeButtonWrapper.classList.add("persona-ml-auto");
|
|
3124
3745
|
}
|
|
3125
3746
|
}
|
|
3126
3747
|
}
|
|
@@ -3165,9 +3786,13 @@ export const createAgentExperience = (
|
|
|
3165
3786
|
recalcPanelHeight();
|
|
3166
3787
|
refreshCloseButton();
|
|
3167
3788
|
|
|
3168
|
-
// Re-render messages if
|
|
3789
|
+
// Re-render messages if config affecting message rendering changed
|
|
3169
3790
|
const toolCallConfigChanged = JSON.stringify(nextConfig.toolCall) !== JSON.stringify(previousToolCallConfig);
|
|
3170
|
-
|
|
3791
|
+
const messageActionsChanged = JSON.stringify(config.messageActions) !== JSON.stringify(previousMessageActions);
|
|
3792
|
+
const layoutMessagesChanged = JSON.stringify(config.layout?.messages) !== JSON.stringify(previousLayoutMessages);
|
|
3793
|
+
const messagesConfigChanged = toolCallConfigChanged || messageActionsChanged || layoutMessagesChanged;
|
|
3794
|
+
if (messagesConfigChanged && session) {
|
|
3795
|
+
configVersion++;
|
|
3171
3796
|
renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);
|
|
3172
3797
|
}
|
|
3173
3798
|
|
|
@@ -3181,8 +3806,8 @@ export const createAgentExperience = (
|
|
|
3181
3806
|
const headerIconSize = launcher.headerIconSize ?? "48px";
|
|
3182
3807
|
|
|
3183
3808
|
if (iconHolder) {
|
|
3184
|
-
const headerEl = container.querySelector(".
|
|
3185
|
-
const headerCopy = headerEl?.querySelector(".
|
|
3809
|
+
const headerEl = container.querySelector(".persona-border-b-persona-divider");
|
|
3810
|
+
const headerCopy = headerEl?.querySelector(".persona-flex-col");
|
|
3186
3811
|
|
|
3187
3812
|
// Handle hide/show
|
|
3188
3813
|
if (shouldHideIcon) {
|
|
@@ -3232,7 +3857,7 @@ export const createAgentExperience = (
|
|
|
3232
3857
|
const newImg = document.createElement("img");
|
|
3233
3858
|
newImg.src = launcher.iconUrl;
|
|
3234
3859
|
newImg.alt = "";
|
|
3235
|
-
newImg.className = "
|
|
3860
|
+
newImg.className = "persona-rounded-xl persona-object-cover";
|
|
3236
3861
|
newImg.style.height = headerIconSize;
|
|
3237
3862
|
newImg.style.width = headerIconSize;
|
|
3238
3863
|
iconHolder.replaceChildren(newImg);
|
|
@@ -3283,7 +3908,7 @@ export const createAgentExperience = (
|
|
|
3283
3908
|
// Update placement if changed - move the wrapper (not just the button) to preserve tooltip
|
|
3284
3909
|
const { closeButtonWrapper } = panelElements;
|
|
3285
3910
|
const isTopRight = closeButtonPlacement === "top-right";
|
|
3286
|
-
const currentlyTopRight = closeButtonWrapper?.classList.contains("
|
|
3911
|
+
const currentlyTopRight = closeButtonWrapper?.classList.contains("persona-absolute");
|
|
3287
3912
|
|
|
3288
3913
|
if (closeButtonWrapper && isTopRight !== currentlyTopRight) {
|
|
3289
3914
|
// Placement changed - need to move wrapper and update classes
|
|
@@ -3291,16 +3916,16 @@ export const createAgentExperience = (
|
|
|
3291
3916
|
|
|
3292
3917
|
// Update wrapper classes
|
|
3293
3918
|
if (isTopRight) {
|
|
3294
|
-
closeButtonWrapper.className = "
|
|
3919
|
+
closeButtonWrapper.className = "persona-absolute persona-top-4 persona-right-4 persona-z-50";
|
|
3295
3920
|
container.style.position = "relative";
|
|
3296
3921
|
container.appendChild(closeButtonWrapper);
|
|
3297
3922
|
} else {
|
|
3298
3923
|
// Check if clear chat is inline to determine if we need ml-auto
|
|
3299
3924
|
const clearChatPlacement = launcher.clearChat?.placement ?? "inline";
|
|
3300
3925
|
const clearChatEnabled = launcher.clearChat?.enabled ?? true;
|
|
3301
|
-
closeButtonWrapper.className = (clearChatEnabled && clearChatPlacement === "inline") ? "" : "
|
|
3926
|
+
closeButtonWrapper.className = (clearChatEnabled && clearChatPlacement === "inline") ? "" : "persona-ml-auto";
|
|
3302
3927
|
// Find header element
|
|
3303
|
-
const header = container.querySelector(".
|
|
3928
|
+
const header = container.querySelector(".persona-border-b-persona-divider");
|
|
3304
3929
|
if (header) {
|
|
3305
3930
|
header.appendChild(closeButtonWrapper);
|
|
3306
3931
|
}
|
|
@@ -3310,18 +3935,18 @@ export const createAgentExperience = (
|
|
|
3310
3935
|
// Apply close button styling from config
|
|
3311
3936
|
if (launcher.closeButtonColor) {
|
|
3312
3937
|
closeButton.style.color = launcher.closeButtonColor;
|
|
3313
|
-
closeButton.classList.remove("
|
|
3938
|
+
closeButton.classList.remove("persona-text-persona-muted");
|
|
3314
3939
|
} else {
|
|
3315
3940
|
closeButton.style.color = "";
|
|
3316
|
-
closeButton.classList.add("
|
|
3941
|
+
closeButton.classList.add("persona-text-persona-muted");
|
|
3317
3942
|
}
|
|
3318
3943
|
|
|
3319
3944
|
if (launcher.closeButtonBackgroundColor) {
|
|
3320
3945
|
closeButton.style.backgroundColor = launcher.closeButtonBackgroundColor;
|
|
3321
|
-
closeButton.classList.remove("hover:
|
|
3946
|
+
closeButton.classList.remove("hover:persona-bg-gray-100");
|
|
3322
3947
|
} else {
|
|
3323
3948
|
closeButton.style.backgroundColor = "";
|
|
3324
|
-
closeButton.classList.add("hover:
|
|
3949
|
+
closeButton.classList.add("hover:persona-bg-gray-100");
|
|
3325
3950
|
}
|
|
3326
3951
|
|
|
3327
3952
|
// Apply border if width and/or color are provided
|
|
@@ -3329,18 +3954,18 @@ export const createAgentExperience = (
|
|
|
3329
3954
|
const borderWidth = launcher.closeButtonBorderWidth || "0px";
|
|
3330
3955
|
const borderColor = launcher.closeButtonBorderColor || "transparent";
|
|
3331
3956
|
closeButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
3332
|
-
closeButton.classList.remove("
|
|
3957
|
+
closeButton.classList.remove("persona-border-none");
|
|
3333
3958
|
} else {
|
|
3334
3959
|
closeButton.style.border = "";
|
|
3335
|
-
closeButton.classList.add("
|
|
3960
|
+
closeButton.classList.add("persona-border-none");
|
|
3336
3961
|
}
|
|
3337
3962
|
|
|
3338
3963
|
if (launcher.closeButtonBorderRadius) {
|
|
3339
3964
|
closeButton.style.borderRadius = launcher.closeButtonBorderRadius;
|
|
3340
|
-
closeButton.classList.remove("
|
|
3965
|
+
closeButton.classList.remove("persona-rounded-full");
|
|
3341
3966
|
} else {
|
|
3342
3967
|
closeButton.style.borderRadius = "";
|
|
3343
|
-
closeButton.classList.add("
|
|
3968
|
+
closeButton.classList.add("persona-rounded-full");
|
|
3344
3969
|
}
|
|
3345
3970
|
|
|
3346
3971
|
// Update padding
|
|
@@ -3392,13 +4017,21 @@ export const createAgentExperience = (
|
|
|
3392
4017
|
const showTooltip = () => {
|
|
3393
4018
|
if (portaledTooltip || !closeButton) return; // Already showing or button doesn't exist
|
|
3394
4019
|
|
|
4020
|
+
const tooltipDocument = closeButton.ownerDocument;
|
|
4021
|
+
const tooltipContainer = tooltipDocument.body;
|
|
4022
|
+
if (!tooltipContainer) return;
|
|
4023
|
+
|
|
3395
4024
|
// Create tooltip element
|
|
3396
|
-
portaledTooltip =
|
|
4025
|
+
portaledTooltip = createElementInDocument(
|
|
4026
|
+
tooltipDocument,
|
|
4027
|
+
"div",
|
|
4028
|
+
"persona-clear-chat-tooltip"
|
|
4029
|
+
);
|
|
3397
4030
|
portaledTooltip.textContent = closeButtonTooltipText;
|
|
3398
4031
|
|
|
3399
4032
|
// Add arrow
|
|
3400
|
-
const arrow =
|
|
3401
|
-
arrow.className = "
|
|
4033
|
+
const arrow = createElementInDocument(tooltipDocument, "div");
|
|
4034
|
+
arrow.className = "persona-clear-chat-tooltip-arrow";
|
|
3402
4035
|
portaledTooltip.appendChild(arrow);
|
|
3403
4036
|
|
|
3404
4037
|
// Get button position
|
|
@@ -3411,7 +4044,7 @@ export const createAgentExperience = (
|
|
|
3411
4044
|
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
3412
4045
|
|
|
3413
4046
|
// Append to body
|
|
3414
|
-
|
|
4047
|
+
tooltipContainer.appendChild(portaledTooltip);
|
|
3415
4048
|
};
|
|
3416
4049
|
|
|
3417
4050
|
const hideTooltip = () => {
|
|
@@ -3462,36 +4095,36 @@ export const createAgentExperience = (
|
|
|
3462
4095
|
|
|
3463
4096
|
// When clear chat is hidden, close button needs ml-auto to stay right-aligned
|
|
3464
4097
|
const { closeButtonWrapper } = panelElements;
|
|
3465
|
-
if (closeButtonWrapper && !closeButtonWrapper.classList.contains("
|
|
4098
|
+
if (closeButtonWrapper && !closeButtonWrapper.classList.contains("persona-absolute")) {
|
|
3466
4099
|
if (shouldShowClearChat) {
|
|
3467
|
-
closeButtonWrapper.classList.remove("
|
|
4100
|
+
closeButtonWrapper.classList.remove("persona-ml-auto");
|
|
3468
4101
|
} else {
|
|
3469
|
-
closeButtonWrapper.classList.add("
|
|
4102
|
+
closeButtonWrapper.classList.add("persona-ml-auto");
|
|
3470
4103
|
}
|
|
3471
4104
|
}
|
|
3472
4105
|
|
|
3473
4106
|
// Update placement if changed
|
|
3474
4107
|
const isTopRight = clearChatPlacement === "top-right";
|
|
3475
|
-
const currentlyTopRight = clearChatButtonWrapper.classList.contains("
|
|
4108
|
+
const currentlyTopRight = clearChatButtonWrapper.classList.contains("persona-absolute");
|
|
3476
4109
|
|
|
3477
4110
|
if (isTopRight !== currentlyTopRight && shouldShowClearChat) {
|
|
3478
4111
|
clearChatButtonWrapper.remove();
|
|
3479
4112
|
|
|
3480
4113
|
if (isTopRight) {
|
|
3481
|
-
// Don't use
|
|
4114
|
+
// Don't use persona-clear-chat-button-wrapper class for top-right mode as its
|
|
3482
4115
|
// display: inline-flex causes alignment issues with the close button
|
|
3483
|
-
clearChatButtonWrapper.className = "
|
|
4116
|
+
clearChatButtonWrapper.className = "persona-absolute persona-top-4 persona-z-50";
|
|
3484
4117
|
// Position to the left of the close button (which is at right: 1rem/16px)
|
|
3485
4118
|
// Close button is ~32px wide, plus small gap = 48px from right
|
|
3486
4119
|
clearChatButtonWrapper.style.right = "48px";
|
|
3487
4120
|
container.style.position = "relative";
|
|
3488
4121
|
container.appendChild(clearChatButtonWrapper);
|
|
3489
4122
|
} else {
|
|
3490
|
-
clearChatButtonWrapper.className = "
|
|
4123
|
+
clearChatButtonWrapper.className = "persona-relative persona-ml-auto persona-clear-chat-button-wrapper";
|
|
3491
4124
|
// Clear the inline right style when switching back to inline mode
|
|
3492
4125
|
clearChatButtonWrapper.style.right = "";
|
|
3493
4126
|
// Find header and insert before close button
|
|
3494
|
-
const header = container.querySelector(".
|
|
4127
|
+
const header = container.querySelector(".persona-border-b-persona-divider");
|
|
3495
4128
|
const closeButtonWrapperEl = panelElements.closeButtonWrapper;
|
|
3496
4129
|
if (header && closeButtonWrapperEl && closeButtonWrapperEl.parentElement === header) {
|
|
3497
4130
|
header.insertBefore(clearChatButtonWrapper, closeButtonWrapperEl);
|
|
@@ -3502,13 +4135,13 @@ export const createAgentExperience = (
|
|
|
3502
4135
|
|
|
3503
4136
|
// Also update close button's ml-auto class based on clear chat position
|
|
3504
4137
|
const closeButtonWrapperEl = panelElements.closeButtonWrapper;
|
|
3505
|
-
if (closeButtonWrapperEl && !closeButtonWrapperEl.classList.contains("
|
|
4138
|
+
if (closeButtonWrapperEl && !closeButtonWrapperEl.classList.contains("persona-absolute")) {
|
|
3506
4139
|
if (isTopRight) {
|
|
3507
4140
|
// Clear chat moved to top-right, close needs ml-auto
|
|
3508
|
-
closeButtonWrapperEl.classList.add("
|
|
4141
|
+
closeButtonWrapperEl.classList.add("persona-ml-auto");
|
|
3509
4142
|
} else {
|
|
3510
4143
|
// Clear chat is inline, close doesn't need ml-auto
|
|
3511
|
-
closeButtonWrapperEl.classList.remove("
|
|
4144
|
+
closeButtonWrapperEl.classList.remove("persona-ml-auto");
|
|
3512
4145
|
}
|
|
3513
4146
|
}
|
|
3514
4147
|
}
|
|
@@ -3534,19 +4167,19 @@ export const createAgentExperience = (
|
|
|
3534
4167
|
// Update icon color
|
|
3535
4168
|
if (clearChatIconColor) {
|
|
3536
4169
|
clearChatButton.style.color = clearChatIconColor;
|
|
3537
|
-
clearChatButton.classList.remove("
|
|
4170
|
+
clearChatButton.classList.remove("persona-text-persona-muted");
|
|
3538
4171
|
} else {
|
|
3539
4172
|
clearChatButton.style.color = "";
|
|
3540
|
-
clearChatButton.classList.add("
|
|
4173
|
+
clearChatButton.classList.add("persona-text-persona-muted");
|
|
3541
4174
|
}
|
|
3542
4175
|
|
|
3543
4176
|
// Update background color
|
|
3544
4177
|
if (clearChatConfig.backgroundColor) {
|
|
3545
4178
|
clearChatButton.style.backgroundColor = clearChatConfig.backgroundColor;
|
|
3546
|
-
clearChatButton.classList.remove("hover:
|
|
4179
|
+
clearChatButton.classList.remove("hover:persona-bg-gray-100");
|
|
3547
4180
|
} else {
|
|
3548
4181
|
clearChatButton.style.backgroundColor = "";
|
|
3549
|
-
clearChatButton.classList.add("hover:
|
|
4182
|
+
clearChatButton.classList.add("hover:persona-bg-gray-100");
|
|
3550
4183
|
}
|
|
3551
4184
|
|
|
3552
4185
|
// Update border
|
|
@@ -3554,19 +4187,19 @@ export const createAgentExperience = (
|
|
|
3554
4187
|
const borderWidth = clearChatConfig.borderWidth || "0px";
|
|
3555
4188
|
const borderColor = clearChatConfig.borderColor || "transparent";
|
|
3556
4189
|
clearChatButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
3557
|
-
clearChatButton.classList.remove("
|
|
4190
|
+
clearChatButton.classList.remove("persona-border-none");
|
|
3558
4191
|
} else {
|
|
3559
4192
|
clearChatButton.style.border = "";
|
|
3560
|
-
clearChatButton.classList.add("
|
|
4193
|
+
clearChatButton.classList.add("persona-border-none");
|
|
3561
4194
|
}
|
|
3562
4195
|
|
|
3563
4196
|
// Update border radius
|
|
3564
4197
|
if (clearChatConfig.borderRadius) {
|
|
3565
4198
|
clearChatButton.style.borderRadius = clearChatConfig.borderRadius;
|
|
3566
|
-
clearChatButton.classList.remove("
|
|
4199
|
+
clearChatButton.classList.remove("persona-rounded-full");
|
|
3567
4200
|
} else {
|
|
3568
4201
|
clearChatButton.style.borderRadius = "";
|
|
3569
|
-
clearChatButton.classList.add("
|
|
4202
|
+
clearChatButton.classList.add("persona-rounded-full");
|
|
3570
4203
|
}
|
|
3571
4204
|
|
|
3572
4205
|
// Update padding
|
|
@@ -3604,13 +4237,21 @@ export const createAgentExperience = (
|
|
|
3604
4237
|
const showTooltip = () => {
|
|
3605
4238
|
if (portaledTooltip || !clearChatButton) return; // Already showing or button doesn't exist
|
|
3606
4239
|
|
|
4240
|
+
const tooltipDocument = clearChatButton.ownerDocument;
|
|
4241
|
+
const tooltipContainer = tooltipDocument.body;
|
|
4242
|
+
if (!tooltipContainer) return;
|
|
4243
|
+
|
|
3607
4244
|
// Create tooltip element
|
|
3608
|
-
portaledTooltip =
|
|
4245
|
+
portaledTooltip = createElementInDocument(
|
|
4246
|
+
tooltipDocument,
|
|
4247
|
+
"div",
|
|
4248
|
+
"persona-clear-chat-tooltip"
|
|
4249
|
+
);
|
|
3609
4250
|
portaledTooltip.textContent = clearChatTooltipText;
|
|
3610
4251
|
|
|
3611
4252
|
// Add arrow
|
|
3612
|
-
const arrow =
|
|
3613
|
-
arrow.className = "
|
|
4253
|
+
const arrow = createElementInDocument(tooltipDocument, "div");
|
|
4254
|
+
arrow.className = "persona-clear-chat-tooltip-arrow";
|
|
3614
4255
|
portaledTooltip.appendChild(arrow);
|
|
3615
4256
|
|
|
3616
4257
|
// Get button position
|
|
@@ -3623,7 +4264,7 @@ export const createAgentExperience = (
|
|
|
3623
4264
|
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
3624
4265
|
|
|
3625
4266
|
// Append to body
|
|
3626
|
-
|
|
4267
|
+
tooltipContainer.appendChild(portaledTooltip);
|
|
3627
4268
|
};
|
|
3628
4269
|
|
|
3629
4270
|
const hideTooltip = () => {
|
|
@@ -3744,18 +4385,18 @@ export const createAgentExperience = (
|
|
|
3744
4385
|
const backgroundColor = voiceConfig.backgroundColor ?? sendButtonConfig.backgroundColor;
|
|
3745
4386
|
if (backgroundColor) {
|
|
3746
4387
|
micButton.style.backgroundColor = backgroundColor;
|
|
3747
|
-
micButton.classList.remove("
|
|
4388
|
+
micButton.classList.remove("persona-bg-persona-primary");
|
|
3748
4389
|
} else {
|
|
3749
4390
|
micButton.style.backgroundColor = "";
|
|
3750
|
-
micButton.classList.add("
|
|
4391
|
+
micButton.classList.add("persona-bg-persona-primary");
|
|
3751
4392
|
}
|
|
3752
4393
|
|
|
3753
4394
|
if (iconColor) {
|
|
3754
4395
|
micButton.style.color = iconColor;
|
|
3755
|
-
micButton.classList.remove("
|
|
4396
|
+
micButton.classList.remove("persona-text-white");
|
|
3756
4397
|
} else if (!iconColor && !sendButtonConfig.textColor) {
|
|
3757
4398
|
micButton.style.color = "";
|
|
3758
|
-
micButton.classList.add("
|
|
4399
|
+
micButton.classList.add("persona-text-white");
|
|
3759
4400
|
}
|
|
3760
4401
|
|
|
3761
4402
|
// Update border styling
|
|
@@ -3789,14 +4430,14 @@ export const createAgentExperience = (
|
|
|
3789
4430
|
}
|
|
3790
4431
|
|
|
3791
4432
|
// Update tooltip
|
|
3792
|
-
const tooltip = micButtonWrapper?.querySelector(".
|
|
4433
|
+
const tooltip = micButtonWrapper?.querySelector(".persona-send-button-tooltip") as HTMLElement | null;
|
|
3793
4434
|
const tooltipText = voiceConfig.tooltipText ?? "Start voice recognition";
|
|
3794
4435
|
const showTooltip = voiceConfig.showTooltip ?? false;
|
|
3795
4436
|
if (showTooltip && tooltipText) {
|
|
3796
4437
|
if (!tooltip) {
|
|
3797
4438
|
// Create tooltip if it doesn't exist
|
|
3798
4439
|
const newTooltip = document.createElement("div");
|
|
3799
|
-
newTooltip.className = "
|
|
4440
|
+
newTooltip.className = "persona-send-button-tooltip";
|
|
3800
4441
|
newTooltip.textContent = tooltipText;
|
|
3801
4442
|
micButtonWrapper?.insertBefore(newTooltip, micButton);
|
|
3802
4443
|
} else {
|
|
@@ -3837,7 +4478,7 @@ export const createAgentExperience = (
|
|
|
3837
4478
|
|
|
3838
4479
|
// Create previews container if not exists
|
|
3839
4480
|
if (!attachmentPreviewsContainer) {
|
|
3840
|
-
attachmentPreviewsContainer = createElement("div", "
|
|
4481
|
+
attachmentPreviewsContainer = createElement("div", "persona-attachment-previews persona-flex persona-flex-wrap persona-gap-2 persona-mb-2");
|
|
3841
4482
|
attachmentPreviewsContainer.style.display = "none";
|
|
3842
4483
|
composerForm.insertBefore(attachmentPreviewsContainer, textarea);
|
|
3843
4484
|
}
|
|
@@ -3854,12 +4495,12 @@ export const createAgentExperience = (
|
|
|
3854
4495
|
}
|
|
3855
4496
|
|
|
3856
4497
|
// Create attachment button wrapper
|
|
3857
|
-
attachmentButtonWrapper = createElement("div", "
|
|
4498
|
+
attachmentButtonWrapper = createElement("div", "persona-send-button-wrapper");
|
|
3858
4499
|
|
|
3859
4500
|
// Create attachment button
|
|
3860
4501
|
attachmentButton = createElement(
|
|
3861
4502
|
"button",
|
|
3862
|
-
"
|
|
4503
|
+
"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer persona-attachment-button"
|
|
3863
4504
|
) as HTMLButtonElement;
|
|
3864
4505
|
attachmentButton.type = "button";
|
|
3865
4506
|
attachmentButton.setAttribute("aria-label", attachmentsConfig.buttonTooltipText ?? "Attach file");
|
|
@@ -3878,14 +4519,14 @@ export const createAgentExperience = (
|
|
|
3878
4519
|
attachmentButton.style.fontSize = "18px";
|
|
3879
4520
|
attachmentButton.style.lineHeight = "1";
|
|
3880
4521
|
attachmentButton.style.backgroundColor = "transparent";
|
|
3881
|
-
attachmentButton.style.color = "var(--
|
|
4522
|
+
attachmentButton.style.color = "var(--persona-primary, #111827)";
|
|
3882
4523
|
attachmentButton.style.border = "none";
|
|
3883
4524
|
attachmentButton.style.borderRadius = "6px";
|
|
3884
4525
|
attachmentButton.style.transition = "background-color 0.15s ease";
|
|
3885
4526
|
|
|
3886
4527
|
// Add hover effect via mouseenter/mouseleave
|
|
3887
4528
|
attachmentButton.addEventListener("mouseenter", () => {
|
|
3888
|
-
attachmentButton!.style.backgroundColor = "rgba(0, 0, 0, 0.05)";
|
|
4529
|
+
attachmentButton!.style.backgroundColor = "var(--persona-palette-colors-black-alpha-50, rgba(0, 0, 0, 0.05))";
|
|
3889
4530
|
});
|
|
3890
4531
|
attachmentButton.addEventListener("mouseleave", () => {
|
|
3891
4532
|
attachmentButton!.style.backgroundColor = "transparent";
|
|
@@ -3907,7 +4548,7 @@ export const createAgentExperience = (
|
|
|
3907
4548
|
|
|
3908
4549
|
// Add tooltip
|
|
3909
4550
|
const attachTooltipText = attachmentsConfig.buttonTooltipText ?? "Attach file";
|
|
3910
|
-
const tooltip = createElement("div", "
|
|
4551
|
+
const tooltip = createElement("div", "persona-send-button-tooltip");
|
|
3911
4552
|
tooltip.textContent = attachTooltipText;
|
|
3912
4553
|
attachmentButtonWrapper.appendChild(tooltip);
|
|
3913
4554
|
|
|
@@ -3995,7 +4636,7 @@ export const createAgentExperience = (
|
|
|
3995
4636
|
if (textColor) {
|
|
3996
4637
|
sendButton.style.color = textColor;
|
|
3997
4638
|
} else {
|
|
3998
|
-
sendButton.classList.add("
|
|
4639
|
+
sendButton.classList.add("persona-text-white");
|
|
3999
4640
|
}
|
|
4000
4641
|
}
|
|
4001
4642
|
} else {
|
|
@@ -4003,18 +4644,18 @@ export const createAgentExperience = (
|
|
|
4003
4644
|
if (textColor) {
|
|
4004
4645
|
sendButton.style.color = textColor;
|
|
4005
4646
|
} else {
|
|
4006
|
-
sendButton.classList.add("
|
|
4647
|
+
sendButton.classList.add("persona-text-white");
|
|
4007
4648
|
}
|
|
4008
4649
|
}
|
|
4009
4650
|
|
|
4010
4651
|
// Update classes
|
|
4011
|
-
sendButton.className = "
|
|
4652
|
+
sendButton.className = "persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer";
|
|
4012
4653
|
|
|
4013
4654
|
if (backgroundColor) {
|
|
4014
4655
|
sendButton.style.backgroundColor = backgroundColor;
|
|
4015
|
-
sendButton.classList.remove("
|
|
4656
|
+
sendButton.classList.remove("persona-bg-persona-primary");
|
|
4016
4657
|
} else {
|
|
4017
|
-
sendButton.classList.add("
|
|
4658
|
+
sendButton.classList.add("persona-bg-persona-primary");
|
|
4018
4659
|
}
|
|
4019
4660
|
} else {
|
|
4020
4661
|
// Text mode: existing behavior
|
|
@@ -4027,19 +4668,19 @@ export const createAgentExperience = (
|
|
|
4027
4668
|
sendButton.style.lineHeight = "";
|
|
4028
4669
|
|
|
4029
4670
|
// Update classes
|
|
4030
|
-
sendButton.className = "
|
|
4671
|
+
sendButton.className = "persona-rounded-button persona-bg-persona-accent persona-px-4 persona-py-2 persona-text-sm persona-font-semibold persona-text-white disabled:persona-opacity-50 persona-cursor-pointer";
|
|
4031
4672
|
|
|
4032
4673
|
if (backgroundColor) {
|
|
4033
4674
|
sendButton.style.backgroundColor = backgroundColor;
|
|
4034
|
-
sendButton.classList.remove("
|
|
4675
|
+
sendButton.classList.remove("persona-bg-persona-accent");
|
|
4035
4676
|
} else {
|
|
4036
|
-
sendButton.classList.add("
|
|
4677
|
+
sendButton.classList.add("persona-bg-persona-accent");
|
|
4037
4678
|
}
|
|
4038
4679
|
|
|
4039
4680
|
if (textColor) {
|
|
4040
4681
|
sendButton.style.color = textColor;
|
|
4041
4682
|
} else {
|
|
4042
|
-
sendButton.classList.add("
|
|
4683
|
+
sendButton.classList.add("persona-text-white");
|
|
4043
4684
|
}
|
|
4044
4685
|
}
|
|
4045
4686
|
|
|
@@ -4074,12 +4715,12 @@ export const createAgentExperience = (
|
|
|
4074
4715
|
}
|
|
4075
4716
|
|
|
4076
4717
|
// Update tooltip
|
|
4077
|
-
const tooltip = sendButtonWrapper?.querySelector(".
|
|
4718
|
+
const tooltip = sendButtonWrapper?.querySelector(".persona-send-button-tooltip") as HTMLElement | null;
|
|
4078
4719
|
if (showTooltip && tooltipText) {
|
|
4079
4720
|
if (!tooltip) {
|
|
4080
4721
|
// Create tooltip if it doesn't exist
|
|
4081
4722
|
const newTooltip = document.createElement("div");
|
|
4082
|
-
newTooltip.className = "
|
|
4723
|
+
newTooltip.className = "persona-send-button-tooltip";
|
|
4083
4724
|
newTooltip.textContent = tooltipText;
|
|
4084
4725
|
sendButtonWrapper?.insertBefore(newTooltip, sendButton);
|
|
4085
4726
|
} else {
|
|
@@ -4122,7 +4763,9 @@ export const createAgentExperience = (
|
|
|
4122
4763
|
},
|
|
4123
4764
|
clearChat() {
|
|
4124
4765
|
// Clear messages in session (this will trigger onMessagesChanged which re-renders)
|
|
4766
|
+
artifactsPaneUserHidden = false;
|
|
4125
4767
|
session.clearMessages();
|
|
4768
|
+
messageCache.clear();
|
|
4126
4769
|
|
|
4127
4770
|
// Always clear the default localStorage key
|
|
4128
4771
|
try {
|
|
@@ -4338,6 +4981,31 @@ export const createAgentExperience = (
|
|
|
4338
4981
|
isEventStreamVisible(): boolean {
|
|
4339
4982
|
return eventStreamVisible;
|
|
4340
4983
|
},
|
|
4984
|
+
showArtifacts(): void {
|
|
4985
|
+
if (!artifactsSidebarEnabled(config)) return;
|
|
4986
|
+
artifactsPaneUserHidden = false;
|
|
4987
|
+
syncArtifactPane();
|
|
4988
|
+
artifactPaneApi?.setMobileOpen(true);
|
|
4989
|
+
},
|
|
4990
|
+
hideArtifacts(): void {
|
|
4991
|
+
if (!artifactsSidebarEnabled(config)) return;
|
|
4992
|
+
artifactsPaneUserHidden = true;
|
|
4993
|
+
syncArtifactPane();
|
|
4994
|
+
},
|
|
4995
|
+
upsertArtifact(manual: PersonaArtifactManualUpsert): PersonaArtifactRecord | null {
|
|
4996
|
+
if (!artifactsSidebarEnabled(config)) return null;
|
|
4997
|
+
// Programmatic adds should surface the pane even if the user previously hit Close.
|
|
4998
|
+
artifactsPaneUserHidden = false;
|
|
4999
|
+
return session.upsertArtifact(manual);
|
|
5000
|
+
},
|
|
5001
|
+
selectArtifact(id: string): void {
|
|
5002
|
+
if (!artifactsSidebarEnabled(config)) return;
|
|
5003
|
+
session.selectArtifact(id);
|
|
5004
|
+
},
|
|
5005
|
+
clearArtifacts(): void {
|
|
5006
|
+
if (!artifactsSidebarEnabled(config)) return;
|
|
5007
|
+
session.clearArtifacts();
|
|
5008
|
+
},
|
|
4341
5009
|
focusInput(): boolean {
|
|
4342
5010
|
if (launcherEnabled && !open) return false;
|
|
4343
5011
|
if (!textarea) return false;
|
|
@@ -4397,7 +5065,7 @@ export const createAgentExperience = (
|
|
|
4397
5065
|
}
|
|
4398
5066
|
|
|
4399
5067
|
// Remove any existing feedback forms
|
|
4400
|
-
const existingFeedback = messagesWrapper.querySelector('.
|
|
5068
|
+
const existingFeedback = messagesWrapper.querySelector('.persona-feedback-container');
|
|
4401
5069
|
if (existingFeedback) {
|
|
4402
5070
|
existingFeedback.remove();
|
|
4403
5071
|
}
|
|
@@ -4424,7 +5092,7 @@ export const createAgentExperience = (
|
|
|
4424
5092
|
}
|
|
4425
5093
|
|
|
4426
5094
|
// Remove any existing feedback forms
|
|
4427
|
-
const existingFeedback = messagesWrapper.querySelector('.
|
|
5095
|
+
const existingFeedback = messagesWrapper.querySelector('.persona-feedback-container');
|
|
4428
5096
|
if (existingFeedback) {
|
|
4429
5097
|
existingFeedback.remove();
|
|
4430
5098
|
}
|
|
@@ -4523,6 +5191,51 @@ export const createAgentExperience = (
|
|
|
4523
5191
|
window.removeEventListener("persona:hideEventStream", handleHideEvent);
|
|
4524
5192
|
});
|
|
4525
5193
|
}
|
|
5194
|
+
|
|
5195
|
+
const handleShowArtifacts = (e: Event) => {
|
|
5196
|
+
const detail = (e as CustomEvent).detail;
|
|
5197
|
+
if (!detail?.instanceId || detail.instanceId === instanceId) {
|
|
5198
|
+
controller.showArtifacts();
|
|
5199
|
+
}
|
|
5200
|
+
};
|
|
5201
|
+
const handleHideArtifacts = (e: Event) => {
|
|
5202
|
+
const detail = (e as CustomEvent).detail;
|
|
5203
|
+
if (!detail?.instanceId || detail.instanceId === instanceId) {
|
|
5204
|
+
controller.hideArtifacts();
|
|
5205
|
+
}
|
|
5206
|
+
};
|
|
5207
|
+
const handleUpsertArtifact = (e: Event) => {
|
|
5208
|
+
const detail = (e as CustomEvent).detail;
|
|
5209
|
+
if (detail?.instanceId && detail.instanceId !== instanceId) return;
|
|
5210
|
+
if (detail?.artifact) {
|
|
5211
|
+
controller.upsertArtifact(detail.artifact as PersonaArtifactManualUpsert);
|
|
5212
|
+
}
|
|
5213
|
+
};
|
|
5214
|
+
const handleSelectArtifact = (e: Event) => {
|
|
5215
|
+
const detail = (e as CustomEvent).detail;
|
|
5216
|
+
if (detail?.instanceId && detail.instanceId !== instanceId) return;
|
|
5217
|
+
if (typeof detail?.id === "string") {
|
|
5218
|
+
controller.selectArtifact(detail.id);
|
|
5219
|
+
}
|
|
5220
|
+
};
|
|
5221
|
+
const handleClearArtifacts = (e: Event) => {
|
|
5222
|
+
const detail = (e as CustomEvent).detail;
|
|
5223
|
+
if (!detail?.instanceId || detail.instanceId === instanceId) {
|
|
5224
|
+
controller.clearArtifacts();
|
|
5225
|
+
}
|
|
5226
|
+
};
|
|
5227
|
+
window.addEventListener("persona:showArtifacts", handleShowArtifacts);
|
|
5228
|
+
window.addEventListener("persona:hideArtifacts", handleHideArtifacts);
|
|
5229
|
+
window.addEventListener("persona:upsertArtifact", handleUpsertArtifact);
|
|
5230
|
+
window.addEventListener("persona:selectArtifact", handleSelectArtifact);
|
|
5231
|
+
window.addEventListener("persona:clearArtifacts", handleClearArtifacts);
|
|
5232
|
+
destroyCallbacks.push(() => {
|
|
5233
|
+
window.removeEventListener("persona:showArtifacts", handleShowArtifacts);
|
|
5234
|
+
window.removeEventListener("persona:hideArtifacts", handleHideArtifacts);
|
|
5235
|
+
window.removeEventListener("persona:upsertArtifact", handleUpsertArtifact);
|
|
5236
|
+
window.removeEventListener("persona:selectArtifact", handleSelectArtifact);
|
|
5237
|
+
window.removeEventListener("persona:clearArtifacts", handleClearArtifacts);
|
|
5238
|
+
});
|
|
4526
5239
|
}
|
|
4527
5240
|
|
|
4528
5241
|
// ============================================================================
|