vanilla-agent 1.3.0 → 1.4.1
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 +219 -3
- package/dist/index.cjs +7 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -1
- package/dist/index.d.ts +131 -1
- package/dist/index.global.js +51 -48
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/widget.css +21 -0
- package/package.json +4 -2
- package/src/client.test.ts +197 -0
- package/src/client.ts +330 -15
- package/src/components/forms.ts +2 -0
- package/src/components/message-bubble.ts +9 -4
- package/src/components/messages.ts +5 -3
- package/src/components/panel.ts +1 -0
- package/src/components/reasoning-bubble.ts +26 -8
- package/src/components/tool-bubble.ts +139 -22
- package/src/index.ts +9 -1
- package/src/plugins/registry.ts +2 -0
- package/src/plugins/types.ts +2 -0
- package/src/runtime/init.ts +4 -1
- package/src/session.ts +4 -0
- package/src/styles/widget.css +21 -0
- package/src/types.ts +107 -0
- package/src/ui.ts +145 -5
- package/src/utils/constants.ts +2 -0
- package/src/utils/dom.ts +2 -0
- package/src/utils/formatting.test.ts +160 -0
- package/src/utils/formatting.ts +252 -1
- package/src/utils/positioning.ts +2 -0
- package/src/utils/theme.ts +2 -0
|
@@ -78,10 +78,15 @@ export const createStandardBubble = (
|
|
|
78
78
|
});
|
|
79
79
|
bubble.appendChild(contentDiv);
|
|
80
80
|
|
|
81
|
-
// Add typing indicator if this is a streaming assistant message
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
// Add typing indicator if this is a streaming assistant message
|
|
82
|
+
// Show it when streaming starts (even if content is empty), hide it once content appears
|
|
83
|
+
if (message.streaming && message.role === "assistant") {
|
|
84
|
+
// Only show typing indicator if content is empty or just starting
|
|
85
|
+
// Hide it once we have substantial content
|
|
86
|
+
if (!message.content || !message.content.trim()) {
|
|
87
|
+
const typingIndicator = createTypingIndicator();
|
|
88
|
+
bubble.appendChild(typingIndicator);
|
|
89
|
+
}
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
return bubble;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createElement, createFragment } from "../utils/dom";
|
|
2
|
-
import { AgentWidgetMessage } from "../types";
|
|
2
|
+
import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
|
|
3
3
|
import { MessageTransform } from "./message-bubble";
|
|
4
4
|
import { createStandardBubble } from "./message-bubble";
|
|
5
5
|
import { createReasoningBubble } from "./reasoning-bubble";
|
|
@@ -10,7 +10,8 @@ export const renderMessages = (
|
|
|
10
10
|
messages: AgentWidgetMessage[],
|
|
11
11
|
transform: MessageTransform,
|
|
12
12
|
showReasoning: boolean,
|
|
13
|
-
showToolCalls: boolean
|
|
13
|
+
showToolCalls: boolean,
|
|
14
|
+
config?: AgentWidgetConfig
|
|
14
15
|
) => {
|
|
15
16
|
container.innerHTML = "";
|
|
16
17
|
const fragment = createFragment();
|
|
@@ -22,7 +23,7 @@ export const renderMessages = (
|
|
|
22
23
|
bubble = createReasoningBubble(message);
|
|
23
24
|
} else if (message.variant === "tool" && message.toolCall) {
|
|
24
25
|
if (!showToolCalls) return;
|
|
25
|
-
bubble = createToolBubble(message);
|
|
26
|
+
bubble = createToolBubble(message, config);
|
|
26
27
|
} else {
|
|
27
28
|
bubble = createStandardBubble(message, transform);
|
|
28
29
|
}
|
|
@@ -43,3 +44,4 @@ export const renderMessages = (
|
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
|
package/src/components/panel.ts
CHANGED
|
@@ -443,6 +443,7 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
|
|
|
443
443
|
"div",
|
|
444
444
|
"tvw-flex tvw-flex-1 tvw-min-h-0 tvw-flex-col tvw-gap-6 tvw-overflow-y-auto tvw-bg-cw-container tvw-px-6 tvw-py-6"
|
|
445
445
|
);
|
|
446
|
+
body.id = "vanilla-agent-scroll-container";
|
|
446
447
|
const introCard = createElement(
|
|
447
448
|
"div",
|
|
448
449
|
"tvw-rounded-2xl tvw-bg-cw-surface tvw-p-6 tvw-shadow-sm"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createElement } from "../utils/dom";
|
|
2
2
|
import { AgentWidgetMessage } from "../types";
|
|
3
3
|
import { describeReasonStatus } from "../utils/formatting";
|
|
4
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
4
5
|
|
|
5
6
|
// Expansion state per widget instance
|
|
6
7
|
const reasoningExpansionState = new Set<string>();
|
|
@@ -10,6 +11,7 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
10
11
|
const bubble = createElement(
|
|
11
12
|
"div",
|
|
12
13
|
[
|
|
14
|
+
"tvw-w-full",
|
|
13
15
|
"tvw-max-w-[85%]",
|
|
14
16
|
"tvw-rounded-2xl",
|
|
15
17
|
"tvw-bg-cw-surface",
|
|
@@ -36,7 +38,7 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
36
38
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
37
39
|
|
|
38
40
|
const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
|
|
39
|
-
const title = createElement("span", "tvw-text-xs tvw-
|
|
41
|
+
const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
|
|
40
42
|
title.textContent = "Thinking...";
|
|
41
43
|
headerContent.appendChild(title);
|
|
42
44
|
|
|
@@ -50,13 +52,20 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
50
52
|
title.style.display = "";
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
55
|
+
const toggleIcon = createElement("div", "tvw-flex tvw-items-center");
|
|
56
|
+
const iconColor = "currentColor";
|
|
57
|
+
const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
|
|
58
|
+
if (chevronIcon) {
|
|
59
|
+
toggleIcon.appendChild(chevronIcon);
|
|
60
|
+
} else {
|
|
61
|
+
// Fallback to text if icon fails
|
|
62
|
+
toggleIcon.textContent = expanded ? "Hide" : "Show";
|
|
63
|
+
}
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-ml-auto");
|
|
66
|
+
headerMeta.append(toggleIcon);
|
|
67
|
+
|
|
68
|
+
header.append(headerContent, headerMeta);
|
|
60
69
|
|
|
61
70
|
const content = createElement(
|
|
62
71
|
"div",
|
|
@@ -78,7 +87,16 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
78
87
|
|
|
79
88
|
const applyExpansionState = () => {
|
|
80
89
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
81
|
-
|
|
90
|
+
// Update chevron icon
|
|
91
|
+
toggleIcon.innerHTML = "";
|
|
92
|
+
const iconColor = "currentColor";
|
|
93
|
+
const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
|
|
94
|
+
if (chevronIcon) {
|
|
95
|
+
toggleIcon.appendChild(chevronIcon);
|
|
96
|
+
} else {
|
|
97
|
+
// Fallback to text if icon fails
|
|
98
|
+
toggleIcon.textContent = expanded ? "Hide" : "Show";
|
|
99
|
+
}
|
|
82
100
|
content.style.display = expanded ? "" : "none";
|
|
83
101
|
};
|
|
84
102
|
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { createElement } from "../utils/dom";
|
|
2
|
-
import { AgentWidgetMessage } from "../types";
|
|
2
|
+
import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
|
|
3
3
|
import { formatUnknownValue, describeToolTitle } from "../utils/formatting";
|
|
4
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
4
5
|
|
|
5
6
|
// Expansion state per widget instance
|
|
6
7
|
const toolExpansionState = new Set<string>();
|
|
7
8
|
|
|
8
|
-
export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
9
|
+
export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidgetConfig): HTMLElement => {
|
|
9
10
|
const tool = message.toolCall;
|
|
11
|
+
const toolCallConfig = config?.toolCall ?? {};
|
|
12
|
+
|
|
10
13
|
const bubble = createElement(
|
|
11
14
|
"div",
|
|
12
15
|
[
|
|
16
|
+
"tvw-w-full",
|
|
13
17
|
"tvw-max-w-[85%]",
|
|
14
18
|
"tvw-rounded-2xl",
|
|
15
19
|
"tvw-bg-cw-surface",
|
|
@@ -23,6 +27,20 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
|
23
27
|
].join(" ")
|
|
24
28
|
);
|
|
25
29
|
|
|
30
|
+
// Apply bubble-level styles
|
|
31
|
+
if (toolCallConfig.backgroundColor) {
|
|
32
|
+
bubble.style.backgroundColor = toolCallConfig.backgroundColor;
|
|
33
|
+
}
|
|
34
|
+
if (toolCallConfig.borderColor) {
|
|
35
|
+
bubble.style.borderColor = toolCallConfig.borderColor;
|
|
36
|
+
}
|
|
37
|
+
if (toolCallConfig.borderWidth) {
|
|
38
|
+
bubble.style.borderWidth = toolCallConfig.borderWidth;
|
|
39
|
+
}
|
|
40
|
+
if (toolCallConfig.borderRadius) {
|
|
41
|
+
bubble.style.borderRadius = toolCallConfig.borderRadius;
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
if (!tool) {
|
|
27
45
|
return bubble;
|
|
28
46
|
}
|
|
@@ -35,25 +53,39 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
|
35
53
|
header.type = "button";
|
|
36
54
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
37
55
|
|
|
56
|
+
// Apply header styles
|
|
57
|
+
if (toolCallConfig.headerBackgroundColor) {
|
|
58
|
+
header.style.backgroundColor = toolCallConfig.headerBackgroundColor;
|
|
59
|
+
}
|
|
60
|
+
if (toolCallConfig.headerPaddingX) {
|
|
61
|
+
header.style.paddingLeft = toolCallConfig.headerPaddingX;
|
|
62
|
+
header.style.paddingRight = toolCallConfig.headerPaddingX;
|
|
63
|
+
}
|
|
64
|
+
if (toolCallConfig.headerPaddingY) {
|
|
65
|
+
header.style.paddingTop = toolCallConfig.headerPaddingY;
|
|
66
|
+
header.style.paddingBottom = toolCallConfig.headerPaddingY;
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
|
|
39
70
|
const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
|
|
71
|
+
if (toolCallConfig.headerTextColor) {
|
|
72
|
+
title.style.color = toolCallConfig.headerTextColor;
|
|
73
|
+
}
|
|
40
74
|
title.textContent = describeToolTitle(tool);
|
|
41
75
|
headerContent.appendChild(title);
|
|
42
76
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
const toggleIcon = createElement("div", "tvw-flex tvw-items-center");
|
|
78
|
+
const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
|
|
79
|
+
const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
|
|
80
|
+
if (chevronIcon) {
|
|
81
|
+
toggleIcon.appendChild(chevronIcon);
|
|
82
|
+
} else {
|
|
83
|
+
// Fallback to text if icon fails
|
|
84
|
+
toggleIcon.textContent = expanded ? "Hide" : "Show";
|
|
47
85
|
}
|
|
48
86
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
"tvw-text-xs tvw-text-cw-primary"
|
|
52
|
-
);
|
|
53
|
-
toggleLabel.textContent = expanded ? "Hide" : "Show";
|
|
54
|
-
|
|
55
|
-
const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-gap-2");
|
|
56
|
-
headerMeta.append(toggleLabel);
|
|
87
|
+
const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-gap-2 tvw-ml-auto");
|
|
88
|
+
headerMeta.append(toggleIcon);
|
|
57
89
|
|
|
58
90
|
header.append(headerContent, headerMeta);
|
|
59
91
|
|
|
@@ -63,17 +95,60 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
|
63
95
|
);
|
|
64
96
|
content.style.display = expanded ? "" : "none";
|
|
65
97
|
|
|
98
|
+
// Apply content styles
|
|
99
|
+
if (toolCallConfig.contentBackgroundColor) {
|
|
100
|
+
content.style.backgroundColor = toolCallConfig.contentBackgroundColor;
|
|
101
|
+
}
|
|
102
|
+
if (toolCallConfig.contentTextColor) {
|
|
103
|
+
content.style.color = toolCallConfig.contentTextColor;
|
|
104
|
+
}
|
|
105
|
+
if (toolCallConfig.contentPaddingX) {
|
|
106
|
+
content.style.paddingLeft = toolCallConfig.contentPaddingX;
|
|
107
|
+
content.style.paddingRight = toolCallConfig.contentPaddingX;
|
|
108
|
+
}
|
|
109
|
+
if (toolCallConfig.contentPaddingY) {
|
|
110
|
+
content.style.paddingTop = toolCallConfig.contentPaddingY;
|
|
111
|
+
content.style.paddingBottom = toolCallConfig.contentPaddingY;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Add tool name at the top of content
|
|
115
|
+
if (tool.name) {
|
|
116
|
+
const toolName = createElement("div", "tvw-text-xs tvw-text-cw-muted tvw-italic");
|
|
117
|
+
if (toolCallConfig.contentTextColor) {
|
|
118
|
+
toolName.style.color = toolCallConfig.contentTextColor;
|
|
119
|
+
} else if (toolCallConfig.headerTextColor) {
|
|
120
|
+
toolName.style.color = toolCallConfig.headerTextColor;
|
|
121
|
+
}
|
|
122
|
+
toolName.textContent = tool.name;
|
|
123
|
+
content.appendChild(toolName);
|
|
124
|
+
}
|
|
125
|
+
|
|
66
126
|
if (tool.args !== undefined) {
|
|
67
127
|
const argsBlock = createElement("div", "tvw-space-y-1");
|
|
68
128
|
const argsLabel = createElement(
|
|
69
129
|
"div",
|
|
70
|
-
"tvw-
|
|
130
|
+
"tvw-text-xs tvw-text-cw-muted"
|
|
71
131
|
);
|
|
132
|
+
if (toolCallConfig.labelTextColor) {
|
|
133
|
+
argsLabel.style.color = toolCallConfig.labelTextColor;
|
|
134
|
+
}
|
|
72
135
|
argsLabel.textContent = "Arguments";
|
|
73
136
|
const argsPre = createElement(
|
|
74
137
|
"pre",
|
|
75
|
-
"tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-
|
|
138
|
+
"tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
|
|
76
139
|
);
|
|
140
|
+
// Ensure font size matches header text (0.75rem / 12px)
|
|
141
|
+
argsPre.style.fontSize = "0.75rem";
|
|
142
|
+
argsPre.style.lineHeight = "1rem";
|
|
143
|
+
if (toolCallConfig.codeBlockBackgroundColor) {
|
|
144
|
+
argsPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
|
|
145
|
+
}
|
|
146
|
+
if (toolCallConfig.codeBlockBorderColor) {
|
|
147
|
+
argsPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
|
|
148
|
+
}
|
|
149
|
+
if (toolCallConfig.codeBlockTextColor) {
|
|
150
|
+
argsPre.style.color = toolCallConfig.codeBlockTextColor;
|
|
151
|
+
}
|
|
77
152
|
argsPre.textContent = formatUnknownValue(tool.args);
|
|
78
153
|
argsBlock.append(argsLabel, argsPre);
|
|
79
154
|
content.appendChild(argsBlock);
|
|
@@ -83,13 +158,28 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
|
83
158
|
const logsBlock = createElement("div", "tvw-space-y-1");
|
|
84
159
|
const logsLabel = createElement(
|
|
85
160
|
"div",
|
|
86
|
-
"tvw-
|
|
161
|
+
"tvw-text-xs tvw-text-cw-muted"
|
|
87
162
|
);
|
|
163
|
+
if (toolCallConfig.labelTextColor) {
|
|
164
|
+
logsLabel.style.color = toolCallConfig.labelTextColor;
|
|
165
|
+
}
|
|
88
166
|
logsLabel.textContent = "Activity";
|
|
89
167
|
const logsPre = createElement(
|
|
90
168
|
"pre",
|
|
91
|
-
"tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-
|
|
169
|
+
"tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
|
|
92
170
|
);
|
|
171
|
+
// Ensure font size matches header text (0.75rem / 12px)
|
|
172
|
+
logsPre.style.fontSize = "0.75rem";
|
|
173
|
+
logsPre.style.lineHeight = "1rem";
|
|
174
|
+
if (toolCallConfig.codeBlockBackgroundColor) {
|
|
175
|
+
logsPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
|
|
176
|
+
}
|
|
177
|
+
if (toolCallConfig.codeBlockBorderColor) {
|
|
178
|
+
logsPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
|
|
179
|
+
}
|
|
180
|
+
if (toolCallConfig.codeBlockTextColor) {
|
|
181
|
+
logsPre.style.color = toolCallConfig.codeBlockTextColor;
|
|
182
|
+
}
|
|
93
183
|
logsPre.textContent = tool.chunks.join("\n");
|
|
94
184
|
logsBlock.append(logsLabel, logsPre);
|
|
95
185
|
content.appendChild(logsBlock);
|
|
@@ -99,13 +189,28 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
|
99
189
|
const resultBlock = createElement("div", "tvw-space-y-1");
|
|
100
190
|
const resultLabel = createElement(
|
|
101
191
|
"div",
|
|
102
|
-
"tvw-
|
|
192
|
+
"tvw-text-xs tvw-text-cw-muted"
|
|
103
193
|
);
|
|
194
|
+
if (toolCallConfig.labelTextColor) {
|
|
195
|
+
resultLabel.style.color = toolCallConfig.labelTextColor;
|
|
196
|
+
}
|
|
104
197
|
resultLabel.textContent = "Result";
|
|
105
198
|
const resultPre = createElement(
|
|
106
199
|
"pre",
|
|
107
|
-
"tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-
|
|
200
|
+
"tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
|
|
108
201
|
);
|
|
202
|
+
// Ensure font size matches header text (0.75rem / 12px)
|
|
203
|
+
resultPre.style.fontSize = "0.75rem";
|
|
204
|
+
resultPre.style.lineHeight = "1rem";
|
|
205
|
+
if (toolCallConfig.codeBlockBackgroundColor) {
|
|
206
|
+
resultPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
|
|
207
|
+
}
|
|
208
|
+
if (toolCallConfig.codeBlockBorderColor) {
|
|
209
|
+
resultPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
|
|
210
|
+
}
|
|
211
|
+
if (toolCallConfig.codeBlockTextColor) {
|
|
212
|
+
resultPre.style.color = toolCallConfig.codeBlockTextColor;
|
|
213
|
+
}
|
|
109
214
|
resultPre.textContent = formatUnknownValue(tool.result);
|
|
110
215
|
resultBlock.append(resultLabel, resultPre);
|
|
111
216
|
content.appendChild(resultBlock);
|
|
@@ -114,15 +219,27 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
|
|
|
114
219
|
if (tool.status === "complete" && typeof tool.duration === "number") {
|
|
115
220
|
const duration = createElement(
|
|
116
221
|
"div",
|
|
117
|
-
"tvw-
|
|
222
|
+
"tvw-text-xs tvw-text-cw-muted"
|
|
118
223
|
);
|
|
224
|
+
if (toolCallConfig.contentTextColor) {
|
|
225
|
+
duration.style.color = toolCallConfig.contentTextColor;
|
|
226
|
+
}
|
|
119
227
|
duration.textContent = `Duration: ${tool.duration}ms`;
|
|
120
228
|
content.appendChild(duration);
|
|
121
229
|
}
|
|
122
230
|
|
|
123
231
|
const applyToolExpansion = () => {
|
|
124
232
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
125
|
-
|
|
233
|
+
// Update chevron icon
|
|
234
|
+
toggleIcon.innerHTML = "";
|
|
235
|
+
const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
|
|
236
|
+
const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
|
|
237
|
+
if (chevronIcon) {
|
|
238
|
+
toggleIcon.appendChild(chevronIcon);
|
|
239
|
+
} else {
|
|
240
|
+
// Fallback to text if icon fails
|
|
241
|
+
toggleIcon.textContent = expanded ? "Hide" : "Show";
|
|
242
|
+
}
|
|
126
243
|
content.style.display = expanded ? "" : "none";
|
|
127
244
|
};
|
|
128
245
|
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,9 @@ export type {
|
|
|
10
10
|
AgentWidgetInitOptions,
|
|
11
11
|
AgentWidgetMessage,
|
|
12
12
|
AgentWidgetLauncherConfig,
|
|
13
|
-
AgentWidgetEvent
|
|
13
|
+
AgentWidgetEvent,
|
|
14
|
+
AgentWidgetStreamParser,
|
|
15
|
+
AgentWidgetStreamParserResult
|
|
14
16
|
} from "./types";
|
|
15
17
|
|
|
16
18
|
export { initAgentWidgetFn as initAgentWidget };
|
|
@@ -28,6 +30,12 @@ export {
|
|
|
28
30
|
escapeHtml,
|
|
29
31
|
directivePostprocessor
|
|
30
32
|
} from "./postprocessors";
|
|
33
|
+
export {
|
|
34
|
+
createPlainTextParser,
|
|
35
|
+
createJsonStreamParser,
|
|
36
|
+
createRegexJsonParser,
|
|
37
|
+
createXmlParser
|
|
38
|
+
} from "./utils/formatting";
|
|
31
39
|
export type { AgentWidgetInitHandle };
|
|
32
40
|
|
|
33
41
|
// Plugin system exports
|
package/src/plugins/registry.ts
CHANGED
package/src/plugins/types.ts
CHANGED
package/src/runtime/init.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAgentExperience, AgentWidgetController } from "../ui";
|
|
2
|
-
import { AgentWidgetConfig, AgentWidgetInitOptions } from "../types";
|
|
2
|
+
import { AgentWidgetConfig, AgentWidgetInitOptions, AgentWidgetEvent } from "../types";
|
|
3
3
|
|
|
4
4
|
const ensureTarget = (target: string | HTMLElement): HTMLElement => {
|
|
5
5
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
@@ -145,6 +145,9 @@ export const initAgentWidget = (
|
|
|
145
145
|
stopVoiceRecognition() {
|
|
146
146
|
return controller.stopVoiceRecognition();
|
|
147
147
|
},
|
|
148
|
+
injectTestMessage(event: AgentWidgetEvent) {
|
|
149
|
+
controller.injectTestMessage(event);
|
|
150
|
+
},
|
|
148
151
|
destroy() {
|
|
149
152
|
controller.destroy();
|
|
150
153
|
host.remove();
|
package/src/session.ts
CHANGED
|
@@ -60,6 +60,10 @@ export class AgentWidgetSession {
|
|
|
60
60
|
return this.streaming;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
public injectTestEvent(event: AgentWidgetEvent) {
|
|
64
|
+
this.handleEvent(event);
|
|
65
|
+
}
|
|
66
|
+
|
|
63
67
|
public async sendMessage(rawInput: string, options?: { viaVoice?: boolean }) {
|
|
64
68
|
const input = rawInput.trim();
|
|
65
69
|
if (!input) return;
|
package/src/styles/widget.css
CHANGED
|
@@ -56,6 +56,14 @@
|
|
|
56
56
|
justify-content: center;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
.tvw-justify-between {
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.tvw-justify-end {
|
|
64
|
+
justify-content: flex-end;
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
.tvw-gap-1 {
|
|
60
68
|
gap: 0.25rem;
|
|
61
69
|
}
|
|
@@ -160,6 +168,10 @@
|
|
|
160
168
|
background-color: var(--cw-primary, #111827);
|
|
161
169
|
}
|
|
162
170
|
|
|
171
|
+
.tvw-italic {
|
|
172
|
+
font-style: italic;
|
|
173
|
+
}
|
|
174
|
+
|
|
163
175
|
.tvw-text-base {
|
|
164
176
|
font-size: 1rem;
|
|
165
177
|
line-height: 1.5rem;
|
|
@@ -823,3 +835,12 @@ form:focus-within textarea {
|
|
|
823
835
|
margin: 0.5rem 0;
|
|
824
836
|
border-radius: 0.375rem;
|
|
825
837
|
}
|
|
838
|
+
|
|
839
|
+
/* Ensure links in user messages match the text color */
|
|
840
|
+
#vanilla-agent-root .tvw-text-white a,
|
|
841
|
+
#vanilla-agent-root .tvw-text-white a:visited,
|
|
842
|
+
#vanilla-agent-root .tvw-text-white a:hover,
|
|
843
|
+
#vanilla-agent-root .tvw-text-white a:active {
|
|
844
|
+
color: inherit;
|
|
845
|
+
text-decoration: underline;
|
|
846
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -138,6 +138,81 @@ export type AgentWidgetVoiceRecognitionConfig = {
|
|
|
138
138
|
showRecordingIndicator?: boolean;
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
export type AgentWidgetToolCallConfig = {
|
|
142
|
+
backgroundColor?: string;
|
|
143
|
+
borderColor?: string;
|
|
144
|
+
borderWidth?: string;
|
|
145
|
+
borderRadius?: string;
|
|
146
|
+
headerBackgroundColor?: string;
|
|
147
|
+
headerTextColor?: string;
|
|
148
|
+
headerPaddingX?: string;
|
|
149
|
+
headerPaddingY?: string;
|
|
150
|
+
contentBackgroundColor?: string;
|
|
151
|
+
contentTextColor?: string;
|
|
152
|
+
contentPaddingX?: string;
|
|
153
|
+
contentPaddingY?: string;
|
|
154
|
+
codeBlockBackgroundColor?: string;
|
|
155
|
+
codeBlockBorderColor?: string;
|
|
156
|
+
codeBlockTextColor?: string;
|
|
157
|
+
toggleTextColor?: string;
|
|
158
|
+
labelTextColor?: string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Interface for pluggable stream parsers that extract text from streaming responses.
|
|
163
|
+
* Parsers handle incremental parsing to extract text values from structured formats (JSON, XML, etc.).
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* const jsonParser: AgentWidgetStreamParser = {
|
|
168
|
+
* processChunk: async (content) => {
|
|
169
|
+
* // Extract text from JSON - return null if not JSON or text not available yet
|
|
170
|
+
* if (!content.trim().startsWith('{')) return null;
|
|
171
|
+
* const match = content.match(/"text"\s*:\s*"([^"]*)"/);
|
|
172
|
+
* return match ? match[1] : null;
|
|
173
|
+
* },
|
|
174
|
+
* getExtractedText: () => extractedText
|
|
175
|
+
* };
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export interface AgentWidgetStreamParserResult {
|
|
179
|
+
/**
|
|
180
|
+
* The extracted text to display (may be partial during streaming)
|
|
181
|
+
*/
|
|
182
|
+
text: string | null;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Optional: The raw accumulated content (useful for middleware that needs the original format)
|
|
186
|
+
*/
|
|
187
|
+
raw?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface AgentWidgetStreamParser {
|
|
191
|
+
/**
|
|
192
|
+
* Process a chunk of content and return the extracted text (if available).
|
|
193
|
+
* This method is called for each chunk as it arrives during streaming.
|
|
194
|
+
* Return null if the content doesn't match this parser's format or if text is not yet available.
|
|
195
|
+
*
|
|
196
|
+
* @param accumulatedContent - The full accumulated content so far (including new chunk)
|
|
197
|
+
* @returns The extracted text value and optionally raw content, or null if not yet available or format doesn't match
|
|
198
|
+
*/
|
|
199
|
+
processChunk(accumulatedContent: string): Promise<AgentWidgetStreamParserResult | string | null> | AgentWidgetStreamParserResult | string | null;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the currently extracted text value (may be partial).
|
|
203
|
+
* This is called synchronously to get the latest extracted text without processing.
|
|
204
|
+
*
|
|
205
|
+
* @returns The currently extracted text value, or null if not yet available
|
|
206
|
+
*/
|
|
207
|
+
getExtractedText(): string | null;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Clean up any resources when parsing is complete.
|
|
211
|
+
*/
|
|
212
|
+
close?(): Promise<void> | void;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
141
216
|
export type AgentWidgetConfig = {
|
|
142
217
|
apiUrl?: string;
|
|
143
218
|
flowId?: string;
|
|
@@ -159,12 +234,44 @@ export type AgentWidgetConfig = {
|
|
|
159
234
|
sendButton?: AgentWidgetSendButtonConfig;
|
|
160
235
|
statusIndicator?: AgentWidgetStatusIndicatorConfig;
|
|
161
236
|
voiceRecognition?: AgentWidgetVoiceRecognitionConfig;
|
|
237
|
+
toolCall?: AgentWidgetToolCallConfig;
|
|
162
238
|
postprocessMessage?: (context: {
|
|
163
239
|
text: string;
|
|
164
240
|
message: AgentWidgetMessage;
|
|
165
241
|
streaming: boolean;
|
|
166
242
|
}) => string;
|
|
167
243
|
plugins?: AgentWidgetPlugin[];
|
|
244
|
+
/**
|
|
245
|
+
* Custom stream parser for extracting text from streaming structured responses.
|
|
246
|
+
* Handles incremental parsing of JSON, XML, or other formats.
|
|
247
|
+
* If not provided, uses the default JSON parser.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* streamParser: () => ({
|
|
252
|
+
* processChunk: async (content) => {
|
|
253
|
+
* // Return null if not your format, or extracted text if available
|
|
254
|
+
* if (!content.trim().startsWith('{')) return null;
|
|
255
|
+
* return extractText(content);
|
|
256
|
+
* },
|
|
257
|
+
* getExtractedText: () => extractedText
|
|
258
|
+
* })
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
streamParser?: () => AgentWidgetStreamParser;
|
|
262
|
+
/**
|
|
263
|
+
* Additional localStorage key to clear when the clear chat button is clicked.
|
|
264
|
+
* The widget automatically clears `"vanilla-agent-chat-history"` by default.
|
|
265
|
+
* Use this option to clear additional keys (e.g., if you're using a custom storage key).
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* config: {
|
|
270
|
+
* clearChatHistoryStorageKey: "my-custom-chat-history"
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
clearChatHistoryStorageKey?: string;
|
|
168
275
|
};
|
|
169
276
|
|
|
170
277
|
export type AgentWidgetMessageRole = "user" | "assistant" | "system";
|