vanilla-agent 1.11.0 → 1.13.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.
@@ -0,0 +1,1246 @@
1
+ import type { AgentWidgetConfig } from "../types";
2
+
3
+ type ParserType = "plain" | "json" | "regex-json" | "xml";
4
+ export type CodeFormat = "esm" | "script-installer" | "script-manual" | "script-advanced" | "react-component" | "react-advanced";
5
+
6
+ function detectParserTypeFromStreamParser(streamParser: any): ParserType | null {
7
+ if (!streamParser) return null;
8
+ const fnString = streamParser.toString();
9
+ if (fnString.includes("createJsonStreamParser") || fnString.includes("partial-json")) {
10
+ return "json";
11
+ }
12
+ if (fnString.includes("createRegexJsonParser") || fnString.includes("regex")) {
13
+ return "regex-json";
14
+ }
15
+ if (fnString.includes("createXmlParser") || fnString.includes("<text>")) {
16
+ return "xml";
17
+ }
18
+ return null;
19
+ }
20
+
21
+ function getParserTypeFromConfig(config: AgentWidgetConfig): ParserType {
22
+ return config.parserType ?? detectParserTypeFromStreamParser(config.streamParser) ?? "plain";
23
+ }
24
+
25
+ export function generateCodeSnippet(config: any, format: CodeFormat = "esm"): string {
26
+ // Remove non-serializable properties
27
+ const cleanConfig = { ...config };
28
+ delete cleanConfig.postprocessMessage;
29
+ delete cleanConfig.initialMessages;
30
+
31
+ if (format === "esm") {
32
+ return generateESMCode(cleanConfig);
33
+ } else if (format === "script-installer") {
34
+ return generateScriptInstallerCode(cleanConfig);
35
+ } else if (format === "script-advanced") {
36
+ return generateScriptAdvancedCode(cleanConfig);
37
+ } else if (format === "react-component") {
38
+ return generateReactComponentCode(cleanConfig);
39
+ } else if (format === "react-advanced") {
40
+ return generateReactAdvancedCode(cleanConfig);
41
+ } else {
42
+ return generateScriptManualCode(cleanConfig);
43
+ }
44
+ }
45
+
46
+ function generateESMCode(config: any): string {
47
+ const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
48
+ const shouldEmitParserType = parserType !== "plain";
49
+
50
+ const lines: string[] = [
51
+ "import 'vanilla-agent/widget.css';",
52
+ "import { initAgentWidget, markdownPostprocessor } from 'vanilla-agent';",
53
+ "",
54
+ "initAgentWidget({",
55
+ " target: 'body',",
56
+ " config: {"
57
+ ];
58
+
59
+ if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
60
+ if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
61
+ if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
62
+
63
+ if (config.theme) {
64
+ lines.push(" theme: {");
65
+ Object.entries(config.theme).forEach(([key, value]) => {
66
+ lines.push(` ${key}: "${value}",`);
67
+ });
68
+ lines.push(" },");
69
+ }
70
+
71
+ if (config.launcher) {
72
+ lines.push(" launcher: {");
73
+ Object.entries(config.launcher).forEach(([key, value]) => {
74
+ if (typeof value === "string") {
75
+ lines.push(` ${key}: "${value}",`);
76
+ } else if (typeof value === "boolean") {
77
+ lines.push(` ${key}: ${value},`);
78
+ }
79
+ });
80
+ lines.push(" },");
81
+ }
82
+
83
+ if (config.copy) {
84
+ lines.push(" copy: {");
85
+ Object.entries(config.copy).forEach(([key, value]) => {
86
+ lines.push(` ${key}: "${value}",`);
87
+ });
88
+ lines.push(" },");
89
+ }
90
+
91
+ if (config.sendButton) {
92
+ lines.push(" sendButton: {");
93
+ Object.entries(config.sendButton).forEach(([key, value]) => {
94
+ if (typeof value === "string") {
95
+ lines.push(` ${key}: "${value}",`);
96
+ } else if (typeof value === "boolean") {
97
+ lines.push(` ${key}: ${value},`);
98
+ }
99
+ });
100
+ lines.push(" },");
101
+ }
102
+
103
+ if (config.voiceRecognition) {
104
+ lines.push(" voiceRecognition: {");
105
+ Object.entries(config.voiceRecognition).forEach(([key, value]) => {
106
+ if (typeof value === "string") {
107
+ lines.push(` ${key}: "${value}",`);
108
+ } else if (typeof value === "boolean") {
109
+ lines.push(` ${key}: ${value},`);
110
+ } else if (typeof value === "number") {
111
+ lines.push(` ${key}: ${value},`);
112
+ }
113
+ });
114
+ lines.push(" },");
115
+ }
116
+
117
+ if (config.statusIndicator) {
118
+ lines.push(" statusIndicator: {");
119
+ Object.entries(config.statusIndicator).forEach(([key, value]) => {
120
+ if (typeof value === "string") {
121
+ lines.push(` ${key}: "${value}",`);
122
+ } else if (typeof value === "boolean") {
123
+ lines.push(` ${key}: ${value},`);
124
+ }
125
+ });
126
+ lines.push(" },");
127
+ }
128
+
129
+ if (config.features) {
130
+ lines.push(" features: {");
131
+ Object.entries(config.features).forEach(([key, value]) => {
132
+ lines.push(` ${key}: ${value},`);
133
+ });
134
+ lines.push(" },");
135
+ }
136
+
137
+ if (config.suggestionChips && config.suggestionChips.length > 0) {
138
+ lines.push(" suggestionChips: [");
139
+ config.suggestionChips.forEach((chip: string) => {
140
+ lines.push(` "${chip}",`);
141
+ });
142
+ lines.push(" ],");
143
+ }
144
+
145
+ if (config.suggestionChipsConfig) {
146
+ lines.push(" suggestionChipsConfig: {");
147
+ if (config.suggestionChipsConfig.fontFamily) {
148
+ lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
149
+ }
150
+ if (config.suggestionChipsConfig.fontWeight) {
151
+ lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
152
+ }
153
+ if (config.suggestionChipsConfig.paddingX) {
154
+ lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
155
+ }
156
+ if (config.suggestionChipsConfig.paddingY) {
157
+ lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
158
+ }
159
+ lines.push(" },");
160
+ }
161
+
162
+ if (config.debug) {
163
+ lines.push(` debug: ${config.debug},`);
164
+ }
165
+
166
+ lines.push(" postprocessMessage: ({ text }) => markdownPostprocessor(text)");
167
+ lines.push(" }");
168
+ lines.push("});");
169
+
170
+ return lines.join("\n");
171
+ }
172
+
173
+ function generateReactComponentCode(config: any): string {
174
+ const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
175
+ const shouldEmitParserType = parserType !== "plain";
176
+
177
+ const lines: string[] = [
178
+ "// ChatWidget.tsx",
179
+ "'use client'; // Required for Next.js - remove for Vite/CRA",
180
+ "",
181
+ "import { useEffect } from 'react';",
182
+ "import 'vanilla-agent/widget.css';",
183
+ "import { initAgentWidget, markdownPostprocessor } from 'vanilla-agent';",
184
+ "import type { AgentWidgetInitHandle } from 'vanilla-agent';",
185
+ "",
186
+ "export function ChatWidget() {",
187
+ " useEffect(() => {",
188
+ " let handle: AgentWidgetInitHandle | null = null;",
189
+ "",
190
+ " handle = initAgentWidget({",
191
+ " target: 'body',",
192
+ " config: {"
193
+ ];
194
+
195
+ if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
196
+ if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
197
+ if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
198
+
199
+ if (config.theme) {
200
+ lines.push(" theme: {");
201
+ Object.entries(config.theme).forEach(([key, value]) => {
202
+ lines.push(` ${key}: "${value}",`);
203
+ });
204
+ lines.push(" },");
205
+ }
206
+
207
+ if (config.launcher) {
208
+ lines.push(" launcher: {");
209
+ Object.entries(config.launcher).forEach(([key, value]) => {
210
+ if (typeof value === "string") {
211
+ lines.push(` ${key}: "${value}",`);
212
+ } else if (typeof value === "boolean") {
213
+ lines.push(` ${key}: ${value},`);
214
+ }
215
+ });
216
+ lines.push(" },");
217
+ }
218
+
219
+ if (config.copy) {
220
+ lines.push(" copy: {");
221
+ Object.entries(config.copy).forEach(([key, value]) => {
222
+ lines.push(` ${key}: "${value}",`);
223
+ });
224
+ lines.push(" },");
225
+ }
226
+
227
+ if (config.sendButton) {
228
+ lines.push(" sendButton: {");
229
+ Object.entries(config.sendButton).forEach(([key, value]) => {
230
+ if (typeof value === "string") {
231
+ lines.push(` ${key}: "${value}",`);
232
+ } else if (typeof value === "boolean") {
233
+ lines.push(` ${key}: ${value},`);
234
+ }
235
+ });
236
+ lines.push(" },");
237
+ }
238
+
239
+ if (config.voiceRecognition) {
240
+ lines.push(" voiceRecognition: {");
241
+ Object.entries(config.voiceRecognition).forEach(([key, value]) => {
242
+ if (typeof value === "string") {
243
+ lines.push(` ${key}: "${value}",`);
244
+ } else if (typeof value === "boolean") {
245
+ lines.push(` ${key}: ${value},`);
246
+ } else if (typeof value === "number") {
247
+ lines.push(` ${key}: ${value},`);
248
+ }
249
+ });
250
+ lines.push(" },");
251
+ }
252
+
253
+ if (config.statusIndicator) {
254
+ lines.push(" statusIndicator: {");
255
+ Object.entries(config.statusIndicator).forEach(([key, value]) => {
256
+ if (typeof value === "string") {
257
+ lines.push(` ${key}: "${value}",`);
258
+ } else if (typeof value === "boolean") {
259
+ lines.push(` ${key}: ${value},`);
260
+ }
261
+ });
262
+ lines.push(" },");
263
+ }
264
+
265
+ if (config.features) {
266
+ lines.push(" features: {");
267
+ Object.entries(config.features).forEach(([key, value]) => {
268
+ lines.push(` ${key}: ${value},`);
269
+ });
270
+ lines.push(" },");
271
+ }
272
+
273
+ if (config.suggestionChips && config.suggestionChips.length > 0) {
274
+ lines.push(" suggestionChips: [");
275
+ config.suggestionChips.forEach((chip: string) => {
276
+ lines.push(` "${chip}",`);
277
+ });
278
+ lines.push(" ],");
279
+ }
280
+
281
+ if (config.suggestionChipsConfig) {
282
+ lines.push(" suggestionChipsConfig: {");
283
+ if (config.suggestionChipsConfig.fontFamily) {
284
+ lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
285
+ }
286
+ if (config.suggestionChipsConfig.fontWeight) {
287
+ lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
288
+ }
289
+ if (config.suggestionChipsConfig.paddingX) {
290
+ lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
291
+ }
292
+ if (config.suggestionChipsConfig.paddingY) {
293
+ lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
294
+ }
295
+ lines.push(" },");
296
+ }
297
+
298
+ if (config.debug) {
299
+ lines.push(` debug: ${config.debug},`);
300
+ }
301
+
302
+ lines.push(" postprocessMessage: ({ text }) => markdownPostprocessor(text)");
303
+ lines.push(" }");
304
+ lines.push(" });");
305
+ lines.push("");
306
+ lines.push(" // Cleanup on unmount");
307
+ lines.push(" return () => {");
308
+ lines.push(" if (handle) {");
309
+ lines.push(" handle.destroy();");
310
+ lines.push(" }");
311
+ lines.push(" };");
312
+ lines.push(" }, []);");
313
+ lines.push("");
314
+ lines.push(" return null; // Widget injects itself into the DOM");
315
+ lines.push("}");
316
+ lines.push("");
317
+ lines.push("// Usage in your app:");
318
+ lines.push("// import { ChatWidget } from './components/ChatWidget';");
319
+ lines.push("//");
320
+ lines.push("// export default function App() {");
321
+ lines.push("// return (");
322
+ lines.push("// <div>");
323
+ lines.push("// {/* Your app content */}");
324
+ lines.push("// <ChatWidget />");
325
+ lines.push("// </div>");
326
+ lines.push("// );");
327
+ lines.push("// }");
328
+
329
+ return lines.join("\n");
330
+ }
331
+
332
+ function generateReactAdvancedCode(config: any): string {
333
+ const lines: string[] = [
334
+ "// ChatWidgetAdvanced.tsx",
335
+ "'use client'; // Required for Next.js - remove for Vite/CRA",
336
+ "",
337
+ "import { useEffect } from 'react';",
338
+ "import 'vanilla-agent/widget.css';",
339
+ "import {",
340
+ " initAgentWidget,",
341
+ " createFlexibleJsonStreamParser,",
342
+ " defaultJsonActionParser,",
343
+ " defaultActionHandlers,",
344
+ " markdownPostprocessor",
345
+ "} from 'vanilla-agent';",
346
+ "import type { AgentWidgetInitHandle } from 'vanilla-agent';",
347
+ "",
348
+ "const STORAGE_KEY = 'chat-widget-state';",
349
+ "const PROCESSED_ACTIONS_KEY = 'chat-widget-processed-actions';",
350
+ "",
351
+ "// Types for DOM elements",
352
+ "interface PageElement {",
353
+ " type: string;",
354
+ " tagName: string;",
355
+ " selector: string;",
356
+ " innerText: string;",
357
+ " href?: string;",
358
+ "}",
359
+ "",
360
+ "interface DOMContext {",
361
+ " page_elements: PageElement[];",
362
+ " page_element_count: number;",
363
+ " element_types: Record<string, number>;",
364
+ " page_url: string;",
365
+ " page_title: string;",
366
+ " timestamp: string;",
367
+ "}",
368
+ "",
369
+ "// DOM context provider - extracts page elements for AI context",
370
+ "const collectDOMContext = (): DOMContext => {",
371
+ " const selectors = {",
372
+ " products: '[data-product-id], .product-card, .product-item, [role=\"article\"]',",
373
+ " buttons: 'button, [role=\"button\"], .btn',",
374
+ " links: 'a[href]',",
375
+ " inputs: 'input, textarea, select'",
376
+ " };",
377
+ "",
378
+ " const elements: PageElement[] = [];",
379
+ " Object.entries(selectors).forEach(([type, selector]) => {",
380
+ " document.querySelectorAll(selector).forEach((element) => {",
381
+ " if (!(element instanceof HTMLElement)) return;",
382
+ " ",
383
+ " // Exclude elements within the widget",
384
+ " const widgetHost = element.closest('.vanilla-agent-host');",
385
+ " if (widgetHost) return;",
386
+ " ",
387
+ " const text = element.innerText?.trim();",
388
+ " if (!text) return;",
389
+ "",
390
+ " const selectorString =",
391
+ " element.id ? `#${element.id}` :",
392
+ " element.getAttribute('data-testid') ? `[data-testid=\"${element.getAttribute('data-testid')}\"]` :",
393
+ " element.getAttribute('data-product-id') ? `[data-product-id=\"${element.getAttribute('data-product-id')}\"]` :",
394
+ " element.tagName.toLowerCase();",
395
+ "",
396
+ " const elementData: PageElement = {",
397
+ " type,",
398
+ " tagName: element.tagName.toLowerCase(),",
399
+ " selector: selectorString,",
400
+ " innerText: text.substring(0, 200)",
401
+ " };",
402
+ "",
403
+ " if (type === 'links' && element instanceof HTMLAnchorElement && element.href) {",
404
+ " elementData.href = element.href;",
405
+ " }",
406
+ "",
407
+ " elements.push(elementData);",
408
+ " });",
409
+ " });",
410
+ "",
411
+ " const counts = elements.reduce((acc, el) => {",
412
+ " acc[el.type] = (acc[el.type] || 0) + 1;",
413
+ " return acc;",
414
+ " }, {} as Record<string, number>);",
415
+ "",
416
+ " return {",
417
+ " page_elements: elements.slice(0, 50),",
418
+ " page_element_count: elements.length,",
419
+ " element_types: counts,",
420
+ " page_url: window.location.href,",
421
+ " page_title: document.title,",
422
+ " timestamp: new Date().toISOString()",
423
+ " };",
424
+ "};",
425
+ "",
426
+ "export function ChatWidgetAdvanced() {",
427
+ " useEffect(() => {",
428
+ " let handle: AgentWidgetInitHandle | null = null;",
429
+ "",
430
+ " // Load saved state",
431
+ " const loadSavedMessages = () => {",
432
+ " const savedState = localStorage.getItem(STORAGE_KEY);",
433
+ " if (savedState) {",
434
+ " try {",
435
+ " const { messages } = JSON.parse(savedState);",
436
+ " return messages || [];",
437
+ " } catch (e) {",
438
+ " console.error('Failed to load saved state:', e);",
439
+ " }",
440
+ " }",
441
+ " return [];",
442
+ " };",
443
+ "",
444
+ " handle = initAgentWidget({",
445
+ " target: 'body',",
446
+ " config: {"
447
+ ];
448
+
449
+ if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
450
+ if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
451
+
452
+ if (config.theme) {
453
+ lines.push(" theme: {");
454
+ Object.entries(config.theme).forEach(([key, value]) => {
455
+ lines.push(` ${key}: "${value}",`);
456
+ });
457
+ lines.push(" },");
458
+ }
459
+
460
+ if (config.launcher) {
461
+ lines.push(" launcher: {");
462
+ Object.entries(config.launcher).forEach(([key, value]) => {
463
+ if (typeof value === "string") {
464
+ lines.push(` ${key}: "${value}",`);
465
+ } else if (typeof value === "boolean") {
466
+ lines.push(` ${key}: ${value},`);
467
+ }
468
+ });
469
+ lines.push(" },");
470
+ }
471
+
472
+ if (config.copy) {
473
+ lines.push(" copy: {");
474
+ Object.entries(config.copy).forEach(([key, value]) => {
475
+ lines.push(` ${key}: "${value}",`);
476
+ });
477
+ lines.push(" },");
478
+ }
479
+
480
+ if (config.sendButton) {
481
+ lines.push(" sendButton: {");
482
+ Object.entries(config.sendButton).forEach(([key, value]) => {
483
+ if (typeof value === "string") {
484
+ lines.push(` ${key}: "${value}",`);
485
+ } else if (typeof value === "boolean") {
486
+ lines.push(` ${key}: ${value},`);
487
+ }
488
+ });
489
+ lines.push(" },");
490
+ }
491
+
492
+ if (config.voiceRecognition) {
493
+ lines.push(" voiceRecognition: {");
494
+ Object.entries(config.voiceRecognition).forEach(([key, value]) => {
495
+ if (typeof value === "string") {
496
+ lines.push(` ${key}: "${value}",`);
497
+ } else if (typeof value === "boolean") {
498
+ lines.push(` ${key}: ${value},`);
499
+ } else if (typeof value === "number") {
500
+ lines.push(` ${key}: ${value},`);
501
+ }
502
+ });
503
+ lines.push(" },");
504
+ }
505
+
506
+ if (config.statusIndicator) {
507
+ lines.push(" statusIndicator: {");
508
+ Object.entries(config.statusIndicator).forEach(([key, value]) => {
509
+ if (typeof value === "string") {
510
+ lines.push(` ${key}: "${value}",`);
511
+ } else if (typeof value === "boolean") {
512
+ lines.push(` ${key}: ${value},`);
513
+ }
514
+ });
515
+ lines.push(" },");
516
+ }
517
+
518
+ if (config.features) {
519
+ lines.push(" features: {");
520
+ Object.entries(config.features).forEach(([key, value]) => {
521
+ lines.push(` ${key}: ${value},`);
522
+ });
523
+ lines.push(" },");
524
+ }
525
+
526
+ if (config.suggestionChips && config.suggestionChips.length > 0) {
527
+ lines.push(" suggestionChips: [");
528
+ config.suggestionChips.forEach((chip: string) => {
529
+ lines.push(` "${chip}",`);
530
+ });
531
+ lines.push(" ],");
532
+ }
533
+
534
+ if (config.suggestionChipsConfig) {
535
+ lines.push(" suggestionChipsConfig: {");
536
+ if (config.suggestionChipsConfig.fontFamily) {
537
+ lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
538
+ }
539
+ if (config.suggestionChipsConfig.fontWeight) {
540
+ lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
541
+ }
542
+ if (config.suggestionChipsConfig.paddingX) {
543
+ lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
544
+ }
545
+ if (config.suggestionChipsConfig.paddingY) {
546
+ lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
547
+ }
548
+ lines.push(" },");
549
+ }
550
+
551
+ if (config.debug) {
552
+ lines.push(` debug: ${config.debug},`);
553
+ }
554
+
555
+ lines.push(" initialMessages: loadSavedMessages(),");
556
+ lines.push(" // Flexible JSON stream parser for handling structured actions");
557
+ lines.push(" streamParser: () => createFlexibleJsonStreamParser((parsed: any) => {");
558
+ lines.push(" if (!parsed || typeof parsed !== 'object') return null;");
559
+ lines.push(" // Extract display text based on action type");
560
+ lines.push(" if (parsed.action === 'nav_then_click') return 'Navigating...';");
561
+ lines.push(" if (parsed.action === 'message') return parsed.text || '';");
562
+ lines.push(" if (parsed.action === 'message_and_click') return parsed.text || 'Processing...';");
563
+ lines.push(" return parsed.text || null;");
564
+ lines.push(" }),");
565
+ lines.push(" // Action parsers to detect JSON actions in responses");
566
+ lines.push(" actionParsers: [");
567
+ lines.push(" defaultJsonActionParser,");
568
+ lines.push(" // Custom parser for markdown-wrapped JSON");
569
+ lines.push(" ({ text, message }: any) => {");
570
+ lines.push(" const jsonSource = (message as any).rawContent || text || message.content;");
571
+ lines.push(" if (!jsonSource || typeof jsonSource !== 'string') return null;");
572
+ lines.push(" // Strip markdown code fences");
573
+ lines.push(" let cleanJson = jsonSource");
574
+ lines.push(" .replace(/^```(?:json)?\\s*\\n?/, '')");
575
+ lines.push(" .replace(/\\n?```\\s*$/, '')");
576
+ lines.push(" .trim();");
577
+ lines.push(" if (!cleanJson.startsWith('{') || !cleanJson.endsWith('}')) return null;");
578
+ lines.push(" try {");
579
+ lines.push(" const parsed = JSON.parse(cleanJson);");
580
+ lines.push(" if (parsed.action) return { type: parsed.action, payload: parsed };");
581
+ lines.push(" } catch (e) { return null; }");
582
+ lines.push(" return null;");
583
+ lines.push(" }");
584
+ lines.push(" ],");
585
+ lines.push(" // Action handlers for navigation and other actions");
586
+ lines.push(" actionHandlers: [");
587
+ lines.push(" defaultActionHandlers.message,");
588
+ lines.push(" defaultActionHandlers.messageAndClick,");
589
+ lines.push(" // Handler for nav_then_click action");
590
+ lines.push(" (action: any, context: any) => {");
591
+ lines.push(" if (action.type !== 'nav_then_click') return;");
592
+ lines.push(" const payload = action.payload || action.raw || {};");
593
+ lines.push(" const url = payload?.page;");
594
+ lines.push(" const text = payload?.on_load_text || 'Navigating...';");
595
+ lines.push(" if (!url) return { handled: true, displayText: text };");
596
+ lines.push(" // Check if already processed");
597
+ lines.push(" const messageId = context.message?.id;");
598
+ lines.push(" const processedActions = JSON.parse(localStorage.getItem(PROCESSED_ACTIONS_KEY) || '[]');");
599
+ lines.push(" const actionKey = `nav_${messageId}_${url}`;");
600
+ lines.push(" if (processedActions.includes(actionKey)) {");
601
+ lines.push(" return { handled: true, displayText: text };");
602
+ lines.push(" }");
603
+ lines.push(" processedActions.push(actionKey);");
604
+ lines.push(" localStorage.setItem(PROCESSED_ACTIONS_KEY, JSON.stringify(processedActions));");
605
+ lines.push(" const targetUrl = url.startsWith('http') ? url : new URL(url, window.location.origin).toString();");
606
+ lines.push(" window.location.href = targetUrl;");
607
+ lines.push(" return { handled: true, displayText: text };");
608
+ lines.push(" }");
609
+ lines.push(" ],");
610
+ lines.push(" postprocessMessage: ({ text }) => markdownPostprocessor(text),");
611
+ lines.push(" requestMiddleware: ({ payload }) => {");
612
+ lines.push(" return {");
613
+ lines.push(" ...payload,");
614
+ lines.push(" metadata: collectDOMContext()");
615
+ lines.push(" };");
616
+ lines.push(" }");
617
+ lines.push(" }");
618
+ lines.push(" });");
619
+ lines.push("");
620
+ lines.push(" // Save state on message events");
621
+ lines.push(" const handleMessage = () => {");
622
+ lines.push(" const session = handle?.getSession?.();");
623
+ lines.push(" if (session) {");
624
+ lines.push(" localStorage.setItem(STORAGE_KEY, JSON.stringify({");
625
+ lines.push(" messages: session.messages,");
626
+ lines.push(" timestamp: new Date().toISOString()");
627
+ lines.push(" }));");
628
+ lines.push(" }");
629
+ lines.push(" };");
630
+ lines.push("");
631
+ lines.push(" // Clear state on clear chat");
632
+ lines.push(" const handleClearChat = () => {");
633
+ lines.push(" localStorage.removeItem(STORAGE_KEY);");
634
+ lines.push(" localStorage.removeItem(PROCESSED_ACTIONS_KEY);");
635
+ lines.push(" };");
636
+ lines.push("");
637
+ lines.push(" window.addEventListener('vanilla-agent:message', handleMessage);");
638
+ lines.push(" window.addEventListener('vanilla-agent:clear-chat', handleClearChat);");
639
+ lines.push("");
640
+ lines.push(" // Cleanup on unmount");
641
+ lines.push(" return () => {");
642
+ lines.push(" window.removeEventListener('vanilla-agent:message', handleMessage);");
643
+ lines.push(" window.removeEventListener('vanilla-agent:clear-chat', handleClearChat);");
644
+ lines.push(" if (handle) {");
645
+ lines.push(" handle.destroy();");
646
+ lines.push(" }");
647
+ lines.push(" };");
648
+ lines.push(" }, []);");
649
+ lines.push("");
650
+ lines.push(" return null; // Widget injects itself into the DOM");
651
+ lines.push("}");
652
+ lines.push("");
653
+ lines.push("// Usage: Collects DOM context for AI-powered navigation");
654
+ lines.push("// Features:");
655
+ lines.push("// - Extracts page elements (products, buttons, links)");
656
+ lines.push("// - Persists chat history across page loads");
657
+ lines.push("// - Handles navigation actions (nav_then_click)");
658
+ lines.push("// - Processes structured JSON actions from AI");
659
+ lines.push("//");
660
+ lines.push("// Example usage in Next.js:");
661
+ lines.push("// import { ChatWidgetAdvanced } from './components/ChatWidgetAdvanced';");
662
+ lines.push("//");
663
+ lines.push("// export default function RootLayout({ children }) {");
664
+ lines.push("// return (");
665
+ lines.push("// <html lang=\"en\">");
666
+ lines.push("// <body>");
667
+ lines.push("// {children}");
668
+ lines.push("// <ChatWidgetAdvanced />");
669
+ lines.push("// </body>");
670
+ lines.push("// </html>");
671
+ lines.push("// );");
672
+ lines.push("// }");
673
+
674
+ return lines.join("\n");
675
+ }
676
+
677
+ function generateScriptInstallerCode(config: any): string {
678
+ const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
679
+ const shouldEmitParserType = parserType !== "plain";
680
+
681
+ const lines: string[] = [
682
+ "<script>",
683
+ " window.siteAgentConfig = {",
684
+ " target: 'body',",
685
+ " config: {"
686
+ ];
687
+
688
+ if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
689
+ if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
690
+ if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
691
+
692
+ if (config.theme) {
693
+ lines.push(" theme: {");
694
+ Object.entries(config.theme).forEach(([key, value]) => {
695
+ lines.push(` ${key}: "${value}",`);
696
+ });
697
+ lines.push(" },");
698
+ }
699
+
700
+ if (config.launcher) {
701
+ lines.push(" launcher: {");
702
+ Object.entries(config.launcher).forEach(([key, value]) => {
703
+ if (typeof value === "string") {
704
+ lines.push(` ${key}: "${value}",`);
705
+ } else if (typeof value === "boolean") {
706
+ lines.push(` ${key}: ${value},`);
707
+ }
708
+ });
709
+ lines.push(" },");
710
+ }
711
+
712
+ if (config.copy) {
713
+ lines.push(" copy: {");
714
+ Object.entries(config.copy).forEach(([key, value]) => {
715
+ lines.push(` ${key}: "${value}",`);
716
+ });
717
+ lines.push(" },");
718
+ }
719
+
720
+ if (config.sendButton) {
721
+ lines.push(" sendButton: {");
722
+ Object.entries(config.sendButton).forEach(([key, value]) => {
723
+ if (typeof value === "string") {
724
+ lines.push(` ${key}: "${value}",`);
725
+ } else if (typeof value === "boolean") {
726
+ lines.push(` ${key}: ${value},`);
727
+ }
728
+ });
729
+ lines.push(" },");
730
+ }
731
+
732
+ if (config.voiceRecognition) {
733
+ lines.push(" voiceRecognition: {");
734
+ Object.entries(config.voiceRecognition).forEach(([key, value]) => {
735
+ if (typeof value === "string") {
736
+ lines.push(` ${key}: "${value}",`);
737
+ } else if (typeof value === "boolean") {
738
+ lines.push(` ${key}: ${value},`);
739
+ } else if (typeof value === "number") {
740
+ lines.push(` ${key}: ${value},`);
741
+ }
742
+ });
743
+ lines.push(" },");
744
+ }
745
+
746
+ if (config.statusIndicator) {
747
+ lines.push(" statusIndicator: {");
748
+ Object.entries(config.statusIndicator).forEach(([key, value]) => {
749
+ if (typeof value === "string") {
750
+ lines.push(` ${key}: "${value}",`);
751
+ } else if (typeof value === "boolean") {
752
+ lines.push(` ${key}: ${value},`);
753
+ }
754
+ });
755
+ lines.push(" },");
756
+ }
757
+
758
+ if (config.features) {
759
+ lines.push(" features: {");
760
+ Object.entries(config.features).forEach(([key, value]) => {
761
+ lines.push(` ${key}: ${value},`);
762
+ });
763
+ lines.push(" },");
764
+ }
765
+
766
+ if (config.suggestionChips && config.suggestionChips.length > 0) {
767
+ lines.push(" suggestionChips: [");
768
+ config.suggestionChips.forEach((chip: string) => {
769
+ lines.push(` "${chip}",`);
770
+ });
771
+ lines.push(" ],");
772
+ }
773
+
774
+ if (config.suggestionChipsConfig) {
775
+ lines.push(" suggestionChipsConfig: {");
776
+ if (config.suggestionChipsConfig.fontFamily) {
777
+ lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
778
+ }
779
+ if (config.suggestionChipsConfig.fontWeight) {
780
+ lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
781
+ }
782
+ if (config.suggestionChipsConfig.paddingX) {
783
+ lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
784
+ }
785
+ if (config.suggestionChipsConfig.paddingY) {
786
+ lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
787
+ }
788
+ lines.push(" },");
789
+ }
790
+
791
+ if (config.debug) {
792
+ lines.push(` debug: ${config.debug},`);
793
+ }
794
+
795
+ lines.push(" postprocessMessage: ({ text }) => window.AgentWidget.markdownPostprocessor(text)");
796
+ lines.push(" }");
797
+ lines.push(" };");
798
+ lines.push("</script>");
799
+ lines.push("<script src=\"https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/install.global.js\"></script>");
800
+
801
+ return lines.join("\n");
802
+ }
803
+
804
+ function generateScriptManualCode(config: any): string {
805
+ const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
806
+ const shouldEmitParserType = parserType !== "plain";
807
+
808
+ const lines: string[] = [
809
+ "<!-- Load CSS -->",
810
+ "<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/widget.css\" />",
811
+ "",
812
+ "<!-- Load JavaScript -->",
813
+ "<script src=\"https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/index.global.js\"></script>",
814
+ "",
815
+ "<!-- Initialize widget -->",
816
+ "<script>",
817
+ " window.AgentWidget.initAgentWidget({",
818
+ " target: 'body',",
819
+ " config: {"
820
+ ];
821
+
822
+ if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
823
+ if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
824
+ if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
825
+
826
+ if (config.theme) {
827
+ lines.push(" theme: {");
828
+ Object.entries(config.theme).forEach(([key, value]) => {
829
+ lines.push(` ${key}: "${value}",`);
830
+ });
831
+ lines.push(" },");
832
+ }
833
+
834
+ if (config.launcher) {
835
+ lines.push(" launcher: {");
836
+ Object.entries(config.launcher).forEach(([key, value]) => {
837
+ if (typeof value === "string") {
838
+ lines.push(` ${key}: "${value}",`);
839
+ } else if (typeof value === "boolean") {
840
+ lines.push(` ${key}: ${value},`);
841
+ }
842
+ });
843
+ lines.push(" },");
844
+ }
845
+
846
+ if (config.copy) {
847
+ lines.push(" copy: {");
848
+ Object.entries(config.copy).forEach(([key, value]) => {
849
+ lines.push(` ${key}: "${value}",`);
850
+ });
851
+ lines.push(" },");
852
+ }
853
+
854
+ if (config.sendButton) {
855
+ lines.push(" sendButton: {");
856
+ Object.entries(config.sendButton).forEach(([key, value]) => {
857
+ if (typeof value === "string") {
858
+ lines.push(` ${key}: "${value}",`);
859
+ } else if (typeof value === "boolean") {
860
+ lines.push(` ${key}: ${value},`);
861
+ }
862
+ });
863
+ lines.push(" },");
864
+ }
865
+
866
+ if (config.voiceRecognition) {
867
+ lines.push(" voiceRecognition: {");
868
+ Object.entries(config.voiceRecognition).forEach(([key, value]) => {
869
+ if (typeof value === "string") {
870
+ lines.push(` ${key}: "${value}",`);
871
+ } else if (typeof value === "boolean") {
872
+ lines.push(` ${key}: ${value},`);
873
+ } else if (typeof value === "number") {
874
+ lines.push(` ${key}: ${value},`);
875
+ }
876
+ });
877
+ lines.push(" },");
878
+ }
879
+
880
+ if (config.statusIndicator) {
881
+ lines.push(" statusIndicator: {");
882
+ Object.entries(config.statusIndicator).forEach(([key, value]) => {
883
+ if (typeof value === "string") {
884
+ lines.push(` ${key}: "${value}",`);
885
+ } else if (typeof value === "boolean") {
886
+ lines.push(` ${key}: ${value},`);
887
+ }
888
+ });
889
+ lines.push(" },");
890
+ }
891
+
892
+ if (config.features) {
893
+ lines.push(" features: {");
894
+ Object.entries(config.features).forEach(([key, value]) => {
895
+ lines.push(` ${key}: ${value},`);
896
+ });
897
+ lines.push(" },");
898
+ }
899
+
900
+ if (config.suggestionChips && config.suggestionChips.length > 0) {
901
+ lines.push(" suggestionChips: [");
902
+ config.suggestionChips.forEach((chip: string) => {
903
+ lines.push(` "${chip}",`);
904
+ });
905
+ lines.push(" ],");
906
+ }
907
+
908
+ if (config.suggestionChipsConfig) {
909
+ lines.push(" suggestionChipsConfig: {");
910
+ if (config.suggestionChipsConfig.fontFamily) {
911
+ lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
912
+ }
913
+ if (config.suggestionChipsConfig.fontWeight) {
914
+ lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
915
+ }
916
+ if (config.suggestionChipsConfig.paddingX) {
917
+ lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
918
+ }
919
+ if (config.suggestionChipsConfig.paddingY) {
920
+ lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
921
+ }
922
+ lines.push(" },");
923
+ }
924
+
925
+ if (config.debug) {
926
+ lines.push(` debug: ${config.debug},`);
927
+ }
928
+
929
+ lines.push(" postprocessMessage: ({ text }) => window.AgentWidget.markdownPostprocessor(text)");
930
+ lines.push(" }");
931
+ lines.push(" });");
932
+ lines.push("</script>");
933
+
934
+ return lines.join("\n");
935
+ }
936
+
937
+ function generateScriptAdvancedCode(config: any): string {
938
+ const lines: string[] = [
939
+ "<!-- Load CSS -->",
940
+ "<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/widget.css\" />",
941
+ "",
942
+ "<!-- Chat Widget Configuration -->",
943
+ "<script>",
944
+ " window.ChatWidgetConfig = {"
945
+ ];
946
+
947
+ if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
948
+ if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
949
+
950
+ if (config.theme) {
951
+ lines.push(" theme: {");
952
+ Object.entries(config.theme).forEach(([key, value]) => {
953
+ lines.push(` ${key}: "${value}",`);
954
+ });
955
+ lines.push(" },");
956
+ }
957
+
958
+ if (config.launcher) {
959
+ lines.push(" launcher: {");
960
+ Object.entries(config.launcher).forEach(([key, value]) => {
961
+ if (typeof value === "string") {
962
+ lines.push(` ${key}: "${value}",`);
963
+ } else if (typeof value === "boolean") {
964
+ lines.push(` ${key}: ${value},`);
965
+ }
966
+ });
967
+ lines.push(" },");
968
+ }
969
+
970
+ if (config.copy) {
971
+ lines.push(" copy: {");
972
+ Object.entries(config.copy).forEach(([key, value]) => {
973
+ lines.push(` ${key}: "${value}",`);
974
+ });
975
+ lines.push(" },");
976
+ }
977
+
978
+ if (config.sendButton) {
979
+ lines.push(" sendButton: {");
980
+ Object.entries(config.sendButton).forEach(([key, value]) => {
981
+ if (typeof value === "string") {
982
+ lines.push(` ${key}: "${value}",`);
983
+ } else if (typeof value === "boolean") {
984
+ lines.push(` ${key}: ${value},`);
985
+ }
986
+ });
987
+ lines.push(" },");
988
+ }
989
+
990
+ if (config.voiceRecognition) {
991
+ lines.push(" voiceRecognition: {");
992
+ Object.entries(config.voiceRecognition).forEach(([key, value]) => {
993
+ if (typeof value === "string") {
994
+ lines.push(` ${key}: "${value}",`);
995
+ } else if (typeof value === "boolean") {
996
+ lines.push(` ${key}: ${value},`);
997
+ } else if (typeof value === "number") {
998
+ lines.push(` ${key}: ${value},`);
999
+ }
1000
+ });
1001
+ lines.push(" },");
1002
+ }
1003
+
1004
+ if (config.statusIndicator) {
1005
+ lines.push(" statusIndicator: {");
1006
+ Object.entries(config.statusIndicator).forEach(([key, value]) => {
1007
+ if (typeof value === "string") {
1008
+ lines.push(` ${key}: "${value}",`);
1009
+ } else if (typeof value === "boolean") {
1010
+ lines.push(` ${key}: ${value},`);
1011
+ }
1012
+ });
1013
+ lines.push(" },");
1014
+ }
1015
+
1016
+ if (config.features) {
1017
+ lines.push(" features: {");
1018
+ Object.entries(config.features).forEach(([key, value]) => {
1019
+ lines.push(` ${key}: ${value},`);
1020
+ });
1021
+ lines.push(" },");
1022
+ }
1023
+
1024
+ if (config.suggestionChips && config.suggestionChips.length > 0) {
1025
+ lines.push(" suggestionChips: [");
1026
+ config.suggestionChips.forEach((chip: string) => {
1027
+ lines.push(` "${chip}",`);
1028
+ });
1029
+ lines.push(" ],");
1030
+ }
1031
+
1032
+ lines.push(" };");
1033
+ lines.push("</script>");
1034
+ lines.push("");
1035
+ lines.push("<!-- Load the widget library -->");
1036
+ lines.push("<script src=\"https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/index.global.js\"></script>");
1037
+ lines.push("");
1038
+ lines.push("<!-- Chat Widget Script with DOM Helper -->");
1039
+ lines.push("<script>");
1040
+ lines.push(" (function () {");
1041
+ lines.push(" 'use strict';");
1042
+ lines.push(" ");
1043
+ lines.push(" const STORAGE_KEY = 'chat-widget-state';");
1044
+ lines.push(" const PROCESSED_ACTIONS_KEY = 'chat-widget-processed-actions';");
1045
+ lines.push("");
1046
+ lines.push(" // DOM context provider - extracts page elements for AI context");
1047
+ lines.push(" const domContextProvider = () => {");
1048
+ lines.push(" const selectors = {");
1049
+ lines.push(" products: '[data-product-id], .product-card, .product-item, [role=\"article\"]',");
1050
+ lines.push(" buttons: 'button, [role=\"button\"], .btn',");
1051
+ lines.push(" links: 'a[href]',");
1052
+ lines.push(" inputs: 'input, textarea, select'");
1053
+ lines.push(" };");
1054
+ lines.push("");
1055
+ lines.push(" const elements = [];");
1056
+ lines.push(" Object.entries(selectors).forEach(([type, selector]) => {");
1057
+ lines.push(" document.querySelectorAll(selector).forEach((element) => {");
1058
+ lines.push(" if (!(element instanceof HTMLElement)) return;");
1059
+ lines.push(" ");
1060
+ lines.push(" // Exclude elements within the widget");
1061
+ lines.push(" const widgetHost = element.closest('.vanilla-agent-host');");
1062
+ lines.push(" if (widgetHost) return;");
1063
+ lines.push(" ");
1064
+ lines.push(" const text = element.innerText?.trim();");
1065
+ lines.push(" if (!text) return;");
1066
+ lines.push("");
1067
+ lines.push(" const selectorString =");
1068
+ lines.push(" element.id ? `#${element.id}` :");
1069
+ lines.push(" element.getAttribute('data-testid') ? `[data-testid=\"${element.getAttribute('data-testid')}\"]` :");
1070
+ lines.push(" element.getAttribute('data-product-id') ? `[data-product-id=\"${element.getAttribute('data-product-id')}\"]` :");
1071
+ lines.push(" element.tagName.toLowerCase();");
1072
+ lines.push("");
1073
+ lines.push(" const elementData = {");
1074
+ lines.push(" type,");
1075
+ lines.push(" tagName: element.tagName.toLowerCase(),");
1076
+ lines.push(" selector: selectorString,");
1077
+ lines.push(" innerText: text.substring(0, 200)");
1078
+ lines.push(" };");
1079
+ lines.push("");
1080
+ lines.push(" if (type === 'links' && element instanceof HTMLAnchorElement && element.href) {");
1081
+ lines.push(" elementData.href = element.href;");
1082
+ lines.push(" }");
1083
+ lines.push("");
1084
+ lines.push(" elements.push(elementData);");
1085
+ lines.push(" });");
1086
+ lines.push(" });");
1087
+ lines.push("");
1088
+ lines.push(" const counts = elements.reduce((acc, el) => {");
1089
+ lines.push(" acc[el.type] = (acc[el.type] || 0) + 1;");
1090
+ lines.push(" return acc;");
1091
+ lines.push(" }, {});");
1092
+ lines.push("");
1093
+ lines.push(" return {");
1094
+ lines.push(" page_elements: elements.slice(0, 50),");
1095
+ lines.push(" page_element_count: elements.length,");
1096
+ lines.push(" element_types: counts,");
1097
+ lines.push(" page_url: window.location.href,");
1098
+ lines.push(" page_title: document.title,");
1099
+ lines.push(" timestamp: new Date().toISOString()");
1100
+ lines.push(" };");
1101
+ lines.push(" };");
1102
+ lines.push("");
1103
+ lines.push(" const createWidgetConfig = (agentWidget) => ({");
1104
+ lines.push(" ...window.ChatWidgetConfig,");
1105
+ lines.push(" // Flexible JSON stream parser for handling structured actions");
1106
+ lines.push(" streamParser: () => agentWidget.createFlexibleJsonStreamParser((parsed) => {");
1107
+ lines.push(" if (!parsed || typeof parsed !== 'object') return null;");
1108
+ lines.push(" ");
1109
+ lines.push(" // Extract display text based on action type");
1110
+ lines.push(" if (parsed.action === 'nav_then_click') {");
1111
+ lines.push(" return 'Navigating...';");
1112
+ lines.push(" } else if (parsed.action === 'message') {");
1113
+ lines.push(" return parsed.text || '';");
1114
+ lines.push(" } else if (parsed.action === 'message_and_click') {");
1115
+ lines.push(" return parsed.text || 'Processing...';");
1116
+ lines.push(" }");
1117
+ lines.push(" ");
1118
+ lines.push(" return parsed.text || null;");
1119
+ lines.push(" }),");
1120
+ lines.push(" // Action parsers to detect JSON actions in responses");
1121
+ lines.push(" actionParsers: [");
1122
+ lines.push(" agentWidget.defaultJsonActionParser,");
1123
+ lines.push(" // Custom parser for markdown-wrapped JSON");
1124
+ lines.push(" ({ text, message }) => {");
1125
+ lines.push(" const jsonSource = message.rawContent || text || message.content;");
1126
+ lines.push(" if (!jsonSource || typeof jsonSource !== 'string') return null;");
1127
+ lines.push(" ");
1128
+ lines.push(" // Strip markdown code fences");
1129
+ lines.push(" let cleanJson = jsonSource");
1130
+ lines.push(" .replace(/^```(?:json)?\\s*\\n?/, '')");
1131
+ lines.push(" .replace(/\\n?```\\s*$/, '')");
1132
+ lines.push(" .trim();");
1133
+ lines.push(" ");
1134
+ lines.push(" if (!cleanJson.startsWith('{') || !cleanJson.endsWith('}')) return null;");
1135
+ lines.push(" ");
1136
+ lines.push(" try {");
1137
+ lines.push(" const parsed = JSON.parse(cleanJson);");
1138
+ lines.push(" if (parsed.action) {");
1139
+ lines.push(" return { type: parsed.action, payload: parsed };");
1140
+ lines.push(" }");
1141
+ lines.push(" } catch (e) {");
1142
+ lines.push(" return null;");
1143
+ lines.push(" }");
1144
+ lines.push(" return null;");
1145
+ lines.push(" }");
1146
+ lines.push(" ],");
1147
+ lines.push(" // Action handlers for navigation and other actions");
1148
+ lines.push(" actionHandlers: [");
1149
+ lines.push(" agentWidget.defaultActionHandlers.message,");
1150
+ lines.push(" agentWidget.defaultActionHandlers.messageAndClick,");
1151
+ lines.push(" // Handler for nav_then_click action");
1152
+ lines.push(" (action, context) => {");
1153
+ lines.push(" if (action.type !== 'nav_then_click') return;");
1154
+ lines.push(" ");
1155
+ lines.push(" const payload = action.payload || action.raw || {};");
1156
+ lines.push(" const url = payload?.page;");
1157
+ lines.push(" const text = payload?.on_load_text || 'Navigating...';");
1158
+ lines.push(" ");
1159
+ lines.push(" if (!url) return { handled: true, displayText: text };");
1160
+ lines.push(" ");
1161
+ lines.push(" // Check if already processed");
1162
+ lines.push(" const messageId = context.message?.id;");
1163
+ lines.push(" const processedActions = JSON.parse(localStorage.getItem(PROCESSED_ACTIONS_KEY) || '[]');");
1164
+ lines.push(" const actionKey = `nav_${messageId}_${url}`;");
1165
+ lines.push(" ");
1166
+ lines.push(" if (processedActions.includes(actionKey)) {");
1167
+ lines.push(" return { handled: true, displayText: text };");
1168
+ lines.push(" }");
1169
+ lines.push(" ");
1170
+ lines.push(" processedActions.push(actionKey);");
1171
+ lines.push(" localStorage.setItem(PROCESSED_ACTIONS_KEY, JSON.stringify(processedActions));");
1172
+ lines.push(" ");
1173
+ lines.push(" const targetUrl = url.startsWith('http')");
1174
+ lines.push(" ? url");
1175
+ lines.push(" : new URL(url, window.location.origin).toString();");
1176
+ lines.push(" ");
1177
+ lines.push(" window.location.href = targetUrl;");
1178
+ lines.push(" ");
1179
+ lines.push(" return { handled: true, displayText: text };");
1180
+ lines.push(" }");
1181
+ lines.push(" ],");
1182
+ lines.push(" // Send DOM context with each request");
1183
+ lines.push(" requestMiddleware: ({ payload }) => ({");
1184
+ lines.push(" ...payload,");
1185
+ lines.push(" metadata: domContextProvider()");
1186
+ lines.push(" }),");
1187
+ lines.push(" postprocessMessage: ({ text }) => agentWidget.markdownPostprocessor(text)");
1188
+ lines.push(" });");
1189
+ lines.push("");
1190
+ lines.push(" // Initialize widget when DOM is loaded");
1191
+ lines.push(" function init() {");
1192
+ lines.push(" const agentWidget = window.AgentWidget;");
1193
+ lines.push(" if (!agentWidget) {");
1194
+ lines.push(" console.error('AgentWidget not loaded');");
1195
+ lines.push(" return;");
1196
+ lines.push(" }");
1197
+ lines.push("");
1198
+ lines.push(" const widgetConfig = createWidgetConfig(agentWidget);");
1199
+ lines.push("");
1200
+ lines.push(" // Load saved state");
1201
+ lines.push(" const savedState = localStorage.getItem(STORAGE_KEY);");
1202
+ lines.push(" if (savedState) {");
1203
+ lines.push(" try {");
1204
+ lines.push(" const { messages } = JSON.parse(savedState);");
1205
+ lines.push(" widgetConfig.initialMessages = messages || [];");
1206
+ lines.push(" } catch (e) {");
1207
+ lines.push(" console.error('Failed to load saved state:', e);");
1208
+ lines.push(" }");
1209
+ lines.push(" }");
1210
+ lines.push("");
1211
+ lines.push(" // Initialize widget with DOM context");
1212
+ lines.push(" const handle = agentWidget.initAgentWidget({");
1213
+ lines.push(" target: 'body',");
1214
+ lines.push(" useShadowDom: false,");
1215
+ lines.push(" config: widgetConfig");
1216
+ lines.push(" });");
1217
+ lines.push("");
1218
+ lines.push(" // Save state on message events");
1219
+ lines.push(" window.addEventListener('vanilla-agent:message', (event) => {");
1220
+ lines.push(" const session = handle.getSession?.();");
1221
+ lines.push(" if (session) {");
1222
+ lines.push(" localStorage.setItem(STORAGE_KEY, JSON.stringify({");
1223
+ lines.push(" messages: session.messages,");
1224
+ lines.push(" timestamp: new Date().toISOString()");
1225
+ lines.push(" }));");
1226
+ lines.push(" }");
1227
+ lines.push(" });");
1228
+ lines.push("");
1229
+ lines.push(" // Clear state on clear chat");
1230
+ lines.push(" window.addEventListener('vanilla-agent:clear-chat', () => {");
1231
+ lines.push(" localStorage.removeItem(STORAGE_KEY);");
1232
+ lines.push(" localStorage.removeItem(PROCESSED_ACTIONS_KEY);");
1233
+ lines.push(" });");
1234
+ lines.push(" }");
1235
+ lines.push("");
1236
+ lines.push(" // Initialize when DOM is ready");
1237
+ lines.push(" if (document.readyState === 'loading') {");
1238
+ lines.push(" document.addEventListener('DOMContentLoaded', init);");
1239
+ lines.push(" } else {");
1240
+ lines.push(" init();");
1241
+ lines.push(" }");
1242
+ lines.push(" })();");
1243
+ lines.push("</script>");
1244
+
1245
+ return lines.join("\n");
1246
+ }