vanilla-agent 1.28.0 → 1.30.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/dist/index.cjs +24 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +46 -46
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +24 -24
- package/dist/index.js.map +1 -1
- package/dist/widget.css +21 -16
- package/package.json +2 -1
- package/src/@types/idiomorph.d.ts +37 -0
- package/src/components/message-bubble.ts +6 -0
- package/src/components/reasoning-bubble.ts +3 -0
- package/src/components/tool-bubble.ts +3 -0
- package/src/styles/tailwind.css +2 -1
- package/src/styles/widget.css +21 -16
- package/src/ui.ts +137 -26
- package/src/utils/morph.ts +36 -0
package/dist/widget.css
CHANGED
|
@@ -86,7 +86,8 @@
|
|
|
86
86
|
gap: 1.5rem;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
/* Widget CSS Variables - scoped to widget root to avoid polluting global namespace */
|
|
90
|
+
#vanilla-agent-root {
|
|
90
91
|
--cw-radius-sm: 0.75rem;
|
|
91
92
|
--cw-radius-md: 1rem;
|
|
92
93
|
--cw-radius-lg: 1.5rem;
|
|
@@ -119,19 +120,19 @@
|
|
|
119
120
|
--cw-md-h6-margin: 0.5rem 0 0.25rem;
|
|
120
121
|
--cw-md-h6-line-height: 1.5;
|
|
121
122
|
|
|
122
|
-
/* Markdown Table Variables
|
|
123
|
+
/* Markdown Table Variables */
|
|
123
124
|
--cw-md-table-border-color: #e5e7eb;
|
|
124
125
|
--cw-md-table-header-bg: #f8fafc;
|
|
125
126
|
--cw-md-table-header-weight: 600;
|
|
126
127
|
--cw-md-table-cell-padding: 0.5rem 0.75rem;
|
|
127
128
|
--cw-md-table-border-radius: 0.375rem;
|
|
128
129
|
|
|
129
|
-
/* Markdown Horizontal Rule Variables
|
|
130
|
+
/* Markdown Horizontal Rule Variables */
|
|
130
131
|
--cw-md-hr-color: #e5e7eb;
|
|
131
132
|
--cw-md-hr-height: 1px;
|
|
132
133
|
--cw-md-hr-margin: 1rem 0;
|
|
133
134
|
|
|
134
|
-
/* Markdown Blockquote Variables
|
|
135
|
+
/* Markdown Blockquote Variables */
|
|
135
136
|
--cw-md-blockquote-border-color: #3b82f6;
|
|
136
137
|
--cw-md-blockquote-border-width: 3px;
|
|
137
138
|
--cw-md-blockquote-padding: 0.5rem 1rem;
|
|
@@ -140,7 +141,7 @@
|
|
|
140
141
|
--cw-md-blockquote-text-color: #6b7280;
|
|
141
142
|
--cw-md-blockquote-font-style: italic;
|
|
142
143
|
|
|
143
|
-
/* Markdown Code Block Variables
|
|
144
|
+
/* Markdown Code Block Variables */
|
|
144
145
|
--cw-md-code-block-bg: #f3f4f6;
|
|
145
146
|
--cw-md-code-block-border-color: #e5e7eb;
|
|
146
147
|
--cw-md-code-block-text-color: inherit;
|
|
@@ -148,7 +149,7 @@
|
|
|
148
149
|
--cw-md-code-block-border-radius: 0.375rem;
|
|
149
150
|
--cw-md-code-block-font-size: 0.875rem;
|
|
150
151
|
|
|
151
|
-
/* Markdown Inline Code Variables
|
|
152
|
+
/* Markdown Inline Code Variables */
|
|
152
153
|
--cw-md-inline-code-bg: #f3f4f6;
|
|
153
154
|
--cw-md-inline-code-padding: 0.125rem 0.375rem;
|
|
154
155
|
--cw-md-inline-code-border-radius: 0.25rem;
|
|
@@ -677,7 +678,7 @@
|
|
|
677
678
|
}
|
|
678
679
|
|
|
679
680
|
/* Ensure textarea in composer form has no border on focus */
|
|
680
|
-
textarea:focus {
|
|
681
|
+
.tvw-widget-composer textarea:focus {
|
|
681
682
|
border: none !important;
|
|
682
683
|
outline: none !important;
|
|
683
684
|
border-width: 0 !important;
|
|
@@ -687,11 +688,11 @@ textarea:focus {
|
|
|
687
688
|
}
|
|
688
689
|
|
|
689
690
|
/* Prevent form container from showing focus styles */
|
|
690
|
-
|
|
691
|
+
.tvw-widget-composer:focus-within {
|
|
691
692
|
outline: none !important;
|
|
692
693
|
}
|
|
693
694
|
|
|
694
|
-
|
|
695
|
+
.tvw-widget-composer:focus-within textarea {
|
|
695
696
|
border: none !important;
|
|
696
697
|
outline: none !important;
|
|
697
698
|
}
|
|
@@ -804,7 +805,7 @@ form:focus-within textarea {
|
|
|
804
805
|
}
|
|
805
806
|
|
|
806
807
|
/* Typing indicator animation */
|
|
807
|
-
@keyframes typing {
|
|
808
|
+
@keyframes tvw-typing {
|
|
808
809
|
0%, 100% {
|
|
809
810
|
opacity: 0.5;
|
|
810
811
|
transform: translateY(0);
|
|
@@ -816,7 +817,7 @@ form:focus-within textarea {
|
|
|
816
817
|
}
|
|
817
818
|
|
|
818
819
|
.tvw-animate-typing {
|
|
819
|
-
animation: typing 1s infinite;
|
|
820
|
+
animation: tvw-typing 1s infinite;
|
|
820
821
|
}
|
|
821
822
|
|
|
822
823
|
.tvw-space-x-1 > * + * {
|
|
@@ -848,7 +849,7 @@ form:focus-within textarea {
|
|
|
848
849
|
}
|
|
849
850
|
|
|
850
851
|
/* Voice recognition recording animation */
|
|
851
|
-
@keyframes voice-recording-pulse {
|
|
852
|
+
@keyframes tvw-voice-recording-pulse {
|
|
852
853
|
0%, 100% {
|
|
853
854
|
opacity: 1;
|
|
854
855
|
transform: scale(1);
|
|
@@ -860,11 +861,11 @@ form:focus-within textarea {
|
|
|
860
861
|
}
|
|
861
862
|
|
|
862
863
|
.tvw-voice-recording {
|
|
863
|
-
animation: voice-recording-pulse 1.5s ease-in-out infinite;
|
|
864
|
+
animation: tvw-voice-recording-pulse 1.5s ease-in-out infinite;
|
|
864
865
|
}
|
|
865
866
|
|
|
866
867
|
.tvw-voice-recording svg {
|
|
867
|
-
animation: voice-recording-pulse 1.5s ease-in-out infinite;
|
|
868
|
+
animation: tvw-voice-recording-pulse 1.5s ease-in-out infinite;
|
|
868
869
|
}
|
|
869
870
|
|
|
870
871
|
/* Markdown content overflow handling */
|
|
@@ -1239,8 +1240,12 @@ form:focus-within textarea {
|
|
|
1239
1240
|
margin-top: 0.5rem;
|
|
1240
1241
|
padding-top: 0.5rem;
|
|
1241
1242
|
border-top: 1px solid var(--cw-divider, #f1f5f9);
|
|
1242
|
-
|
|
1243
|
-
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/* Fade in animation only for "always" visibility mode (not hover) */
|
|
1246
|
+
/* forwards ensures final state is kept, idiomorph preserves element so animation only plays once */
|
|
1247
|
+
.tvw-message-actions:not(.tvw-message-actions-hover) {
|
|
1248
|
+
animation: tvw-message-actions-fade-in 0.3s ease-out forwards;
|
|
1244
1249
|
}
|
|
1245
1250
|
|
|
1246
1251
|
/* Action bar alignment */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "Themeable, plugable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"idiomorph": "^0.7.4",
|
|
26
27
|
"lucide": "^0.552.0",
|
|
27
28
|
"marked": "^12.0.2",
|
|
28
29
|
"partial-json": "^0.1.7",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
declare module "idiomorph" {
|
|
2
|
+
export interface IdiomorphCallbacks {
|
|
3
|
+
beforeNodeAdded?: (node: Node) => boolean | void;
|
|
4
|
+
afterNodeAdded?: (node: Node) => void;
|
|
5
|
+
beforeNodeMorphed?: (oldNode: Node, newNode: Node) => boolean | void;
|
|
6
|
+
afterNodeMorphed?: (oldNode: Node, newNode: Node) => void;
|
|
7
|
+
beforeNodeRemoved?: (node: Node) => boolean | void;
|
|
8
|
+
afterNodeRemoved?: (node: Node) => void;
|
|
9
|
+
beforeAttributeUpdated?: (
|
|
10
|
+
attributeName: string,
|
|
11
|
+
node: Node,
|
|
12
|
+
mutationType: "update" | "remove"
|
|
13
|
+
) => boolean | void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IdiomorphOptions {
|
|
17
|
+
morphStyle?: "outerHTML" | "innerHTML";
|
|
18
|
+
ignoreActive?: boolean;
|
|
19
|
+
ignoreActiveValue?: boolean;
|
|
20
|
+
restoreFocus?: boolean;
|
|
21
|
+
callbacks?: IdiomorphCallbacks;
|
|
22
|
+
head?: {
|
|
23
|
+
style?: "merge" | "append" | "morph" | "none";
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IdiomorphStatic {
|
|
28
|
+
morph(
|
|
29
|
+
oldNode: Element | Document,
|
|
30
|
+
newContent: Element | Node | string,
|
|
31
|
+
options?: IdiomorphOptions
|
|
32
|
+
): void;
|
|
33
|
+
defaults: IdiomorphOptions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const Idiomorph: IdiomorphStatic;
|
|
37
|
+
}
|
|
@@ -246,6 +246,9 @@ export const createMessageActions = (
|
|
|
246
246
|
visibility === "hover" ? "tvw-message-actions-hover" : ""
|
|
247
247
|
}`
|
|
248
248
|
);
|
|
249
|
+
// Set id for idiomorph matching (prevents recreation on morph)
|
|
250
|
+
container.id = `actions-${message.id}`;
|
|
251
|
+
container.setAttribute("data-actions-for", message.id);
|
|
249
252
|
|
|
250
253
|
// Track vote state for this message
|
|
251
254
|
let currentVote: "upvote" | "downvote" | null = null;
|
|
@@ -414,6 +417,9 @@ export const createStandardBubble = (
|
|
|
414
417
|
// Create the bubble element
|
|
415
418
|
const classes = getBubbleClasses(message.role, layout);
|
|
416
419
|
const bubble = createElement("div", classes.join(" "));
|
|
420
|
+
// Set id for idiomorph matching
|
|
421
|
+
bubble.id = `bubble-${message.id}`;
|
|
422
|
+
bubble.setAttribute("data-message-id", message.id);
|
|
417
423
|
|
|
418
424
|
// Add message content
|
|
419
425
|
const contentDiv = document.createElement("div");
|
|
@@ -26,6 +26,9 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
26
26
|
"tvw-py-0"
|
|
27
27
|
].join(" ")
|
|
28
28
|
);
|
|
29
|
+
// Set id for idiomorph matching
|
|
30
|
+
bubble.id = `bubble-${message.id}`;
|
|
31
|
+
bubble.setAttribute("data-message-id", message.id);
|
|
29
32
|
|
|
30
33
|
if (!reasoning) {
|
|
31
34
|
return bubble;
|
|
@@ -28,6 +28,9 @@ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidg
|
|
|
28
28
|
"tvw-py-0"
|
|
29
29
|
].join(" ")
|
|
30
30
|
);
|
|
31
|
+
// Set id for idiomorph matching
|
|
32
|
+
bubble.id = `bubble-${message.id}`;
|
|
33
|
+
bubble.setAttribute("data-message-id", message.id);
|
|
31
34
|
|
|
32
35
|
// Apply bubble-level styles
|
|
33
36
|
if (toolCallConfig.backgroundColor) {
|
package/src/styles/tailwind.css
CHANGED
package/src/styles/widget.css
CHANGED
|
@@ -86,7 +86,8 @@
|
|
|
86
86
|
gap: 1.5rem;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
/* Widget CSS Variables - scoped to widget root to avoid polluting global namespace */
|
|
90
|
+
#vanilla-agent-root {
|
|
90
91
|
--cw-radius-sm: 0.75rem;
|
|
91
92
|
--cw-radius-md: 1rem;
|
|
92
93
|
--cw-radius-lg: 1.5rem;
|
|
@@ -119,19 +120,19 @@
|
|
|
119
120
|
--cw-md-h6-margin: 0.5rem 0 0.25rem;
|
|
120
121
|
--cw-md-h6-line-height: 1.5;
|
|
121
122
|
|
|
122
|
-
/* Markdown Table Variables
|
|
123
|
+
/* Markdown Table Variables */
|
|
123
124
|
--cw-md-table-border-color: #e5e7eb;
|
|
124
125
|
--cw-md-table-header-bg: #f8fafc;
|
|
125
126
|
--cw-md-table-header-weight: 600;
|
|
126
127
|
--cw-md-table-cell-padding: 0.5rem 0.75rem;
|
|
127
128
|
--cw-md-table-border-radius: 0.375rem;
|
|
128
129
|
|
|
129
|
-
/* Markdown Horizontal Rule Variables
|
|
130
|
+
/* Markdown Horizontal Rule Variables */
|
|
130
131
|
--cw-md-hr-color: #e5e7eb;
|
|
131
132
|
--cw-md-hr-height: 1px;
|
|
132
133
|
--cw-md-hr-margin: 1rem 0;
|
|
133
134
|
|
|
134
|
-
/* Markdown Blockquote Variables
|
|
135
|
+
/* Markdown Blockquote Variables */
|
|
135
136
|
--cw-md-blockquote-border-color: #3b82f6;
|
|
136
137
|
--cw-md-blockquote-border-width: 3px;
|
|
137
138
|
--cw-md-blockquote-padding: 0.5rem 1rem;
|
|
@@ -140,7 +141,7 @@
|
|
|
140
141
|
--cw-md-blockquote-text-color: #6b7280;
|
|
141
142
|
--cw-md-blockquote-font-style: italic;
|
|
142
143
|
|
|
143
|
-
/* Markdown Code Block Variables
|
|
144
|
+
/* Markdown Code Block Variables */
|
|
144
145
|
--cw-md-code-block-bg: #f3f4f6;
|
|
145
146
|
--cw-md-code-block-border-color: #e5e7eb;
|
|
146
147
|
--cw-md-code-block-text-color: inherit;
|
|
@@ -148,7 +149,7 @@
|
|
|
148
149
|
--cw-md-code-block-border-radius: 0.375rem;
|
|
149
150
|
--cw-md-code-block-font-size: 0.875rem;
|
|
150
151
|
|
|
151
|
-
/* Markdown Inline Code Variables
|
|
152
|
+
/* Markdown Inline Code Variables */
|
|
152
153
|
--cw-md-inline-code-bg: #f3f4f6;
|
|
153
154
|
--cw-md-inline-code-padding: 0.125rem 0.375rem;
|
|
154
155
|
--cw-md-inline-code-border-radius: 0.25rem;
|
|
@@ -677,7 +678,7 @@
|
|
|
677
678
|
}
|
|
678
679
|
|
|
679
680
|
/* Ensure textarea in composer form has no border on focus */
|
|
680
|
-
textarea:focus {
|
|
681
|
+
.tvw-widget-composer textarea:focus {
|
|
681
682
|
border: none !important;
|
|
682
683
|
outline: none !important;
|
|
683
684
|
border-width: 0 !important;
|
|
@@ -687,11 +688,11 @@ textarea:focus {
|
|
|
687
688
|
}
|
|
688
689
|
|
|
689
690
|
/* Prevent form container from showing focus styles */
|
|
690
|
-
|
|
691
|
+
.tvw-widget-composer:focus-within {
|
|
691
692
|
outline: none !important;
|
|
692
693
|
}
|
|
693
694
|
|
|
694
|
-
|
|
695
|
+
.tvw-widget-composer:focus-within textarea {
|
|
695
696
|
border: none !important;
|
|
696
697
|
outline: none !important;
|
|
697
698
|
}
|
|
@@ -804,7 +805,7 @@ form:focus-within textarea {
|
|
|
804
805
|
}
|
|
805
806
|
|
|
806
807
|
/* Typing indicator animation */
|
|
807
|
-
@keyframes typing {
|
|
808
|
+
@keyframes tvw-typing {
|
|
808
809
|
0%, 100% {
|
|
809
810
|
opacity: 0.5;
|
|
810
811
|
transform: translateY(0);
|
|
@@ -816,7 +817,7 @@ form:focus-within textarea {
|
|
|
816
817
|
}
|
|
817
818
|
|
|
818
819
|
.tvw-animate-typing {
|
|
819
|
-
animation: typing 1s infinite;
|
|
820
|
+
animation: tvw-typing 1s infinite;
|
|
820
821
|
}
|
|
821
822
|
|
|
822
823
|
.tvw-space-x-1 > * + * {
|
|
@@ -848,7 +849,7 @@ form:focus-within textarea {
|
|
|
848
849
|
}
|
|
849
850
|
|
|
850
851
|
/* Voice recognition recording animation */
|
|
851
|
-
@keyframes voice-recording-pulse {
|
|
852
|
+
@keyframes tvw-voice-recording-pulse {
|
|
852
853
|
0%, 100% {
|
|
853
854
|
opacity: 1;
|
|
854
855
|
transform: scale(1);
|
|
@@ -860,11 +861,11 @@ form:focus-within textarea {
|
|
|
860
861
|
}
|
|
861
862
|
|
|
862
863
|
.tvw-voice-recording {
|
|
863
|
-
animation: voice-recording-pulse 1.5s ease-in-out infinite;
|
|
864
|
+
animation: tvw-voice-recording-pulse 1.5s ease-in-out infinite;
|
|
864
865
|
}
|
|
865
866
|
|
|
866
867
|
.tvw-voice-recording svg {
|
|
867
|
-
animation: voice-recording-pulse 1.5s ease-in-out infinite;
|
|
868
|
+
animation: tvw-voice-recording-pulse 1.5s ease-in-out infinite;
|
|
868
869
|
}
|
|
869
870
|
|
|
870
871
|
/* Markdown content overflow handling */
|
|
@@ -1239,8 +1240,12 @@ form:focus-within textarea {
|
|
|
1239
1240
|
margin-top: 0.5rem;
|
|
1240
1241
|
padding-top: 0.5rem;
|
|
1241
1242
|
border-top: 1px solid var(--cw-divider, #f1f5f9);
|
|
1242
|
-
|
|
1243
|
-
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/* Fade in animation only for "always" visibility mode (not hover) */
|
|
1246
|
+
/* forwards ensures final state is kept, idiomorph preserves element so animation only plays once */
|
|
1247
|
+
.tvw-message-actions:not(.tvw-message-actions-hover) {
|
|
1248
|
+
animation: tvw-message-actions-fade-in 0.3s ease-out forwards;
|
|
1244
1249
|
}
|
|
1245
1250
|
|
|
1246
1251
|
/* Action bar alignment */
|
package/src/ui.ts
CHANGED
|
@@ -17,9 +17,11 @@ import {
|
|
|
17
17
|
import { applyThemeVariables, createThemeObserver } from "./utils/theme";
|
|
18
18
|
import { renderLucideIcon } from "./utils/icons";
|
|
19
19
|
import { createElement } from "./utils/dom";
|
|
20
|
+
import { morphMessages } from "./utils/morph";
|
|
20
21
|
import { statusCopy } from "./utils/constants";
|
|
21
22
|
import { createLauncherButton } from "./components/launcher";
|
|
22
23
|
import { createWrapper, buildPanel, buildHeader, buildComposer, attachHeaderToContainer } from "./components/panel";
|
|
24
|
+
import { buildHeaderWithLayout } from "./components/header-layouts";
|
|
23
25
|
import { positionMap } from "./utils/positioning";
|
|
24
26
|
import type { HeaderElements, ComposerElements } from "./components/panel";
|
|
25
27
|
import { MessageTransform, MessageActionCallbacks } from "./components/message-bubble";
|
|
@@ -235,6 +237,7 @@ export const createAgentExperience = (
|
|
|
235
237
|
let autoExpand = config.launcher?.autoExpand ?? false;
|
|
236
238
|
let prevAutoExpand = autoExpand;
|
|
237
239
|
let prevLauncherEnabled = launcherEnabled;
|
|
240
|
+
let prevHeaderLayout = config.layout?.header?.layout;
|
|
238
241
|
let open = launcherEnabled ? autoExpand : true;
|
|
239
242
|
let postprocess = buildPostprocessor(config, actionManager);
|
|
240
243
|
let showReasoning = config.features?.showReasoning ?? true;
|
|
@@ -904,8 +907,8 @@ export const createAgentExperience = (
|
|
|
904
907
|
messages: AgentWidgetMessage[],
|
|
905
908
|
transform: MessageTransform
|
|
906
909
|
) => {
|
|
907
|
-
container
|
|
908
|
-
const
|
|
910
|
+
// Build new content in a temporary container for morphing
|
|
911
|
+
const tempContainer = document.createElement("div");
|
|
909
912
|
|
|
910
913
|
messages.forEach((message) => {
|
|
911
914
|
let bubble: HTMLElement | null = null;
|
|
@@ -976,8 +979,8 @@ export const createAgentExperience = (
|
|
|
976
979
|
});
|
|
977
980
|
if (componentBubble) {
|
|
978
981
|
// Wrap component in standard bubble styling
|
|
979
|
-
const
|
|
980
|
-
|
|
982
|
+
const componentWrapper = document.createElement("div");
|
|
983
|
+
componentWrapper.className = [
|
|
981
984
|
"vanilla-message-bubble",
|
|
982
985
|
"tvw-max-w-[85%]",
|
|
983
986
|
"tvw-rounded-2xl",
|
|
@@ -986,7 +989,9 @@ export const createAgentExperience = (
|
|
|
986
989
|
"tvw-border-cw-message-border",
|
|
987
990
|
"tvw-p-4"
|
|
988
991
|
].join(" ");
|
|
989
|
-
|
|
992
|
+
// Set id for idiomorph matching
|
|
993
|
+
componentWrapper.id = `bubble-${message.id}`;
|
|
994
|
+
componentWrapper.setAttribute("data-message-id", message.id);
|
|
990
995
|
|
|
991
996
|
// Add text content above component if present (combined text+component response)
|
|
992
997
|
if (message.content && message.content.trim()) {
|
|
@@ -998,11 +1003,11 @@ export const createAgentExperience = (
|
|
|
998
1003
|
streaming: Boolean(message.streaming),
|
|
999
1004
|
raw: message.rawContent
|
|
1000
1005
|
});
|
|
1001
|
-
|
|
1006
|
+
componentWrapper.appendChild(textDiv);
|
|
1002
1007
|
}
|
|
1003
1008
|
|
|
1004
|
-
|
|
1005
|
-
bubble =
|
|
1009
|
+
componentWrapper.appendChild(componentBubble);
|
|
1010
|
+
bubble = componentWrapper;
|
|
1006
1011
|
}
|
|
1007
1012
|
}
|
|
1008
1013
|
}
|
|
@@ -1048,11 +1053,14 @@ export const createAgentExperience = (
|
|
|
1048
1053
|
|
|
1049
1054
|
const wrapper = document.createElement("div");
|
|
1050
1055
|
wrapper.className = "tvw-flex";
|
|
1056
|
+
// Set id for idiomorph matching
|
|
1057
|
+
wrapper.id = `wrapper-${message.id}`;
|
|
1058
|
+
wrapper.setAttribute("data-wrapper-id", message.id);
|
|
1051
1059
|
if (message.role === "user") {
|
|
1052
1060
|
wrapper.classList.add("tvw-justify-end");
|
|
1053
1061
|
}
|
|
1054
1062
|
wrapper.appendChild(bubble);
|
|
1055
|
-
|
|
1063
|
+
tempContainer.appendChild(wrapper);
|
|
1056
1064
|
});
|
|
1057
1065
|
|
|
1058
1066
|
// Add standalone typing indicator only if streaming but no assistant message is streaming yet
|
|
@@ -1085,16 +1093,21 @@ export const createAgentExperience = (
|
|
|
1085
1093
|
"tvw-px-5",
|
|
1086
1094
|
"tvw-py-3"
|
|
1087
1095
|
].join(" ");
|
|
1096
|
+
typingBubble.setAttribute("data-typing-indicator", "true");
|
|
1088
1097
|
|
|
1089
1098
|
typingBubble.appendChild(typingIndicator);
|
|
1090
1099
|
|
|
1091
1100
|
const typingWrapper = document.createElement("div");
|
|
1092
1101
|
typingWrapper.className = "tvw-flex";
|
|
1102
|
+
// Set id for idiomorph matching
|
|
1103
|
+
typingWrapper.id = "wrapper-typing-indicator";
|
|
1104
|
+
typingWrapper.setAttribute("data-wrapper-id", "typing-indicator");
|
|
1093
1105
|
typingWrapper.appendChild(typingBubble);
|
|
1094
|
-
|
|
1106
|
+
tempContainer.appendChild(typingWrapper);
|
|
1095
1107
|
}
|
|
1096
1108
|
|
|
1097
|
-
container
|
|
1109
|
+
// Use idiomorph to morph the container contents
|
|
1110
|
+
morphMessages(container, tempContainer);
|
|
1098
1111
|
// Defer scroll to next frame for smoother animation and to prevent jolt
|
|
1099
1112
|
// This allows the browser to update layout (e.g., typing indicator removal) before scrolling
|
|
1100
1113
|
// Use double RAF to ensure layout has fully settled before starting scroll animation
|
|
@@ -1902,6 +1915,66 @@ export const createAgentExperience = (
|
|
|
1902
1915
|
headerSubtitle.textContent = config.launcher.subtitle;
|
|
1903
1916
|
}
|
|
1904
1917
|
|
|
1918
|
+
// Update header layout if it changed
|
|
1919
|
+
const headerLayoutConfig = config.layout?.header;
|
|
1920
|
+
const headerLayoutChanged = headerLayoutConfig?.layout !== prevHeaderLayout;
|
|
1921
|
+
|
|
1922
|
+
if (headerLayoutChanged && header) {
|
|
1923
|
+
// Rebuild header with new layout
|
|
1924
|
+
const newHeaderElements = headerLayoutConfig
|
|
1925
|
+
? buildHeaderWithLayout(config, headerLayoutConfig, {
|
|
1926
|
+
showClose: launcherEnabled,
|
|
1927
|
+
onClose: () => setOpenState(false, "user")
|
|
1928
|
+
})
|
|
1929
|
+
: buildHeader({
|
|
1930
|
+
config,
|
|
1931
|
+
showClose: launcherEnabled,
|
|
1932
|
+
onClose: () => setOpenState(false, "user")
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
// Replace the old header with the new one
|
|
1936
|
+
header.replaceWith(newHeaderElements.header);
|
|
1937
|
+
|
|
1938
|
+
// Update references
|
|
1939
|
+
header = newHeaderElements.header;
|
|
1940
|
+
iconHolder = newHeaderElements.iconHolder;
|
|
1941
|
+
headerTitle = newHeaderElements.headerTitle;
|
|
1942
|
+
headerSubtitle = newHeaderElements.headerSubtitle;
|
|
1943
|
+
closeButton = newHeaderElements.closeButton;
|
|
1944
|
+
|
|
1945
|
+
prevHeaderLayout = headerLayoutConfig?.layout;
|
|
1946
|
+
} else if (headerLayoutConfig) {
|
|
1947
|
+
// Apply visibility settings without rebuilding
|
|
1948
|
+
if (iconHolder) {
|
|
1949
|
+
iconHolder.style.display = headerLayoutConfig.showIcon === false ? "none" : "";
|
|
1950
|
+
}
|
|
1951
|
+
if (headerTitle) {
|
|
1952
|
+
headerTitle.style.display = headerLayoutConfig.showTitle === false ? "none" : "";
|
|
1953
|
+
}
|
|
1954
|
+
if (headerSubtitle) {
|
|
1955
|
+
headerSubtitle.style.display = headerLayoutConfig.showSubtitle === false ? "none" : "";
|
|
1956
|
+
}
|
|
1957
|
+
if (closeButton) {
|
|
1958
|
+
closeButton.style.display = headerLayoutConfig.showCloseButton === false ? "none" : "";
|
|
1959
|
+
}
|
|
1960
|
+
if (panelElements.clearChatButtonWrapper) {
|
|
1961
|
+
// showClearChat explicitly controls visibility when set
|
|
1962
|
+
const showClearChat = headerLayoutConfig.showClearChat;
|
|
1963
|
+
if (showClearChat !== undefined) {
|
|
1964
|
+
panelElements.clearChatButtonWrapper.style.display = showClearChat ? "" : "none";
|
|
1965
|
+
// When clear chat is hidden, close button needs ml-auto to stay right-aligned
|
|
1966
|
+
const { closeButtonWrapper } = panelElements;
|
|
1967
|
+
if (closeButtonWrapper && !closeButtonWrapper.classList.contains("tvw-absolute")) {
|
|
1968
|
+
if (showClearChat) {
|
|
1969
|
+
closeButtonWrapper.classList.remove("tvw-ml-auto");
|
|
1970
|
+
} else {
|
|
1971
|
+
closeButtonWrapper.classList.add("tvw-ml-auto");
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1905
1978
|
// Only update open state if launcher enabled state changed or autoExpand value changed
|
|
1906
1979
|
const launcherEnabledChanged = launcherEnabled !== prevLauncherEnabled;
|
|
1907
1980
|
const autoExpandChanged = autoExpand !== prevAutoExpand;
|
|
@@ -1937,20 +2010,23 @@ export const createAgentExperience = (
|
|
|
1937
2010
|
// Update panel icon sizes
|
|
1938
2011
|
const launcher = config.launcher ?? {};
|
|
1939
2012
|
const headerIconHidden = launcher.headerIconHidden ?? false;
|
|
2013
|
+
const layoutShowIcon = config.layout?.header?.showIcon;
|
|
2014
|
+
// Hide icon if either headerIconHidden is true OR layout.header.showIcon is false
|
|
2015
|
+
const shouldHideIcon = headerIconHidden || layoutShowIcon === false;
|
|
1940
2016
|
const headerIconName = launcher.headerIconName;
|
|
1941
2017
|
const headerIconSize = launcher.headerIconSize ?? "48px";
|
|
1942
|
-
|
|
2018
|
+
|
|
1943
2019
|
if (iconHolder) {
|
|
1944
|
-
const
|
|
1945
|
-
const headerCopy =
|
|
1946
|
-
|
|
2020
|
+
const headerEl = container.querySelector(".tvw-border-b-cw-divider");
|
|
2021
|
+
const headerCopy = headerEl?.querySelector(".tvw-flex-col");
|
|
2022
|
+
|
|
1947
2023
|
// Handle hide/show
|
|
1948
|
-
if (
|
|
2024
|
+
if (shouldHideIcon) {
|
|
1949
2025
|
// Hide iconHolder
|
|
1950
2026
|
iconHolder.style.display = "none";
|
|
1951
2027
|
// Ensure headerCopy is still in header
|
|
1952
|
-
if (
|
|
1953
|
-
|
|
2028
|
+
if (headerEl && headerCopy && !headerEl.contains(headerCopy)) {
|
|
2029
|
+
headerEl.insertBefore(headerCopy, headerEl.firstChild);
|
|
1954
2030
|
}
|
|
1955
2031
|
} else {
|
|
1956
2032
|
// Show iconHolder
|
|
@@ -1959,13 +2035,13 @@ export const createAgentExperience = (
|
|
|
1959
2035
|
iconHolder.style.width = headerIconSize;
|
|
1960
2036
|
|
|
1961
2037
|
// Ensure iconHolder is before headerCopy in header
|
|
1962
|
-
if (
|
|
1963
|
-
if (!
|
|
1964
|
-
|
|
2038
|
+
if (headerEl && headerCopy) {
|
|
2039
|
+
if (!headerEl.contains(iconHolder)) {
|
|
2040
|
+
headerEl.insertBefore(iconHolder, headerCopy);
|
|
1965
2041
|
} else if (iconHolder.nextSibling !== headerCopy) {
|
|
1966
2042
|
// Reorder if needed
|
|
1967
2043
|
iconHolder.remove();
|
|
1968
|
-
|
|
2044
|
+
headerEl.insertBefore(iconHolder, headerCopy);
|
|
1969
2045
|
}
|
|
1970
2046
|
}
|
|
1971
2047
|
|
|
@@ -2015,7 +2091,26 @@ export const createAgentExperience = (
|
|
|
2015
2091
|
}
|
|
2016
2092
|
}
|
|
2017
2093
|
}
|
|
2094
|
+
|
|
2095
|
+
// Handle title/subtitle visibility from layout config
|
|
2096
|
+
const layoutShowTitle = config.layout?.header?.showTitle;
|
|
2097
|
+
const layoutShowSubtitle = config.layout?.header?.showSubtitle;
|
|
2098
|
+
if (headerTitle) {
|
|
2099
|
+
headerTitle.style.display = layoutShowTitle === false ? "none" : "";
|
|
2100
|
+
}
|
|
2101
|
+
if (headerSubtitle) {
|
|
2102
|
+
headerSubtitle.style.display = layoutShowSubtitle === false ? "none" : "";
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2018
2105
|
if (closeButton) {
|
|
2106
|
+
// Handle close button visibility from layout config
|
|
2107
|
+
const layoutShowCloseButton = config.layout?.header?.showCloseButton;
|
|
2108
|
+
if (layoutShowCloseButton === false) {
|
|
2109
|
+
closeButton.style.display = "none";
|
|
2110
|
+
} else {
|
|
2111
|
+
closeButton.style.display = "";
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2019
2114
|
const closeButtonSize = launcher.closeButtonSize ?? "32px";
|
|
2020
2115
|
const closeButtonPlacement = launcher.closeButtonPlacement ?? "inline";
|
|
2021
2116
|
closeButton.style.height = closeButtonSize;
|
|
@@ -2189,17 +2284,33 @@ export const createAgentExperience = (
|
|
|
2189
2284
|
if (clearChatButton) {
|
|
2190
2285
|
const clearChatConfig = launcher.clearChat ?? {};
|
|
2191
2286
|
const clearChatEnabled = clearChatConfig.enabled ?? true;
|
|
2287
|
+
const layoutShowClearChat = config.layout?.header?.showClearChat;
|
|
2288
|
+
// layout.header.showClearChat takes precedence if explicitly set
|
|
2289
|
+
// Otherwise fall back to launcher.clearChat.enabled
|
|
2290
|
+
const shouldShowClearChat = layoutShowClearChat !== undefined
|
|
2291
|
+
? layoutShowClearChat
|
|
2292
|
+
: clearChatEnabled;
|
|
2192
2293
|
const clearChatPlacement = clearChatConfig.placement ?? "inline";
|
|
2193
2294
|
|
|
2194
|
-
// Show/hide button based on
|
|
2295
|
+
// Show/hide button based on layout config (primary) or launcher config (fallback)
|
|
2195
2296
|
if (clearChatButtonWrapper) {
|
|
2196
|
-
clearChatButtonWrapper.style.display =
|
|
2297
|
+
clearChatButtonWrapper.style.display = shouldShowClearChat ? "" : "none";
|
|
2298
|
+
|
|
2299
|
+
// When clear chat is hidden, close button needs ml-auto to stay right-aligned
|
|
2300
|
+
const { closeButtonWrapper } = panelElements;
|
|
2301
|
+
if (closeButtonWrapper && !closeButtonWrapper.classList.contains("tvw-absolute")) {
|
|
2302
|
+
if (shouldShowClearChat) {
|
|
2303
|
+
closeButtonWrapper.classList.remove("tvw-ml-auto");
|
|
2304
|
+
} else {
|
|
2305
|
+
closeButtonWrapper.classList.add("tvw-ml-auto");
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2197
2308
|
|
|
2198
2309
|
// Update placement if changed
|
|
2199
2310
|
const isTopRight = clearChatPlacement === "top-right";
|
|
2200
2311
|
const currentlyTopRight = clearChatButtonWrapper.classList.contains("tvw-absolute");
|
|
2201
2312
|
|
|
2202
|
-
if (isTopRight !== currentlyTopRight &&
|
|
2313
|
+
if (isTopRight !== currentlyTopRight && shouldShowClearChat) {
|
|
2203
2314
|
clearChatButtonWrapper.remove();
|
|
2204
2315
|
|
|
2205
2316
|
if (isTopRight) {
|
|
@@ -2239,7 +2350,7 @@ export const createAgentExperience = (
|
|
|
2239
2350
|
}
|
|
2240
2351
|
}
|
|
2241
2352
|
|
|
2242
|
-
if (
|
|
2353
|
+
if (shouldShowClearChat) {
|
|
2243
2354
|
// Update size
|
|
2244
2355
|
const clearChatSize = clearChatConfig.size ?? "32px";
|
|
2245
2356
|
clearChatButton.style.height = clearChatSize;
|