vanilla-agent 0.2.0 → 1.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 +53 -21
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -61
- package/dist/index.d.ts +98 -61
- package/dist/index.global.js +36 -30
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +5 -5
- 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 +33 -0
- package/package.json +2 -2
- package/src/client.ts +14 -14
- package/src/components/forms.ts +7 -5
- package/src/components/launcher.ts +4 -4
- package/src/components/message-bubble.ts +3 -3
- package/src/components/messages.ts +4 -2
- package/src/components/panel.ts +254 -13
- package/src/components/reasoning-bubble.ts +2 -2
- package/src/components/suggestions.ts +4 -4
- package/src/components/tool-bubble.ts +2 -2
- package/src/defaults.ts +180 -0
- package/src/index.ts +21 -18
- package/src/install.ts +8 -8
- package/src/plugins/registry.ts +7 -5
- package/src/plugins/types.ts +13 -11
- package/src/runtime/init.ts +11 -8
- package/src/session.ts +32 -23
- package/src/styles/widget.css +33 -0
- package/src/types.ts +56 -31
- package/src/ui.ts +330 -22
- package/src/utils/constants.ts +4 -2
- package/src/utils/dom.ts +2 -0
- package/src/utils/formatting.ts +8 -6
- package/src/utils/icons.ts +1 -1
- package/src/utils/positioning.ts +2 -0
- package/src/utils/theme.ts +4 -2
package/src/types.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AgentWidgetPlugin } from "./plugins/types";
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type AgentWidgetFeatureFlags = {
|
|
4
4
|
showReasoning?: boolean;
|
|
5
5
|
showToolCalls?: boolean;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
export type
|
|
8
|
+
export type AgentWidgetTheme = {
|
|
9
9
|
primary?: string;
|
|
10
10
|
secondary?: string;
|
|
11
11
|
surface?: string;
|
|
@@ -24,6 +24,9 @@ export type ChatWidgetTheme = {
|
|
|
24
24
|
closeButtonColor?: string;
|
|
25
25
|
closeButtonBackgroundColor?: string;
|
|
26
26
|
closeButtonBorderColor?: string;
|
|
27
|
+
clearChatIconColor?: string;
|
|
28
|
+
clearChatBackgroundColor?: string;
|
|
29
|
+
clearChatBorderColor?: string;
|
|
27
30
|
micIconColor?: string;
|
|
28
31
|
micBackgroundColor?: string;
|
|
29
32
|
micBorderColor?: string;
|
|
@@ -39,7 +42,7 @@ export type ChatWidgetTheme = {
|
|
|
39
42
|
buttonRadius?: string;
|
|
40
43
|
};
|
|
41
44
|
|
|
42
|
-
export type
|
|
45
|
+
export type AgentWidgetLauncherConfig = {
|
|
43
46
|
enabled?: boolean;
|
|
44
47
|
title?: string;
|
|
45
48
|
subtitle?: string;
|
|
@@ -68,10 +71,17 @@ export type ChatWidgetLauncherConfig = {
|
|
|
68
71
|
closeButtonBorderWidth?: string;
|
|
69
72
|
closeButtonBorderColor?: string;
|
|
70
73
|
closeButtonBorderRadius?: string;
|
|
74
|
+
closeButtonPaddingX?: string;
|
|
75
|
+
closeButtonPaddingY?: string;
|
|
71
76
|
closeButtonPlacement?: "inline" | "top-right";
|
|
77
|
+
closeButtonIconName?: string;
|
|
78
|
+
closeButtonIconText?: string;
|
|
79
|
+
closeButtonTooltipText?: string;
|
|
80
|
+
closeButtonShowTooltip?: boolean;
|
|
81
|
+
clearChat?: AgentWidgetClearChatConfig;
|
|
72
82
|
};
|
|
73
83
|
|
|
74
|
-
export type
|
|
84
|
+
export type AgentWidgetSendButtonConfig = {
|
|
75
85
|
borderWidth?: string;
|
|
76
86
|
borderColor?: string;
|
|
77
87
|
paddingX?: string;
|
|
@@ -86,7 +96,22 @@ export type ChatWidgetSendButtonConfig = {
|
|
|
86
96
|
size?: string;
|
|
87
97
|
};
|
|
88
98
|
|
|
89
|
-
export type
|
|
99
|
+
export type AgentWidgetClearChatConfig = {
|
|
100
|
+
enabled?: boolean;
|
|
101
|
+
iconName?: string;
|
|
102
|
+
iconColor?: string;
|
|
103
|
+
backgroundColor?: string;
|
|
104
|
+
borderWidth?: string;
|
|
105
|
+
borderColor?: string;
|
|
106
|
+
borderRadius?: string;
|
|
107
|
+
size?: string;
|
|
108
|
+
paddingX?: string;
|
|
109
|
+
paddingY?: string;
|
|
110
|
+
tooltipText?: string;
|
|
111
|
+
showTooltip?: boolean;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type AgentWidgetStatusIndicatorConfig = {
|
|
90
115
|
visible?: boolean;
|
|
91
116
|
idleText?: string;
|
|
92
117
|
connectingText?: string;
|
|
@@ -94,7 +119,7 @@ export type ChatWidgetStatusIndicatorConfig = {
|
|
|
94
119
|
errorText?: string;
|
|
95
120
|
};
|
|
96
121
|
|
|
97
|
-
export type
|
|
122
|
+
export type AgentWidgetVoiceRecognitionConfig = {
|
|
98
123
|
enabled?: boolean;
|
|
99
124
|
pauseDuration?: number;
|
|
100
125
|
iconName?: string;
|
|
@@ -113,7 +138,7 @@ export type ChatWidgetVoiceRecognitionConfig = {
|
|
|
113
138
|
showRecordingIndicator?: boolean;
|
|
114
139
|
};
|
|
115
140
|
|
|
116
|
-
export type
|
|
141
|
+
export type AgentWidgetConfig = {
|
|
117
142
|
apiUrl?: string;
|
|
118
143
|
flowId?: string;
|
|
119
144
|
headers?: Record<string, string>;
|
|
@@ -123,28 +148,28 @@ export type ChatWidgetConfig = {
|
|
|
123
148
|
inputPlaceholder?: string;
|
|
124
149
|
sendButtonLabel?: string;
|
|
125
150
|
};
|
|
126
|
-
theme?:
|
|
127
|
-
features?:
|
|
128
|
-
launcher?:
|
|
129
|
-
initialMessages?:
|
|
151
|
+
theme?: AgentWidgetTheme;
|
|
152
|
+
features?: AgentWidgetFeatureFlags;
|
|
153
|
+
launcher?: AgentWidgetLauncherConfig;
|
|
154
|
+
initialMessages?: AgentWidgetMessage[];
|
|
130
155
|
suggestionChips?: string[];
|
|
131
156
|
debug?: boolean;
|
|
132
157
|
formEndpoint?: string;
|
|
133
158
|
launcherWidth?: string;
|
|
134
|
-
sendButton?:
|
|
135
|
-
statusIndicator?:
|
|
136
|
-
voiceRecognition?:
|
|
159
|
+
sendButton?: AgentWidgetSendButtonConfig;
|
|
160
|
+
statusIndicator?: AgentWidgetStatusIndicatorConfig;
|
|
161
|
+
voiceRecognition?: AgentWidgetVoiceRecognitionConfig;
|
|
137
162
|
postprocessMessage?: (context: {
|
|
138
163
|
text: string;
|
|
139
|
-
message:
|
|
164
|
+
message: AgentWidgetMessage;
|
|
140
165
|
streaming: boolean;
|
|
141
166
|
}) => string;
|
|
142
|
-
plugins?:
|
|
167
|
+
plugins?: AgentWidgetPlugin[];
|
|
143
168
|
};
|
|
144
169
|
|
|
145
|
-
export type
|
|
170
|
+
export type AgentWidgetMessageRole = "user" | "assistant" | "system";
|
|
146
171
|
|
|
147
|
-
export type
|
|
172
|
+
export type AgentWidgetReasoning = {
|
|
148
173
|
id: string;
|
|
149
174
|
status: "pending" | "streaming" | "complete";
|
|
150
175
|
chunks: string[];
|
|
@@ -153,7 +178,7 @@ export type ChatWidgetReasoning = {
|
|
|
153
178
|
durationMs?: number;
|
|
154
179
|
};
|
|
155
180
|
|
|
156
|
-
export type
|
|
181
|
+
export type AgentWidgetToolCall = {
|
|
157
182
|
id: string;
|
|
158
183
|
name?: string;
|
|
159
184
|
status: "pending" | "running" | "complete";
|
|
@@ -166,29 +191,29 @@ export type ChatWidgetToolCall = {
|
|
|
166
191
|
durationMs?: number;
|
|
167
192
|
};
|
|
168
193
|
|
|
169
|
-
export type
|
|
194
|
+
export type AgentWidgetMessageVariant = "assistant" | "reasoning" | "tool";
|
|
170
195
|
|
|
171
|
-
export type
|
|
196
|
+
export type AgentWidgetMessage = {
|
|
172
197
|
id: string;
|
|
173
|
-
role:
|
|
198
|
+
role: AgentWidgetMessageRole;
|
|
174
199
|
content: string;
|
|
175
200
|
createdAt: string;
|
|
176
201
|
streaming?: boolean;
|
|
177
|
-
variant?:
|
|
202
|
+
variant?: AgentWidgetMessageVariant;
|
|
178
203
|
sequence?: number;
|
|
179
|
-
reasoning?:
|
|
180
|
-
toolCall?:
|
|
181
|
-
tools?:
|
|
204
|
+
reasoning?: AgentWidgetReasoning;
|
|
205
|
+
toolCall?: AgentWidgetToolCall;
|
|
206
|
+
tools?: AgentWidgetToolCall[];
|
|
182
207
|
};
|
|
183
208
|
|
|
184
|
-
export type
|
|
185
|
-
| { type: "message"; message:
|
|
209
|
+
export type AgentWidgetEvent =
|
|
210
|
+
| { type: "message"; message: AgentWidgetMessage }
|
|
186
211
|
| { type: "status"; status: "connecting" | "connected" | "error" | "idle" }
|
|
187
212
|
| { type: "error"; error: Error };
|
|
188
213
|
|
|
189
|
-
export type
|
|
214
|
+
export type AgentWidgetInitOptions = {
|
|
190
215
|
target: string | HTMLElement;
|
|
191
|
-
config?:
|
|
216
|
+
config?: AgentWidgetConfig;
|
|
192
217
|
useShadowDom?: boolean;
|
|
193
218
|
onReady?: () => void;
|
|
194
219
|
};
|
package/src/ui.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { escapeHtml } from "./postprocessors";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { AgentWidgetSession, AgentWidgetSessionStatus } from "./session";
|
|
3
|
+
import { AgentWidgetConfig, AgentWidgetMessage } from "./types";
|
|
4
4
|
import { applyThemeVariables } from "./utils/theme";
|
|
5
5
|
import { renderLucideIcon } from "./utils/icons";
|
|
6
6
|
import { createElement } from "./utils/dom";
|
|
@@ -14,16 +14,18 @@ import { createToolBubble } from "./components/tool-bubble";
|
|
|
14
14
|
import { createSuggestions } from "./components/suggestions";
|
|
15
15
|
import { enhanceWithForms } from "./components/forms";
|
|
16
16
|
import { pluginRegistry } from "./plugins/registry";
|
|
17
|
+
import { mergeWithDefaults } from "./defaults";
|
|
17
18
|
|
|
18
19
|
type Controller = {
|
|
19
|
-
update: (config:
|
|
20
|
+
update: (config: AgentWidgetConfig) => void;
|
|
20
21
|
destroy: () => void;
|
|
21
22
|
open: () => void;
|
|
22
23
|
close: () => void;
|
|
23
24
|
toggle: () => void;
|
|
25
|
+
clearChat: () => void;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
const buildPostprocessor = (cfg?:
|
|
28
|
+
const buildPostprocessor = (cfg?: AgentWidgetConfig): MessageTransform => {
|
|
27
29
|
if (cfg?.postprocessMessage) {
|
|
28
30
|
return (context) =>
|
|
29
31
|
cfg.postprocessMessage!({
|
|
@@ -35,16 +37,16 @@ const buildPostprocessor = (cfg?: ChatWidgetConfig): MessageTransform => {
|
|
|
35
37
|
return ({ text }) => escapeHtml(text);
|
|
36
38
|
};
|
|
37
39
|
|
|
38
|
-
export const
|
|
40
|
+
export const createAgentExperience = (
|
|
39
41
|
mount: HTMLElement,
|
|
40
|
-
initialConfig?:
|
|
42
|
+
initialConfig?: AgentWidgetConfig
|
|
41
43
|
): Controller => {
|
|
42
44
|
// Tailwind config uses important: "#vanilla-agent-root", so ensure mount has this ID
|
|
43
45
|
if (!mount.id || mount.id !== "vanilla-agent-root") {
|
|
44
46
|
mount.id = "vanilla-agent-root";
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
let config =
|
|
49
|
+
let config = mergeWithDefaults(initialConfig) as AgentWidgetConfig;
|
|
48
50
|
applyThemeVariables(mount, config);
|
|
49
51
|
|
|
50
52
|
// Get plugins for this instance
|
|
@@ -61,7 +63,7 @@ export const createChatExperience = (
|
|
|
61
63
|
|
|
62
64
|
// Get status indicator config
|
|
63
65
|
const statusConfig = config.statusIndicator ?? {};
|
|
64
|
-
const getStatusText = (status:
|
|
66
|
+
const getStatusText = (status: AgentWidgetSessionStatus): string => {
|
|
65
67
|
if (status === "idle") return statusConfig.idleText ?? statusCopy.idle;
|
|
66
68
|
if (status === "connecting") return statusConfig.connectingText ?? statusCopy.connecting;
|
|
67
69
|
if (status === "connected") return statusConfig.connectedText ?? statusCopy.connected;
|
|
@@ -97,7 +99,7 @@ export const createChatExperience = (
|
|
|
97
99
|
const destroyCallbacks: Array<() => void> = [];
|
|
98
100
|
const suggestionsManager = createSuggestions(suggestions);
|
|
99
101
|
let closeHandler: (() => void) | null = null;
|
|
100
|
-
let session:
|
|
102
|
+
let session: AgentWidgetSession;
|
|
101
103
|
let isStreaming = false;
|
|
102
104
|
let shouldAutoScroll = true;
|
|
103
105
|
let lastScrollTop = 0;
|
|
@@ -178,7 +180,7 @@ export const createChatExperience = (
|
|
|
178
180
|
// Message rendering with plugin support
|
|
179
181
|
const renderMessagesWithPlugins = (
|
|
180
182
|
container: HTMLElement,
|
|
181
|
-
messages:
|
|
183
|
+
messages: AgentWidgetMessage[],
|
|
182
184
|
transform: MessageTransform
|
|
183
185
|
) => {
|
|
184
186
|
container.innerHTML = "";
|
|
@@ -259,9 +261,28 @@ export const createChatExperience = (
|
|
|
259
261
|
// Add typing indicator if streaming and there's at least one user message
|
|
260
262
|
if (isStreaming && messages.some((msg) => msg.role === "user")) {
|
|
261
263
|
const typingIndicator = createTypingIndicator();
|
|
264
|
+
|
|
265
|
+
// Create a bubble wrapper for the typing indicator (similar to assistant messages)
|
|
266
|
+
const typingBubble = document.createElement("div");
|
|
267
|
+
typingBubble.className = [
|
|
268
|
+
"tvw-max-w-[85%]",
|
|
269
|
+
"tvw-rounded-2xl",
|
|
270
|
+
"tvw-text-sm",
|
|
271
|
+
"tvw-leading-relaxed",
|
|
272
|
+
"tvw-shadow-sm",
|
|
273
|
+
"tvw-bg-cw-surface",
|
|
274
|
+
"tvw-border",
|
|
275
|
+
"tvw-border-cw-message-border",
|
|
276
|
+
"tvw-text-cw-primary",
|
|
277
|
+
"tvw-px-5",
|
|
278
|
+
"tvw-py-3"
|
|
279
|
+
].join(" ");
|
|
280
|
+
|
|
281
|
+
typingBubble.appendChild(typingIndicator);
|
|
282
|
+
|
|
262
283
|
const typingWrapper = document.createElement("div");
|
|
263
|
-
typingWrapper.className = "tvw-flex
|
|
264
|
-
typingWrapper.appendChild(
|
|
284
|
+
typingWrapper.className = "tvw-flex";
|
|
285
|
+
typingWrapper.appendChild(typingBubble);
|
|
265
286
|
fragment.appendChild(typingWrapper);
|
|
266
287
|
}
|
|
267
288
|
|
|
@@ -317,9 +338,14 @@ export const createChatExperience = (
|
|
|
317
338
|
introSubtitle.textContent =
|
|
318
339
|
config.copy?.welcomeSubtitle ??
|
|
319
340
|
"Ask anything about your account or products.";
|
|
320
|
-
textarea.placeholder = config.copy?.inputPlaceholder ?? "
|
|
321
|
-
|
|
322
|
-
|
|
341
|
+
textarea.placeholder = config.copy?.inputPlaceholder ?? "How can I help...";
|
|
342
|
+
|
|
343
|
+
// Only update send button text if NOT using icon mode
|
|
344
|
+
const useIcon = config.sendButton?.useIcon ?? false;
|
|
345
|
+
if (!useIcon) {
|
|
346
|
+
sendButton.textContent = config.copy?.sendButtonLabel ?? "Send";
|
|
347
|
+
}
|
|
348
|
+
|
|
323
349
|
// Update textarea font family and weight
|
|
324
350
|
const fontFamily = config.theme?.inputFontFamily ?? "sans-serif";
|
|
325
351
|
const fontWeight = config.theme?.inputFontWeight ?? "400";
|
|
@@ -340,7 +366,7 @@ export const createChatExperience = (
|
|
|
340
366
|
textarea.style.fontWeight = fontWeight;
|
|
341
367
|
};
|
|
342
368
|
|
|
343
|
-
session = new
|
|
369
|
+
session = new AgentWidgetSession(config, {
|
|
344
370
|
onMessagesChanged(messages) {
|
|
345
371
|
renderMessagesWithPlugins(messagesWrapper, messages, postprocess);
|
|
346
372
|
// Re-render suggestions to hide them after first user message
|
|
@@ -359,7 +385,7 @@ export const createChatExperience = (
|
|
|
359
385
|
},
|
|
360
386
|
onStatusChanged(status) {
|
|
361
387
|
const currentStatusConfig = config.statusIndicator ?? {};
|
|
362
|
-
const getCurrentStatusText = (status:
|
|
388
|
+
const getCurrentStatusText = (status: AgentWidgetSessionStatus): string => {
|
|
363
389
|
if (status === "idle") return currentStatusConfig.idleText ?? statusCopy.idle;
|
|
364
390
|
if (status === "connecting") return currentStatusConfig.connectingText ?? statusCopy.connecting;
|
|
365
391
|
if (status === "connected") return currentStatusConfig.connectedText ?? statusCopy.connected;
|
|
@@ -568,7 +594,7 @@ export const createChatExperience = (
|
|
|
568
594
|
};
|
|
569
595
|
|
|
570
596
|
// Function to create mic button dynamically
|
|
571
|
-
const createMicButton = (voiceConfig:
|
|
597
|
+
const createMicButton = (voiceConfig: AgentWidgetConfig['voiceRecognition'], sendButtonConfig: AgentWidgetConfig['sendButton']): { micButton: HTMLButtonElement; micButtonWrapper: HTMLElement } | null => {
|
|
572
598
|
const hasSpeechRecognition =
|
|
573
599
|
typeof window !== 'undefined' &&
|
|
574
600
|
(typeof (window as any).webkitSpeechRecognition !== 'undefined' ||
|
|
@@ -711,7 +737,7 @@ export const createChatExperience = (
|
|
|
711
737
|
return;
|
|
712
738
|
}
|
|
713
739
|
const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
|
|
714
|
-
const width = launcherWidth ?? "min(
|
|
740
|
+
const width = launcherWidth ?? "min(400px, calc(100vw - 24px))";
|
|
715
741
|
panel.style.width = width;
|
|
716
742
|
panel.style.maxWidth = width;
|
|
717
743
|
const viewportHeight = window.innerHeight;
|
|
@@ -777,6 +803,25 @@ export const createChatExperience = (
|
|
|
777
803
|
|
|
778
804
|
refreshCloseButton();
|
|
779
805
|
|
|
806
|
+
// Setup clear chat button click handler
|
|
807
|
+
const setupClearChatButton = () => {
|
|
808
|
+
const { clearChatButton } = panelElements;
|
|
809
|
+
if (!clearChatButton) return;
|
|
810
|
+
|
|
811
|
+
clearChatButton.addEventListener("click", () => {
|
|
812
|
+
// Clear messages in session (this will trigger onMessagesChanged which re-renders)
|
|
813
|
+
session.clearMessages();
|
|
814
|
+
|
|
815
|
+
// Dispatch custom event for external handlers (e.g., localStorage clearing in examples)
|
|
816
|
+
const clearEvent = new CustomEvent("vanilla-agent:clear-chat", {
|
|
817
|
+
detail: { timestamp: new Date().toISOString() }
|
|
818
|
+
});
|
|
819
|
+
window.dispatchEvent(clearEvent);
|
|
820
|
+
});
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
setupClearChatButton();
|
|
824
|
+
|
|
780
825
|
composerForm.addEventListener("submit", handleSubmit);
|
|
781
826
|
textarea.addEventListener("keydown", handleInputEnter);
|
|
782
827
|
|
|
@@ -796,7 +841,7 @@ export const createChatExperience = (
|
|
|
796
841
|
}
|
|
797
842
|
|
|
798
843
|
return {
|
|
799
|
-
update(nextConfig:
|
|
844
|
+
update(nextConfig: AgentWidgetConfig) {
|
|
800
845
|
config = { ...config, ...nextConfig };
|
|
801
846
|
applyThemeVariables(mount, config);
|
|
802
847
|
|
|
@@ -995,6 +1040,259 @@ export const createChatExperience = (
|
|
|
995
1040
|
closeButton.style.borderRadius = "";
|
|
996
1041
|
closeButton.classList.add("tvw-rounded-full");
|
|
997
1042
|
}
|
|
1043
|
+
|
|
1044
|
+
// Update padding
|
|
1045
|
+
if (launcher.closeButtonPaddingX) {
|
|
1046
|
+
closeButton.style.paddingLeft = launcher.closeButtonPaddingX;
|
|
1047
|
+
closeButton.style.paddingRight = launcher.closeButtonPaddingX;
|
|
1048
|
+
} else {
|
|
1049
|
+
closeButton.style.paddingLeft = "";
|
|
1050
|
+
closeButton.style.paddingRight = "";
|
|
1051
|
+
}
|
|
1052
|
+
if (launcher.closeButtonPaddingY) {
|
|
1053
|
+
closeButton.style.paddingTop = launcher.closeButtonPaddingY;
|
|
1054
|
+
closeButton.style.paddingBottom = launcher.closeButtonPaddingY;
|
|
1055
|
+
} else {
|
|
1056
|
+
closeButton.style.paddingTop = "";
|
|
1057
|
+
closeButton.style.paddingBottom = "";
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Update icon
|
|
1061
|
+
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
1062
|
+
const closeButtonIconText = launcher.closeButtonIconText ?? "×";
|
|
1063
|
+
|
|
1064
|
+
// Clear existing content and render new icon
|
|
1065
|
+
closeButton.innerHTML = "";
|
|
1066
|
+
const iconSvg = renderLucideIcon(closeButtonIconName, "20px", launcher.closeButtonColor || "", 2);
|
|
1067
|
+
if (iconSvg) {
|
|
1068
|
+
closeButton.appendChild(iconSvg);
|
|
1069
|
+
} else {
|
|
1070
|
+
closeButton.textContent = closeButtonIconText;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Update tooltip
|
|
1074
|
+
const { closeButtonWrapper } = panelElements;
|
|
1075
|
+
const closeButtonTooltipText = launcher.closeButtonTooltipText ?? "Close chat";
|
|
1076
|
+
const closeButtonShowTooltip = launcher.closeButtonShowTooltip ?? true;
|
|
1077
|
+
|
|
1078
|
+
closeButton.setAttribute("aria-label", closeButtonTooltipText);
|
|
1079
|
+
|
|
1080
|
+
if (closeButtonWrapper) {
|
|
1081
|
+
// Clean up old tooltip event listeners if they exist
|
|
1082
|
+
if ((closeButtonWrapper as any)._cleanupTooltip) {
|
|
1083
|
+
(closeButtonWrapper as any)._cleanupTooltip();
|
|
1084
|
+
delete (closeButtonWrapper as any)._cleanupTooltip;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Set up new portaled tooltip with event listeners
|
|
1088
|
+
if (closeButtonShowTooltip && closeButtonTooltipText) {
|
|
1089
|
+
let portaledTooltip: HTMLElement | null = null;
|
|
1090
|
+
|
|
1091
|
+
const showTooltip = () => {
|
|
1092
|
+
if (portaledTooltip || !closeButton) return; // Already showing or button doesn't exist
|
|
1093
|
+
|
|
1094
|
+
// Create tooltip element
|
|
1095
|
+
portaledTooltip = createElement("div", "tvw-clear-chat-tooltip");
|
|
1096
|
+
portaledTooltip.textContent = closeButtonTooltipText;
|
|
1097
|
+
|
|
1098
|
+
// Add arrow
|
|
1099
|
+
const arrow = createElement("div");
|
|
1100
|
+
arrow.className = "tvw-clear-chat-tooltip-arrow";
|
|
1101
|
+
portaledTooltip.appendChild(arrow);
|
|
1102
|
+
|
|
1103
|
+
// Get button position
|
|
1104
|
+
const buttonRect = closeButton.getBoundingClientRect();
|
|
1105
|
+
|
|
1106
|
+
// Position tooltip above button
|
|
1107
|
+
portaledTooltip.style.position = "fixed";
|
|
1108
|
+
portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
|
|
1109
|
+
portaledTooltip.style.top = `${buttonRect.top - 8}px`;
|
|
1110
|
+
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
1111
|
+
|
|
1112
|
+
// Append to body
|
|
1113
|
+
document.body.appendChild(portaledTooltip);
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
const hideTooltip = () => {
|
|
1117
|
+
if (portaledTooltip && portaledTooltip.parentNode) {
|
|
1118
|
+
portaledTooltip.parentNode.removeChild(portaledTooltip);
|
|
1119
|
+
portaledTooltip = null;
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
// Add event listeners
|
|
1124
|
+
closeButtonWrapper.addEventListener("mouseenter", showTooltip);
|
|
1125
|
+
closeButtonWrapper.addEventListener("mouseleave", hideTooltip);
|
|
1126
|
+
closeButton.addEventListener("focus", showTooltip);
|
|
1127
|
+
closeButton.addEventListener("blur", hideTooltip);
|
|
1128
|
+
|
|
1129
|
+
// Store cleanup function on the wrapper for later use
|
|
1130
|
+
(closeButtonWrapper as any)._cleanupTooltip = () => {
|
|
1131
|
+
hideTooltip();
|
|
1132
|
+
if (closeButtonWrapper) {
|
|
1133
|
+
closeButtonWrapper.removeEventListener("mouseenter", showTooltip);
|
|
1134
|
+
closeButtonWrapper.removeEventListener("mouseleave", hideTooltip);
|
|
1135
|
+
}
|
|
1136
|
+
if (closeButton) {
|
|
1137
|
+
closeButton.removeEventListener("focus", showTooltip);
|
|
1138
|
+
closeButton.removeEventListener("blur", hideTooltip);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Update clear chat button styling from config
|
|
1146
|
+
const { clearChatButton, clearChatButtonWrapper } = panelElements;
|
|
1147
|
+
if (clearChatButton) {
|
|
1148
|
+
const clearChatConfig = launcher.clearChat ?? {};
|
|
1149
|
+
const clearChatEnabled = clearChatConfig.enabled ?? true;
|
|
1150
|
+
|
|
1151
|
+
// Show/hide button based on enabled state
|
|
1152
|
+
if (clearChatButtonWrapper) {
|
|
1153
|
+
clearChatButtonWrapper.style.display = clearChatEnabled ? "" : "none";
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (clearChatEnabled) {
|
|
1157
|
+
// Update size
|
|
1158
|
+
const clearChatSize = clearChatConfig.size ?? "32px";
|
|
1159
|
+
clearChatButton.style.height = clearChatSize;
|
|
1160
|
+
clearChatButton.style.width = clearChatSize;
|
|
1161
|
+
|
|
1162
|
+
// Update icon
|
|
1163
|
+
const clearChatIconName = clearChatConfig.iconName ?? "refresh-cw";
|
|
1164
|
+
const clearChatIconColor = clearChatConfig.iconColor ?? "";
|
|
1165
|
+
|
|
1166
|
+
// Clear existing icon and render new one
|
|
1167
|
+
clearChatButton.innerHTML = "";
|
|
1168
|
+
const iconSvg = renderLucideIcon(clearChatIconName, "20px", clearChatIconColor || "", 2);
|
|
1169
|
+
if (iconSvg) {
|
|
1170
|
+
clearChatButton.appendChild(iconSvg);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Update icon color
|
|
1174
|
+
if (clearChatIconColor) {
|
|
1175
|
+
clearChatButton.style.color = clearChatIconColor;
|
|
1176
|
+
clearChatButton.classList.remove("tvw-text-cw-muted");
|
|
1177
|
+
} else {
|
|
1178
|
+
clearChatButton.style.color = "";
|
|
1179
|
+
clearChatButton.classList.add("tvw-text-cw-muted");
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Update background color
|
|
1183
|
+
if (clearChatConfig.backgroundColor) {
|
|
1184
|
+
clearChatButton.style.backgroundColor = clearChatConfig.backgroundColor;
|
|
1185
|
+
clearChatButton.classList.remove("hover:tvw-bg-gray-100");
|
|
1186
|
+
} else {
|
|
1187
|
+
clearChatButton.style.backgroundColor = "";
|
|
1188
|
+
clearChatButton.classList.add("hover:tvw-bg-gray-100");
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Update border
|
|
1192
|
+
if (clearChatConfig.borderWidth || clearChatConfig.borderColor) {
|
|
1193
|
+
const borderWidth = clearChatConfig.borderWidth || "0px";
|
|
1194
|
+
const borderColor = clearChatConfig.borderColor || "transparent";
|
|
1195
|
+
clearChatButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
1196
|
+
clearChatButton.classList.remove("tvw-border-none");
|
|
1197
|
+
} else {
|
|
1198
|
+
clearChatButton.style.border = "";
|
|
1199
|
+
clearChatButton.classList.add("tvw-border-none");
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// Update border radius
|
|
1203
|
+
if (clearChatConfig.borderRadius) {
|
|
1204
|
+
clearChatButton.style.borderRadius = clearChatConfig.borderRadius;
|
|
1205
|
+
clearChatButton.classList.remove("tvw-rounded-full");
|
|
1206
|
+
} else {
|
|
1207
|
+
clearChatButton.style.borderRadius = "";
|
|
1208
|
+
clearChatButton.classList.add("tvw-rounded-full");
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Update padding
|
|
1212
|
+
if (clearChatConfig.paddingX) {
|
|
1213
|
+
clearChatButton.style.paddingLeft = clearChatConfig.paddingX;
|
|
1214
|
+
clearChatButton.style.paddingRight = clearChatConfig.paddingX;
|
|
1215
|
+
} else {
|
|
1216
|
+
clearChatButton.style.paddingLeft = "";
|
|
1217
|
+
clearChatButton.style.paddingRight = "";
|
|
1218
|
+
}
|
|
1219
|
+
if (clearChatConfig.paddingY) {
|
|
1220
|
+
clearChatButton.style.paddingTop = clearChatConfig.paddingY;
|
|
1221
|
+
clearChatButton.style.paddingBottom = clearChatConfig.paddingY;
|
|
1222
|
+
} else {
|
|
1223
|
+
clearChatButton.style.paddingTop = "";
|
|
1224
|
+
clearChatButton.style.paddingBottom = "";
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const clearChatTooltipText = clearChatConfig.tooltipText ?? "Clear chat";
|
|
1228
|
+
const clearChatShowTooltip = clearChatConfig.showTooltip ?? true;
|
|
1229
|
+
|
|
1230
|
+
clearChatButton.setAttribute("aria-label", clearChatTooltipText);
|
|
1231
|
+
|
|
1232
|
+
if (clearChatButtonWrapper) {
|
|
1233
|
+
// Clean up old tooltip event listeners if they exist
|
|
1234
|
+
if ((clearChatButtonWrapper as any)._cleanupTooltip) {
|
|
1235
|
+
(clearChatButtonWrapper as any)._cleanupTooltip();
|
|
1236
|
+
delete (clearChatButtonWrapper as any)._cleanupTooltip;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Set up new portaled tooltip with event listeners
|
|
1240
|
+
if (clearChatShowTooltip && clearChatTooltipText) {
|
|
1241
|
+
let portaledTooltip: HTMLElement | null = null;
|
|
1242
|
+
|
|
1243
|
+
const showTooltip = () => {
|
|
1244
|
+
if (portaledTooltip || !clearChatButton) return; // Already showing or button doesn't exist
|
|
1245
|
+
|
|
1246
|
+
// Create tooltip element
|
|
1247
|
+
portaledTooltip = createElement("div", "tvw-clear-chat-tooltip");
|
|
1248
|
+
portaledTooltip.textContent = clearChatTooltipText;
|
|
1249
|
+
|
|
1250
|
+
// Add arrow
|
|
1251
|
+
const arrow = createElement("div");
|
|
1252
|
+
arrow.className = "tvw-clear-chat-tooltip-arrow";
|
|
1253
|
+
portaledTooltip.appendChild(arrow);
|
|
1254
|
+
|
|
1255
|
+
// Get button position
|
|
1256
|
+
const buttonRect = clearChatButton.getBoundingClientRect();
|
|
1257
|
+
|
|
1258
|
+
// Position tooltip above button
|
|
1259
|
+
portaledTooltip.style.position = "fixed";
|
|
1260
|
+
portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
|
|
1261
|
+
portaledTooltip.style.top = `${buttonRect.top - 8}px`;
|
|
1262
|
+
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
1263
|
+
|
|
1264
|
+
// Append to body
|
|
1265
|
+
document.body.appendChild(portaledTooltip);
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
const hideTooltip = () => {
|
|
1269
|
+
if (portaledTooltip && portaledTooltip.parentNode) {
|
|
1270
|
+
portaledTooltip.parentNode.removeChild(portaledTooltip);
|
|
1271
|
+
portaledTooltip = null;
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
// Add event listeners
|
|
1276
|
+
clearChatButtonWrapper.addEventListener("mouseenter", showTooltip);
|
|
1277
|
+
clearChatButtonWrapper.addEventListener("mouseleave", hideTooltip);
|
|
1278
|
+
clearChatButton.addEventListener("focus", showTooltip);
|
|
1279
|
+
clearChatButton.addEventListener("blur", hideTooltip);
|
|
1280
|
+
|
|
1281
|
+
// Store cleanup function on the button for later use
|
|
1282
|
+
(clearChatButtonWrapper as any)._cleanupTooltip = () => {
|
|
1283
|
+
hideTooltip();
|
|
1284
|
+
if (clearChatButtonWrapper) {
|
|
1285
|
+
clearChatButtonWrapper.removeEventListener("mouseenter", showTooltip);
|
|
1286
|
+
clearChatButtonWrapper.removeEventListener("mouseleave", hideTooltip);
|
|
1287
|
+
}
|
|
1288
|
+
if (clearChatButton) {
|
|
1289
|
+
clearChatButton.removeEventListener("focus", showTooltip);
|
|
1290
|
+
clearChatButton.removeEventListener("blur", hideTooltip);
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
998
1296
|
}
|
|
999
1297
|
|
|
1000
1298
|
postprocess = buildPostprocessor(config);
|
|
@@ -1289,7 +1587,7 @@ export const createChatExperience = (
|
|
|
1289
1587
|
// Update status text if status is currently set
|
|
1290
1588
|
if (session) {
|
|
1291
1589
|
const currentStatus = session.getStatus();
|
|
1292
|
-
const getCurrentStatusText = (status:
|
|
1590
|
+
const getCurrentStatusText = (status: AgentWidgetSessionStatus): string => {
|
|
1293
1591
|
if (status === "idle") return statusIndicatorConfig.idleText ?? statusCopy.idle;
|
|
1294
1592
|
if (status === "connecting") return statusIndicatorConfig.connectingText ?? statusCopy.connecting;
|
|
1295
1593
|
if (status === "connected") return statusIndicatorConfig.connectedText ?? statusCopy.connected;
|
|
@@ -1311,6 +1609,16 @@ export const createChatExperience = (
|
|
|
1311
1609
|
if (!launcherEnabled) return;
|
|
1312
1610
|
setOpenState(!open);
|
|
1313
1611
|
},
|
|
1612
|
+
clearChat() {
|
|
1613
|
+
// Clear messages in session (this will trigger onMessagesChanged which re-renders)
|
|
1614
|
+
session.clearMessages();
|
|
1615
|
+
|
|
1616
|
+
// Dispatch custom event for external handlers (e.g., localStorage clearing in examples)
|
|
1617
|
+
const clearEvent = new CustomEvent("vanilla-agent:clear-chat", {
|
|
1618
|
+
detail: { timestamp: new Date().toISOString() }
|
|
1619
|
+
});
|
|
1620
|
+
window.dispatchEvent(clearEvent);
|
|
1621
|
+
},
|
|
1314
1622
|
destroy() {
|
|
1315
1623
|
destroyCallbacks.forEach((cb) => cb());
|
|
1316
1624
|
wrapper.remove();
|
|
@@ -1322,4 +1630,4 @@ export const createChatExperience = (
|
|
|
1322
1630
|
};
|
|
1323
1631
|
};
|
|
1324
1632
|
|
|
1325
|
-
export type
|
|
1633
|
+
export type AgentWidgetController = Controller;
|
package/src/utils/constants.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AgentWidgetSessionStatus } from "../session";
|
|
2
2
|
|
|
3
|
-
export const statusCopy: Record<
|
|
3
|
+
export const statusCopy: Record<AgentWidgetSessionStatus, string> = {
|
|
4
4
|
idle: "Online",
|
|
5
5
|
connecting: "Connecting…",
|
|
6
6
|
connected: "Streaming…",
|
|
@@ -9,3 +9,5 @@ export const statusCopy: Record<ChatWidgetSessionStatus, string> = {
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
|
|
13
|
+
|