vanilla-agent 1.30.0 → 1.32.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 +21 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -2
- package/dist/index.d.ts +46 -2
- package/dist/index.global.js +44 -44
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +21 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +25 -6
- package/src/components/panel.ts +19 -2
- package/src/components/reasoning-bubble.ts +30 -23
- package/src/components/tool-bubble.ts +31 -23
- package/src/types.ts +44 -0
- package/src/ui.ts +94 -10
- package/src/utils/actions.ts +7 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.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",
|
package/src/client.ts
CHANGED
|
@@ -139,15 +139,21 @@ export class AgentWidgetClient {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
private async _doInitSession(): Promise<ClientSession> {
|
|
142
|
+
// Get stored session_id if available (for session resumption)
|
|
143
|
+
const storedSessionId = this.config.getStoredSessionId?.() || null;
|
|
144
|
+
|
|
145
|
+
const requestBody: Record<string, unknown> = {
|
|
146
|
+
token: this.config.clientToken,
|
|
147
|
+
...(this.config.flowId && { flow_id: this.config.flowId }),
|
|
148
|
+
...(storedSessionId && { session_id: storedSessionId }),
|
|
149
|
+
};
|
|
150
|
+
|
|
142
151
|
const response = await fetch(this.getClientApiUrl('init'), {
|
|
143
152
|
method: 'POST',
|
|
144
153
|
headers: {
|
|
145
154
|
'Content-Type': 'application/json',
|
|
146
155
|
},
|
|
147
|
-
body: JSON.stringify(
|
|
148
|
-
token: this.config.clientToken,
|
|
149
|
-
flow_id: this.config.flowId,
|
|
150
|
-
}),
|
|
156
|
+
body: JSON.stringify(requestBody),
|
|
151
157
|
});
|
|
152
158
|
|
|
153
159
|
if (!response.ok) {
|
|
@@ -162,6 +168,12 @@ export class AgentWidgetClient {
|
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
const data: ClientInitResponse = await response.json();
|
|
171
|
+
|
|
172
|
+
// Store the new session_id for future resumption
|
|
173
|
+
if (this.config.setStoredSessionId) {
|
|
174
|
+
this.config.setStoredSessionId(data.session_id);
|
|
175
|
+
}
|
|
176
|
+
|
|
165
177
|
return {
|
|
166
178
|
sessionId: data.session_id,
|
|
167
179
|
expiresAt: new Date(data.expires_at),
|
|
@@ -381,6 +393,13 @@ export class AgentWidgetClient {
|
|
|
381
393
|
const basePayload = await this.buildPayload(options.messages);
|
|
382
394
|
|
|
383
395
|
// Build the chat request payload with message IDs for feedback tracking
|
|
396
|
+
// Filter out session_id from metadata if present (it's only for local storage)
|
|
397
|
+
const sanitizedMetadata = basePayload.metadata
|
|
398
|
+
? Object.fromEntries(
|
|
399
|
+
Object.entries(basePayload.metadata).filter(([key]) => key !== 'session_id')
|
|
400
|
+
)
|
|
401
|
+
: undefined;
|
|
402
|
+
|
|
384
403
|
const chatRequest: ClientChatRequest = {
|
|
385
404
|
session_id: session.sessionId,
|
|
386
405
|
messages: options.messages.map(m => ({
|
|
@@ -390,8 +409,8 @@ export class AgentWidgetClient {
|
|
|
390
409
|
})),
|
|
391
410
|
// Include pre-generated assistant message ID if provided
|
|
392
411
|
...(options.assistantMessageId && { assistant_message_id: options.assistantMessageId }),
|
|
393
|
-
// Include metadata/context from middleware if present
|
|
394
|
-
...(
|
|
412
|
+
// Include metadata/context from middleware if present (excluding session_id)
|
|
413
|
+
...(sanitizedMetadata && Object.keys(sanitizedMetadata).length > 0 && { metadata: sanitizedMetadata }),
|
|
395
414
|
...(basePayload.context && { context: basePayload.context }),
|
|
396
415
|
};
|
|
397
416
|
|
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;
|
|
@@ -41,6 +68,8 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
41
68
|
) as HTMLButtonElement;
|
|
42
69
|
header.type = "button";
|
|
43
70
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
71
|
+
header.setAttribute("data-expand-header", "true");
|
|
72
|
+
header.setAttribute("data-bubble-type", "reasoning");
|
|
44
73
|
|
|
45
74
|
const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
|
|
46
75
|
const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
|
|
@@ -105,28 +134,6 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
|
|
|
105
134
|
content.style.display = expanded ? "" : "none";
|
|
106
135
|
};
|
|
107
136
|
|
|
108
|
-
const toggleExpansion = () => {
|
|
109
|
-
expanded = !expanded;
|
|
110
|
-
if (expanded) {
|
|
111
|
-
reasoningExpansionState.add(message.id);
|
|
112
|
-
} else {
|
|
113
|
-
reasoningExpansionState.delete(message.id);
|
|
114
|
-
}
|
|
115
|
-
applyExpansionState();
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
header.addEventListener("pointerdown", (event) => {
|
|
119
|
-
event.preventDefault();
|
|
120
|
-
toggleExpansion();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
header.addEventListener("keydown", (event) => {
|
|
124
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
125
|
-
event.preventDefault();
|
|
126
|
-
toggleExpansion();
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
137
|
applyExpansionState();
|
|
131
138
|
|
|
132
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;
|
|
@@ -57,6 +85,8 @@ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidg
|
|
|
57
85
|
) as HTMLButtonElement;
|
|
58
86
|
header.type = "button";
|
|
59
87
|
header.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
88
|
+
header.setAttribute("data-expand-header", "true");
|
|
89
|
+
header.setAttribute("data-bubble-type", "tool");
|
|
60
90
|
|
|
61
91
|
// Apply header styles
|
|
62
92
|
if (toolCallConfig.headerBackgroundColor) {
|
|
@@ -248,28 +278,6 @@ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidg
|
|
|
248
278
|
content.style.display = expanded ? "" : "none";
|
|
249
279
|
};
|
|
250
280
|
|
|
251
|
-
const toggleToolExpansion = () => {
|
|
252
|
-
expanded = !expanded;
|
|
253
|
-
if (expanded) {
|
|
254
|
-
toolExpansionState.add(message.id);
|
|
255
|
-
} else {
|
|
256
|
-
toolExpansionState.delete(message.id);
|
|
257
|
-
}
|
|
258
|
-
applyToolExpansion();
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
header.addEventListener("pointerdown", (event) => {
|
|
262
|
-
event.preventDefault();
|
|
263
|
-
toggleToolExpansion();
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
header.addEventListener("keydown", (event) => {
|
|
267
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
268
|
-
event.preventDefault();
|
|
269
|
-
toggleToolExpansion();
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
281
|
applyToolExpansion();
|
|
274
282
|
|
|
275
283
|
bubble.append(header, content);
|
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
|
// ============================================================================
|
|
@@ -1101,6 +1114,37 @@ export type AgentWidgetConfig = {
|
|
|
1101
1114
|
* ```
|
|
1102
1115
|
*/
|
|
1103
1116
|
onSessionExpired?: () => void;
|
|
1117
|
+
/**
|
|
1118
|
+
* Get stored session ID for session resumption (client token mode only).
|
|
1119
|
+
* Called when initializing a new session to check if there's a previous session_id
|
|
1120
|
+
* that should be passed to /client/init to resume the same conversation record.
|
|
1121
|
+
*
|
|
1122
|
+
* @example
|
|
1123
|
+
* ```typescript
|
|
1124
|
+
* config: {
|
|
1125
|
+
* getStoredSessionId: () => {
|
|
1126
|
+
* const stored = localStorage.getItem('session_id');
|
|
1127
|
+
* return stored || null;
|
|
1128
|
+
* }
|
|
1129
|
+
* }
|
|
1130
|
+
* ```
|
|
1131
|
+
*/
|
|
1132
|
+
getStoredSessionId?: () => string | null;
|
|
1133
|
+
/**
|
|
1134
|
+
* Store session ID for session resumption (client token mode only).
|
|
1135
|
+
* Called when a new session is initialized to persist the session_id
|
|
1136
|
+
* so it can be used to resume the conversation later.
|
|
1137
|
+
*
|
|
1138
|
+
* @example
|
|
1139
|
+
* ```typescript
|
|
1140
|
+
* config: {
|
|
1141
|
+
* setStoredSessionId: (sessionId) => {
|
|
1142
|
+
* localStorage.setItem('session_id', sessionId);
|
|
1143
|
+
* }
|
|
1144
|
+
* }
|
|
1145
|
+
* ```
|
|
1146
|
+
*/
|
|
1147
|
+
setStoredSessionId?: (sessionId: string) => void;
|
|
1104
1148
|
/**
|
|
1105
1149
|
* Static headers to include with each request.
|
|
1106
1150
|
* For dynamic headers (e.g., auth tokens), use `getHeaders` instead.
|
package/src/ui.ts
CHANGED
|
@@ -26,8 +26,8 @@ import { positionMap } from "./utils/positioning";
|
|
|
26
26
|
import type { HeaderElements, ComposerElements } from "./components/panel";
|
|
27
27
|
import { MessageTransform, MessageActionCallbacks } from "./components/message-bubble";
|
|
28
28
|
import { createStandardBubble, createTypingIndicator } from "./components/message-bubble";
|
|
29
|
-
import { createReasoningBubble } from "./components/reasoning-bubble";
|
|
30
|
-
import { createToolBubble } from "./components/tool-bubble";
|
|
29
|
+
import { createReasoningBubble, reasoningExpansionState, updateReasoningBubbleUI } from "./components/reasoning-bubble";
|
|
30
|
+
import { createToolBubble, toolExpansionState, updateToolBubbleUI } from "./components/tool-bubble";
|
|
31
31
|
import { createSuggestions } from "./components/suggestions";
|
|
32
32
|
import { enhanceWithForms } from "./components/forms";
|
|
33
33
|
import { pluginRegistry } from "./plugins/registry";
|
|
@@ -204,8 +204,8 @@ export const createAgentExperience = (
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
const
|
|
208
|
-
const
|
|
207
|
+
const getSessionMetadata = () => persistentMetadata;
|
|
208
|
+
const updateSessionMetadata = (
|
|
209
209
|
updater: (prev: Record<string, unknown>) => Record<string, unknown>
|
|
210
210
|
) => {
|
|
211
211
|
const next = updater({ ...persistentMetadata }) ?? {};
|
|
@@ -226,8 +226,8 @@ export const createAgentExperience = (
|
|
|
226
226
|
let actionManager = createActionManager({
|
|
227
227
|
parsers: resolvedActionParsers,
|
|
228
228
|
handlers: resolvedActionHandlers,
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
getSessionMetadata,
|
|
230
|
+
updateSessionMetadata,
|
|
231
231
|
emit: eventBus.emit,
|
|
232
232
|
documentRef: typeof document !== "undefined" ? document : null
|
|
233
233
|
});
|
|
@@ -453,6 +453,60 @@ export const createAgentExperience = (
|
|
|
453
453
|
// Render custom slots
|
|
454
454
|
renderSlots();
|
|
455
455
|
|
|
456
|
+
// Add event delegation for reasoning and tool bubble expansion
|
|
457
|
+
// This handles clicks even after idiomorph morphs the DOM
|
|
458
|
+
const handleBubbleExpansion = (event: Event) => {
|
|
459
|
+
const target = event.target as HTMLElement;
|
|
460
|
+
|
|
461
|
+
// Check if the click/keypress is on an expand header button
|
|
462
|
+
const headerButton = target.closest('button[data-expand-header="true"]') as HTMLElement;
|
|
463
|
+
if (!headerButton) return;
|
|
464
|
+
|
|
465
|
+
// Find the parent bubble element
|
|
466
|
+
const bubble = headerButton.closest('.vanilla-reasoning-bubble, .vanilla-tool-bubble') as HTMLElement;
|
|
467
|
+
if (!bubble) return;
|
|
468
|
+
|
|
469
|
+
// Get message ID from bubble
|
|
470
|
+
const messageId = bubble.getAttribute('data-message-id');
|
|
471
|
+
if (!messageId) return;
|
|
472
|
+
|
|
473
|
+
const bubbleType = headerButton.getAttribute('data-bubble-type');
|
|
474
|
+
|
|
475
|
+
// Toggle expansion state
|
|
476
|
+
if (bubbleType === 'reasoning') {
|
|
477
|
+
if (reasoningExpansionState.has(messageId)) {
|
|
478
|
+
reasoningExpansionState.delete(messageId);
|
|
479
|
+
} else {
|
|
480
|
+
reasoningExpansionState.add(messageId);
|
|
481
|
+
}
|
|
482
|
+
updateReasoningBubbleUI(messageId, bubble);
|
|
483
|
+
} else if (bubbleType === 'tool') {
|
|
484
|
+
if (toolExpansionState.has(messageId)) {
|
|
485
|
+
toolExpansionState.delete(messageId);
|
|
486
|
+
} else {
|
|
487
|
+
toolExpansionState.add(messageId);
|
|
488
|
+
}
|
|
489
|
+
updateToolBubbleUI(messageId, bubble, config);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Attach event listeners to messagesWrapper for event delegation
|
|
494
|
+
messagesWrapper.addEventListener('pointerdown', (event) => {
|
|
495
|
+
const target = event.target as HTMLElement;
|
|
496
|
+
if (target.closest('button[data-expand-header="true"]')) {
|
|
497
|
+
event.preventDefault();
|
|
498
|
+
handleBubbleExpansion(event);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
messagesWrapper.addEventListener('keydown', (event) => {
|
|
503
|
+
const target = event.target as HTMLElement;
|
|
504
|
+
if ((event.key === 'Enter' || event.key === ' ') && target.closest('button[data-expand-header="true"]')) {
|
|
505
|
+
event.preventDefault();
|
|
506
|
+
handleBubbleExpansion(event);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
456
510
|
panel.appendChild(container);
|
|
457
511
|
mount.appendChild(wrapper);
|
|
458
512
|
|
|
@@ -704,7 +758,7 @@ export const createAgentExperience = (
|
|
|
704
758
|
});
|
|
705
759
|
};
|
|
706
760
|
const persistVoiceMetadata = () => {
|
|
707
|
-
|
|
761
|
+
updateSessionMetadata((prev) => ({
|
|
708
762
|
...prev,
|
|
709
763
|
voiceState: {
|
|
710
764
|
active: voiceState.active,
|
|
@@ -1228,6 +1282,24 @@ export const createAgentExperience = (
|
|
|
1228
1282
|
textarea.style.fontWeight = fontWeight;
|
|
1229
1283
|
};
|
|
1230
1284
|
|
|
1285
|
+
// Add session ID persistence callbacks for client token mode
|
|
1286
|
+
// These allow the widget to resume conversations by passing session_id to /client/init
|
|
1287
|
+
if (config.clientToken) {
|
|
1288
|
+
config = {
|
|
1289
|
+
...config,
|
|
1290
|
+
getStoredSessionId: () => {
|
|
1291
|
+
const storedId = persistentMetadata['session_id'];
|
|
1292
|
+
return typeof storedId === 'string' ? storedId : null;
|
|
1293
|
+
},
|
|
1294
|
+
setStoredSessionId: (sessionId: string) => {
|
|
1295
|
+
updateSessionMetadata((prev) => ({
|
|
1296
|
+
...prev,
|
|
1297
|
+
session_id: sessionId,
|
|
1298
|
+
}));
|
|
1299
|
+
},
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1231
1303
|
session = new AgentWidgetSession(config, {
|
|
1232
1304
|
onMessagesChanged(messages) {
|
|
1233
1305
|
renderMessagesWithPlugins(messagesWrapper, messages, postprocess);
|
|
@@ -1975,6 +2047,18 @@ export const createAgentExperience = (
|
|
|
1975
2047
|
}
|
|
1976
2048
|
}
|
|
1977
2049
|
|
|
2050
|
+
// Update header visibility based on layout.showHeader
|
|
2051
|
+
const showHeader = config.layout?.showHeader !== false; // default to true
|
|
2052
|
+
if (header) {
|
|
2053
|
+
header.style.display = showHeader ? "" : "none";
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// Update footer visibility based on layout.showFooter
|
|
2057
|
+
const showFooter = config.layout?.showFooter !== false; // default to true
|
|
2058
|
+
if (footer) {
|
|
2059
|
+
footer.style.display = showFooter ? "" : "none";
|
|
2060
|
+
}
|
|
2061
|
+
|
|
1978
2062
|
// Only update open state if launcher enabled state changed or autoExpand value changed
|
|
1979
2063
|
const launcherEnabledChanged = launcherEnabled !== prevLauncherEnabled;
|
|
1980
2064
|
const autoExpandChanged = autoExpand !== prevAutoExpand;
|
|
@@ -2504,8 +2588,8 @@ export const createAgentExperience = (
|
|
|
2504
2588
|
actionManager = createActionManager({
|
|
2505
2589
|
parsers: nextParsers,
|
|
2506
2590
|
handlers: nextHandlers,
|
|
2507
|
-
|
|
2508
|
-
|
|
2591
|
+
getSessionMetadata,
|
|
2592
|
+
updateSessionMetadata,
|
|
2509
2593
|
emit: eventBus.emit,
|
|
2510
2594
|
documentRef: typeof document !== "undefined" ? document : null
|
|
2511
2595
|
});
|
|
@@ -2949,7 +3033,7 @@ export const createAgentExperience = (
|
|
|
2949
3033
|
updatePersistentMetadata(
|
|
2950
3034
|
updater: (prev: Record<string, unknown>) => Record<string, unknown>
|
|
2951
3035
|
) {
|
|
2952
|
-
|
|
3036
|
+
updateSessionMetadata(updater);
|
|
2953
3037
|
},
|
|
2954
3038
|
on(event, handler) {
|
|
2955
3039
|
return eventBus.on(event, handler);
|
package/src/utils/actions.ts
CHANGED
|
@@ -19,8 +19,8 @@ type ActionManagerProcessContext = {
|
|
|
19
19
|
type ActionManagerOptions = {
|
|
20
20
|
parsers: AgentWidgetActionParser[];
|
|
21
21
|
handlers: AgentWidgetActionHandler[];
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
getSessionMetadata: () => Record<string, unknown>;
|
|
23
|
+
updateSessionMetadata: (
|
|
24
24
|
updater: (prev: Record<string, unknown>) => Record<string, unknown>
|
|
25
25
|
) => void;
|
|
26
26
|
emit: <K extends keyof AgentWidgetControllerEventMap>(
|
|
@@ -123,18 +123,18 @@ const ensureArrayOfStrings = (value: unknown): string[] => {
|
|
|
123
123
|
|
|
124
124
|
export const createActionManager = (options: ActionManagerOptions) => {
|
|
125
125
|
let processedIds = new Set(
|
|
126
|
-
ensureArrayOfStrings(options.
|
|
126
|
+
ensureArrayOfStrings(options.getSessionMetadata().processedActionMessageIds)
|
|
127
127
|
);
|
|
128
128
|
|
|
129
129
|
const syncFromMetadata = () => {
|
|
130
130
|
processedIds = new Set(
|
|
131
|
-
ensureArrayOfStrings(options.
|
|
131
|
+
ensureArrayOfStrings(options.getSessionMetadata().processedActionMessageIds)
|
|
132
132
|
);
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
const persistProcessedIds = () => {
|
|
136
136
|
const latestIds = Array.from(processedIds);
|
|
137
|
-
options.
|
|
137
|
+
options.updateSessionMetadata((prev) => ({
|
|
138
138
|
...prev,
|
|
139
139
|
processedActionMessageIds: latestIds
|
|
140
140
|
}));
|
|
@@ -195,8 +195,8 @@ export const createActionManager = (options: ActionManagerOptions) => {
|
|
|
195
195
|
try {
|
|
196
196
|
const handlerResult = handler(action, {
|
|
197
197
|
message: context.message,
|
|
198
|
-
metadata: options.
|
|
199
|
-
updateMetadata: options.
|
|
198
|
+
metadata: options.getSessionMetadata(),
|
|
199
|
+
updateMetadata: options.updateSessionMetadata,
|
|
200
200
|
document: options.documentRef
|
|
201
201
|
} as AgentWidgetActionContext) as AgentWidgetActionHandlerResult | void;
|
|
202
202
|
|