vanilla-agent 1.4.1 → 1.6.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 +43 -5
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +163 -23
- package/dist/index.d.ts +163 -23
- package/dist/index.global.js +52 -52
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/widget.css +25 -8
- package/package.json +1 -1
- package/src/client.ts +115 -21
- package/src/components/launcher.ts +1 -1
- package/src/components/message-bubble.ts +6 -1
- package/src/components/reasoning-bubble.ts +2 -0
- package/src/components/tool-bubble.ts +2 -0
- package/src/index.ts +6 -0
- package/src/runtime/init.ts +5 -33
- package/src/session.ts +15 -0
- package/src/styles/widget.css +25 -8
- package/src/types.ts +140 -1
- package/src/ui.ts +414 -20
- package/src/utils/actions.ts +228 -0
- package/src/utils/events.ts +41 -0
- package/src/utils/formatting.ts +22 -10
- package/src/utils/storage.ts +72 -0
package/src/ui.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { escapeHtml } from "./postprocessors";
|
|
2
2
|
import { AgentWidgetSession, AgentWidgetSessionStatus } from "./session";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AgentWidgetConfig,
|
|
5
|
+
AgentWidgetMessage,
|
|
6
|
+
AgentWidgetEvent,
|
|
7
|
+
AgentWidgetStorageAdapter,
|
|
8
|
+
AgentWidgetStoredState,
|
|
9
|
+
AgentWidgetControllerEventMap,
|
|
10
|
+
AgentWidgetVoiceStateEvent
|
|
11
|
+
} from "./types";
|
|
4
12
|
import { applyThemeVariables } from "./utils/theme";
|
|
5
13
|
import { renderLucideIcon } from "./utils/icons";
|
|
6
14
|
import { createElement } from "./utils/dom";
|
|
@@ -15,9 +23,29 @@ import { createSuggestions } from "./components/suggestions";
|
|
|
15
23
|
import { enhanceWithForms } from "./components/forms";
|
|
16
24
|
import { pluginRegistry } from "./plugins/registry";
|
|
17
25
|
import { mergeWithDefaults } from "./defaults";
|
|
26
|
+
import { createEventBus } from "./utils/events";
|
|
27
|
+
import {
|
|
28
|
+
createActionManager,
|
|
29
|
+
defaultActionHandlers,
|
|
30
|
+
defaultJsonActionParser
|
|
31
|
+
} from "./utils/actions";
|
|
18
32
|
|
|
19
33
|
// Default localStorage key for chat history (automatically cleared on clear chat)
|
|
20
34
|
const DEFAULT_CHAT_HISTORY_STORAGE_KEY = "vanilla-agent-chat-history";
|
|
35
|
+
const VOICE_STATE_RESTORE_WINDOW = 30 * 1000;
|
|
36
|
+
|
|
37
|
+
const ensureRecord = (value: unknown): Record<string, unknown> => {
|
|
38
|
+
if (!value || typeof value !== "object") {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
return { ...(value as Record<string, unknown>) };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const stripStreamingFromMessages = (messages: AgentWidgetMessage[]) =>
|
|
45
|
+
messages.map((message) => ({
|
|
46
|
+
...message,
|
|
47
|
+
streaming: false
|
|
48
|
+
}));
|
|
21
49
|
|
|
22
50
|
type Controller = {
|
|
23
51
|
update: (config: AgentWidgetConfig) => void;
|
|
@@ -31,23 +59,58 @@ type Controller = {
|
|
|
31
59
|
startVoiceRecognition: () => boolean;
|
|
32
60
|
stopVoiceRecognition: () => boolean;
|
|
33
61
|
injectTestMessage: (event: AgentWidgetEvent) => void;
|
|
62
|
+
getMessages: () => AgentWidgetMessage[];
|
|
63
|
+
getStatus: () => AgentWidgetSessionStatus;
|
|
64
|
+
getPersistentMetadata: () => Record<string, unknown>;
|
|
65
|
+
updatePersistentMetadata: (
|
|
66
|
+
updater: (prev: Record<string, unknown>) => Record<string, unknown>
|
|
67
|
+
) => void;
|
|
68
|
+
on: <K extends keyof AgentWidgetControllerEventMap>(
|
|
69
|
+
event: K,
|
|
70
|
+
handler: (payload: AgentWidgetControllerEventMap[K]) => void
|
|
71
|
+
) => () => void;
|
|
72
|
+
off: <K extends keyof AgentWidgetControllerEventMap>(
|
|
73
|
+
event: K,
|
|
74
|
+
handler: (payload: AgentWidgetControllerEventMap[K]) => void
|
|
75
|
+
) => void;
|
|
34
76
|
};
|
|
35
77
|
|
|
36
|
-
const buildPostprocessor = (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
78
|
+
const buildPostprocessor = (
|
|
79
|
+
cfg: AgentWidgetConfig | undefined,
|
|
80
|
+
actionManager?: ReturnType<typeof createActionManager>
|
|
81
|
+
): MessageTransform => {
|
|
82
|
+
return (context) => {
|
|
83
|
+
let nextText = context.text ?? "";
|
|
84
|
+
const rawPayload = context.message.rawContent ?? null;
|
|
85
|
+
|
|
86
|
+
if (actionManager) {
|
|
87
|
+
const actionOverride = actionManager.process({
|
|
88
|
+
text: nextText,
|
|
89
|
+
raw: rawPayload ?? nextText,
|
|
41
90
|
message: context.message,
|
|
42
91
|
streaming: context.streaming
|
|
43
92
|
});
|
|
44
|
-
|
|
45
|
-
|
|
93
|
+
if (actionOverride !== null) {
|
|
94
|
+
nextText = actionOverride;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (cfg?.postprocessMessage) {
|
|
99
|
+
return cfg.postprocessMessage({
|
|
100
|
+
...context,
|
|
101
|
+
text: nextText,
|
|
102
|
+
raw: rawPayload ?? context.text ?? ""
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return escapeHtml(nextText);
|
|
107
|
+
};
|
|
46
108
|
};
|
|
47
109
|
|
|
48
110
|
export const createAgentExperience = (
|
|
49
111
|
mount: HTMLElement,
|
|
50
|
-
initialConfig?: AgentWidgetConfig
|
|
112
|
+
initialConfig?: AgentWidgetConfig,
|
|
113
|
+
runtimeOptions?: { debugTools?: boolean }
|
|
51
114
|
): Controller => {
|
|
52
115
|
// Tailwind config uses important: "#vanilla-agent-root", so ensure mount has this ID
|
|
53
116
|
if (!mount.id || mount.id !== "vanilla-agent-root") {
|
|
@@ -59,13 +122,70 @@ export const createAgentExperience = (
|
|
|
59
122
|
|
|
60
123
|
// Get plugins for this instance
|
|
61
124
|
const plugins = pluginRegistry.getForInstance(config.plugins);
|
|
125
|
+
const eventBus = createEventBus<AgentWidgetControllerEventMap>();
|
|
126
|
+
|
|
127
|
+
const storageAdapter: AgentWidgetStorageAdapter | undefined =
|
|
128
|
+
config.storageAdapter;
|
|
129
|
+
let persistentMetadata: Record<string, unknown> = {};
|
|
130
|
+
let pendingStoredState: Promise<AgentWidgetStoredState | null> | null = null;
|
|
131
|
+
|
|
132
|
+
if (storageAdapter?.load) {
|
|
133
|
+
try {
|
|
134
|
+
const storedState = storageAdapter.load();
|
|
135
|
+
if (storedState && typeof (storedState as Promise<any>).then === "function") {
|
|
136
|
+
pendingStoredState = storedState as Promise<AgentWidgetStoredState | null>;
|
|
137
|
+
} else if (storedState) {
|
|
138
|
+
const immediateState = storedState as AgentWidgetStoredState;
|
|
139
|
+
if (immediateState.metadata) {
|
|
140
|
+
persistentMetadata = ensureRecord(immediateState.metadata);
|
|
141
|
+
}
|
|
142
|
+
if (immediateState.messages?.length) {
|
|
143
|
+
config = { ...config, initialMessages: immediateState.messages };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (typeof console !== "undefined") {
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.error("[AgentWidget] Failed to load stored state:", error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const getMetadata = () => persistentMetadata;
|
|
155
|
+
const updateMetadata = (
|
|
156
|
+
updater: (prev: Record<string, unknown>) => Record<string, unknown>
|
|
157
|
+
) => {
|
|
158
|
+
const next = updater({ ...persistentMetadata }) ?? {};
|
|
159
|
+
persistentMetadata = next;
|
|
160
|
+
persistState();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const resolvedActionParsers =
|
|
164
|
+
config.actionParsers && config.actionParsers.length
|
|
165
|
+
? config.actionParsers
|
|
166
|
+
: [defaultJsonActionParser];
|
|
167
|
+
|
|
168
|
+
const resolvedActionHandlers =
|
|
169
|
+
config.actionHandlers && config.actionHandlers.length
|
|
170
|
+
? config.actionHandlers
|
|
171
|
+
: [defaultActionHandlers.message, defaultActionHandlers.messageAndClick];
|
|
172
|
+
|
|
173
|
+
let actionManager = createActionManager({
|
|
174
|
+
parsers: resolvedActionParsers,
|
|
175
|
+
handlers: resolvedActionHandlers,
|
|
176
|
+
getMetadata,
|
|
177
|
+
updateMetadata,
|
|
178
|
+
emit: eventBus.emit,
|
|
179
|
+
documentRef: typeof document !== "undefined" ? document : null
|
|
180
|
+
});
|
|
181
|
+
actionManager.syncFromMetadata();
|
|
62
182
|
|
|
63
183
|
let launcherEnabled = config.launcher?.enabled ?? true;
|
|
64
184
|
let autoExpand = config.launcher?.autoExpand ?? false;
|
|
65
185
|
let prevAutoExpand = autoExpand;
|
|
66
186
|
let prevLauncherEnabled = launcherEnabled;
|
|
67
187
|
let open = launcherEnabled ? autoExpand : true;
|
|
68
|
-
let postprocess = buildPostprocessor(config);
|
|
188
|
+
let postprocess = buildPostprocessor(config, actionManager);
|
|
69
189
|
let showReasoning = config.features?.showReasoning ?? true;
|
|
70
190
|
let showToolCalls = config.features?.showToolCalls ?? true;
|
|
71
191
|
|
|
@@ -121,6 +241,77 @@ export const createAgentExperience = (
|
|
|
121
241
|
const AUTO_SCROLL_BLOCK_TIME = 2000;
|
|
122
242
|
const USER_SCROLL_THRESHOLD = 5;
|
|
123
243
|
const BOTTOM_THRESHOLD = 50;
|
|
244
|
+
const messageState = new Map<
|
|
245
|
+
string,
|
|
246
|
+
{ streaming?: boolean; role: AgentWidgetMessage["role"] }
|
|
247
|
+
>();
|
|
248
|
+
const voiceState = {
|
|
249
|
+
active: false,
|
|
250
|
+
manuallyDeactivated: false,
|
|
251
|
+
lastUserMessageWasVoice: false
|
|
252
|
+
};
|
|
253
|
+
const voiceAutoResumeMode = config.voiceRecognition?.autoResume ?? false;
|
|
254
|
+
const emitVoiceState = (source: AgentWidgetVoiceStateEvent["source"]) => {
|
|
255
|
+
eventBus.emit("voice:state", {
|
|
256
|
+
active: voiceState.active,
|
|
257
|
+
source,
|
|
258
|
+
timestamp: Date.now()
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
const persistVoiceMetadata = () => {
|
|
262
|
+
updateMetadata((prev) => ({
|
|
263
|
+
...prev,
|
|
264
|
+
voiceState: {
|
|
265
|
+
active: voiceState.active,
|
|
266
|
+
timestamp: Date.now(),
|
|
267
|
+
manuallyDeactivated: voiceState.manuallyDeactivated
|
|
268
|
+
}
|
|
269
|
+
}));
|
|
270
|
+
};
|
|
271
|
+
const maybeRestoreVoiceFromMetadata = () => {
|
|
272
|
+
if (config.voiceRecognition?.enabled === false) return;
|
|
273
|
+
const rawVoiceState = ensureRecord((persistentMetadata as any).voiceState);
|
|
274
|
+
const wasActive = Boolean(rawVoiceState.active);
|
|
275
|
+
const timestamp = Number(rawVoiceState.timestamp ?? 0);
|
|
276
|
+
voiceState.manuallyDeactivated = Boolean(rawVoiceState.manuallyDeactivated);
|
|
277
|
+
if (wasActive && Date.now() - timestamp < VOICE_STATE_RESTORE_WINDOW) {
|
|
278
|
+
setTimeout(() => {
|
|
279
|
+
if (!voiceState.active) {
|
|
280
|
+
voiceState.manuallyDeactivated = false;
|
|
281
|
+
startVoiceRecognition("restore");
|
|
282
|
+
}
|
|
283
|
+
}, 1000);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const getMessagesForPersistence = () =>
|
|
288
|
+
session ? stripStreamingFromMessages(session.getMessages()) : [];
|
|
289
|
+
|
|
290
|
+
function persistState(messagesOverride?: AgentWidgetMessage[]) {
|
|
291
|
+
if (!storageAdapter?.save || !session) return;
|
|
292
|
+
const payload = {
|
|
293
|
+
messages: messagesOverride
|
|
294
|
+
? stripStreamingFromMessages(messagesOverride)
|
|
295
|
+
: getMessagesForPersistence(),
|
|
296
|
+
metadata: persistentMetadata
|
|
297
|
+
};
|
|
298
|
+
try {
|
|
299
|
+
const result = storageAdapter.save(payload);
|
|
300
|
+
if (result instanceof Promise) {
|
|
301
|
+
result.catch((error) => {
|
|
302
|
+
if (typeof console !== "undefined") {
|
|
303
|
+
// eslint-disable-next-line no-console
|
|
304
|
+
console.error("[AgentWidget] Failed to persist state:", error);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (typeof console !== "undefined") {
|
|
310
|
+
// eslint-disable-next-line no-console
|
|
311
|
+
console.error("[AgentWidget] Failed to persist state:", error);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
124
315
|
|
|
125
316
|
const scheduleAutoScroll = (force = false) => {
|
|
126
317
|
if (!shouldAutoScroll) return;
|
|
@@ -224,6 +415,38 @@ export const createAgentExperience = (
|
|
|
224
415
|
smoothScrollRAF = requestAnimationFrame(animate);
|
|
225
416
|
};
|
|
226
417
|
|
|
418
|
+
const trackMessages = (messages: AgentWidgetMessage[]) => {
|
|
419
|
+
const nextState = new Map<
|
|
420
|
+
string,
|
|
421
|
+
{ streaming?: boolean; role: AgentWidgetMessage["role"] }
|
|
422
|
+
>();
|
|
423
|
+
|
|
424
|
+
messages.forEach((message) => {
|
|
425
|
+
const previous = messageState.get(message.id);
|
|
426
|
+
nextState.set(message.id, {
|
|
427
|
+
streaming: message.streaming,
|
|
428
|
+
role: message.role
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
if (!previous && message.role === "assistant") {
|
|
432
|
+
eventBus.emit("assistant:message", message);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (
|
|
436
|
+
message.role === "assistant" &&
|
|
437
|
+
previous?.streaming &&
|
|
438
|
+
message.streaming === false
|
|
439
|
+
) {
|
|
440
|
+
eventBus.emit("assistant:complete", message);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
messageState.clear();
|
|
445
|
+
nextState.forEach((value, key) => {
|
|
446
|
+
messageState.set(key, value);
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
|
|
227
450
|
|
|
228
451
|
// Message rendering with plugin support
|
|
229
452
|
const renderMessagesWithPlugins = (
|
|
@@ -445,6 +668,13 @@ export const createAgentExperience = (
|
|
|
445
668
|
}
|
|
446
669
|
}
|
|
447
670
|
scheduleAutoScroll(!isStreaming);
|
|
671
|
+
trackMessages(messages);
|
|
672
|
+
|
|
673
|
+
const lastUserMessage = [...messages]
|
|
674
|
+
.reverse()
|
|
675
|
+
.find((msg) => msg.role === "user");
|
|
676
|
+
voiceState.lastUserMessageWasVoice = Boolean(lastUserMessage?.viaVoice);
|
|
677
|
+
persistState(messages);
|
|
448
678
|
},
|
|
449
679
|
onStatusChanged(status) {
|
|
450
680
|
const currentStatusConfig = config.statusIndicator ?? {};
|
|
@@ -470,6 +700,26 @@ export const createAgentExperience = (
|
|
|
470
700
|
}
|
|
471
701
|
});
|
|
472
702
|
|
|
703
|
+
if (pendingStoredState) {
|
|
704
|
+
pendingStoredState
|
|
705
|
+
.then((state) => {
|
|
706
|
+
if (!state) return;
|
|
707
|
+
if (state.metadata) {
|
|
708
|
+
persistentMetadata = ensureRecord(state.metadata);
|
|
709
|
+
actionManager.syncFromMetadata();
|
|
710
|
+
}
|
|
711
|
+
if (state.messages?.length) {
|
|
712
|
+
session.hydrateMessages(state.messages);
|
|
713
|
+
}
|
|
714
|
+
})
|
|
715
|
+
.catch((error) => {
|
|
716
|
+
if (typeof console !== "undefined") {
|
|
717
|
+
// eslint-disable-next-line no-console
|
|
718
|
+
console.error("[AgentWidget] Failed to hydrate stored state:", error);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
|
|
473
723
|
const handleSubmit = (event: Event) => {
|
|
474
724
|
event.preventDefault();
|
|
475
725
|
const value = textarea.value.trim();
|
|
@@ -500,7 +750,9 @@ export const createAgentExperience = (
|
|
|
500
750
|
return (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition || null;
|
|
501
751
|
};
|
|
502
752
|
|
|
503
|
-
const startVoiceRecognition = (
|
|
753
|
+
const startVoiceRecognition = (
|
|
754
|
+
source: AgentWidgetVoiceStateEvent["source"] = "user"
|
|
755
|
+
) => {
|
|
504
756
|
if (isRecording || session.isStreaming()) return;
|
|
505
757
|
|
|
506
758
|
const SpeechRecognitionClass = getSpeechRecognitionClass();
|
|
@@ -579,6 +831,12 @@ export const createAgentExperience = (
|
|
|
579
831
|
try {
|
|
580
832
|
speechRecognition.start();
|
|
581
833
|
isRecording = true;
|
|
834
|
+
voiceState.active = true;
|
|
835
|
+
if (source !== "system") {
|
|
836
|
+
voiceState.manuallyDeactivated = false;
|
|
837
|
+
}
|
|
838
|
+
emitVoiceState(source);
|
|
839
|
+
persistVoiceMetadata();
|
|
582
840
|
if (micButton) {
|
|
583
841
|
// Store original styles
|
|
584
842
|
originalMicStyles = {
|
|
@@ -612,11 +870,13 @@ export const createAgentExperience = (
|
|
|
612
870
|
micButton.setAttribute("aria-label", "Stop voice recognition");
|
|
613
871
|
}
|
|
614
872
|
} catch (error) {
|
|
615
|
-
stopVoiceRecognition();
|
|
873
|
+
stopVoiceRecognition("system");
|
|
616
874
|
}
|
|
617
875
|
};
|
|
618
876
|
|
|
619
|
-
const stopVoiceRecognition = (
|
|
877
|
+
const stopVoiceRecognition = (
|
|
878
|
+
source: AgentWidgetVoiceStateEvent["source"] = "user"
|
|
879
|
+
) => {
|
|
620
880
|
if (!isRecording) return;
|
|
621
881
|
|
|
622
882
|
isRecording = false;
|
|
@@ -634,6 +894,10 @@ export const createAgentExperience = (
|
|
|
634
894
|
speechRecognition = null;
|
|
635
895
|
}
|
|
636
896
|
|
|
897
|
+
voiceState.active = false;
|
|
898
|
+
emitVoiceState(source);
|
|
899
|
+
persistVoiceMetadata();
|
|
900
|
+
|
|
637
901
|
if (micButton) {
|
|
638
902
|
micButton.classList.remove("tvw-voice-recording");
|
|
639
903
|
|
|
@@ -754,14 +1018,18 @@ export const createAgentExperience = (
|
|
|
754
1018
|
if (isRecording) {
|
|
755
1019
|
// Stop recording and submit
|
|
756
1020
|
const finalValue = textarea.value.trim();
|
|
757
|
-
|
|
1021
|
+
voiceState.manuallyDeactivated = true;
|
|
1022
|
+
persistVoiceMetadata();
|
|
1023
|
+
stopVoiceRecognition("user");
|
|
758
1024
|
if (finalValue) {
|
|
759
1025
|
textarea.value = "";
|
|
760
1026
|
session.sendMessage(finalValue);
|
|
761
1027
|
}
|
|
762
1028
|
} else {
|
|
763
1029
|
// Start recording
|
|
764
|
-
|
|
1030
|
+
voiceState.manuallyDeactivated = false;
|
|
1031
|
+
persistVoiceMetadata();
|
|
1032
|
+
startVoiceRecognition("user");
|
|
765
1033
|
}
|
|
766
1034
|
};
|
|
767
1035
|
|
|
@@ -769,13 +1037,27 @@ export const createAgentExperience = (
|
|
|
769
1037
|
micButton.addEventListener("click", handleMicButtonClick);
|
|
770
1038
|
|
|
771
1039
|
destroyCallbacks.push(() => {
|
|
772
|
-
stopVoiceRecognition();
|
|
1040
|
+
stopVoiceRecognition("system");
|
|
773
1041
|
if (micButton) {
|
|
774
1042
|
micButton.removeEventListener("click", handleMicButtonClick);
|
|
775
1043
|
}
|
|
776
1044
|
});
|
|
777
1045
|
}
|
|
778
1046
|
|
|
1047
|
+
const autoResumeUnsub = eventBus.on("assistant:complete", () => {
|
|
1048
|
+
if (!voiceAutoResumeMode) return;
|
|
1049
|
+
if (voiceState.active || voiceState.manuallyDeactivated) return;
|
|
1050
|
+
if (voiceAutoResumeMode === "assistant" && !voiceState.lastUserMessageWasVoice) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
setTimeout(() => {
|
|
1054
|
+
if (!voiceState.active && !voiceState.manuallyDeactivated) {
|
|
1055
|
+
startVoiceRecognition("auto");
|
|
1056
|
+
}
|
|
1057
|
+
}, 600);
|
|
1058
|
+
});
|
|
1059
|
+
destroyCallbacks.push(autoResumeUnsub);
|
|
1060
|
+
|
|
779
1061
|
const toggleOpen = () => {
|
|
780
1062
|
setOpenState(!open);
|
|
781
1063
|
};
|
|
@@ -792,6 +1074,7 @@ export const createAgentExperience = (
|
|
|
792
1074
|
updateCopy();
|
|
793
1075
|
setComposerDisabled(session.isStreaming());
|
|
794
1076
|
scheduleAutoScroll(true);
|
|
1077
|
+
maybeRestoreVoiceFromMetadata();
|
|
795
1078
|
|
|
796
1079
|
const recalcPanelHeight = () => {
|
|
797
1080
|
if (!launcherEnabled) {
|
|
@@ -902,6 +1185,27 @@ export const createAgentExperience = (
|
|
|
902
1185
|
detail: { timestamp: new Date().toISOString() }
|
|
903
1186
|
});
|
|
904
1187
|
window.dispatchEvent(clearEvent);
|
|
1188
|
+
|
|
1189
|
+
if (storageAdapter?.clear) {
|
|
1190
|
+
try {
|
|
1191
|
+
const result = storageAdapter.clear();
|
|
1192
|
+
if (result instanceof Promise) {
|
|
1193
|
+
result.catch((error) => {
|
|
1194
|
+
if (typeof console !== "undefined") {
|
|
1195
|
+
// eslint-disable-next-line no-console
|
|
1196
|
+
console.error("[AgentWidget] Failed to clear storage adapter:", error);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
if (typeof console !== "undefined") {
|
|
1202
|
+
// eslint-disable-next-line no-console
|
|
1203
|
+
console.error("[AgentWidget] Failed to clear storage adapter:", error);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
persistentMetadata = {};
|
|
1208
|
+
actionManager.syncFromMetadata();
|
|
905
1209
|
});
|
|
906
1210
|
};
|
|
907
1211
|
|
|
@@ -925,7 +1229,7 @@ export const createAgentExperience = (
|
|
|
925
1229
|
});
|
|
926
1230
|
}
|
|
927
1231
|
|
|
928
|
-
|
|
1232
|
+
const controller: Controller = {
|
|
929
1233
|
update(nextConfig: AgentWidgetConfig) {
|
|
930
1234
|
const previousToolCallConfig = config.toolCall;
|
|
931
1235
|
config = { ...config, ...nextConfig };
|
|
@@ -1387,7 +1691,25 @@ export const createAgentExperience = (
|
|
|
1387
1691
|
}
|
|
1388
1692
|
}
|
|
1389
1693
|
|
|
1390
|
-
|
|
1694
|
+
const nextParsers =
|
|
1695
|
+
config.actionParsers && config.actionParsers.length
|
|
1696
|
+
? config.actionParsers
|
|
1697
|
+
: [defaultJsonActionParser];
|
|
1698
|
+
const nextHandlers =
|
|
1699
|
+
config.actionHandlers && config.actionHandlers.length
|
|
1700
|
+
? config.actionHandlers
|
|
1701
|
+
: [defaultActionHandlers.message, defaultActionHandlers.messageAndClick];
|
|
1702
|
+
|
|
1703
|
+
actionManager = createActionManager({
|
|
1704
|
+
parsers: nextParsers,
|
|
1705
|
+
handlers: nextHandlers,
|
|
1706
|
+
getMetadata,
|
|
1707
|
+
updateMetadata,
|
|
1708
|
+
emit: eventBus.emit,
|
|
1709
|
+
documentRef: typeof document !== "undefined" ? document : null
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
postprocess = buildPostprocessor(config, actionManager);
|
|
1391
1713
|
session.updateConfig(config);
|
|
1392
1714
|
renderMessagesWithPlugins(
|
|
1393
1715
|
messagesWrapper,
|
|
@@ -1732,6 +2054,27 @@ export const createAgentExperience = (
|
|
|
1732
2054
|
detail: { timestamp: new Date().toISOString() }
|
|
1733
2055
|
});
|
|
1734
2056
|
window.dispatchEvent(clearEvent);
|
|
2057
|
+
|
|
2058
|
+
if (storageAdapter?.clear) {
|
|
2059
|
+
try {
|
|
2060
|
+
const result = storageAdapter.clear();
|
|
2061
|
+
if (result instanceof Promise) {
|
|
2062
|
+
result.catch((error) => {
|
|
2063
|
+
if (typeof console !== "undefined") {
|
|
2064
|
+
// eslint-disable-next-line no-console
|
|
2065
|
+
console.error("[AgentWidget] Failed to clear storage adapter:", error);
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
} catch (error) {
|
|
2070
|
+
if (typeof console !== "undefined") {
|
|
2071
|
+
// eslint-disable-next-line no-console
|
|
2072
|
+
console.error("[AgentWidget] Failed to clear storage adapter:", error);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
persistentMetadata = {};
|
|
2077
|
+
actionManager.syncFromMetadata();
|
|
1735
2078
|
},
|
|
1736
2079
|
setMessage(message: string): boolean {
|
|
1737
2080
|
if (!textarea) return false;
|
|
@@ -1773,13 +2116,17 @@ export const createAgentExperience = (
|
|
|
1773
2116
|
setOpenState(true);
|
|
1774
2117
|
}
|
|
1775
2118
|
|
|
1776
|
-
|
|
2119
|
+
voiceState.manuallyDeactivated = false;
|
|
2120
|
+
persistVoiceMetadata();
|
|
2121
|
+
startVoiceRecognition("user");
|
|
1777
2122
|
return true;
|
|
1778
2123
|
},
|
|
1779
2124
|
stopVoiceRecognition(): boolean {
|
|
1780
2125
|
if (!isRecording) return false;
|
|
1781
2126
|
|
|
1782
|
-
|
|
2127
|
+
voiceState.manuallyDeactivated = true;
|
|
2128
|
+
persistVoiceMetadata();
|
|
2129
|
+
stopVoiceRecognition("user");
|
|
1783
2130
|
return true;
|
|
1784
2131
|
},
|
|
1785
2132
|
injectTestMessage(event: AgentWidgetEvent) {
|
|
@@ -1789,6 +2136,26 @@ export const createAgentExperience = (
|
|
|
1789
2136
|
}
|
|
1790
2137
|
session.injectTestEvent(event);
|
|
1791
2138
|
},
|
|
2139
|
+
getMessages() {
|
|
2140
|
+
return session.getMessages();
|
|
2141
|
+
},
|
|
2142
|
+
getStatus() {
|
|
2143
|
+
return session.getStatus();
|
|
2144
|
+
},
|
|
2145
|
+
getPersistentMetadata() {
|
|
2146
|
+
return { ...persistentMetadata };
|
|
2147
|
+
},
|
|
2148
|
+
updatePersistentMetadata(
|
|
2149
|
+
updater: (prev: Record<string, unknown>) => Record<string, unknown>
|
|
2150
|
+
) {
|
|
2151
|
+
updateMetadata(updater);
|
|
2152
|
+
},
|
|
2153
|
+
on(event, handler) {
|
|
2154
|
+
return eventBus.on(event, handler);
|
|
2155
|
+
},
|
|
2156
|
+
off(event, handler) {
|
|
2157
|
+
eventBus.off(event, handler);
|
|
2158
|
+
},
|
|
1792
2159
|
destroy() {
|
|
1793
2160
|
destroyCallbacks.forEach((cb) => cb());
|
|
1794
2161
|
wrapper.remove();
|
|
@@ -1798,6 +2165,33 @@ export const createAgentExperience = (
|
|
|
1798
2165
|
}
|
|
1799
2166
|
}
|
|
1800
2167
|
};
|
|
2168
|
+
|
|
2169
|
+
const shouldExposeDebugApi =
|
|
2170
|
+
(runtimeOptions?.debugTools ?? false) || Boolean(config.debug);
|
|
2171
|
+
|
|
2172
|
+
if (shouldExposeDebugApi && typeof window !== "undefined") {
|
|
2173
|
+
const previousDebug = (window as any).AgentWidgetBrowser;
|
|
2174
|
+
const debugApi = {
|
|
2175
|
+
controller,
|
|
2176
|
+
getMessages: controller.getMessages,
|
|
2177
|
+
getStatus: controller.getStatus,
|
|
2178
|
+
getMetadata: controller.getPersistentMetadata,
|
|
2179
|
+
updateMetadata: controller.updatePersistentMetadata,
|
|
2180
|
+
clearHistory: () => controller.clearChat(),
|
|
2181
|
+
setVoiceActive: (active: boolean) =>
|
|
2182
|
+
active
|
|
2183
|
+
? controller.startVoiceRecognition()
|
|
2184
|
+
: controller.stopVoiceRecognition()
|
|
2185
|
+
};
|
|
2186
|
+
(window as any).AgentWidgetBrowser = debugApi;
|
|
2187
|
+
destroyCallbacks.push(() => {
|
|
2188
|
+
if ((window as any).AgentWidgetBrowser === debugApi) {
|
|
2189
|
+
(window as any).AgentWidgetBrowser = previousDebug;
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
return controller;
|
|
1801
2195
|
};
|
|
1802
2196
|
|
|
1803
2197
|
export type AgentWidgetController = Controller;
|