@runtypelabs/persona 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1080 -0
- package/dist/index.cjs +140 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2626 -0
- package/dist/index.d.ts +2626 -0
- package/dist/index.global.js +1843 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +140 -0
- package/dist/index.js.map +1 -0
- package/dist/install.global.js +2 -0
- package/dist/install.global.js.map +1 -0
- package/dist/widget.css +1627 -0
- package/package.json +79 -0
- package/src/@types/idiomorph.d.ts +37 -0
- package/src/client.test.ts +387 -0
- package/src/client.ts +1589 -0
- package/src/components/composer-builder.ts +530 -0
- package/src/components/feedback.ts +379 -0
- package/src/components/forms.ts +170 -0
- package/src/components/header-builder.ts +455 -0
- package/src/components/header-layouts.ts +303 -0
- package/src/components/launcher.ts +193 -0
- package/src/components/message-bubble.ts +528 -0
- package/src/components/messages.ts +54 -0
- package/src/components/panel.ts +204 -0
- package/src/components/reasoning-bubble.ts +144 -0
- package/src/components/registry.ts +87 -0
- package/src/components/suggestions.ts +97 -0
- package/src/components/tool-bubble.ts +288 -0
- package/src/defaults.ts +321 -0
- package/src/index.ts +175 -0
- package/src/install.ts +284 -0
- package/src/plugins/registry.ts +77 -0
- package/src/plugins/types.ts +95 -0
- package/src/postprocessors.ts +194 -0
- package/src/runtime/init.ts +162 -0
- package/src/session.ts +376 -0
- package/src/styles/tailwind.css +20 -0
- package/src/styles/widget.css +1627 -0
- package/src/types.ts +1635 -0
- package/src/ui.ts +3341 -0
- package/src/utils/actions.ts +227 -0
- package/src/utils/attachment-manager.ts +384 -0
- package/src/utils/code-generators.test.ts +500 -0
- package/src/utils/code-generators.ts +1806 -0
- package/src/utils/component-middleware.ts +137 -0
- package/src/utils/component-parser.ts +119 -0
- package/src/utils/constants.ts +16 -0
- package/src/utils/content.ts +306 -0
- package/src/utils/dom.ts +25 -0
- package/src/utils/events.ts +41 -0
- package/src/utils/formatting.test.ts +166 -0
- package/src/utils/formatting.ts +470 -0
- package/src/utils/icons.ts +92 -0
- package/src/utils/message-id.ts +37 -0
- package/src/utils/morph.ts +36 -0
- package/src/utils/positioning.ts +17 -0
- package/src/utils/storage.ts +72 -0
- package/src/utils/theme.ts +105 -0
- package/src/widget.css +1 -0
- package/widget.css +1 -0
|
@@ -0,0 +1,1806 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Hook code templates for code generation.
|
|
8
|
+
* Each hook can be provided as a string (code template) OR as an actual function.
|
|
9
|
+
* Functions are automatically serialized via `.toString()`.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: When providing functions:
|
|
12
|
+
* - Functions must be self-contained (no external variables/closures)
|
|
13
|
+
* - External variables will be undefined when the generated code runs
|
|
14
|
+
* - Use arrow functions or regular function expressions
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Both of these work:
|
|
19
|
+
*
|
|
20
|
+
* // As string:
|
|
21
|
+
* { getHeaders: "async () => ({ 'Authorization': 'Bearer token' })" }
|
|
22
|
+
*
|
|
23
|
+
* // As function (recommended - better IDE support):
|
|
24
|
+
* { getHeaders: async () => ({ 'Authorization': 'Bearer token' }) }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export type CodeGeneratorHooks = {
|
|
28
|
+
/**
|
|
29
|
+
* Custom getHeaders function.
|
|
30
|
+
* Should return an object with header key-value pairs.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* async () => ({ 'Authorization': `Bearer ${await getAuthToken()}` })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
getHeaders?: string | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Custom onFeedback callback for message actions.
|
|
41
|
+
* Receives a feedback object with type, messageId, and message.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* (feedback) => { console.log('Feedback:', feedback.type); }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
onFeedback?: string | ((feedback: { type: string; messageId: string; message: unknown }) => void);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Custom onCopy callback for message actions.
|
|
52
|
+
* Receives the message that was copied.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* (message) => { analytics.track('message_copied', { id: message.id }); }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
onCopy?: string | ((message: unknown) => void);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Custom requestMiddleware function.
|
|
63
|
+
* Receives { payload, config } context.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* ({ payload }) => ({ ...payload, metadata: { pageUrl: window.location.href } })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
requestMiddleware?: string | ((context: { payload: unknown; config: unknown }) => unknown);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Custom action handlers array.
|
|
74
|
+
* Array of handler functions.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* [
|
|
79
|
+
* (action, context) => {
|
|
80
|
+
* if (action.type === 'custom') {
|
|
81
|
+
* return { handled: true };
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* ]
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
actionHandlers?: string | Array<(action: unknown, context: unknown) => unknown>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Custom action parsers array.
|
|
91
|
+
* Array of parser functions.
|
|
92
|
+
*/
|
|
93
|
+
actionParsers?: string | Array<(context: unknown) => unknown>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Custom postprocessMessage function.
|
|
97
|
+
* Receives { text, message, streaming, raw } context.
|
|
98
|
+
* Will override the default markdownPostprocessor.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* ({ text }) => customMarkdownProcessor(text)
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
postprocessMessage?: string | ((context: { text: string; message?: unknown; streaming?: boolean; raw?: string }) => string);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Custom context providers array.
|
|
109
|
+
* Array of provider functions.
|
|
110
|
+
*/
|
|
111
|
+
contextProviders?: string | Array<() => unknown>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Custom stream parser factory.
|
|
115
|
+
* Should be a function that returns a StreamParser.
|
|
116
|
+
*/
|
|
117
|
+
streamParser?: string | (() => unknown);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Options for code generation beyond format selection.
|
|
122
|
+
*/
|
|
123
|
+
export type CodeGeneratorOptions = {
|
|
124
|
+
/**
|
|
125
|
+
* Custom hook code to inject into the generated snippet.
|
|
126
|
+
* Hooks are JavaScript/TypeScript code strings that will be
|
|
127
|
+
* inserted at appropriate locations in the output.
|
|
128
|
+
*/
|
|
129
|
+
hooks?: CodeGeneratorHooks;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Whether to include comments explaining each hook.
|
|
133
|
+
* @default true
|
|
134
|
+
*/
|
|
135
|
+
includeHookComments?: boolean;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Internal type for normalized hooks (always strings)
|
|
139
|
+
type NormalizedHooks = {
|
|
140
|
+
[K in keyof CodeGeneratorHooks]: string | undefined;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Serialize a hook value (string, function, or array of functions) to a string.
|
|
145
|
+
*/
|
|
146
|
+
function serializeHook(hook: string | Function | Function[] | undefined): string | undefined {
|
|
147
|
+
if (hook === undefined) return undefined;
|
|
148
|
+
if (typeof hook === 'string') return hook;
|
|
149
|
+
if (Array.isArray(hook)) {
|
|
150
|
+
return `[${hook.map(fn => fn.toString()).join(', ')}]`;
|
|
151
|
+
}
|
|
152
|
+
return hook.toString();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Normalize hooks by converting any functions to their string representations.
|
|
157
|
+
*/
|
|
158
|
+
function normalizeHooks(hooks: CodeGeneratorHooks | undefined): NormalizedHooks | undefined {
|
|
159
|
+
if (!hooks) return undefined;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
getHeaders: serializeHook(hooks.getHeaders),
|
|
163
|
+
onFeedback: serializeHook(hooks.onFeedback),
|
|
164
|
+
onCopy: serializeHook(hooks.onCopy),
|
|
165
|
+
requestMiddleware: serializeHook(hooks.requestMiddleware),
|
|
166
|
+
actionHandlers: serializeHook(hooks.actionHandlers),
|
|
167
|
+
actionParsers: serializeHook(hooks.actionParsers),
|
|
168
|
+
postprocessMessage: serializeHook(hooks.postprocessMessage),
|
|
169
|
+
contextProviders: serializeHook(hooks.contextProviders),
|
|
170
|
+
streamParser: serializeHook(hooks.streamParser),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// =============================================================================
|
|
175
|
+
// Template Literals for Code Generation
|
|
176
|
+
// These are injected into generated code as-is.
|
|
177
|
+
// =============================================================================
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Template: Parser for JSON wrapped in markdown code fences (TypeScript).
|
|
181
|
+
* @internal
|
|
182
|
+
*/
|
|
183
|
+
const TEMPLATE_MARKDOWN_JSON_PARSER_TS = `({ text, message }: any) => {
|
|
184
|
+
const jsonSource = (message as any).rawContent || text || message.content;
|
|
185
|
+
if (!jsonSource || typeof jsonSource !== 'string') return null;
|
|
186
|
+
let cleanJson = jsonSource
|
|
187
|
+
.replace(/^\`\`\`(?:json)?\\s*\\n?/, '')
|
|
188
|
+
.replace(/\\n?\`\`\`\\s*$/, '')
|
|
189
|
+
.trim();
|
|
190
|
+
if (!cleanJson.startsWith('{') || !cleanJson.endsWith('}')) return null;
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(cleanJson);
|
|
193
|
+
if (parsed.action) return { type: parsed.action, payload: parsed };
|
|
194
|
+
} catch (e) { return null; }
|
|
195
|
+
return null;
|
|
196
|
+
}`;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Template: Parser for JSON wrapped in markdown code fences (ES5).
|
|
200
|
+
* @internal
|
|
201
|
+
*/
|
|
202
|
+
const TEMPLATE_MARKDOWN_JSON_PARSER_ES5 = `function(ctx) {
|
|
203
|
+
var jsonSource = ctx.message.rawContent || ctx.text || ctx.message.content;
|
|
204
|
+
if (!jsonSource || typeof jsonSource !== 'string') return null;
|
|
205
|
+
var cleanJson = jsonSource
|
|
206
|
+
.replace(/^\`\`\`(?:json)?\\s*\\n?/, '')
|
|
207
|
+
.replace(/\\n?\`\`\`\\s*$/, '')
|
|
208
|
+
.trim();
|
|
209
|
+
if (!cleanJson.startsWith('{') || !cleanJson.endsWith('}')) return null;
|
|
210
|
+
try {
|
|
211
|
+
var parsed = JSON.parse(cleanJson);
|
|
212
|
+
if (parsed.action) return { type: parsed.action, payload: parsed };
|
|
213
|
+
} catch (e) { return null; }
|
|
214
|
+
return null;
|
|
215
|
+
}`;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Template: Handler for nav_then_click actions (TypeScript).
|
|
219
|
+
* @internal
|
|
220
|
+
*/
|
|
221
|
+
const TEMPLATE_NAV_THEN_CLICK_HANDLER_TS = `(action: any, context: any) => {
|
|
222
|
+
if (action.type !== 'nav_then_click') return;
|
|
223
|
+
const payload = action.payload || action.raw || {};
|
|
224
|
+
const url = payload?.page;
|
|
225
|
+
const text = payload?.on_load_text || 'Navigating...';
|
|
226
|
+
if (!url) return { handled: true, displayText: text };
|
|
227
|
+
const messageId = context.message?.id;
|
|
228
|
+
const processedActions = JSON.parse(localStorage.getItem(PROCESSED_ACTIONS_KEY) || '[]');
|
|
229
|
+
const actionKey = \`nav_\${messageId}_\${url}\`;
|
|
230
|
+
if (processedActions.includes(actionKey)) {
|
|
231
|
+
return { handled: true, displayText: text };
|
|
232
|
+
}
|
|
233
|
+
processedActions.push(actionKey);
|
|
234
|
+
localStorage.setItem(PROCESSED_ACTIONS_KEY, JSON.stringify(processedActions));
|
|
235
|
+
const targetUrl = url.startsWith('http') ? url : new URL(url, window.location.origin).toString();
|
|
236
|
+
window.location.href = targetUrl;
|
|
237
|
+
return { handled: true, displayText: text };
|
|
238
|
+
}`;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Template: Handler for nav_then_click actions (ES5).
|
|
242
|
+
* @internal
|
|
243
|
+
*/
|
|
244
|
+
const TEMPLATE_NAV_THEN_CLICK_HANDLER_ES5 = `function(action, context) {
|
|
245
|
+
if (action.type !== 'nav_then_click') return;
|
|
246
|
+
var payload = action.payload || action.raw || {};
|
|
247
|
+
var url = payload.page;
|
|
248
|
+
var text = payload.on_load_text || 'Navigating...';
|
|
249
|
+
if (!url) return { handled: true, displayText: text };
|
|
250
|
+
var messageId = context.message ? context.message.id : null;
|
|
251
|
+
var processedActions = JSON.parse(localStorage.getItem(PROCESSED_ACTIONS_KEY) || '[]');
|
|
252
|
+
var actionKey = 'nav_' + messageId + '_' + url;
|
|
253
|
+
if (processedActions.includes(actionKey)) {
|
|
254
|
+
return { handled: true, displayText: text };
|
|
255
|
+
}
|
|
256
|
+
processedActions.push(actionKey);
|
|
257
|
+
localStorage.setItem(PROCESSED_ACTIONS_KEY, JSON.stringify(processedActions));
|
|
258
|
+
var targetUrl = url.startsWith('http') ? url : new URL(url, window.location.origin).toString();
|
|
259
|
+
window.location.href = targetUrl;
|
|
260
|
+
return { handled: true, displayText: text };
|
|
261
|
+
}`;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Template: Stream parser callback (TypeScript).
|
|
265
|
+
* @internal
|
|
266
|
+
*/
|
|
267
|
+
const TEMPLATE_STREAM_PARSER_CALLBACK_TS = `(parsed: any) => {
|
|
268
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
269
|
+
if (parsed.action === 'nav_then_click') return 'Navigating...';
|
|
270
|
+
if (parsed.action === 'message') return parsed.text || '';
|
|
271
|
+
if (parsed.action === 'message_and_click') return parsed.text || 'Processing...';
|
|
272
|
+
return parsed.text || null;
|
|
273
|
+
}`;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Template: Stream parser callback (ES5).
|
|
277
|
+
* @internal
|
|
278
|
+
*/
|
|
279
|
+
const TEMPLATE_STREAM_PARSER_CALLBACK_ES5 = `function(parsed) {
|
|
280
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
281
|
+
if (parsed.action === 'nav_then_click') return 'Navigating...';
|
|
282
|
+
if (parsed.action === 'message') return parsed.text || '';
|
|
283
|
+
if (parsed.action === 'message_and_click') return parsed.text || 'Processing...';
|
|
284
|
+
return parsed.text || null;
|
|
285
|
+
}`;
|
|
286
|
+
|
|
287
|
+
function detectParserTypeFromStreamParser(streamParser: any): ParserType | null {
|
|
288
|
+
if (!streamParser) return null;
|
|
289
|
+
const fnString = streamParser.toString();
|
|
290
|
+
if (fnString.includes("createJsonStreamParser") || fnString.includes("partial-json")) {
|
|
291
|
+
return "json";
|
|
292
|
+
}
|
|
293
|
+
if (fnString.includes("createRegexJsonParser") || fnString.includes("regex")) {
|
|
294
|
+
return "regex-json";
|
|
295
|
+
}
|
|
296
|
+
if (fnString.includes("createXmlParser") || fnString.includes("<text>")) {
|
|
297
|
+
return "xml";
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getParserTypeFromConfig(config: AgentWidgetConfig): ParserType {
|
|
303
|
+
return config.parserType ?? detectParserTypeFromStreamParser(config.streamParser) ?? "plain";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Helper to generate toolCall config
|
|
307
|
+
function generateToolCallConfig(config: any, indent: string): string[] {
|
|
308
|
+
const lines: string[] = [];
|
|
309
|
+
if (config.toolCall) {
|
|
310
|
+
lines.push(`${indent}toolCall: {`);
|
|
311
|
+
Object.entries(config.toolCall).forEach(([key, value]) => {
|
|
312
|
+
if (typeof value === "string") {
|
|
313
|
+
lines.push(`${indent} ${key}: "${value}",`);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
lines.push(`${indent}},`);
|
|
317
|
+
}
|
|
318
|
+
return lines;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Helper to generate messageActions config (with optional hook callbacks)
|
|
322
|
+
function generateMessageActionsConfig(config: any, indent: string, hooks?: CodeGeneratorHooks): string[] {
|
|
323
|
+
const lines: string[] = [];
|
|
324
|
+
const hasSerializableProps = config.messageActions && Object.entries(config.messageActions).some(
|
|
325
|
+
([key, value]) => key !== "onFeedback" && key !== "onCopy" && value !== undefined
|
|
326
|
+
);
|
|
327
|
+
const hasHookCallbacks = hooks?.onFeedback || hooks?.onCopy;
|
|
328
|
+
|
|
329
|
+
if (hasSerializableProps || hasHookCallbacks) {
|
|
330
|
+
lines.push(`${indent}messageActions: {`);
|
|
331
|
+
|
|
332
|
+
// Add serializable properties from config
|
|
333
|
+
if (config.messageActions) {
|
|
334
|
+
Object.entries(config.messageActions).forEach(([key, value]) => {
|
|
335
|
+
// Skip function callbacks - we'll add from hooks if provided
|
|
336
|
+
if (key === "onFeedback" || key === "onCopy") return;
|
|
337
|
+
if (typeof value === "string") {
|
|
338
|
+
lines.push(`${indent} ${key}: "${value}",`);
|
|
339
|
+
} else if (typeof value === "boolean") {
|
|
340
|
+
lines.push(`${indent} ${key}: ${value},`);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Add hook callbacks
|
|
346
|
+
if (hooks?.onFeedback) {
|
|
347
|
+
lines.push(`${indent} onFeedback: ${hooks.onFeedback},`);
|
|
348
|
+
}
|
|
349
|
+
if (hooks?.onCopy) {
|
|
350
|
+
lines.push(`${indent} onCopy: ${hooks.onCopy},`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
lines.push(`${indent}},`);
|
|
354
|
+
}
|
|
355
|
+
return lines;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Helper to generate markdown config (excluding renderer functions)
|
|
359
|
+
function generateMarkdownConfig(config: any, indent: string): string[] {
|
|
360
|
+
const lines: string[] = [];
|
|
361
|
+
if (config.markdown) {
|
|
362
|
+
const hasOptions = config.markdown.options && Object.keys(config.markdown.options).length > 0;
|
|
363
|
+
const hasDisableDefaultStyles = config.markdown.disableDefaultStyles !== undefined;
|
|
364
|
+
|
|
365
|
+
if (hasOptions || hasDisableDefaultStyles) {
|
|
366
|
+
lines.push(`${indent}markdown: {`);
|
|
367
|
+
|
|
368
|
+
if (hasOptions) {
|
|
369
|
+
lines.push(`${indent} options: {`);
|
|
370
|
+
Object.entries(config.markdown.options).forEach(([key, value]) => {
|
|
371
|
+
if (typeof value === "string") {
|
|
372
|
+
lines.push(`${indent} ${key}: "${value}",`);
|
|
373
|
+
} else if (typeof value === "boolean") {
|
|
374
|
+
lines.push(`${indent} ${key}: ${value},`);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
lines.push(`${indent} },`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (hasDisableDefaultStyles) {
|
|
381
|
+
lines.push(`${indent} disableDefaultStyles: ${config.markdown.disableDefaultStyles},`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
lines.push(`${indent}},`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return lines;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Helper to generate layout config (excluding render functions and slots)
|
|
391
|
+
function generateLayoutConfig(config: any, indent: string): string[] {
|
|
392
|
+
const lines: string[] = [];
|
|
393
|
+
if (config.layout) {
|
|
394
|
+
const hasHeader = config.layout.header && Object.keys(config.layout.header).some(
|
|
395
|
+
(key: string) => key !== "render"
|
|
396
|
+
);
|
|
397
|
+
const hasMessages = config.layout.messages && Object.keys(config.layout.messages).some(
|
|
398
|
+
(key: string) => key !== "renderUserMessage" && key !== "renderAssistantMessage"
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (hasHeader || hasMessages) {
|
|
402
|
+
lines.push(`${indent}layout: {`);
|
|
403
|
+
|
|
404
|
+
// Header config (excluding render function)
|
|
405
|
+
if (hasHeader) {
|
|
406
|
+
lines.push(`${indent} header: {`);
|
|
407
|
+
Object.entries(config.layout.header).forEach(([key, value]) => {
|
|
408
|
+
if (key === "render") return; // Skip render function
|
|
409
|
+
if (typeof value === "string") {
|
|
410
|
+
lines.push(`${indent} ${key}: "${value}",`);
|
|
411
|
+
} else if (typeof value === "boolean") {
|
|
412
|
+
lines.push(`${indent} ${key}: ${value},`);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
lines.push(`${indent} },`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Messages config (excluding render functions)
|
|
419
|
+
if (hasMessages) {
|
|
420
|
+
lines.push(`${indent} messages: {`);
|
|
421
|
+
Object.entries(config.layout.messages).forEach(([key, value]) => {
|
|
422
|
+
// Skip render functions
|
|
423
|
+
if (key === "renderUserMessage" || key === "renderAssistantMessage") return;
|
|
424
|
+
|
|
425
|
+
if (key === "avatar" && typeof value === "object" && value !== null) {
|
|
426
|
+
lines.push(`${indent} avatar: {`);
|
|
427
|
+
Object.entries(value as Record<string, unknown>).forEach(([avatarKey, avatarValue]) => {
|
|
428
|
+
if (typeof avatarValue === "string") {
|
|
429
|
+
lines.push(`${indent} ${avatarKey}: "${avatarValue}",`);
|
|
430
|
+
} else if (typeof avatarValue === "boolean") {
|
|
431
|
+
lines.push(`${indent} ${avatarKey}: ${avatarValue},`);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
lines.push(`${indent} },`);
|
|
435
|
+
} else if (key === "timestamp" && typeof value === "object" && value !== null) {
|
|
436
|
+
// Only emit serializable timestamp properties (skip format function)
|
|
437
|
+
const hasSerializableTimestamp = Object.entries(value as Record<string, unknown>).some(
|
|
438
|
+
([k]) => k !== "format"
|
|
439
|
+
);
|
|
440
|
+
if (hasSerializableTimestamp) {
|
|
441
|
+
lines.push(`${indent} timestamp: {`);
|
|
442
|
+
Object.entries(value as Record<string, unknown>).forEach(([tsKey, tsValue]) => {
|
|
443
|
+
if (tsKey === "format") return; // Skip format function
|
|
444
|
+
if (typeof tsValue === "string") {
|
|
445
|
+
lines.push(`${indent} ${tsKey}: "${tsValue}",`);
|
|
446
|
+
} else if (typeof tsValue === "boolean") {
|
|
447
|
+
lines.push(`${indent} ${tsKey}: ${tsValue},`);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
lines.push(`${indent} },`);
|
|
451
|
+
}
|
|
452
|
+
} else if (typeof value === "string") {
|
|
453
|
+
lines.push(`${indent} ${key}: "${value}",`);
|
|
454
|
+
} else if (typeof value === "boolean") {
|
|
455
|
+
lines.push(`${indent} ${key}: ${value},`);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
lines.push(`${indent} },`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
lines.push(`${indent}},`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return lines;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Helper to generate hook-related config lines
|
|
468
|
+
function generateHooksConfig(hooks: CodeGeneratorHooks | undefined, indent: string): string[] {
|
|
469
|
+
const lines: string[] = [];
|
|
470
|
+
if (!hooks) return lines;
|
|
471
|
+
|
|
472
|
+
if (hooks.getHeaders) {
|
|
473
|
+
lines.push(`${indent}getHeaders: ${hooks.getHeaders},`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (hooks.requestMiddleware) {
|
|
477
|
+
lines.push(`${indent}requestMiddleware: ${hooks.requestMiddleware},`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (hooks.actionParsers) {
|
|
481
|
+
lines.push(`${indent}actionParsers: ${hooks.actionParsers},`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (hooks.actionHandlers) {
|
|
485
|
+
lines.push(`${indent}actionHandlers: ${hooks.actionHandlers},`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (hooks.contextProviders) {
|
|
489
|
+
lines.push(`${indent}contextProviders: ${hooks.contextProviders},`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (hooks.streamParser) {
|
|
493
|
+
lines.push(`${indent}streamParser: ${hooks.streamParser},`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return lines;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function generateCodeSnippet(
|
|
500
|
+
config: any,
|
|
501
|
+
format: CodeFormat = "esm",
|
|
502
|
+
options?: CodeGeneratorOptions
|
|
503
|
+
): string {
|
|
504
|
+
// Remove non-serializable properties
|
|
505
|
+
const cleanConfig = { ...config };
|
|
506
|
+
delete cleanConfig.postprocessMessage;
|
|
507
|
+
delete cleanConfig.initialMessages;
|
|
508
|
+
|
|
509
|
+
// Normalize hooks - convert functions to strings via .toString()
|
|
510
|
+
const normalizedOptions: CodeGeneratorOptions | undefined = options
|
|
511
|
+
? { ...options, hooks: normalizeHooks(options.hooks) as CodeGeneratorHooks }
|
|
512
|
+
: undefined;
|
|
513
|
+
|
|
514
|
+
if (format === "esm") {
|
|
515
|
+
return generateESMCode(cleanConfig, normalizedOptions);
|
|
516
|
+
} else if (format === "script-installer") {
|
|
517
|
+
return generateScriptInstallerCode(cleanConfig);
|
|
518
|
+
} else if (format === "script-advanced") {
|
|
519
|
+
return generateScriptAdvancedCode(cleanConfig, normalizedOptions);
|
|
520
|
+
} else if (format === "react-component") {
|
|
521
|
+
return generateReactComponentCode(cleanConfig, normalizedOptions);
|
|
522
|
+
} else if (format === "react-advanced") {
|
|
523
|
+
return generateReactAdvancedCode(cleanConfig, normalizedOptions);
|
|
524
|
+
} else {
|
|
525
|
+
return generateScriptManualCode(cleanConfig, normalizedOptions);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function generateESMCode(config: any, options?: CodeGeneratorOptions): string {
|
|
530
|
+
const hooks = options?.hooks;
|
|
531
|
+
const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
|
|
532
|
+
const shouldEmitParserType = parserType !== "plain";
|
|
533
|
+
|
|
534
|
+
const lines: string[] = [
|
|
535
|
+
"import '@runtypelabs/persona/widget.css';",
|
|
536
|
+
"import { initAgentWidget, markdownPostprocessor } from '@runtypelabs/persona';",
|
|
537
|
+
"",
|
|
538
|
+
"initAgentWidget({",
|
|
539
|
+
" target: 'body',",
|
|
540
|
+
" config: {"
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
|
|
544
|
+
if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
|
|
545
|
+
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
546
|
+
if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
|
|
547
|
+
|
|
548
|
+
if (config.theme) {
|
|
549
|
+
lines.push(" theme: {");
|
|
550
|
+
Object.entries(config.theme).forEach(([key, value]) => {
|
|
551
|
+
lines.push(` ${key}: "${value}",`);
|
|
552
|
+
});
|
|
553
|
+
lines.push(" },");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (config.launcher) {
|
|
557
|
+
lines.push(" launcher: {");
|
|
558
|
+
Object.entries(config.launcher).forEach(([key, value]) => {
|
|
559
|
+
if (typeof value === "string") {
|
|
560
|
+
lines.push(` ${key}: "${value}",`);
|
|
561
|
+
} else if (typeof value === "boolean") {
|
|
562
|
+
lines.push(` ${key}: ${value},`);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
lines.push(" },");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (config.copy) {
|
|
569
|
+
lines.push(" copy: {");
|
|
570
|
+
Object.entries(config.copy).forEach(([key, value]) => {
|
|
571
|
+
lines.push(` ${key}: "${value}",`);
|
|
572
|
+
});
|
|
573
|
+
lines.push(" },");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (config.sendButton) {
|
|
577
|
+
lines.push(" sendButton: {");
|
|
578
|
+
Object.entries(config.sendButton).forEach(([key, value]) => {
|
|
579
|
+
if (typeof value === "string") {
|
|
580
|
+
lines.push(` ${key}: "${value}",`);
|
|
581
|
+
} else if (typeof value === "boolean") {
|
|
582
|
+
lines.push(` ${key}: ${value},`);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
lines.push(" },");
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (config.voiceRecognition) {
|
|
589
|
+
lines.push(" voiceRecognition: {");
|
|
590
|
+
Object.entries(config.voiceRecognition).forEach(([key, value]) => {
|
|
591
|
+
if (typeof value === "string") {
|
|
592
|
+
lines.push(` ${key}: "${value}",`);
|
|
593
|
+
} else if (typeof value === "boolean") {
|
|
594
|
+
lines.push(` ${key}: ${value},`);
|
|
595
|
+
} else if (typeof value === "number") {
|
|
596
|
+
lines.push(` ${key}: ${value},`);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
lines.push(" },");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (config.statusIndicator) {
|
|
603
|
+
lines.push(" statusIndicator: {");
|
|
604
|
+
Object.entries(config.statusIndicator).forEach(([key, value]) => {
|
|
605
|
+
if (typeof value === "string") {
|
|
606
|
+
lines.push(` ${key}: "${value}",`);
|
|
607
|
+
} else if (typeof value === "boolean") {
|
|
608
|
+
lines.push(` ${key}: ${value},`);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
lines.push(" },");
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (config.features) {
|
|
615
|
+
lines.push(" features: {");
|
|
616
|
+
Object.entries(config.features).forEach(([key, value]) => {
|
|
617
|
+
lines.push(` ${key}: ${value},`);
|
|
618
|
+
});
|
|
619
|
+
lines.push(" },");
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (config.suggestionChips && config.suggestionChips.length > 0) {
|
|
623
|
+
lines.push(" suggestionChips: [");
|
|
624
|
+
config.suggestionChips.forEach((chip: string) => {
|
|
625
|
+
lines.push(` "${chip}",`);
|
|
626
|
+
});
|
|
627
|
+
lines.push(" ],");
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (config.suggestionChipsConfig) {
|
|
631
|
+
lines.push(" suggestionChipsConfig: {");
|
|
632
|
+
if (config.suggestionChipsConfig.fontFamily) {
|
|
633
|
+
lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
|
|
634
|
+
}
|
|
635
|
+
if (config.suggestionChipsConfig.fontWeight) {
|
|
636
|
+
lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
|
|
637
|
+
}
|
|
638
|
+
if (config.suggestionChipsConfig.paddingX) {
|
|
639
|
+
lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
|
|
640
|
+
}
|
|
641
|
+
if (config.suggestionChipsConfig.paddingY) {
|
|
642
|
+
lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
|
|
643
|
+
}
|
|
644
|
+
lines.push(" },");
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Add toolCall config
|
|
648
|
+
lines.push(...generateToolCallConfig(config, " "));
|
|
649
|
+
|
|
650
|
+
// Add messageActions config (with hook callbacks if provided)
|
|
651
|
+
lines.push(...generateMessageActionsConfig(config, " ", hooks));
|
|
652
|
+
|
|
653
|
+
// Add markdown config
|
|
654
|
+
lines.push(...generateMarkdownConfig(config, " "));
|
|
655
|
+
|
|
656
|
+
// Add layout config
|
|
657
|
+
lines.push(...generateLayoutConfig(config, " "));
|
|
658
|
+
|
|
659
|
+
// Add hook-based config (getHeaders, requestMiddleware, actionParsers, actionHandlers, etc.)
|
|
660
|
+
lines.push(...generateHooksConfig(hooks, " "));
|
|
661
|
+
|
|
662
|
+
if (config.debug) {
|
|
663
|
+
lines.push(` debug: ${config.debug},`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Use custom postprocessMessage if provided, otherwise default
|
|
667
|
+
if (hooks?.postprocessMessage) {
|
|
668
|
+
lines.push(` postprocessMessage: ${hooks.postprocessMessage}`);
|
|
669
|
+
} else {
|
|
670
|
+
lines.push(" postprocessMessage: ({ text }) => markdownPostprocessor(text)");
|
|
671
|
+
}
|
|
672
|
+
lines.push(" }");
|
|
673
|
+
lines.push("});");
|
|
674
|
+
|
|
675
|
+
return lines.join("\n");
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function generateReactComponentCode(config: any, options?: CodeGeneratorOptions): string {
|
|
679
|
+
const hooks = options?.hooks;
|
|
680
|
+
const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
|
|
681
|
+
const shouldEmitParserType = parserType !== "plain";
|
|
682
|
+
|
|
683
|
+
const lines: string[] = [
|
|
684
|
+
"// ChatWidget.tsx",
|
|
685
|
+
"'use client'; // Required for Next.js - remove for Vite/CRA",
|
|
686
|
+
"",
|
|
687
|
+
"import { useEffect } from 'react';",
|
|
688
|
+
"import '@runtypelabs/persona/widget.css';",
|
|
689
|
+
"import { initAgentWidget, markdownPostprocessor } from '@runtypelabs/persona';",
|
|
690
|
+
"import type { AgentWidgetInitHandle } from '@runtypelabs/persona';",
|
|
691
|
+
"",
|
|
692
|
+
"export function ChatWidget() {",
|
|
693
|
+
" useEffect(() => {",
|
|
694
|
+
" let handle: AgentWidgetInitHandle | null = null;",
|
|
695
|
+
"",
|
|
696
|
+
" handle = initAgentWidget({",
|
|
697
|
+
" target: 'body',",
|
|
698
|
+
" config: {"
|
|
699
|
+
];
|
|
700
|
+
|
|
701
|
+
if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
|
|
702
|
+
if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
|
|
703
|
+
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
704
|
+
if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
|
|
705
|
+
|
|
706
|
+
if (config.theme) {
|
|
707
|
+
lines.push(" theme: {");
|
|
708
|
+
Object.entries(config.theme).forEach(([key, value]) => {
|
|
709
|
+
lines.push(` ${key}: "${value}",`);
|
|
710
|
+
});
|
|
711
|
+
lines.push(" },");
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (config.launcher) {
|
|
715
|
+
lines.push(" launcher: {");
|
|
716
|
+
Object.entries(config.launcher).forEach(([key, value]) => {
|
|
717
|
+
if (typeof value === "string") {
|
|
718
|
+
lines.push(` ${key}: "${value}",`);
|
|
719
|
+
} else if (typeof value === "boolean") {
|
|
720
|
+
lines.push(` ${key}: ${value},`);
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
lines.push(" },");
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (config.copy) {
|
|
727
|
+
lines.push(" copy: {");
|
|
728
|
+
Object.entries(config.copy).forEach(([key, value]) => {
|
|
729
|
+
lines.push(` ${key}: "${value}",`);
|
|
730
|
+
});
|
|
731
|
+
lines.push(" },");
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (config.sendButton) {
|
|
735
|
+
lines.push(" sendButton: {");
|
|
736
|
+
Object.entries(config.sendButton).forEach(([key, value]) => {
|
|
737
|
+
if (typeof value === "string") {
|
|
738
|
+
lines.push(` ${key}: "${value}",`);
|
|
739
|
+
} else if (typeof value === "boolean") {
|
|
740
|
+
lines.push(` ${key}: ${value},`);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
lines.push(" },");
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (config.voiceRecognition) {
|
|
747
|
+
lines.push(" voiceRecognition: {");
|
|
748
|
+
Object.entries(config.voiceRecognition).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
|
+
} else if (typeof value === "number") {
|
|
754
|
+
lines.push(` ${key}: ${value},`);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
lines.push(" },");
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (config.statusIndicator) {
|
|
761
|
+
lines.push(" statusIndicator: {");
|
|
762
|
+
Object.entries(config.statusIndicator).forEach(([key, value]) => {
|
|
763
|
+
if (typeof value === "string") {
|
|
764
|
+
lines.push(` ${key}: "${value}",`);
|
|
765
|
+
} else if (typeof value === "boolean") {
|
|
766
|
+
lines.push(` ${key}: ${value},`);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
lines.push(" },");
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (config.features) {
|
|
773
|
+
lines.push(" features: {");
|
|
774
|
+
Object.entries(config.features).forEach(([key, value]) => {
|
|
775
|
+
lines.push(` ${key}: ${value},`);
|
|
776
|
+
});
|
|
777
|
+
lines.push(" },");
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (config.suggestionChips && config.suggestionChips.length > 0) {
|
|
781
|
+
lines.push(" suggestionChips: [");
|
|
782
|
+
config.suggestionChips.forEach((chip: string) => {
|
|
783
|
+
lines.push(` "${chip}",`);
|
|
784
|
+
});
|
|
785
|
+
lines.push(" ],");
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (config.suggestionChipsConfig) {
|
|
789
|
+
lines.push(" suggestionChipsConfig: {");
|
|
790
|
+
if (config.suggestionChipsConfig.fontFamily) {
|
|
791
|
+
lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
|
|
792
|
+
}
|
|
793
|
+
if (config.suggestionChipsConfig.fontWeight) {
|
|
794
|
+
lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
|
|
795
|
+
}
|
|
796
|
+
if (config.suggestionChipsConfig.paddingX) {
|
|
797
|
+
lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
|
|
798
|
+
}
|
|
799
|
+
if (config.suggestionChipsConfig.paddingY) {
|
|
800
|
+
lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
|
|
801
|
+
}
|
|
802
|
+
lines.push(" },");
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Add toolCall config
|
|
806
|
+
lines.push(...generateToolCallConfig(config, " "));
|
|
807
|
+
|
|
808
|
+
// Add messageActions config (with hook callbacks if provided)
|
|
809
|
+
lines.push(...generateMessageActionsConfig(config, " ", hooks));
|
|
810
|
+
|
|
811
|
+
// Add markdown config
|
|
812
|
+
lines.push(...generateMarkdownConfig(config, " "));
|
|
813
|
+
|
|
814
|
+
// Add layout config
|
|
815
|
+
lines.push(...generateLayoutConfig(config, " "));
|
|
816
|
+
|
|
817
|
+
// Add hook-based config (getHeaders, requestMiddleware, actionParsers, actionHandlers, etc.)
|
|
818
|
+
lines.push(...generateHooksConfig(hooks, " "));
|
|
819
|
+
|
|
820
|
+
if (config.debug) {
|
|
821
|
+
lines.push(` debug: ${config.debug},`);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Use custom postprocessMessage if provided, otherwise default
|
|
825
|
+
if (hooks?.postprocessMessage) {
|
|
826
|
+
lines.push(` postprocessMessage: ${hooks.postprocessMessage}`);
|
|
827
|
+
} else {
|
|
828
|
+
lines.push(" postprocessMessage: ({ text }) => markdownPostprocessor(text)");
|
|
829
|
+
}
|
|
830
|
+
lines.push(" }");
|
|
831
|
+
lines.push(" });");
|
|
832
|
+
lines.push("");
|
|
833
|
+
lines.push(" // Cleanup on unmount");
|
|
834
|
+
lines.push(" return () => {");
|
|
835
|
+
lines.push(" if (handle) {");
|
|
836
|
+
lines.push(" handle.destroy();");
|
|
837
|
+
lines.push(" }");
|
|
838
|
+
lines.push(" };");
|
|
839
|
+
lines.push(" }, []);");
|
|
840
|
+
lines.push("");
|
|
841
|
+
lines.push(" return null; // Widget injects itself into the DOM");
|
|
842
|
+
lines.push("}");
|
|
843
|
+
lines.push("");
|
|
844
|
+
lines.push("// Usage in your app:");
|
|
845
|
+
lines.push("// import { ChatWidget } from './components/ChatWidget';");
|
|
846
|
+
lines.push("//");
|
|
847
|
+
lines.push("// export default function App() {");
|
|
848
|
+
lines.push("// return (");
|
|
849
|
+
lines.push("// <div>");
|
|
850
|
+
lines.push("// {/* Your app content */}");
|
|
851
|
+
lines.push("// <ChatWidget />");
|
|
852
|
+
lines.push("// </div>");
|
|
853
|
+
lines.push("// );");
|
|
854
|
+
lines.push("// }");
|
|
855
|
+
|
|
856
|
+
return lines.join("\n");
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function generateReactAdvancedCode(config: any, options?: CodeGeneratorOptions): string {
|
|
860
|
+
const hooks = options?.hooks;
|
|
861
|
+
const lines: string[] = [
|
|
862
|
+
"// ChatWidgetAdvanced.tsx",
|
|
863
|
+
"'use client'; // Required for Next.js - remove for Vite/CRA",
|
|
864
|
+
"",
|
|
865
|
+
"import { useEffect } from 'react';",
|
|
866
|
+
"import '@runtypelabs/persona/widget.css';",
|
|
867
|
+
"import {",
|
|
868
|
+
" initAgentWidget,",
|
|
869
|
+
" createFlexibleJsonStreamParser,",
|
|
870
|
+
" defaultJsonActionParser,",
|
|
871
|
+
" defaultActionHandlers,",
|
|
872
|
+
" markdownPostprocessor",
|
|
873
|
+
"} from '@runtypelabs/persona';",
|
|
874
|
+
"import type { AgentWidgetInitHandle } from '@runtypelabs/persona';",
|
|
875
|
+
"",
|
|
876
|
+
"const STORAGE_KEY = 'chat-widget-state';",
|
|
877
|
+
"const PROCESSED_ACTIONS_KEY = 'chat-widget-processed-actions';",
|
|
878
|
+
"",
|
|
879
|
+
"// Types for DOM elements",
|
|
880
|
+
"interface PageElement {",
|
|
881
|
+
" type: string;",
|
|
882
|
+
" tagName: string;",
|
|
883
|
+
" selector: string;",
|
|
884
|
+
" innerText: string;",
|
|
885
|
+
" href?: string;",
|
|
886
|
+
"}",
|
|
887
|
+
"",
|
|
888
|
+
"interface DOMContext {",
|
|
889
|
+
" page_elements: PageElement[];",
|
|
890
|
+
" page_element_count: number;",
|
|
891
|
+
" element_types: Record<string, number>;",
|
|
892
|
+
" page_url: string;",
|
|
893
|
+
" page_title: string;",
|
|
894
|
+
" timestamp: string;",
|
|
895
|
+
"}",
|
|
896
|
+
"",
|
|
897
|
+
"// DOM context provider - extracts page elements for AI context",
|
|
898
|
+
"const collectDOMContext = (): DOMContext => {",
|
|
899
|
+
" const selectors = {",
|
|
900
|
+
" products: '[data-product-id], .product-card, .product-item, [role=\"article\"]',",
|
|
901
|
+
" buttons: 'button, [role=\"button\"], .btn',",
|
|
902
|
+
" links: 'a[href]',",
|
|
903
|
+
" inputs: 'input, textarea, select'",
|
|
904
|
+
" };",
|
|
905
|
+
"",
|
|
906
|
+
" const elements: PageElement[] = [];",
|
|
907
|
+
" Object.entries(selectors).forEach(([type, selector]) => {",
|
|
908
|
+
" document.querySelectorAll(selector).forEach((element) => {",
|
|
909
|
+
" if (!(element instanceof HTMLElement)) return;",
|
|
910
|
+
" ",
|
|
911
|
+
" // Exclude elements within the widget",
|
|
912
|
+
" const widgetHost = element.closest('.persona-host');",
|
|
913
|
+
" if (widgetHost) return;",
|
|
914
|
+
" ",
|
|
915
|
+
" const text = element.innerText?.trim();",
|
|
916
|
+
" if (!text) return;",
|
|
917
|
+
"",
|
|
918
|
+
" const selectorString =",
|
|
919
|
+
" element.id ? `#${element.id}` :",
|
|
920
|
+
" element.getAttribute('data-testid') ? `[data-testid=\"${element.getAttribute('data-testid')}\"]` :",
|
|
921
|
+
" element.getAttribute('data-product-id') ? `[data-product-id=\"${element.getAttribute('data-product-id')}\"]` :",
|
|
922
|
+
" element.tagName.toLowerCase();",
|
|
923
|
+
"",
|
|
924
|
+
" const elementData: PageElement = {",
|
|
925
|
+
" type,",
|
|
926
|
+
" tagName: element.tagName.toLowerCase(),",
|
|
927
|
+
" selector: selectorString,",
|
|
928
|
+
" innerText: text.substring(0, 200)",
|
|
929
|
+
" };",
|
|
930
|
+
"",
|
|
931
|
+
" if (type === 'links' && element instanceof HTMLAnchorElement && element.href) {",
|
|
932
|
+
" elementData.href = element.href;",
|
|
933
|
+
" }",
|
|
934
|
+
"",
|
|
935
|
+
" elements.push(elementData);",
|
|
936
|
+
" });",
|
|
937
|
+
" });",
|
|
938
|
+
"",
|
|
939
|
+
" const counts = elements.reduce((acc, el) => {",
|
|
940
|
+
" acc[el.type] = (acc[el.type] || 0) + 1;",
|
|
941
|
+
" return acc;",
|
|
942
|
+
" }, {} as Record<string, number>);",
|
|
943
|
+
"",
|
|
944
|
+
" return {",
|
|
945
|
+
" page_elements: elements.slice(0, 50),",
|
|
946
|
+
" page_element_count: elements.length,",
|
|
947
|
+
" element_types: counts,",
|
|
948
|
+
" page_url: window.location.href,",
|
|
949
|
+
" page_title: document.title,",
|
|
950
|
+
" timestamp: new Date().toISOString()",
|
|
951
|
+
" };",
|
|
952
|
+
"};",
|
|
953
|
+
"",
|
|
954
|
+
"export function ChatWidgetAdvanced() {",
|
|
955
|
+
" useEffect(() => {",
|
|
956
|
+
" let handle: AgentWidgetInitHandle | null = null;",
|
|
957
|
+
"",
|
|
958
|
+
" // Load saved state",
|
|
959
|
+
" const loadSavedMessages = () => {",
|
|
960
|
+
" const savedState = localStorage.getItem(STORAGE_KEY);",
|
|
961
|
+
" if (savedState) {",
|
|
962
|
+
" try {",
|
|
963
|
+
" const { messages } = JSON.parse(savedState);",
|
|
964
|
+
" return messages || [];",
|
|
965
|
+
" } catch (e) {",
|
|
966
|
+
" console.error('Failed to load saved state:', e);",
|
|
967
|
+
" }",
|
|
968
|
+
" }",
|
|
969
|
+
" return [];",
|
|
970
|
+
" };",
|
|
971
|
+
"",
|
|
972
|
+
" handle = initAgentWidget({",
|
|
973
|
+
" target: 'body',",
|
|
974
|
+
" config: {"
|
|
975
|
+
];
|
|
976
|
+
|
|
977
|
+
if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
|
|
978
|
+
if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
|
|
979
|
+
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
980
|
+
|
|
981
|
+
if (config.theme) {
|
|
982
|
+
lines.push(" theme: {");
|
|
983
|
+
Object.entries(config.theme).forEach(([key, value]) => {
|
|
984
|
+
lines.push(` ${key}: "${value}",`);
|
|
985
|
+
});
|
|
986
|
+
lines.push(" },");
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (config.launcher) {
|
|
990
|
+
lines.push(" launcher: {");
|
|
991
|
+
Object.entries(config.launcher).forEach(([key, value]) => {
|
|
992
|
+
if (typeof value === "string") {
|
|
993
|
+
lines.push(` ${key}: "${value}",`);
|
|
994
|
+
} else if (typeof value === "boolean") {
|
|
995
|
+
lines.push(` ${key}: ${value},`);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
lines.push(" },");
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (config.copy) {
|
|
1002
|
+
lines.push(" copy: {");
|
|
1003
|
+
Object.entries(config.copy).forEach(([key, value]) => {
|
|
1004
|
+
lines.push(` ${key}: "${value}",`);
|
|
1005
|
+
});
|
|
1006
|
+
lines.push(" },");
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (config.sendButton) {
|
|
1010
|
+
lines.push(" sendButton: {");
|
|
1011
|
+
Object.entries(config.sendButton).forEach(([key, value]) => {
|
|
1012
|
+
if (typeof value === "string") {
|
|
1013
|
+
lines.push(` ${key}: "${value}",`);
|
|
1014
|
+
} else if (typeof value === "boolean") {
|
|
1015
|
+
lines.push(` ${key}: ${value},`);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
lines.push(" },");
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (config.voiceRecognition) {
|
|
1022
|
+
lines.push(" voiceRecognition: {");
|
|
1023
|
+
Object.entries(config.voiceRecognition).forEach(([key, value]) => {
|
|
1024
|
+
if (typeof value === "string") {
|
|
1025
|
+
lines.push(` ${key}: "${value}",`);
|
|
1026
|
+
} else if (typeof value === "boolean") {
|
|
1027
|
+
lines.push(` ${key}: ${value},`);
|
|
1028
|
+
} else if (typeof value === "number") {
|
|
1029
|
+
lines.push(` ${key}: ${value},`);
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
lines.push(" },");
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (config.statusIndicator) {
|
|
1036
|
+
lines.push(" statusIndicator: {");
|
|
1037
|
+
Object.entries(config.statusIndicator).forEach(([key, value]) => {
|
|
1038
|
+
if (typeof value === "string") {
|
|
1039
|
+
lines.push(` ${key}: "${value}",`);
|
|
1040
|
+
} else if (typeof value === "boolean") {
|
|
1041
|
+
lines.push(` ${key}: ${value},`);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
lines.push(" },");
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (config.features) {
|
|
1048
|
+
lines.push(" features: {");
|
|
1049
|
+
Object.entries(config.features).forEach(([key, value]) => {
|
|
1050
|
+
lines.push(` ${key}: ${value},`);
|
|
1051
|
+
});
|
|
1052
|
+
lines.push(" },");
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (config.suggestionChips && config.suggestionChips.length > 0) {
|
|
1056
|
+
lines.push(" suggestionChips: [");
|
|
1057
|
+
config.suggestionChips.forEach((chip: string) => {
|
|
1058
|
+
lines.push(` "${chip}",`);
|
|
1059
|
+
});
|
|
1060
|
+
lines.push(" ],");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (config.suggestionChipsConfig) {
|
|
1064
|
+
lines.push(" suggestionChipsConfig: {");
|
|
1065
|
+
if (config.suggestionChipsConfig.fontFamily) {
|
|
1066
|
+
lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
|
|
1067
|
+
}
|
|
1068
|
+
if (config.suggestionChipsConfig.fontWeight) {
|
|
1069
|
+
lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
|
|
1070
|
+
}
|
|
1071
|
+
if (config.suggestionChipsConfig.paddingX) {
|
|
1072
|
+
lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
|
|
1073
|
+
}
|
|
1074
|
+
if (config.suggestionChipsConfig.paddingY) {
|
|
1075
|
+
lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
|
|
1076
|
+
}
|
|
1077
|
+
lines.push(" },");
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Add toolCall config
|
|
1081
|
+
lines.push(...generateToolCallConfig(config, " "));
|
|
1082
|
+
|
|
1083
|
+
// Add messageActions config (with hook callbacks if provided)
|
|
1084
|
+
lines.push(...generateMessageActionsConfig(config, " ", hooks));
|
|
1085
|
+
|
|
1086
|
+
// Add markdown config
|
|
1087
|
+
lines.push(...generateMarkdownConfig(config, " "));
|
|
1088
|
+
|
|
1089
|
+
// Add layout config
|
|
1090
|
+
lines.push(...generateLayoutConfig(config, " "));
|
|
1091
|
+
|
|
1092
|
+
// Add getHeaders if provided
|
|
1093
|
+
if (hooks?.getHeaders) {
|
|
1094
|
+
lines.push(` getHeaders: ${hooks.getHeaders},`);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Add contextProviders if provided
|
|
1098
|
+
if (hooks?.contextProviders) {
|
|
1099
|
+
lines.push(` contextProviders: ${hooks.contextProviders},`);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (config.debug) {
|
|
1103
|
+
lines.push(` debug: ${config.debug},`);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
lines.push(" initialMessages: loadSavedMessages(),");
|
|
1107
|
+
|
|
1108
|
+
// Stream parser - use custom if provided, otherwise default
|
|
1109
|
+
if (hooks?.streamParser) {
|
|
1110
|
+
lines.push(` streamParser: ${hooks.streamParser},`);
|
|
1111
|
+
} else {
|
|
1112
|
+
lines.push(" // Flexible JSON stream parser for handling structured actions");
|
|
1113
|
+
lines.push(` streamParser: () => createFlexibleJsonStreamParser(${TEMPLATE_STREAM_PARSER_CALLBACK_TS}),`);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Action parsers - merge custom with defaults if provided
|
|
1117
|
+
if (hooks?.actionParsers) {
|
|
1118
|
+
lines.push(" // Action parsers (custom merged with defaults)");
|
|
1119
|
+
lines.push(` actionParsers: [...(${hooks.actionParsers}), defaultJsonActionParser,`);
|
|
1120
|
+
lines.push(` // Built-in parser for markdown-wrapped JSON`);
|
|
1121
|
+
lines.push(` ${TEMPLATE_MARKDOWN_JSON_PARSER_TS}`);
|
|
1122
|
+
lines.push(" ],");
|
|
1123
|
+
} else {
|
|
1124
|
+
lines.push(" // Action parsers to detect JSON actions in responses");
|
|
1125
|
+
lines.push(" actionParsers: [");
|
|
1126
|
+
lines.push(" defaultJsonActionParser,");
|
|
1127
|
+
lines.push(` // Parser for markdown-wrapped JSON`);
|
|
1128
|
+
lines.push(` ${TEMPLATE_MARKDOWN_JSON_PARSER_TS}`);
|
|
1129
|
+
lines.push(" ],");
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Action handlers - merge custom with defaults if provided
|
|
1133
|
+
if (hooks?.actionHandlers) {
|
|
1134
|
+
lines.push(" // Action handlers (custom merged with defaults)");
|
|
1135
|
+
lines.push(` actionHandlers: [...(${hooks.actionHandlers}),`);
|
|
1136
|
+
lines.push(" defaultActionHandlers.message,");
|
|
1137
|
+
lines.push(" defaultActionHandlers.messageAndClick,");
|
|
1138
|
+
lines.push(` // Built-in handler for nav_then_click action`);
|
|
1139
|
+
lines.push(` ${TEMPLATE_NAV_THEN_CLICK_HANDLER_TS}`);
|
|
1140
|
+
lines.push(" ],");
|
|
1141
|
+
} else {
|
|
1142
|
+
lines.push(" // Action handlers for navigation and other actions");
|
|
1143
|
+
lines.push(" actionHandlers: [");
|
|
1144
|
+
lines.push(" defaultActionHandlers.message,");
|
|
1145
|
+
lines.push(" defaultActionHandlers.messageAndClick,");
|
|
1146
|
+
lines.push(` // Handler for nav_then_click action`);
|
|
1147
|
+
lines.push(` ${TEMPLATE_NAV_THEN_CLICK_HANDLER_TS}`);
|
|
1148
|
+
lines.push(" ],");
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// postprocessMessage - use custom if provided, otherwise default
|
|
1152
|
+
if (hooks?.postprocessMessage) {
|
|
1153
|
+
lines.push(` postprocessMessage: ${hooks.postprocessMessage},`);
|
|
1154
|
+
} else {
|
|
1155
|
+
lines.push(" postprocessMessage: ({ text }) => markdownPostprocessor(text),");
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// requestMiddleware - merge custom with DOM context if provided
|
|
1159
|
+
if (hooks?.requestMiddleware) {
|
|
1160
|
+
lines.push(" // Request middleware (custom merged with DOM context)");
|
|
1161
|
+
lines.push(" requestMiddleware: ({ payload, config }) => {");
|
|
1162
|
+
lines.push(` const customResult = (${hooks.requestMiddleware})({ payload, config });`);
|
|
1163
|
+
lines.push(" const merged = customResult || payload;");
|
|
1164
|
+
lines.push(" return {");
|
|
1165
|
+
lines.push(" ...merged,");
|
|
1166
|
+
lines.push(" metadata: { ...merged.metadata, ...collectDOMContext() }");
|
|
1167
|
+
lines.push(" };");
|
|
1168
|
+
lines.push(" }");
|
|
1169
|
+
} else {
|
|
1170
|
+
lines.push(" requestMiddleware: ({ payload }) => {");
|
|
1171
|
+
lines.push(" return {");
|
|
1172
|
+
lines.push(" ...payload,");
|
|
1173
|
+
lines.push(" metadata: collectDOMContext()");
|
|
1174
|
+
lines.push(" };");
|
|
1175
|
+
lines.push(" }");
|
|
1176
|
+
}
|
|
1177
|
+
lines.push(" }");
|
|
1178
|
+
lines.push(" });");
|
|
1179
|
+
lines.push("");
|
|
1180
|
+
lines.push(" // Save state on message events");
|
|
1181
|
+
lines.push(" const handleMessage = () => {");
|
|
1182
|
+
lines.push(" const session = handle?.getSession?.();");
|
|
1183
|
+
lines.push(" if (session) {");
|
|
1184
|
+
lines.push(" localStorage.setItem(STORAGE_KEY, JSON.stringify({");
|
|
1185
|
+
lines.push(" messages: session.messages,");
|
|
1186
|
+
lines.push(" timestamp: new Date().toISOString()");
|
|
1187
|
+
lines.push(" }));");
|
|
1188
|
+
lines.push(" }");
|
|
1189
|
+
lines.push(" };");
|
|
1190
|
+
lines.push("");
|
|
1191
|
+
lines.push(" // Clear state on clear chat");
|
|
1192
|
+
lines.push(" const handleClearChat = () => {");
|
|
1193
|
+
lines.push(" localStorage.removeItem(STORAGE_KEY);");
|
|
1194
|
+
lines.push(" localStorage.removeItem(PROCESSED_ACTIONS_KEY);");
|
|
1195
|
+
lines.push(" };");
|
|
1196
|
+
lines.push("");
|
|
1197
|
+
lines.push(" window.addEventListener('persona:message', handleMessage);");
|
|
1198
|
+
lines.push(" window.addEventListener('persona:clear-chat', handleClearChat);");
|
|
1199
|
+
lines.push("");
|
|
1200
|
+
lines.push(" // Cleanup on unmount");
|
|
1201
|
+
lines.push(" return () => {");
|
|
1202
|
+
lines.push(" window.removeEventListener('persona:message', handleMessage);");
|
|
1203
|
+
lines.push(" window.removeEventListener('persona:clear-chat', handleClearChat);");
|
|
1204
|
+
lines.push(" if (handle) {");
|
|
1205
|
+
lines.push(" handle.destroy();");
|
|
1206
|
+
lines.push(" }");
|
|
1207
|
+
lines.push(" };");
|
|
1208
|
+
lines.push(" }, []);");
|
|
1209
|
+
lines.push("");
|
|
1210
|
+
lines.push(" return null; // Widget injects itself into the DOM");
|
|
1211
|
+
lines.push("}");
|
|
1212
|
+
lines.push("");
|
|
1213
|
+
lines.push("// Usage: Collects DOM context for AI-powered navigation");
|
|
1214
|
+
lines.push("// Features:");
|
|
1215
|
+
lines.push("// - Extracts page elements (products, buttons, links)");
|
|
1216
|
+
lines.push("// - Persists chat history across page loads");
|
|
1217
|
+
lines.push("// - Handles navigation actions (nav_then_click)");
|
|
1218
|
+
lines.push("// - Processes structured JSON actions from AI");
|
|
1219
|
+
lines.push("//");
|
|
1220
|
+
lines.push("// Example usage in Next.js:");
|
|
1221
|
+
lines.push("// import { ChatWidgetAdvanced } from './components/ChatWidgetAdvanced';");
|
|
1222
|
+
lines.push("//");
|
|
1223
|
+
lines.push("// export default function RootLayout({ children }) {");
|
|
1224
|
+
lines.push("// return (");
|
|
1225
|
+
lines.push("// <html lang=\"en\">");
|
|
1226
|
+
lines.push("// <body>");
|
|
1227
|
+
lines.push("// {children}");
|
|
1228
|
+
lines.push("// <ChatWidgetAdvanced />");
|
|
1229
|
+
lines.push("// </body>");
|
|
1230
|
+
lines.push("// </html>");
|
|
1231
|
+
lines.push("// );");
|
|
1232
|
+
lines.push("// }");
|
|
1233
|
+
|
|
1234
|
+
return lines.join("\n");
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Helper to build a serializable config object for JSON export
|
|
1238
|
+
function buildSerializableConfig(config: any): Record<string, any> {
|
|
1239
|
+
const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
|
|
1240
|
+
const shouldEmitParserType = parserType !== "plain";
|
|
1241
|
+
|
|
1242
|
+
const serializableConfig: Record<string, any> = {};
|
|
1243
|
+
|
|
1244
|
+
if (config.apiUrl) serializableConfig.apiUrl = config.apiUrl;
|
|
1245
|
+
if (config.clientToken) serializableConfig.clientToken = config.clientToken;
|
|
1246
|
+
if (config.flowId) serializableConfig.flowId = config.flowId;
|
|
1247
|
+
if (shouldEmitParserType) serializableConfig.parserType = parserType;
|
|
1248
|
+
if (config.theme) serializableConfig.theme = config.theme;
|
|
1249
|
+
if (config.launcher) serializableConfig.launcher = config.launcher;
|
|
1250
|
+
if (config.copy) serializableConfig.copy = config.copy;
|
|
1251
|
+
if (config.sendButton) serializableConfig.sendButton = config.sendButton;
|
|
1252
|
+
if (config.voiceRecognition) serializableConfig.voiceRecognition = config.voiceRecognition;
|
|
1253
|
+
if (config.statusIndicator) serializableConfig.statusIndicator = config.statusIndicator;
|
|
1254
|
+
if (config.features) serializableConfig.features = config.features;
|
|
1255
|
+
if (config.suggestionChips?.length > 0) serializableConfig.suggestionChips = config.suggestionChips;
|
|
1256
|
+
if (config.suggestionChipsConfig) serializableConfig.suggestionChipsConfig = config.suggestionChipsConfig;
|
|
1257
|
+
if (config.debug) serializableConfig.debug = config.debug;
|
|
1258
|
+
|
|
1259
|
+
// Add toolCall config (only serializable parts)
|
|
1260
|
+
if (config.toolCall) {
|
|
1261
|
+
const toolCallConfig: Record<string, any> = {};
|
|
1262
|
+
Object.entries(config.toolCall).forEach(([key, value]) => {
|
|
1263
|
+
if (typeof value === "string") toolCallConfig[key] = value;
|
|
1264
|
+
});
|
|
1265
|
+
if (Object.keys(toolCallConfig).length > 0) {
|
|
1266
|
+
serializableConfig.toolCall = toolCallConfig;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Add messageActions config (excluding callbacks)
|
|
1271
|
+
if (config.messageActions) {
|
|
1272
|
+
const messageActionsConfig: Record<string, any> = {};
|
|
1273
|
+
Object.entries(config.messageActions).forEach(([key, value]) => {
|
|
1274
|
+
if (key !== "onFeedback" && key !== "onCopy" && value !== undefined) {
|
|
1275
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
1276
|
+
messageActionsConfig[key] = value;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
if (Object.keys(messageActionsConfig).length > 0) {
|
|
1281
|
+
serializableConfig.messageActions = messageActionsConfig;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Add markdown config (excluding renderer functions)
|
|
1286
|
+
if (config.markdown) {
|
|
1287
|
+
const markdownConfig: Record<string, any> = {};
|
|
1288
|
+
if (config.markdown.options) markdownConfig.options = config.markdown.options;
|
|
1289
|
+
if (config.markdown.disableDefaultStyles !== undefined) {
|
|
1290
|
+
markdownConfig.disableDefaultStyles = config.markdown.disableDefaultStyles;
|
|
1291
|
+
}
|
|
1292
|
+
if (Object.keys(markdownConfig).length > 0) {
|
|
1293
|
+
serializableConfig.markdown = markdownConfig;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// Add layout config (excluding render functions)
|
|
1298
|
+
if (config.layout) {
|
|
1299
|
+
const layoutConfig: Record<string, any> = {};
|
|
1300
|
+
|
|
1301
|
+
if (config.layout.header) {
|
|
1302
|
+
const headerConfig: Record<string, any> = {};
|
|
1303
|
+
Object.entries(config.layout.header).forEach(([key, value]) => {
|
|
1304
|
+
if (key !== "render" && (typeof value === "string" || typeof value === "boolean")) {
|
|
1305
|
+
headerConfig[key] = value;
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
if (Object.keys(headerConfig).length > 0) {
|
|
1309
|
+
layoutConfig.header = headerConfig;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if (config.layout.messages) {
|
|
1314
|
+
const messagesConfig: Record<string, any> = {};
|
|
1315
|
+
Object.entries(config.layout.messages).forEach(([key, value]) => {
|
|
1316
|
+
if (key !== "renderUserMessage" && key !== "renderAssistantMessage") {
|
|
1317
|
+
if (key === "avatar" && typeof value === "object" && value !== null) {
|
|
1318
|
+
messagesConfig.avatar = value;
|
|
1319
|
+
} else if (key === "timestamp" && typeof value === "object" && value !== null) {
|
|
1320
|
+
// Exclude format function
|
|
1321
|
+
const tsConfig: Record<string, any> = {};
|
|
1322
|
+
Object.entries(value as Record<string, unknown>).forEach(([tsKey, tsValue]) => {
|
|
1323
|
+
if (tsKey !== "format" && (typeof tsValue === "string" || typeof tsValue === "boolean")) {
|
|
1324
|
+
tsConfig[tsKey] = tsValue;
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
if (Object.keys(tsConfig).length > 0) {
|
|
1328
|
+
messagesConfig.timestamp = tsConfig;
|
|
1329
|
+
}
|
|
1330
|
+
} else if (typeof value === "string" || typeof value === "boolean") {
|
|
1331
|
+
messagesConfig[key] = value;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
if (Object.keys(messagesConfig).length > 0) {
|
|
1336
|
+
layoutConfig.messages = messagesConfig;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
if (Object.keys(layoutConfig).length > 0) {
|
|
1341
|
+
serializableConfig.layout = layoutConfig;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
return serializableConfig;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function generateScriptInstallerCode(config: any): string {
|
|
1349
|
+
const serializableConfig = buildSerializableConfig(config);
|
|
1350
|
+
|
|
1351
|
+
// Escape single quotes in JSON for HTML attribute
|
|
1352
|
+
const configJson = JSON.stringify(serializableConfig, null, 0).replace(/'/g, "'");
|
|
1353
|
+
|
|
1354
|
+
return `<script src="https://cdn.jsdelivr.net/npm/@runtypelabs/persona@latest/dist/install.global.js" data-config='${configJson}'></script>`;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function generateScriptManualCode(config: any, options?: CodeGeneratorOptions): string {
|
|
1358
|
+
const hooks = options?.hooks;
|
|
1359
|
+
const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);
|
|
1360
|
+
const shouldEmitParserType = parserType !== "plain";
|
|
1361
|
+
|
|
1362
|
+
const lines: string[] = [
|
|
1363
|
+
"<!-- Load CSS -->",
|
|
1364
|
+
"<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@runtypelabs/persona@latest/dist/widget.css\" />",
|
|
1365
|
+
"",
|
|
1366
|
+
"<!-- Load JavaScript -->",
|
|
1367
|
+
"<script src=\"https://cdn.jsdelivr.net/npm/@runtypelabs/persona@latest/dist/index.global.js\"></script>",
|
|
1368
|
+
"",
|
|
1369
|
+
"<!-- Initialize widget -->",
|
|
1370
|
+
"<script>",
|
|
1371
|
+
" window.AgentWidget.initAgentWidget({",
|
|
1372
|
+
" target: 'body',",
|
|
1373
|
+
" config: {"
|
|
1374
|
+
];
|
|
1375
|
+
|
|
1376
|
+
if (config.apiUrl) lines.push(` apiUrl: "${config.apiUrl}",`);
|
|
1377
|
+
if (config.clientToken) lines.push(` clientToken: "${config.clientToken}",`);
|
|
1378
|
+
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
1379
|
+
if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
|
|
1380
|
+
|
|
1381
|
+
if (config.theme) {
|
|
1382
|
+
lines.push(" theme: {");
|
|
1383
|
+
Object.entries(config.theme).forEach(([key, value]) => {
|
|
1384
|
+
lines.push(` ${key}: "${value}",`);
|
|
1385
|
+
});
|
|
1386
|
+
lines.push(" },");
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (config.launcher) {
|
|
1390
|
+
lines.push(" launcher: {");
|
|
1391
|
+
Object.entries(config.launcher).forEach(([key, value]) => {
|
|
1392
|
+
if (typeof value === "string") {
|
|
1393
|
+
lines.push(` ${key}: "${value}",`);
|
|
1394
|
+
} else if (typeof value === "boolean") {
|
|
1395
|
+
lines.push(` ${key}: ${value},`);
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
lines.push(" },");
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
if (config.copy) {
|
|
1402
|
+
lines.push(" copy: {");
|
|
1403
|
+
Object.entries(config.copy).forEach(([key, value]) => {
|
|
1404
|
+
lines.push(` ${key}: "${value}",`);
|
|
1405
|
+
});
|
|
1406
|
+
lines.push(" },");
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (config.sendButton) {
|
|
1410
|
+
lines.push(" sendButton: {");
|
|
1411
|
+
Object.entries(config.sendButton).forEach(([key, value]) => {
|
|
1412
|
+
if (typeof value === "string") {
|
|
1413
|
+
lines.push(` ${key}: "${value}",`);
|
|
1414
|
+
} else if (typeof value === "boolean") {
|
|
1415
|
+
lines.push(` ${key}: ${value},`);
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
lines.push(" },");
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (config.voiceRecognition) {
|
|
1422
|
+
lines.push(" voiceRecognition: {");
|
|
1423
|
+
Object.entries(config.voiceRecognition).forEach(([key, value]) => {
|
|
1424
|
+
if (typeof value === "string") {
|
|
1425
|
+
lines.push(` ${key}: "${value}",`);
|
|
1426
|
+
} else if (typeof value === "boolean") {
|
|
1427
|
+
lines.push(` ${key}: ${value},`);
|
|
1428
|
+
} else if (typeof value === "number") {
|
|
1429
|
+
lines.push(` ${key}: ${value},`);
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
lines.push(" },");
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (config.statusIndicator) {
|
|
1436
|
+
lines.push(" statusIndicator: {");
|
|
1437
|
+
Object.entries(config.statusIndicator).forEach(([key, value]) => {
|
|
1438
|
+
if (typeof value === "string") {
|
|
1439
|
+
lines.push(` ${key}: "${value}",`);
|
|
1440
|
+
} else if (typeof value === "boolean") {
|
|
1441
|
+
lines.push(` ${key}: ${value},`);
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
lines.push(" },");
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (config.features) {
|
|
1448
|
+
lines.push(" features: {");
|
|
1449
|
+
Object.entries(config.features).forEach(([key, value]) => {
|
|
1450
|
+
lines.push(` ${key}: ${value},`);
|
|
1451
|
+
});
|
|
1452
|
+
lines.push(" },");
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
if (config.suggestionChips && config.suggestionChips.length > 0) {
|
|
1456
|
+
lines.push(" suggestionChips: [");
|
|
1457
|
+
config.suggestionChips.forEach((chip: string) => {
|
|
1458
|
+
lines.push(` "${chip}",`);
|
|
1459
|
+
});
|
|
1460
|
+
lines.push(" ],");
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
if (config.suggestionChipsConfig) {
|
|
1464
|
+
lines.push(" suggestionChipsConfig: {");
|
|
1465
|
+
if (config.suggestionChipsConfig.fontFamily) {
|
|
1466
|
+
lines.push(` fontFamily: "${config.suggestionChipsConfig.fontFamily}",`);
|
|
1467
|
+
}
|
|
1468
|
+
if (config.suggestionChipsConfig.fontWeight) {
|
|
1469
|
+
lines.push(` fontWeight: "${config.suggestionChipsConfig.fontWeight}",`);
|
|
1470
|
+
}
|
|
1471
|
+
if (config.suggestionChipsConfig.paddingX) {
|
|
1472
|
+
lines.push(` paddingX: "${config.suggestionChipsConfig.paddingX}",`);
|
|
1473
|
+
}
|
|
1474
|
+
if (config.suggestionChipsConfig.paddingY) {
|
|
1475
|
+
lines.push(` paddingY: "${config.suggestionChipsConfig.paddingY}",`);
|
|
1476
|
+
}
|
|
1477
|
+
lines.push(" },");
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// Add toolCall config
|
|
1481
|
+
lines.push(...generateToolCallConfig(config, " "));
|
|
1482
|
+
|
|
1483
|
+
// Add messageActions config (with hook callbacks if provided)
|
|
1484
|
+
lines.push(...generateMessageActionsConfig(config, " ", hooks));
|
|
1485
|
+
|
|
1486
|
+
// Add markdown config
|
|
1487
|
+
lines.push(...generateMarkdownConfig(config, " "));
|
|
1488
|
+
|
|
1489
|
+
// Add layout config
|
|
1490
|
+
lines.push(...generateLayoutConfig(config, " "));
|
|
1491
|
+
|
|
1492
|
+
// Add hook-based config (getHeaders, requestMiddleware, actionParsers, actionHandlers, etc.)
|
|
1493
|
+
lines.push(...generateHooksConfig(hooks, " "));
|
|
1494
|
+
|
|
1495
|
+
if (config.debug) {
|
|
1496
|
+
lines.push(` debug: ${config.debug},`);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Use custom postprocessMessage if provided, otherwise default
|
|
1500
|
+
if (hooks?.postprocessMessage) {
|
|
1501
|
+
lines.push(` postprocessMessage: ${hooks.postprocessMessage}`);
|
|
1502
|
+
} else {
|
|
1503
|
+
lines.push(" postprocessMessage: ({ text }) => window.AgentWidget.markdownPostprocessor(text)");
|
|
1504
|
+
}
|
|
1505
|
+
lines.push(" }");
|
|
1506
|
+
lines.push(" });");
|
|
1507
|
+
lines.push("</script>");
|
|
1508
|
+
|
|
1509
|
+
return lines.join("\n");
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function generateScriptAdvancedCode(config: any, options?: CodeGeneratorOptions): string {
|
|
1513
|
+
const hooks = options?.hooks;
|
|
1514
|
+
const serializableConfig = buildSerializableConfig(config);
|
|
1515
|
+
const configJson = JSON.stringify(serializableConfig, null, 2);
|
|
1516
|
+
|
|
1517
|
+
const lines: string[] = [
|
|
1518
|
+
"<script>",
|
|
1519
|
+
"(function() {",
|
|
1520
|
+
" 'use strict';",
|
|
1521
|
+
"",
|
|
1522
|
+
" // Configuration",
|
|
1523
|
+
` var CONFIG = ${configJson.split('\n').map((line, i) => i === 0 ? line : ' ' + line).join('\n')};`,
|
|
1524
|
+
"",
|
|
1525
|
+
" // Constants",
|
|
1526
|
+
" var CDN_BASE = 'https://cdn.jsdelivr.net/npm/@runtypelabs/persona@latest/dist';",
|
|
1527
|
+
" var STORAGE_KEY = 'chat-widget-state';",
|
|
1528
|
+
" var PROCESSED_ACTIONS_KEY = 'chat-widget-processed-actions';",
|
|
1529
|
+
"",
|
|
1530
|
+
" // DOM context provider - extracts page elements for AI context",
|
|
1531
|
+
" var domContextProvider = function() {",
|
|
1532
|
+
" var selectors = {",
|
|
1533
|
+
" products: '[data-product-id], .product-card, .product-item, [role=\"article\"]',",
|
|
1534
|
+
" buttons: 'button, [role=\"button\"], .btn',",
|
|
1535
|
+
" links: 'a[href]',",
|
|
1536
|
+
" inputs: 'input, textarea, select'",
|
|
1537
|
+
" };",
|
|
1538
|
+
"",
|
|
1539
|
+
" var elements = [];",
|
|
1540
|
+
" Object.entries(selectors).forEach(function(entry) {",
|
|
1541
|
+
" var type = entry[0], selector = entry[1];",
|
|
1542
|
+
" document.querySelectorAll(selector).forEach(function(element) {",
|
|
1543
|
+
" if (!(element instanceof HTMLElement)) return;",
|
|
1544
|
+
" var widgetHost = element.closest('.persona-host');",
|
|
1545
|
+
" if (widgetHost) return;",
|
|
1546
|
+
" var text = element.innerText ? element.innerText.trim() : '';",
|
|
1547
|
+
" if (!text) return;",
|
|
1548
|
+
"",
|
|
1549
|
+
" var selectorString = element.id ? '#' + element.id :",
|
|
1550
|
+
" element.getAttribute('data-testid') ? '[data-testid=\"' + element.getAttribute('data-testid') + '\"]' :",
|
|
1551
|
+
" element.getAttribute('data-product-id') ? '[data-product-id=\"' + element.getAttribute('data-product-id') + '\"]' :",
|
|
1552
|
+
" element.tagName.toLowerCase();",
|
|
1553
|
+
"",
|
|
1554
|
+
" var elementData = {",
|
|
1555
|
+
" type: type,",
|
|
1556
|
+
" tagName: element.tagName.toLowerCase(),",
|
|
1557
|
+
" selector: selectorString,",
|
|
1558
|
+
" innerText: text.substring(0, 200)",
|
|
1559
|
+
" };",
|
|
1560
|
+
"",
|
|
1561
|
+
" if (type === 'links' && element instanceof HTMLAnchorElement && element.href) {",
|
|
1562
|
+
" elementData.href = element.href;",
|
|
1563
|
+
" }",
|
|
1564
|
+
" elements.push(elementData);",
|
|
1565
|
+
" });",
|
|
1566
|
+
" });",
|
|
1567
|
+
"",
|
|
1568
|
+
" var counts = elements.reduce(function(acc, el) {",
|
|
1569
|
+
" acc[el.type] = (acc[el.type] || 0) + 1;",
|
|
1570
|
+
" return acc;",
|
|
1571
|
+
" }, {});",
|
|
1572
|
+
"",
|
|
1573
|
+
" return {",
|
|
1574
|
+
" page_elements: elements.slice(0, 50),",
|
|
1575
|
+
" page_element_count: elements.length,",
|
|
1576
|
+
" element_types: counts,",
|
|
1577
|
+
" page_url: window.location.href,",
|
|
1578
|
+
" page_title: document.title,",
|
|
1579
|
+
" timestamp: new Date().toISOString()",
|
|
1580
|
+
" };",
|
|
1581
|
+
" };",
|
|
1582
|
+
"",
|
|
1583
|
+
" // Load CSS dynamically",
|
|
1584
|
+
" var loadCSS = function() {",
|
|
1585
|
+
" if (document.querySelector('link[data-persona]')) return;",
|
|
1586
|
+
" var link = document.createElement('link');",
|
|
1587
|
+
" link.rel = 'stylesheet';",
|
|
1588
|
+
" link.href = CDN_BASE + '/widget.css';",
|
|
1589
|
+
" link.setAttribute('data-persona', 'true');",
|
|
1590
|
+
" document.head.appendChild(link);",
|
|
1591
|
+
" };",
|
|
1592
|
+
"",
|
|
1593
|
+
" // Load JS dynamically",
|
|
1594
|
+
" var loadJS = function(callback) {",
|
|
1595
|
+
" if (window.AgentWidget) { callback(); return; }",
|
|
1596
|
+
" var script = document.createElement('script');",
|
|
1597
|
+
" script.src = CDN_BASE + '/index.global.js';",
|
|
1598
|
+
" script.onload = callback;",
|
|
1599
|
+
" script.onerror = function() { console.error('Failed to load AgentWidget'); };",
|
|
1600
|
+
" document.head.appendChild(script);",
|
|
1601
|
+
" };",
|
|
1602
|
+
"",
|
|
1603
|
+
" // Create widget config with advanced features",
|
|
1604
|
+
" var createWidgetConfig = function(agentWidget) {",
|
|
1605
|
+
" var widgetConfig = Object.assign({}, CONFIG);",
|
|
1606
|
+
""
|
|
1607
|
+
];
|
|
1608
|
+
|
|
1609
|
+
// Add getHeaders if provided
|
|
1610
|
+
if (hooks?.getHeaders) {
|
|
1611
|
+
lines.push(` widgetConfig.getHeaders = ${hooks.getHeaders};`);
|
|
1612
|
+
lines.push("");
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// Add contextProviders if provided
|
|
1616
|
+
if (hooks?.contextProviders) {
|
|
1617
|
+
lines.push(` widgetConfig.contextProviders = ${hooks.contextProviders};`);
|
|
1618
|
+
lines.push("");
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// Stream parser - use custom if provided, otherwise default
|
|
1622
|
+
if (hooks?.streamParser) {
|
|
1623
|
+
lines.push(` widgetConfig.streamParser = ${hooks.streamParser};`);
|
|
1624
|
+
} else {
|
|
1625
|
+
lines.push(" // Flexible JSON stream parser for handling structured actions");
|
|
1626
|
+
lines.push(" widgetConfig.streamParser = function() {");
|
|
1627
|
+
lines.push(` return agentWidget.createFlexibleJsonStreamParser(${TEMPLATE_STREAM_PARSER_CALLBACK_ES5});`);
|
|
1628
|
+
lines.push(" };");
|
|
1629
|
+
}
|
|
1630
|
+
lines.push("");
|
|
1631
|
+
|
|
1632
|
+
// Action parsers - merge custom with defaults if provided
|
|
1633
|
+
if (hooks?.actionParsers) {
|
|
1634
|
+
lines.push(" // Action parsers (custom merged with defaults)");
|
|
1635
|
+
lines.push(` var customParsers = ${hooks.actionParsers};`);
|
|
1636
|
+
lines.push(" widgetConfig.actionParsers = customParsers.concat([");
|
|
1637
|
+
lines.push(" agentWidget.defaultJsonActionParser,");
|
|
1638
|
+
lines.push(` ${TEMPLATE_MARKDOWN_JSON_PARSER_ES5}`);
|
|
1639
|
+
lines.push(" ]);");
|
|
1640
|
+
} else {
|
|
1641
|
+
lines.push(" // Action parsers to detect JSON actions in responses");
|
|
1642
|
+
lines.push(" widgetConfig.actionParsers = [");
|
|
1643
|
+
lines.push(" agentWidget.defaultJsonActionParser,");
|
|
1644
|
+
lines.push(` ${TEMPLATE_MARKDOWN_JSON_PARSER_ES5}`);
|
|
1645
|
+
lines.push(" ];");
|
|
1646
|
+
}
|
|
1647
|
+
lines.push("");
|
|
1648
|
+
|
|
1649
|
+
// Action handlers - merge custom with defaults if provided
|
|
1650
|
+
if (hooks?.actionHandlers) {
|
|
1651
|
+
lines.push(" // Action handlers (custom merged with defaults)");
|
|
1652
|
+
lines.push(` var customHandlers = ${hooks.actionHandlers};`);
|
|
1653
|
+
lines.push(" widgetConfig.actionHandlers = customHandlers.concat([");
|
|
1654
|
+
lines.push(" agentWidget.defaultActionHandlers.message,");
|
|
1655
|
+
lines.push(" agentWidget.defaultActionHandlers.messageAndClick,");
|
|
1656
|
+
lines.push(` ${TEMPLATE_NAV_THEN_CLICK_HANDLER_ES5}`);
|
|
1657
|
+
lines.push(" ]);");
|
|
1658
|
+
} else {
|
|
1659
|
+
lines.push(" // Action handlers for navigation and other actions");
|
|
1660
|
+
lines.push(" widgetConfig.actionHandlers = [");
|
|
1661
|
+
lines.push(" agentWidget.defaultActionHandlers.message,");
|
|
1662
|
+
lines.push(" agentWidget.defaultActionHandlers.messageAndClick,");
|
|
1663
|
+
lines.push(` ${TEMPLATE_NAV_THEN_CLICK_HANDLER_ES5}`);
|
|
1664
|
+
lines.push(" ];");
|
|
1665
|
+
}
|
|
1666
|
+
lines.push("");
|
|
1667
|
+
|
|
1668
|
+
// requestMiddleware - merge custom with DOM context if provided
|
|
1669
|
+
if (hooks?.requestMiddleware) {
|
|
1670
|
+
lines.push(" // Request middleware (custom merged with DOM context)");
|
|
1671
|
+
lines.push(" widgetConfig.requestMiddleware = function(ctx) {");
|
|
1672
|
+
lines.push(` var customResult = (${hooks.requestMiddleware})(ctx);`);
|
|
1673
|
+
lines.push(" var merged = customResult || ctx.payload;");
|
|
1674
|
+
lines.push(" return Object.assign({}, merged, { metadata: Object.assign({}, merged.metadata, domContextProvider()) });");
|
|
1675
|
+
lines.push(" };");
|
|
1676
|
+
} else {
|
|
1677
|
+
lines.push(" // Send DOM context with each request");
|
|
1678
|
+
lines.push(" widgetConfig.requestMiddleware = function(ctx) {");
|
|
1679
|
+
lines.push(" return Object.assign({}, ctx.payload, { metadata: domContextProvider() });");
|
|
1680
|
+
lines.push(" };");
|
|
1681
|
+
}
|
|
1682
|
+
lines.push("");
|
|
1683
|
+
|
|
1684
|
+
// postprocessMessage - use custom if provided, otherwise default
|
|
1685
|
+
if (hooks?.postprocessMessage) {
|
|
1686
|
+
lines.push(` widgetConfig.postprocessMessage = ${hooks.postprocessMessage};`);
|
|
1687
|
+
} else {
|
|
1688
|
+
lines.push(" // Markdown postprocessor");
|
|
1689
|
+
lines.push(" widgetConfig.postprocessMessage = function(ctx) {");
|
|
1690
|
+
lines.push(" return agentWidget.markdownPostprocessor(ctx.text);");
|
|
1691
|
+
lines.push(" };");
|
|
1692
|
+
}
|
|
1693
|
+
lines.push("");
|
|
1694
|
+
|
|
1695
|
+
// Add messageActions callbacks if provided
|
|
1696
|
+
if (hooks?.onFeedback || hooks?.onCopy) {
|
|
1697
|
+
lines.push(" // Message action callbacks");
|
|
1698
|
+
lines.push(" widgetConfig.messageActions = widgetConfig.messageActions || {};");
|
|
1699
|
+
if (hooks?.onFeedback) {
|
|
1700
|
+
lines.push(` widgetConfig.messageActions.onFeedback = ${hooks.onFeedback};`);
|
|
1701
|
+
}
|
|
1702
|
+
if (hooks?.onCopy) {
|
|
1703
|
+
lines.push(` widgetConfig.messageActions.onCopy = ${hooks.onCopy};`);
|
|
1704
|
+
}
|
|
1705
|
+
lines.push("");
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
lines.push(...[
|
|
1709
|
+
" return widgetConfig;",
|
|
1710
|
+
" };",
|
|
1711
|
+
"",
|
|
1712
|
+
" // Initialize widget",
|
|
1713
|
+
" var init = function() {",
|
|
1714
|
+
" var agentWidget = window.AgentWidget;",
|
|
1715
|
+
" if (!agentWidget) {",
|
|
1716
|
+
" console.error('AgentWidget not loaded');",
|
|
1717
|
+
" return;",
|
|
1718
|
+
" }",
|
|
1719
|
+
"",
|
|
1720
|
+
" var widgetConfig = createWidgetConfig(agentWidget);",
|
|
1721
|
+
"",
|
|
1722
|
+
" // Load saved state",
|
|
1723
|
+
" var savedState = localStorage.getItem(STORAGE_KEY);",
|
|
1724
|
+
" if (savedState) {",
|
|
1725
|
+
" try {",
|
|
1726
|
+
" var parsed = JSON.parse(savedState);",
|
|
1727
|
+
" widgetConfig.initialMessages = parsed.messages || [];",
|
|
1728
|
+
" } catch (e) {",
|
|
1729
|
+
" console.error('Failed to load saved state:', e);",
|
|
1730
|
+
" }",
|
|
1731
|
+
" }",
|
|
1732
|
+
"",
|
|
1733
|
+
" // Initialize widget",
|
|
1734
|
+
" var handle = agentWidget.initAgentWidget({",
|
|
1735
|
+
" target: 'body',",
|
|
1736
|
+
" useShadowDom: false,",
|
|
1737
|
+
" config: widgetConfig",
|
|
1738
|
+
" });",
|
|
1739
|
+
"",
|
|
1740
|
+
" // Save state on message events",
|
|
1741
|
+
" window.addEventListener('persona:message', function() {",
|
|
1742
|
+
" var session = handle.getSession ? handle.getSession() : null;",
|
|
1743
|
+
" if (session) {",
|
|
1744
|
+
" localStorage.setItem(STORAGE_KEY, JSON.stringify({",
|
|
1745
|
+
" messages: session.messages,",
|
|
1746
|
+
" timestamp: new Date().toISOString()",
|
|
1747
|
+
" }));",
|
|
1748
|
+
" }",
|
|
1749
|
+
" });",
|
|
1750
|
+
"",
|
|
1751
|
+
" // Clear state on clear chat",
|
|
1752
|
+
" window.addEventListener('persona:clear-chat', function() {",
|
|
1753
|
+
" localStorage.removeItem(STORAGE_KEY);",
|
|
1754
|
+
" localStorage.removeItem(PROCESSED_ACTIONS_KEY);",
|
|
1755
|
+
" });",
|
|
1756
|
+
" };",
|
|
1757
|
+
"",
|
|
1758
|
+
" // Wait for framework hydration to complete (Next.js, Nuxt, etc.)",
|
|
1759
|
+
" // This prevents the framework from removing dynamically added CSS during reconciliation",
|
|
1760
|
+
" var waitForHydration = function(callback) {",
|
|
1761
|
+
" var executed = false;",
|
|
1762
|
+
" ",
|
|
1763
|
+
" var execute = function() {",
|
|
1764
|
+
" if (executed) return;",
|
|
1765
|
+
" executed = true;",
|
|
1766
|
+
" callback();",
|
|
1767
|
+
" };",
|
|
1768
|
+
"",
|
|
1769
|
+
" var afterDom = function() {",
|
|
1770
|
+
" // Strategy 1: Use requestIdleCallback if available (best for detecting idle after hydration)",
|
|
1771
|
+
" if (typeof requestIdleCallback !== 'undefined') {",
|
|
1772
|
+
" requestIdleCallback(function() {",
|
|
1773
|
+
" // Double requestAnimationFrame ensures at least one full paint cycle completed",
|
|
1774
|
+
" requestAnimationFrame(function() {",
|
|
1775
|
+
" requestAnimationFrame(execute);",
|
|
1776
|
+
" });",
|
|
1777
|
+
" }, { timeout: 3000 }); // Max wait 3 seconds, then proceed anyway",
|
|
1778
|
+
" } else {",
|
|
1779
|
+
" // Strategy 2: Fallback for Safari (no requestIdleCallback)",
|
|
1780
|
+
" // 300ms is typically enough for hydration on most pages",
|
|
1781
|
+
" setTimeout(execute, 300);",
|
|
1782
|
+
" }",
|
|
1783
|
+
" };",
|
|
1784
|
+
"",
|
|
1785
|
+
" if (document.readyState === 'loading') {",
|
|
1786
|
+
" document.addEventListener('DOMContentLoaded', afterDom);",
|
|
1787
|
+
" } else {",
|
|
1788
|
+
" // DOM already ready, but still wait for potential hydration",
|
|
1789
|
+
" afterDom();",
|
|
1790
|
+
" }",
|
|
1791
|
+
" };",
|
|
1792
|
+
"",
|
|
1793
|
+
" // Boot sequence: wait for hydration, then load CSS and JS, then initialize",
|
|
1794
|
+
" // This prevents Next.js/Nuxt/etc. from removing dynamically added CSS during reconciliation",
|
|
1795
|
+
" waitForHydration(function() {",
|
|
1796
|
+
" loadCSS();",
|
|
1797
|
+
" loadJS(function() {",
|
|
1798
|
+
" init();",
|
|
1799
|
+
" });",
|
|
1800
|
+
" });",
|
|
1801
|
+
"})();",
|
|
1802
|
+
"</script>"
|
|
1803
|
+
]);
|
|
1804
|
+
|
|
1805
|
+
return lines.join("\n");
|
|
1806
|
+
}
|