vanilla-agent 1.29.0 → 1.31.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 +23 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.global.js +46 -46
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/dist/widget.css +6 -2
- package/package.json +2 -1
- package/src/@types/idiomorph.d.ts +37 -0
- package/src/components/message-bubble.ts +6 -0
- package/src/components/panel.ts +19 -2
- package/src/components/reasoning-bubble.ts +33 -23
- package/src/components/tool-bubble.ts +34 -23
- package/src/styles/widget.css +6 -2
- package/src/types.ts +13 -0
- package/src/ui.ts +205 -28
- package/src/utils/morph.ts +36 -0
package/dist/widget.css
CHANGED
|
@@ -1240,8 +1240,12 @@
|
|
|
1240
1240
|
margin-top: 0.5rem;
|
|
1241
1241
|
padding-top: 0.5rem;
|
|
1242
1242
|
border-top: 1px solid var(--cw-divider, #f1f5f9);
|
|
1243
|
-
|
|
1244
|
-
|
|
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;
|
|
1245
1249
|
}
|
|
1246
1250
|
|
|
1247
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.31.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");
|
package/src/components/panel.ts
CHANGED
|
@@ -94,6 +94,7 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
|
|
|
94
94
|
|
|
95
95
|
// Build header using layout config if available, otherwise use standard builder
|
|
96
96
|
const headerLayoutConfig = config?.layout?.header;
|
|
97
|
+
const showHeader = config?.layout?.showHeader !== false; // default to true
|
|
97
98
|
const headerElements: HeaderElements = headerLayoutConfig
|
|
98
99
|
? buildHeaderWithLayout(config!, headerLayoutConfig, { showClose })
|
|
99
100
|
: buildHeader({ config, showClose });
|
|
@@ -132,10 +133,26 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
|
|
|
132
133
|
|
|
133
134
|
// Build composer/footer using extracted builder
|
|
134
135
|
const composerElements: ComposerElements = buildComposer({ config });
|
|
136
|
+
const showFooter = config?.layout?.showFooter !== false; // default to true
|
|
135
137
|
|
|
136
138
|
// Assemble container with header, body, and footer
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
if (showHeader) {
|
|
140
|
+
attachHeaderToContainer(container, headerElements, config);
|
|
141
|
+
} else {
|
|
142
|
+
// Hide header completely
|
|
143
|
+
headerElements.header.style.display = 'none';
|
|
144
|
+
attachHeaderToContainer(container, headerElements, config);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
container.append(body);
|
|
148
|
+
|
|
149
|
+
if (showFooter) {
|
|
150
|
+
container.append(composerElements.footer);
|
|
151
|
+
} else {
|
|
152
|
+
// Hide footer completely
|
|
153
|
+
composerElements.footer.style.display = 'none';
|
|
154
|
+
container.append(composerElements.footer);
|
|
155
|
+
}
|
|
139
156
|
|
|
140
157
|
return {
|
|
141
158
|
container,
|
|
@@ -4,7 +4,34 @@ import { describeReasonStatus } from "../utils/formatting";
|
|
|
4
4
|
import { renderLucideIcon } from "../utils/icons";
|
|
5
5
|
|
|
6
6
|
// Expansion state per widget instance
|
|
7
|
-
const reasoningExpansionState = new Set<string>();
|
|
7
|
+
export const reasoningExpansionState = new Set<string>();
|
|
8
|
+
|
|
9
|
+
// Helper function to update reasoning bubble UI after expansion state changes
|
|
10
|
+
export const updateReasoningBubbleUI = (messageId: string, bubble: HTMLElement): void => {
|
|
11
|
+
const expanded = reasoningExpansionState.has(messageId);
|
|
12
|
+
const header = bubble.querySelector('button[data-expand-header="true"]') as HTMLElement;
|
|
13
|
+
const content = bubble.querySelector('.tvw-border-t') as HTMLElement;
|
|
14
|
+
|
|
15
|
+
if (!header || !content) return;
|
|
16
|
+
|
|
17
|
+
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
18
|
+
|
|
19
|
+
// Find toggle icon container - it's the direct child div of headerMeta (which has tvw-ml-auto)
|
|
20
|
+
const headerMeta = header.querySelector('.tvw-ml-auto') as HTMLElement;
|
|
21
|
+
const toggleIcon = headerMeta?.querySelector(':scope > .tvw-flex.tvw-items-center') as HTMLElement;
|
|
22
|
+
if (toggleIcon) {
|
|
23
|
+
toggleIcon.innerHTML = "";
|
|
24
|
+
const iconColor = "currentColor";
|
|
25
|
+
const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
|
|
26
|
+
if (chevronIcon) {
|
|
27
|
+
toggleIcon.appendChild(chevronIcon);
|
|
28
|
+
} else {
|
|
29
|
+
toggleIcon.textContent = expanded ? "Hide" : "Show";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
content.style.display = expanded ? "" : "none";
|
|
34
|
+
};
|
|
8
35
|
|
|
9
36
|
export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
10
37
|
const reasoning = message.reasoning;
|
|
@@ -26,6 +53,9 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
26
53
|
"tvw-py-0"
|
|
27
54
|
].join(" ")
|
|
28
55
|
);
|
|
56
|
+
// Set id for idiomorph matching
|
|
57
|
+
bubble.id = `bubble-${message.id}`;
|
|
58
|
+
bubble.setAttribute("data-message-id", message.id);
|
|
29
59
|
|
|
30
60
|
if (!reasoning) {
|
|
31
61
|
return bubble;
|
|
@@ -38,6 +68,8 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
38
68
|
) as HTMLButtonElement;
|
|
39
69
|
header.type = "button";
|
|
40
70
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
71
|
+
header.setAttribute("data-expand-header", "true");
|
|
72
|
+
header.setAttribute("data-bubble-type", "reasoning");
|
|
41
73
|
|
|
42
74
|
const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
|
|
43
75
|
const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
|
|
@@ -102,28 +134,6 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
102
134
|
content.style.display = expanded ? "" : "none";
|
|
103
135
|
};
|
|
104
136
|
|
|
105
|
-
const toggleExpansion = () => {
|
|
106
|
-
expanded = !expanded;
|
|
107
|
-
if (expanded) {
|
|
108
|
-
reasoningExpansionState.add(message.id);
|
|
109
|
-
} else {
|
|
110
|
-
reasoningExpansionState.delete(message.id);
|
|
111
|
-
}
|
|
112
|
-
applyExpansionState();
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
header.addEventListener("pointerdown", (event) => {
|
|
116
|
-
event.preventDefault();
|
|
117
|
-
toggleExpansion();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
header.addEventListener("keydown", (event) => {
|
|
121
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
122
|
-
event.preventDefault();
|
|
123
|
-
toggleExpansion();
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
137
|
applyExpansionState();
|
|
128
138
|
|
|
129
139
|
bubble.append(header, content);
|
|
@@ -4,7 +4,35 @@ import { formatUnknownValue, describeToolTitle } from "../utils/formatting";
|
|
|
4
4
|
import { renderLucideIcon } from "../utils/icons";
|
|
5
5
|
|
|
6
6
|
// Expansion state per widget instance
|
|
7
|
-
const toolExpansionState = new Set<string>();
|
|
7
|
+
export const toolExpansionState = new Set<string>();
|
|
8
|
+
|
|
9
|
+
// Helper function to update tool bubble UI after expansion state changes
|
|
10
|
+
export const updateToolBubbleUI = (messageId: string, bubble: HTMLElement, config?: AgentWidgetConfig): void => {
|
|
11
|
+
const expanded = toolExpansionState.has(messageId);
|
|
12
|
+
const toolCallConfig = config?.toolCall ?? {};
|
|
13
|
+
const header = bubble.querySelector('button[data-expand-header="true"]') as HTMLElement;
|
|
14
|
+
const content = bubble.querySelector('.tvw-border-t') as HTMLElement;
|
|
15
|
+
|
|
16
|
+
if (!header || !content) return;
|
|
17
|
+
|
|
18
|
+
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
19
|
+
|
|
20
|
+
// Find toggle icon container - it's the direct child div of headerMeta (which has tvw-ml-auto)
|
|
21
|
+
const headerMeta = header.querySelector('.tvw-ml-auto') as HTMLElement;
|
|
22
|
+
const toggleIcon = headerMeta?.querySelector(':scope > .tvw-flex.tvw-items-center') as HTMLElement;
|
|
23
|
+
if (toggleIcon) {
|
|
24
|
+
toggleIcon.innerHTML = "";
|
|
25
|
+
const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
|
|
26
|
+
const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
|
|
27
|
+
if (chevronIcon) {
|
|
28
|
+
toggleIcon.appendChild(chevronIcon);
|
|
29
|
+
} else {
|
|
30
|
+
toggleIcon.textContent = expanded ? "Hide" : "Show";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
content.style.display = expanded ? "" : "none";
|
|
35
|
+
};
|
|
8
36
|
|
|
9
37
|
export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidgetConfig): HTMLElement => {
|
|
10
38
|
const tool = message.toolCall;
|
|
@@ -28,6 +56,9 @@ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidg
|
|
|
28
56
|
"tvw-py-0"
|
|
29
57
|
].join(" ")
|
|
30
58
|
);
|
|
59
|
+
// Set id for idiomorph matching
|
|
60
|
+
bubble.id = `bubble-${message.id}`;
|
|
61
|
+
bubble.setAttribute("data-message-id", message.id);
|
|
31
62
|
|
|
32
63
|
// Apply bubble-level styles
|
|
33
64
|
if (toolCallConfig.backgroundColor) {
|
|
@@ -54,6 +85,8 @@ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidg
|
|
|
54
85
|
) as HTMLButtonElement;
|
|
55
86
|
header.type = "button";
|
|
56
87
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
88
|
+
header.setAttribute("data-expand-header", "true");
|
|
89
|
+
header.setAttribute("data-bubble-type", "tool");
|
|
57
90
|
|
|
58
91
|
// Apply header styles
|
|
59
92
|
if (toolCallConfig.headerBackgroundColor) {
|
|
@@ -245,28 +278,6 @@ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidg
|
|
|
245
278
|
content.style.display = expanded ? "" : "none";
|
|
246
279
|
};
|
|
247
280
|
|
|
248
|
-
const toggleToolExpansion = () => {
|
|
249
|
-
expanded = !expanded;
|
|
250
|
-
if (expanded) {
|
|
251
|
-
toolExpansionState.add(message.id);
|
|
252
|
-
} else {
|
|
253
|
-
toolExpansionState.delete(message.id);
|
|
254
|
-
}
|
|
255
|
-
applyToolExpansion();
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
header.addEventListener("pointerdown", (event) => {
|
|
259
|
-
event.preventDefault();
|
|
260
|
-
toggleToolExpansion();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
header.addEventListener("keydown", (event) => {
|
|
264
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
265
|
-
event.preventDefault();
|
|
266
|
-
toggleToolExpansion();
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
|
|
270
281
|
applyToolExpansion();
|
|
271
282
|
|
|
272
283
|
bubble.append(header, content);
|
package/src/styles/widget.css
CHANGED
|
@@ -1240,8 +1240,12 @@
|
|
|
1240
1240
|
margin-top: 0.5rem;
|
|
1241
1241
|
padding-top: 0.5rem;
|
|
1242
1242
|
border-top: 1px solid var(--cw-divider, #f1f5f9);
|
|
1243
|
-
|
|
1244
|
-
|
|
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;
|
|
1245
1249
|
}
|
|
1246
1250
|
|
|
1247
1251
|
/* Action bar alignment */
|
package/src/types.ts
CHANGED
|
@@ -790,6 +790,19 @@ export type AgentWidgetLayoutConfig = {
|
|
|
790
790
|
messages?: AgentWidgetMessageLayoutConfig;
|
|
791
791
|
/** Slot renderers for custom content injection */
|
|
792
792
|
slots?: Partial<Record<WidgetLayoutSlot, SlotRenderer>>;
|
|
793
|
+
/**
|
|
794
|
+
* Show/hide the header section entirely.
|
|
795
|
+
* When false, the header (including icon, title, buttons) is completely hidden.
|
|
796
|
+
* @default true
|
|
797
|
+
*/
|
|
798
|
+
showHeader?: boolean;
|
|
799
|
+
/**
|
|
800
|
+
* Show/hide the footer/composer section entirely.
|
|
801
|
+
* When false, the footer (including input field, send button, suggestions) is completely hidden.
|
|
802
|
+
* Useful for read-only conversation previews.
|
|
803
|
+
* @default true
|
|
804
|
+
*/
|
|
805
|
+
showFooter?: boolean;
|
|
793
806
|
};
|
|
794
807
|
|
|
795
808
|
// ============================================================================
|