vanilla-agent 1.23.0 → 1.25.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.
@@ -22,6 +22,154 @@ function getParserTypeFromConfig(config: AgentWidgetConfig): ParserType {
22
22
  return config.parserType ?? detectParserTypeFromStreamParser(config.streamParser) ?? "plain";
23
23
  }
24
24
 
25
+ // Helper to generate toolCall config
26
+ function generateToolCallConfig(config: any, indent: string): string[] {
27
+ const lines: string[] = [];
28
+ if (config.toolCall) {
29
+ lines.push(`${indent}toolCall: {`);
30
+ Object.entries(config.toolCall).forEach(([key, value]) => {
31
+ if (typeof value === "string") {
32
+ lines.push(`${indent} ${key}: "${value}",`);
33
+ }
34
+ });
35
+ lines.push(`${indent}},`);
36
+ }
37
+ return lines;
38
+ }
39
+
40
+ // Helper to generate messageActions config (excluding callbacks)
41
+ function generateMessageActionsConfig(config: any, indent: string): string[] {
42
+ const lines: string[] = [];
43
+ if (config.messageActions) {
44
+ const hasSerializableProps = Object.entries(config.messageActions).some(
45
+ ([key, value]) => key !== "onFeedback" && key !== "onCopy" && value !== undefined
46
+ );
47
+ if (hasSerializableProps) {
48
+ lines.push(`${indent}messageActions: {`);
49
+ Object.entries(config.messageActions).forEach(([key, value]) => {
50
+ // Skip function callbacks
51
+ if (key === "onFeedback" || key === "onCopy") return;
52
+ if (typeof value === "string") {
53
+ lines.push(`${indent} ${key}: "${value}",`);
54
+ } else if (typeof value === "boolean") {
55
+ lines.push(`${indent} ${key}: ${value},`);
56
+ }
57
+ });
58
+ lines.push(`${indent}},`);
59
+ }
60
+ }
61
+ return lines;
62
+ }
63
+
64
+ // Helper to generate markdown config (excluding renderer functions)
65
+ function generateMarkdownConfig(config: any, indent: string): string[] {
66
+ const lines: string[] = [];
67
+ if (config.markdown) {
68
+ const hasOptions = config.markdown.options && Object.keys(config.markdown.options).length > 0;
69
+ const hasDisableDefaultStyles = config.markdown.disableDefaultStyles !== undefined;
70
+
71
+ if (hasOptions || hasDisableDefaultStyles) {
72
+ lines.push(`${indent}markdown: {`);
73
+
74
+ if (hasOptions) {
75
+ lines.push(`${indent} options: {`);
76
+ Object.entries(config.markdown.options).forEach(([key, value]) => {
77
+ if (typeof value === "string") {
78
+ lines.push(`${indent} ${key}: "${value}",`);
79
+ } else if (typeof value === "boolean") {
80
+ lines.push(`${indent} ${key}: ${value},`);
81
+ }
82
+ });
83
+ lines.push(`${indent} },`);
84
+ }
85
+
86
+ if (hasDisableDefaultStyles) {
87
+ lines.push(`${indent} disableDefaultStyles: ${config.markdown.disableDefaultStyles},`);
88
+ }
89
+
90
+ lines.push(`${indent}},`);
91
+ }
92
+ }
93
+ return lines;
94
+ }
95
+
96
+ // Helper to generate layout config (excluding render functions and slots)
97
+ function generateLayoutConfig(config: any, indent: string): string[] {
98
+ const lines: string[] = [];
99
+ if (config.layout) {
100
+ const hasHeader = config.layout.header && Object.keys(config.layout.header).some(
101
+ (key: string) => key !== "render"
102
+ );
103
+ const hasMessages = config.layout.messages && Object.keys(config.layout.messages).some(
104
+ (key: string) => key !== "renderUserMessage" && key !== "renderAssistantMessage"
105
+ );
106
+
107
+ if (hasHeader || hasMessages) {
108
+ lines.push(`${indent}layout: {`);
109
+
110
+ // Header config (excluding render function)
111
+ if (hasHeader) {
112
+ lines.push(`${indent} header: {`);
113
+ Object.entries(config.layout.header).forEach(([key, value]) => {
114
+ if (key === "render") return; // Skip render function
115
+ if (typeof value === "string") {
116
+ lines.push(`${indent} ${key}: "${value}",`);
117
+ } else if (typeof value === "boolean") {
118
+ lines.push(`${indent} ${key}: ${value},`);
119
+ }
120
+ });
121
+ lines.push(`${indent} },`);
122
+ }
123
+
124
+ // Messages config (excluding render functions)
125
+ if (hasMessages) {
126
+ lines.push(`${indent} messages: {`);
127
+ Object.entries(config.layout.messages).forEach(([key, value]) => {
128
+ // Skip render functions
129
+ if (key === "renderUserMessage" || key === "renderAssistantMessage") return;
130
+
131
+ if (key === "avatar" && typeof value === "object" && value !== null) {
132
+ lines.push(`${indent} avatar: {`);
133
+ Object.entries(value as Record<string, unknown>).forEach(([avatarKey, avatarValue]) => {
134
+ if (typeof avatarValue === "string") {
135
+ lines.push(`${indent} ${avatarKey}: "${avatarValue}",`);
136
+ } else if (typeof avatarValue === "boolean") {
137
+ lines.push(`${indent} ${avatarKey}: ${avatarValue},`);
138
+ }
139
+ });
140
+ lines.push(`${indent} },`);
141
+ } else if (key === "timestamp" && typeof value === "object" && value !== null) {
142
+ // Only emit serializable timestamp properties (skip format function)
143
+ const hasSerializableTimestamp = Object.entries(value as Record<string, unknown>).some(
144
+ ([k]) => k !== "format"
145
+ );
146
+ if (hasSerializableTimestamp) {
147
+ lines.push(`${indent} timestamp: {`);
148
+ Object.entries(value as Record<string, unknown>).forEach(([tsKey, tsValue]) => {
149
+ if (tsKey === "format") return; // Skip format function
150
+ if (typeof tsValue === "string") {
151
+ lines.push(`${indent} ${tsKey}: "${tsValue}",`);
152
+ } else if (typeof tsValue === "boolean") {
153
+ lines.push(`${indent} ${tsKey}: ${tsValue},`);
154
+ }
155
+ });
156
+ lines.push(`${indent} },`);
157
+ }
158
+ } else if (typeof value === "string") {
159
+ lines.push(`${indent} ${key}: "${value}",`);
160
+ } else if (typeof value === "boolean") {
161
+ lines.push(`${indent} ${key}: ${value},`);
162
+ }
163
+ });
164
+ lines.push(`${indent} },`);
165
+ }
166
+
167
+ lines.push(`${indent}},`);
168
+ }
169
+ }
170
+ return lines;
171
+ }
172
+
25
173
  export function generateCodeSnippet(config: any, format: CodeFormat = "esm"): string {
26
174
  // Remove non-serializable properties
27
175
  const cleanConfig = { ...config };
@@ -159,6 +307,18 @@ function generateESMCode(config: any): string {
159
307
  lines.push(" },");
160
308
  }
161
309
 
310
+ // Add toolCall config
311
+ lines.push(...generateToolCallConfig(config, " "));
312
+
313
+ // Add messageActions config
314
+ lines.push(...generateMessageActionsConfig(config, " "));
315
+
316
+ // Add markdown config
317
+ lines.push(...generateMarkdownConfig(config, " "));
318
+
319
+ // Add layout config
320
+ lines.push(...generateLayoutConfig(config, " "));
321
+
162
322
  if (config.debug) {
163
323
  lines.push(` debug: ${config.debug},`);
164
324
  }
@@ -295,6 +455,18 @@ function generateReactComponentCode(config: any): string {
295
455
  lines.push(" },");
296
456
  }
297
457
 
458
+ // Add toolCall config
459
+ lines.push(...generateToolCallConfig(config, " "));
460
+
461
+ // Add messageActions config
462
+ lines.push(...generateMessageActionsConfig(config, " "));
463
+
464
+ // Add markdown config
465
+ lines.push(...generateMarkdownConfig(config, " "));
466
+
467
+ // Add layout config
468
+ lines.push(...generateLayoutConfig(config, " "));
469
+
298
470
  if (config.debug) {
299
471
  lines.push(` debug: ${config.debug},`);
300
472
  }
@@ -548,6 +720,18 @@ function generateReactAdvancedCode(config: any): string {
548
720
  lines.push(" },");
549
721
  }
550
722
 
723
+ // Add toolCall config
724
+ lines.push(...generateToolCallConfig(config, " "));
725
+
726
+ // Add messageActions config
727
+ lines.push(...generateMessageActionsConfig(config, " "));
728
+
729
+ // Add markdown config
730
+ lines.push(...generateMarkdownConfig(config, " "));
731
+
732
+ // Add layout config
733
+ lines.push(...generateLayoutConfig(config, " "));
734
+
551
735
  if (config.debug) {
552
736
  lines.push(` debug: ${config.debug},`);
553
737
  }
@@ -674,131 +858,123 @@ function generateReactAdvancedCode(config: any): string {
674
858
  return lines.join("\n");
675
859
  }
676
860
 
677
- function generateScriptInstallerCode(config: any): string {
861
+ // Helper to build a serializable config object for JSON export
862
+ function buildSerializableConfig(config: any): Record<string, any> {
678
863
  const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
679
864
  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}",`);
865
+
866
+ const serializableConfig: Record<string, any> = {};
867
+
868
+ if (config.apiUrl) serializableConfig.apiUrl = config.apiUrl;
869
+ if (config.flowId) serializableConfig.flowId = config.flowId;
870
+ if (shouldEmitParserType) serializableConfig.parserType = parserType;
871
+ if (config.theme) serializableConfig.theme = config.theme;
872
+ if (config.launcher) serializableConfig.launcher = config.launcher;
873
+ if (config.copy) serializableConfig.copy = config.copy;
874
+ if (config.sendButton) serializableConfig.sendButton = config.sendButton;
875
+ if (config.voiceRecognition) serializableConfig.voiceRecognition = config.voiceRecognition;
876
+ if (config.statusIndicator) serializableConfig.statusIndicator = config.statusIndicator;
877
+ if (config.features) serializableConfig.features = config.features;
878
+ if (config.suggestionChips?.length > 0) serializableConfig.suggestionChips = config.suggestionChips;
879
+ if (config.suggestionChipsConfig) serializableConfig.suggestionChipsConfig = config.suggestionChipsConfig;
880
+ if (config.debug) serializableConfig.debug = config.debug;
881
+
882
+ // Add toolCall config (only serializable parts)
883
+ if (config.toolCall) {
884
+ const toolCallConfig: Record<string, any> = {};
885
+ Object.entries(config.toolCall).forEach(([key, value]) => {
886
+ if (typeof value === "string") toolCallConfig[key] = value;
716
887
  });
717
- lines.push(" },");
888
+ if (Object.keys(toolCallConfig).length > 0) {
889
+ serializableConfig.toolCall = toolCallConfig;
890
+ }
718
891
  }
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},`);
892
+
893
+ // Add messageActions config (excluding callbacks)
894
+ if (config.messageActions) {
895
+ const messageActionsConfig: Record<string, any> = {};
896
+ Object.entries(config.messageActions).forEach(([key, value]) => {
897
+ if (key !== "onFeedback" && key !== "onCopy" && value !== undefined) {
898
+ if (typeof value === "string" || typeof value === "boolean") {
899
+ messageActionsConfig[key] = value;
900
+ }
727
901
  }
728
902
  });
729
- lines.push(" },");
903
+ if (Object.keys(messageActionsConfig).length > 0) {
904
+ serializableConfig.messageActions = messageActionsConfig;
905
+ }
730
906
  }
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(" },");
907
+
908
+ // Add markdown config (excluding renderer functions)
909
+ if (config.markdown) {
910
+ const markdownConfig: Record<string, any> = {};
911
+ if (config.markdown.options) markdownConfig.options = config.markdown.options;
912
+ if (config.markdown.disableDefaultStyles !== undefined) {
913
+ markdownConfig.disableDefaultStyles = config.markdown.disableDefaultStyles;
914
+ }
915
+ if (Object.keys(markdownConfig).length > 0) {
916
+ serializableConfig.markdown = markdownConfig;
917
+ }
744
918
  }
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},`);
919
+
920
+ // Add layout config (excluding render functions)
921
+ if (config.layout) {
922
+ const layoutConfig: Record<string, any> = {};
923
+
924
+ if (config.layout.header) {
925
+ const headerConfig: Record<string, any> = {};
926
+ Object.entries(config.layout.header).forEach(([key, value]) => {
927
+ if (key !== "render" && (typeof value === "string" || typeof value === "boolean")) {
928
+ headerConfig[key] = value;
929
+ }
930
+ });
931
+ if (Object.keys(headerConfig).length > 0) {
932
+ layoutConfig.header = headerConfig;
753
933
  }
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
934
  }
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}",`);
935
+
936
+ if (config.layout.messages) {
937
+ const messagesConfig: Record<string, any> = {};
938
+ Object.entries(config.layout.messages).forEach(([key, value]) => {
939
+ if (key !== "renderUserMessage" && key !== "renderAssistantMessage") {
940
+ if (key === "avatar" && typeof value === "object" && value !== null) {
941
+ messagesConfig.avatar = value;
942
+ } else if (key === "timestamp" && typeof value === "object" && value !== null) {
943
+ // Exclude format function
944
+ const tsConfig: Record<string, any> = {};
945
+ Object.entries(value as Record<string, unknown>).forEach(([tsKey, tsValue]) => {
946
+ if (tsKey !== "format" && (typeof tsValue === "string" || typeof tsValue === "boolean")) {
947
+ tsConfig[tsKey] = tsValue;
948
+ }
949
+ });
950
+ if (Object.keys(tsConfig).length > 0) {
951
+ messagesConfig.timestamp = tsConfig;
952
+ }
953
+ } else if (typeof value === "string" || typeof value === "boolean") {
954
+ messagesConfig[key] = value;
955
+ }
956
+ }
957
+ });
958
+ if (Object.keys(messagesConfig).length > 0) {
959
+ layoutConfig.messages = messagesConfig;
960
+ }
784
961
  }
785
- if (config.suggestionChipsConfig.paddingY) {
786
- lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
962
+
963
+ if (Object.keys(layoutConfig).length > 0) {
964
+ serializableConfig.layout = layoutConfig;
787
965
  }
788
- lines.push(" },");
789
966
  }
967
+
968
+ return serializableConfig;
969
+ }
790
970
 
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");
971
+ function generateScriptInstallerCode(config: any): string {
972
+ const serializableConfig = buildSerializableConfig(config);
973
+
974
+ // Escape single quotes in JSON for HTML attribute
975
+ const configJson = JSON.stringify(serializableConfig, null, 0).replace(/'/g, "&#39;");
976
+
977
+ return `<script src="https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/install.global.js" data-config='${configJson}'></script>`;
802
978
  }
803
979
 
804
980
  function generateScriptManualCode(config: any): string {
@@ -922,6 +1098,18 @@ function generateScriptManualCode(config: any): string {
922
1098
  lines.push(" },");
923
1099
  }
924
1100
 
1101
+ // Add toolCall config
1102
+ lines.push(...generateToolCallConfig(config, " "));
1103
+
1104
+ // Add messageActions config
1105
+ lines.push(...generateMessageActionsConfig(config, " "));
1106
+
1107
+ // Add markdown config
1108
+ lines.push(...generateMarkdownConfig(config, " "));
1109
+
1110
+ // Add layout config
1111
+ lines.push(...generateLayoutConfig(config, " "));
1112
+
925
1113
  if (config.debug) {
926
1114
  lines.push(` debug: ${config.debug},`);
927
1115
  }
@@ -935,312 +1123,224 @@ function generateScriptManualCode(config: any): string {
935
1123
  }
936
1124
 
937
1125
  function generateScriptAdvancedCode(config: any): string {
1126
+ const serializableConfig = buildSerializableConfig(config);
1127
+ const configJson = JSON.stringify(serializableConfig, null, 2);
1128
+
938
1129
  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
1130
  "<script>",
944
- " window.ChatWidgetConfig = {"
1131
+ "(function() {",
1132
+ " 'use strict';",
1133
+ "",
1134
+ " // Configuration",
1135
+ ` var CONFIG = ${configJson.split('\n').map((line, i) => i === 0 ? line : ' ' + line).join('\n')};`,
1136
+ "",
1137
+ " // Constants",
1138
+ " var CDN_BASE = 'https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist';",
1139
+ " var STORAGE_KEY = 'chat-widget-state';",
1140
+ " var PROCESSED_ACTIONS_KEY = 'chat-widget-processed-actions';",
1141
+ "",
1142
+ " // DOM context provider - extracts page elements for AI context",
1143
+ " var domContextProvider = function() {",
1144
+ " var selectors = {",
1145
+ " products: '[data-product-id], .product-card, .product-item, [role=\"article\"]',",
1146
+ " buttons: 'button, [role=\"button\"], .btn',",
1147
+ " links: 'a[href]',",
1148
+ " inputs: 'input, textarea, select'",
1149
+ " };",
1150
+ "",
1151
+ " var elements = [];",
1152
+ " Object.entries(selectors).forEach(function(entry) {",
1153
+ " var type = entry[0], selector = entry[1];",
1154
+ " document.querySelectorAll(selector).forEach(function(element) {",
1155
+ " if (!(element instanceof HTMLElement)) return;",
1156
+ " var widgetHost = element.closest('.vanilla-agent-host');",
1157
+ " if (widgetHost) return;",
1158
+ " var text = element.innerText ? element.innerText.trim() : '';",
1159
+ " if (!text) return;",
1160
+ "",
1161
+ " var selectorString = element.id ? '#' + element.id :",
1162
+ " element.getAttribute('data-testid') ? '[data-testid=\"' + element.getAttribute('data-testid') + '\"]' :",
1163
+ " element.getAttribute('data-product-id') ? '[data-product-id=\"' + element.getAttribute('data-product-id') + '\"]' :",
1164
+ " element.tagName.toLowerCase();",
1165
+ "",
1166
+ " var elementData = {",
1167
+ " type: type,",
1168
+ " tagName: element.tagName.toLowerCase(),",
1169
+ " selector: selectorString,",
1170
+ " innerText: text.substring(0, 200)",
1171
+ " };",
1172
+ "",
1173
+ " if (type === 'links' && element instanceof HTMLAnchorElement && element.href) {",
1174
+ " elementData.href = element.href;",
1175
+ " }",
1176
+ " elements.push(elementData);",
1177
+ " });",
1178
+ " });",
1179
+ "",
1180
+ " var counts = elements.reduce(function(acc, el) {",
1181
+ " acc[el.type] = (acc[el.type] || 0) + 1;",
1182
+ " return acc;",
1183
+ " }, {});",
1184
+ "",
1185
+ " return {",
1186
+ " page_elements: elements.slice(0, 50),",
1187
+ " page_element_count: elements.length,",
1188
+ " element_types: counts,",
1189
+ " page_url: window.location.href,",
1190
+ " page_title: document.title,",
1191
+ " timestamp: new Date().toISOString()",
1192
+ " };",
1193
+ " };",
1194
+ "",
1195
+ " // Load CSS dynamically",
1196
+ " var loadCSS = function() {",
1197
+ " if (document.querySelector('link[data-vanilla-agent]')) return;",
1198
+ " var link = document.createElement('link');",
1199
+ " link.rel = 'stylesheet';",
1200
+ " link.href = CDN_BASE + '/widget.css';",
1201
+ " link.setAttribute('data-vanilla-agent', 'true');",
1202
+ " document.head.appendChild(link);",
1203
+ " };",
1204
+ "",
1205
+ " // Load JS dynamically",
1206
+ " var loadJS = function(callback) {",
1207
+ " if (window.AgentWidget) { callback(); return; }",
1208
+ " var script = document.createElement('script');",
1209
+ " script.src = CDN_BASE + '/index.global.js';",
1210
+ " script.onload = callback;",
1211
+ " script.onerror = function() { console.error('Failed to load AgentWidget'); };",
1212
+ " document.head.appendChild(script);",
1213
+ " };",
1214
+ "",
1215
+ " // Create widget config with advanced features",
1216
+ " var createWidgetConfig = function(agentWidget) {",
1217
+ " var widgetConfig = Object.assign({}, CONFIG);",
1218
+ "",
1219
+ " // Flexible JSON stream parser for handling structured actions",
1220
+ " widgetConfig.streamParser = function() {",
1221
+ " return agentWidget.createFlexibleJsonStreamParser(function(parsed) {",
1222
+ " if (!parsed || typeof parsed !== 'object') return null;",
1223
+ " if (parsed.action === 'nav_then_click') return 'Navigating...';",
1224
+ " if (parsed.action === 'message') return parsed.text || '';",
1225
+ " if (parsed.action === 'message_and_click') return parsed.text || 'Processing...';",
1226
+ " return parsed.text || null;",
1227
+ " });",
1228
+ " };",
1229
+ "",
1230
+ " // Action parsers to detect JSON actions in responses",
1231
+ " widgetConfig.actionParsers = [",
1232
+ " agentWidget.defaultJsonActionParser,",
1233
+ " function(ctx) {",
1234
+ " var jsonSource = ctx.message.rawContent || ctx.text || ctx.message.content;",
1235
+ " if (!jsonSource || typeof jsonSource !== 'string') return null;",
1236
+ " var cleanJson = jsonSource",
1237
+ " .replace(/^```(?:json)?\\s*\\n?/, '')",
1238
+ " .replace(/\\n?```\\s*$/, '')",
1239
+ " .trim();",
1240
+ " if (!cleanJson.startsWith('{') || !cleanJson.endsWith('}')) return null;",
1241
+ " try {",
1242
+ " var parsed = JSON.parse(cleanJson);",
1243
+ " if (parsed.action) return { type: parsed.action, payload: parsed };",
1244
+ " } catch (e) { return null; }",
1245
+ " return null;",
1246
+ " }",
1247
+ " ];",
1248
+ "",
1249
+ " // Action handlers for navigation and other actions",
1250
+ " widgetConfig.actionHandlers = [",
1251
+ " agentWidget.defaultActionHandlers.message,",
1252
+ " agentWidget.defaultActionHandlers.messageAndClick,",
1253
+ " function(action, context) {",
1254
+ " if (action.type !== 'nav_then_click') return;",
1255
+ " var payload = action.payload || action.raw || {};",
1256
+ " var url = payload.page;",
1257
+ " var text = payload.on_load_text || 'Navigating...';",
1258
+ " if (!url) return { handled: true, displayText: text };",
1259
+ " var messageId = context.message ? context.message.id : null;",
1260
+ " var processedActions = JSON.parse(localStorage.getItem(PROCESSED_ACTIONS_KEY) || '[]');",
1261
+ " var actionKey = 'nav_' + messageId + '_' + url;",
1262
+ " if (processedActions.includes(actionKey)) {",
1263
+ " return { handled: true, displayText: text };",
1264
+ " }",
1265
+ " processedActions.push(actionKey);",
1266
+ " localStorage.setItem(PROCESSED_ACTIONS_KEY, JSON.stringify(processedActions));",
1267
+ " var targetUrl = url.startsWith('http') ? url : new URL(url, window.location.origin).toString();",
1268
+ " window.location.href = targetUrl;",
1269
+ " return { handled: true, displayText: text };",
1270
+ " }",
1271
+ " ];",
1272
+ "",
1273
+ " // Send DOM context with each request",
1274
+ " widgetConfig.requestMiddleware = function(ctx) {",
1275
+ " return Object.assign({}, ctx.payload, { metadata: domContextProvider() });",
1276
+ " };",
1277
+ "",
1278
+ " // Markdown postprocessor",
1279
+ " widgetConfig.postprocessMessage = function(ctx) {",
1280
+ " return agentWidget.markdownPostprocessor(ctx.text);",
1281
+ " };",
1282
+ "",
1283
+ " return widgetConfig;",
1284
+ " };",
1285
+ "",
1286
+ " // Initialize widget",
1287
+ " var init = function() {",
1288
+ " var agentWidget = window.AgentWidget;",
1289
+ " if (!agentWidget) {",
1290
+ " console.error('AgentWidget not loaded');",
1291
+ " return;",
1292
+ " }",
1293
+ "",
1294
+ " var widgetConfig = createWidgetConfig(agentWidget);",
1295
+ "",
1296
+ " // Load saved state",
1297
+ " var savedState = localStorage.getItem(STORAGE_KEY);",
1298
+ " if (savedState) {",
1299
+ " try {",
1300
+ " var parsed = JSON.parse(savedState);",
1301
+ " widgetConfig.initialMessages = parsed.messages || [];",
1302
+ " } catch (e) {",
1303
+ " console.error('Failed to load saved state:', e);",
1304
+ " }",
1305
+ " }",
1306
+ "",
1307
+ " // Initialize widget",
1308
+ " var handle = agentWidget.initAgentWidget({",
1309
+ " target: 'body',",
1310
+ " useShadowDom: false,",
1311
+ " config: widgetConfig",
1312
+ " });",
1313
+ "",
1314
+ " // Save state on message events",
1315
+ " window.addEventListener('vanilla-agent:message', function() {",
1316
+ " var session = handle.getSession ? handle.getSession() : null;",
1317
+ " if (session) {",
1318
+ " localStorage.setItem(STORAGE_KEY, JSON.stringify({",
1319
+ " messages: session.messages,",
1320
+ " timestamp: new Date().toISOString()",
1321
+ " }));",
1322
+ " }",
1323
+ " });",
1324
+ "",
1325
+ " // Clear state on clear chat",
1326
+ " window.addEventListener('vanilla-agent:clear-chat', function() {",
1327
+ " localStorage.removeItem(STORAGE_KEY);",
1328
+ " localStorage.removeItem(PROCESSED_ACTIONS_KEY);",
1329
+ " });",
1330
+ " };",
1331
+ "",
1332
+ " // Boot sequence: load CSS, then JS, then initialize",
1333
+ " loadCSS();",
1334
+ " loadJS(function() {",
1335
+ " if (document.readyState === 'loading') {",
1336
+ " document.addEventListener('DOMContentLoaded', init);",
1337
+ " } else {",
1338
+ " init();",
1339
+ " }",
1340
+ " });",
1341
+ "})();",
1342
+ "</script>"
945
1343
  ];
946
1344
 
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
1345
  return lines.join("\n");
1246
1346
  }