@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.
Files changed (61) hide show
  1. package/README.md +1080 -0
  2. package/dist/index.cjs +140 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +2626 -0
  5. package/dist/index.d.ts +2626 -0
  6. package/dist/index.global.js +1843 -0
  7. package/dist/index.global.js.map +1 -0
  8. package/dist/index.js +140 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/install.global.js +2 -0
  11. package/dist/install.global.js.map +1 -0
  12. package/dist/widget.css +1627 -0
  13. package/package.json +79 -0
  14. package/src/@types/idiomorph.d.ts +37 -0
  15. package/src/client.test.ts +387 -0
  16. package/src/client.ts +1589 -0
  17. package/src/components/composer-builder.ts +530 -0
  18. package/src/components/feedback.ts +379 -0
  19. package/src/components/forms.ts +170 -0
  20. package/src/components/header-builder.ts +455 -0
  21. package/src/components/header-layouts.ts +303 -0
  22. package/src/components/launcher.ts +193 -0
  23. package/src/components/message-bubble.ts +528 -0
  24. package/src/components/messages.ts +54 -0
  25. package/src/components/panel.ts +204 -0
  26. package/src/components/reasoning-bubble.ts +144 -0
  27. package/src/components/registry.ts +87 -0
  28. package/src/components/suggestions.ts +97 -0
  29. package/src/components/tool-bubble.ts +288 -0
  30. package/src/defaults.ts +321 -0
  31. package/src/index.ts +175 -0
  32. package/src/install.ts +284 -0
  33. package/src/plugins/registry.ts +77 -0
  34. package/src/plugins/types.ts +95 -0
  35. package/src/postprocessors.ts +194 -0
  36. package/src/runtime/init.ts +162 -0
  37. package/src/session.ts +376 -0
  38. package/src/styles/tailwind.css +20 -0
  39. package/src/styles/widget.css +1627 -0
  40. package/src/types.ts +1635 -0
  41. package/src/ui.ts +3341 -0
  42. package/src/utils/actions.ts +227 -0
  43. package/src/utils/attachment-manager.ts +384 -0
  44. package/src/utils/code-generators.test.ts +500 -0
  45. package/src/utils/code-generators.ts +1806 -0
  46. package/src/utils/component-middleware.ts +137 -0
  47. package/src/utils/component-parser.ts +119 -0
  48. package/src/utils/constants.ts +16 -0
  49. package/src/utils/content.ts +306 -0
  50. package/src/utils/dom.ts +25 -0
  51. package/src/utils/events.ts +41 -0
  52. package/src/utils/formatting.test.ts +166 -0
  53. package/src/utils/formatting.ts +470 -0
  54. package/src/utils/icons.ts +92 -0
  55. package/src/utils/message-id.ts +37 -0
  56. package/src/utils/morph.ts +36 -0
  57. package/src/utils/positioning.ts +17 -0
  58. package/src/utils/storage.ts +72 -0
  59. package/src/utils/theme.ts +105 -0
  60. package/src/widget.css +1 -0
  61. package/widget.css +1 -0
@@ -0,0 +1,288 @@
1
+ import { createElement } from "../utils/dom";
2
+ import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
3
+ import { formatUnknownValue, describeToolTitle } from "../utils/formatting";
4
+ import { renderLucideIcon } from "../utils/icons";
5
+
6
+ // Expansion state per widget instance
7
+ export const toolExpansionState = new Set<string>();
8
+
9
+ // Helper function to update tool bubble UI after expansion state changes
10
+ export const updateToolBubbleUI = (messageId: string, bubble: HTMLElement, config?: AgentWidgetConfig): void => {
11
+ const expanded = toolExpansionState.has(messageId);
12
+ const toolCallConfig = config?.toolCall ?? {};
13
+ const header = bubble.querySelector('button[data-expand-header="true"]') as HTMLElement;
14
+ const content = bubble.querySelector('.tvw-border-t') as HTMLElement;
15
+
16
+ if (!header || !content) return;
17
+
18
+ header.setAttribute("aria-expanded", expanded ? "true" : "false");
19
+
20
+ // Find toggle icon container - it's the direct child div of headerMeta (which has tvw-ml-auto)
21
+ const headerMeta = header.querySelector('.tvw-ml-auto') as HTMLElement;
22
+ const toggleIcon = headerMeta?.querySelector(':scope > .tvw-flex.tvw-items-center') as HTMLElement;
23
+ if (toggleIcon) {
24
+ toggleIcon.innerHTML = "";
25
+ const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
26
+ const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
27
+ if (chevronIcon) {
28
+ toggleIcon.appendChild(chevronIcon);
29
+ } else {
30
+ toggleIcon.textContent = expanded ? "Hide" : "Show";
31
+ }
32
+ }
33
+
34
+ content.style.display = expanded ? "" : "none";
35
+ };
36
+
37
+ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidgetConfig): HTMLElement => {
38
+ const tool = message.toolCall;
39
+ const toolCallConfig = config?.toolCall ?? {};
40
+
41
+ const bubble = createElement(
42
+ "div",
43
+ [
44
+ "vanilla-message-bubble",
45
+ "vanilla-tool-bubble",
46
+ "tvw-w-full",
47
+ "tvw-max-w-[85%]",
48
+ "tvw-rounded-2xl",
49
+ "tvw-bg-cw-surface",
50
+ "tvw-border",
51
+ "tvw-border-cw-message-border",
52
+ "tvw-text-cw-primary",
53
+ "tvw-shadow-sm",
54
+ "tvw-overflow-hidden",
55
+ "tvw-px-0",
56
+ "tvw-py-0"
57
+ ].join(" ")
58
+ );
59
+ // Set id for idiomorph matching
60
+ bubble.id = `bubble-${message.id}`;
61
+ bubble.setAttribute("data-message-id", message.id);
62
+
63
+ // Apply bubble-level styles
64
+ if (toolCallConfig.backgroundColor) {
65
+ bubble.style.backgroundColor = toolCallConfig.backgroundColor;
66
+ }
67
+ if (toolCallConfig.borderColor) {
68
+ bubble.style.borderColor = toolCallConfig.borderColor;
69
+ }
70
+ if (toolCallConfig.borderWidth) {
71
+ bubble.style.borderWidth = toolCallConfig.borderWidth;
72
+ }
73
+ if (toolCallConfig.borderRadius) {
74
+ bubble.style.borderRadius = toolCallConfig.borderRadius;
75
+ }
76
+
77
+ if (!tool) {
78
+ return bubble;
79
+ }
80
+
81
+ let expanded = toolExpansionState.has(message.id);
82
+ const header = createElement(
83
+ "button",
84
+ "tvw-flex tvw-w-full tvw-items-center tvw-justify-between tvw-gap-3 tvw-bg-transparent tvw-px-4 tvw-py-3 tvw-text-left tvw-cursor-pointer tvw-border-none"
85
+ ) as HTMLButtonElement;
86
+ header.type = "button";
87
+ header.setAttribute("aria-expanded", expanded ? "true" : "false");
88
+ header.setAttribute("data-expand-header", "true");
89
+ header.setAttribute("data-bubble-type", "tool");
90
+
91
+ // Apply header styles
92
+ if (toolCallConfig.headerBackgroundColor) {
93
+ header.style.backgroundColor = toolCallConfig.headerBackgroundColor;
94
+ }
95
+ if (toolCallConfig.headerPaddingX) {
96
+ header.style.paddingLeft = toolCallConfig.headerPaddingX;
97
+ header.style.paddingRight = toolCallConfig.headerPaddingX;
98
+ }
99
+ if (toolCallConfig.headerPaddingY) {
100
+ header.style.paddingTop = toolCallConfig.headerPaddingY;
101
+ header.style.paddingBottom = toolCallConfig.headerPaddingY;
102
+ }
103
+
104
+ const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
105
+ const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
106
+ if (toolCallConfig.headerTextColor) {
107
+ title.style.color = toolCallConfig.headerTextColor;
108
+ }
109
+ title.textContent = describeToolTitle(tool);
110
+ headerContent.appendChild(title);
111
+
112
+ const toggleIcon = createElement("div", "tvw-flex tvw-items-center");
113
+ const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
114
+ const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
115
+ if (chevronIcon) {
116
+ toggleIcon.appendChild(chevronIcon);
117
+ } else {
118
+ // Fallback to text if icon fails
119
+ toggleIcon.textContent = expanded ? "Hide" : "Show";
120
+ }
121
+
122
+ const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-gap-2 tvw-ml-auto");
123
+ headerMeta.append(toggleIcon);
124
+
125
+ header.append(headerContent, headerMeta);
126
+
127
+ const content = createElement(
128
+ "div",
129
+ "tvw-border-t tvw-border-gray-200 tvw-bg-gray-50 tvw-space-y-3 tvw-px-4 tvw-py-3"
130
+ );
131
+ content.style.display = expanded ? "" : "none";
132
+
133
+ // Apply content styles
134
+ if (toolCallConfig.contentBackgroundColor) {
135
+ content.style.backgroundColor = toolCallConfig.contentBackgroundColor;
136
+ }
137
+ if (toolCallConfig.contentTextColor) {
138
+ content.style.color = toolCallConfig.contentTextColor;
139
+ }
140
+ if (toolCallConfig.contentPaddingX) {
141
+ content.style.paddingLeft = toolCallConfig.contentPaddingX;
142
+ content.style.paddingRight = toolCallConfig.contentPaddingX;
143
+ }
144
+ if (toolCallConfig.contentPaddingY) {
145
+ content.style.paddingTop = toolCallConfig.contentPaddingY;
146
+ content.style.paddingBottom = toolCallConfig.contentPaddingY;
147
+ }
148
+
149
+ // Add tool name at the top of content
150
+ if (tool.name) {
151
+ const toolName = createElement("div", "tvw-text-xs tvw-text-cw-muted tvw-italic");
152
+ if (toolCallConfig.contentTextColor) {
153
+ toolName.style.color = toolCallConfig.contentTextColor;
154
+ } else if (toolCallConfig.headerTextColor) {
155
+ toolName.style.color = toolCallConfig.headerTextColor;
156
+ }
157
+ toolName.textContent = tool.name;
158
+ content.appendChild(toolName);
159
+ }
160
+
161
+ if (tool.args !== undefined) {
162
+ const argsBlock = createElement("div", "tvw-space-y-1");
163
+ const argsLabel = createElement(
164
+ "div",
165
+ "tvw-text-xs tvw-text-cw-muted"
166
+ );
167
+ if (toolCallConfig.labelTextColor) {
168
+ argsLabel.style.color = toolCallConfig.labelTextColor;
169
+ }
170
+ argsLabel.textContent = "Arguments";
171
+ const argsPre = createElement(
172
+ "pre",
173
+ "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
174
+ );
175
+ // Ensure font size matches header text (0.75rem / 12px)
176
+ argsPre.style.fontSize = "0.75rem";
177
+ argsPre.style.lineHeight = "1rem";
178
+ if (toolCallConfig.codeBlockBackgroundColor) {
179
+ argsPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
180
+ }
181
+ if (toolCallConfig.codeBlockBorderColor) {
182
+ argsPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
183
+ }
184
+ if (toolCallConfig.codeBlockTextColor) {
185
+ argsPre.style.color = toolCallConfig.codeBlockTextColor;
186
+ }
187
+ argsPre.textContent = formatUnknownValue(tool.args);
188
+ argsBlock.append(argsLabel, argsPre);
189
+ content.appendChild(argsBlock);
190
+ }
191
+
192
+ if (tool.chunks && tool.chunks.length) {
193
+ const logsBlock = createElement("div", "tvw-space-y-1");
194
+ const logsLabel = createElement(
195
+ "div",
196
+ "tvw-text-xs tvw-text-cw-muted"
197
+ );
198
+ if (toolCallConfig.labelTextColor) {
199
+ logsLabel.style.color = toolCallConfig.labelTextColor;
200
+ }
201
+ logsLabel.textContent = "Activity";
202
+ const logsPre = createElement(
203
+ "pre",
204
+ "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
205
+ );
206
+ // Ensure font size matches header text (0.75rem / 12px)
207
+ logsPre.style.fontSize = "0.75rem";
208
+ logsPre.style.lineHeight = "1rem";
209
+ if (toolCallConfig.codeBlockBackgroundColor) {
210
+ logsPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
211
+ }
212
+ if (toolCallConfig.codeBlockBorderColor) {
213
+ logsPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
214
+ }
215
+ if (toolCallConfig.codeBlockTextColor) {
216
+ logsPre.style.color = toolCallConfig.codeBlockTextColor;
217
+ }
218
+ logsPre.textContent = tool.chunks.join("\n");
219
+ logsBlock.append(logsLabel, logsPre);
220
+ content.appendChild(logsBlock);
221
+ }
222
+
223
+ if (tool.status === "complete" && tool.result !== undefined) {
224
+ const resultBlock = createElement("div", "tvw-space-y-1");
225
+ const resultLabel = createElement(
226
+ "div",
227
+ "tvw-text-xs tvw-text-cw-muted"
228
+ );
229
+ if (toolCallConfig.labelTextColor) {
230
+ resultLabel.style.color = toolCallConfig.labelTextColor;
231
+ }
232
+ resultLabel.textContent = "Result";
233
+ const resultPre = createElement(
234
+ "pre",
235
+ "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
236
+ );
237
+ // Ensure font size matches header text (0.75rem / 12px)
238
+ resultPre.style.fontSize = "0.75rem";
239
+ resultPre.style.lineHeight = "1rem";
240
+ if (toolCallConfig.codeBlockBackgroundColor) {
241
+ resultPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
242
+ }
243
+ if (toolCallConfig.codeBlockBorderColor) {
244
+ resultPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
245
+ }
246
+ if (toolCallConfig.codeBlockTextColor) {
247
+ resultPre.style.color = toolCallConfig.codeBlockTextColor;
248
+ }
249
+ resultPre.textContent = formatUnknownValue(tool.result);
250
+ resultBlock.append(resultLabel, resultPre);
251
+ content.appendChild(resultBlock);
252
+ }
253
+
254
+ if (tool.status === "complete" && typeof tool.duration === "number") {
255
+ const duration = createElement(
256
+ "div",
257
+ "tvw-text-xs tvw-text-cw-muted"
258
+ );
259
+ if (toolCallConfig.contentTextColor) {
260
+ duration.style.color = toolCallConfig.contentTextColor;
261
+ }
262
+ duration.textContent = `Duration: ${tool.duration}ms`;
263
+ content.appendChild(duration);
264
+ }
265
+
266
+ const applyToolExpansion = () => {
267
+ header.setAttribute("aria-expanded", expanded ? "true" : "false");
268
+ // Update chevron icon
269
+ toggleIcon.innerHTML = "";
270
+ const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
271
+ const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
272
+ if (chevronIcon) {
273
+ toggleIcon.appendChild(chevronIcon);
274
+ } else {
275
+ // Fallback to text if icon fails
276
+ toggleIcon.textContent = expanded ? "Hide" : "Show";
277
+ }
278
+ content.style.display = expanded ? "" : "none";
279
+ };
280
+
281
+ applyToolExpansion();
282
+
283
+ bubble.append(header, content);
284
+ return bubble;
285
+ };
286
+
287
+
288
+
@@ -0,0 +1,321 @@
1
+ import type { AgentWidgetConfig, AgentWidgetTheme } from "./types";
2
+
3
+ /**
4
+ * Default light theme colors
5
+ */
6
+ export const DEFAULT_LIGHT_THEME: AgentWidgetTheme = {
7
+ primary: "#111827",
8
+ accent: "#1d4ed8",
9
+ surface: "#ffffff",
10
+ muted: "#6b7280",
11
+ container: "#f8fafc",
12
+ border: "#f1f5f9",
13
+ divider: "#f1f5f9",
14
+ messageBorder: "#f1f5f9",
15
+ inputBackground: "#ffffff",
16
+ callToAction: "#000000",
17
+ callToActionBackground: "#ffffff",
18
+ sendButtonBackgroundColor: "#111827",
19
+ sendButtonTextColor: "#ffffff",
20
+ sendButtonBorderColor: "#60a5fa",
21
+ closeButtonColor: "#6b7280",
22
+ closeButtonBackgroundColor: "transparent",
23
+ closeButtonBorderColor: "",
24
+ clearChatIconColor: "#6b7280",
25
+ clearChatBackgroundColor: "transparent",
26
+ clearChatBorderColor: "transparent",
27
+ micIconColor: "#111827",
28
+ micBackgroundColor: "transparent",
29
+ micBorderColor: "transparent",
30
+ recordingIconColor: "#ffffff",
31
+ recordingBackgroundColor: "#ef4444",
32
+ recordingBorderColor: "transparent",
33
+ inputFontFamily: "sans-serif",
34
+ inputFontWeight: "400",
35
+ radiusSm: "0.75rem",
36
+ radiusMd: "1rem",
37
+ radiusLg: "1.5rem",
38
+ launcherRadius: "9999px",
39
+ buttonRadius: "9999px",
40
+ };
41
+
42
+ /**
43
+ * Default dark theme colors
44
+ */
45
+ export const DEFAULT_DARK_THEME: AgentWidgetTheme = {
46
+ primary: "#f9fafb",
47
+ accent: "#3b82f6",
48
+ surface: "#1f2937",
49
+ muted: "#9ca3af",
50
+ container: "#111827",
51
+ border: "#374151",
52
+ divider: "#374151",
53
+ messageBorder: "#374151",
54
+ inputBackground: "#111827",
55
+ callToAction: "#ffffff",
56
+ callToActionBackground: "#374151",
57
+ sendButtonBackgroundColor: "#3b82f6",
58
+ sendButtonTextColor: "#ffffff",
59
+ sendButtonBorderColor: "#60a5fa",
60
+ closeButtonColor: "#9ca3af",
61
+ closeButtonBackgroundColor: "transparent",
62
+ closeButtonBorderColor: "",
63
+ clearChatIconColor: "#9ca3af",
64
+ clearChatBackgroundColor: "transparent",
65
+ clearChatBorderColor: "transparent",
66
+ micIconColor: "#f9fafb",
67
+ micBackgroundColor: "transparent",
68
+ micBorderColor: "transparent",
69
+ recordingIconColor: "#ffffff",
70
+ recordingBackgroundColor: "#ef4444",
71
+ recordingBorderColor: "transparent",
72
+ inputFontFamily: "sans-serif",
73
+ inputFontWeight: "400",
74
+ radiusSm: "0.75rem",
75
+ radiusMd: "1rem",
76
+ radiusLg: "1.5rem",
77
+ launcherRadius: "9999px",
78
+ buttonRadius: "9999px",
79
+ };
80
+
81
+ /**
82
+ * Default widget configuration
83
+ * Single source of truth for all default values
84
+ */
85
+ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
86
+ apiUrl: "http://localhost:43111/api/chat/dispatch",
87
+ // Client token mode defaults (optional, only used when clientToken is set)
88
+ clientToken: undefined,
89
+ theme: DEFAULT_LIGHT_THEME,
90
+ darkTheme: DEFAULT_DARK_THEME,
91
+ colorScheme: "light",
92
+ launcher: {
93
+ enabled: true,
94
+ title: "Chat Assistant",
95
+ subtitle: "Here to help you get answers fast",
96
+ agentIconText: "💬",
97
+ position: "bottom-right",
98
+ width: "min(400px, calc(100vw - 24px))",
99
+ heightOffset: 0,
100
+ autoExpand: false,
101
+ callToActionIconHidden: false,
102
+ agentIconSize: "40px",
103
+ headerIconSize: "40px",
104
+ closeButtonSize: "32px",
105
+ callToActionIconName: "arrow-up-right",
106
+ callToActionIconText: "",
107
+ callToActionIconSize: "32px",
108
+ callToActionIconPadding: "5px",
109
+ callToActionIconColor: "#000000",
110
+ callToActionIconBackgroundColor: "#ffffff",
111
+ closeButtonColor: "#6b7280",
112
+ closeButtonBackgroundColor: "transparent",
113
+ clearChat: {
114
+ iconColor: "#6b7280",
115
+ backgroundColor: "transparent",
116
+ borderColor: "transparent",
117
+ enabled: true,
118
+ placement: "inline",
119
+ iconName: "refresh-cw",
120
+ size: "32px",
121
+ showTooltip: true,
122
+ tooltipText: "Clear chat",
123
+ paddingX: "0px",
124
+ paddingY: "0px",
125
+ },
126
+ headerIconHidden: false,
127
+ border: "1px solid #e5e7eb",
128
+ shadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
129
+ },
130
+ copy: {
131
+ welcomeTitle: "Hello 👋",
132
+ welcomeSubtitle: "Ask anything about your account or products.",
133
+ inputPlaceholder: "How can I help...",
134
+ sendButtonLabel: "Send",
135
+ },
136
+ sendButton: {
137
+ borderWidth: "0px",
138
+ paddingX: "12px",
139
+ paddingY: "10px",
140
+ backgroundColor: "#111827",
141
+ textColor: "#ffffff",
142
+ borderColor: "#60a5fa",
143
+ useIcon: true,
144
+ iconText: "↑",
145
+ size: "40px",
146
+ showTooltip: true,
147
+ tooltipText: "Send message",
148
+ iconName: "send",
149
+ },
150
+ statusIndicator: {
151
+ visible: true,
152
+ idleText: "Online",
153
+ connectingText: "Connecting…",
154
+ connectedText: "Streaming…",
155
+ errorText: "Offline",
156
+ },
157
+ voiceRecognition: {
158
+ enabled: true,
159
+ pauseDuration: 2000,
160
+ iconName: "mic",
161
+ iconSize: "39px",
162
+ borderWidth: "0px",
163
+ paddingX: "9px",
164
+ paddingY: "14px",
165
+ iconColor: "#111827",
166
+ backgroundColor: "transparent",
167
+ borderColor: "transparent",
168
+ recordingIconColor: "#ffffff",
169
+ recordingBackgroundColor: "#ef4444",
170
+ recordingBorderColor: "transparent",
171
+ showTooltip: true,
172
+ tooltipText: "Start voice recognition",
173
+ },
174
+ features: {
175
+ showReasoning: true,
176
+ showToolCalls: true,
177
+ },
178
+ suggestionChips: [
179
+ "What can you help me with?",
180
+ "Tell me about your features",
181
+ "How does this work?",
182
+ ],
183
+ suggestionChipsConfig: {
184
+ fontFamily: "sans-serif",
185
+ fontWeight: "500",
186
+ paddingX: "12px",
187
+ paddingY: "6px",
188
+ },
189
+ layout: {
190
+ header: {
191
+ layout: "default",
192
+ showIcon: true,
193
+ showTitle: true,
194
+ showSubtitle: true,
195
+ showCloseButton: true,
196
+ showClearChat: true,
197
+ },
198
+ messages: {
199
+ layout: "bubble",
200
+ avatar: {
201
+ show: false,
202
+ position: "left",
203
+ },
204
+ timestamp: {
205
+ show: false,
206
+ position: "below",
207
+ },
208
+ groupConsecutive: false,
209
+ },
210
+ slots: {},
211
+ },
212
+ markdown: {
213
+ options: {
214
+ gfm: true,
215
+ breaks: true,
216
+ },
217
+ disableDefaultStyles: false,
218
+ },
219
+ messageActions: {
220
+ enabled: true,
221
+ showCopy: true,
222
+ showUpvote: false, // Requires backend - disabled by default
223
+ showDownvote: false, // Requires backend - disabled by default
224
+ visibility: "hover",
225
+ align: "right",
226
+ layout: "pill-inside",
227
+ },
228
+ debug: false,
229
+ };
230
+
231
+ /**
232
+ * Helper to deep merge user config with defaults
233
+ * This ensures all default values are present while allowing selective overrides
234
+ */
235
+ export function mergeWithDefaults(
236
+ config?: Partial<AgentWidgetConfig>
237
+ ): Partial<AgentWidgetConfig> {
238
+ if (!config) return DEFAULT_WIDGET_CONFIG;
239
+
240
+ return {
241
+ ...DEFAULT_WIDGET_CONFIG,
242
+ ...config,
243
+ theme: {
244
+ ...DEFAULT_WIDGET_CONFIG.theme,
245
+ ...config.theme,
246
+ },
247
+ darkTheme: {
248
+ ...DEFAULT_WIDGET_CONFIG.darkTheme,
249
+ ...config.darkTheme,
250
+ },
251
+ launcher: {
252
+ ...DEFAULT_WIDGET_CONFIG.launcher,
253
+ ...config.launcher,
254
+ clearChat: {
255
+ ...DEFAULT_WIDGET_CONFIG.launcher?.clearChat,
256
+ ...config.launcher?.clearChat,
257
+ },
258
+ },
259
+ copy: {
260
+ ...DEFAULT_WIDGET_CONFIG.copy,
261
+ ...config.copy,
262
+ },
263
+ sendButton: {
264
+ ...DEFAULT_WIDGET_CONFIG.sendButton,
265
+ ...config.sendButton,
266
+ },
267
+ statusIndicator: {
268
+ ...DEFAULT_WIDGET_CONFIG.statusIndicator,
269
+ ...config.statusIndicator,
270
+ },
271
+ voiceRecognition: {
272
+ ...DEFAULT_WIDGET_CONFIG.voiceRecognition,
273
+ ...config.voiceRecognition,
274
+ },
275
+ features: {
276
+ ...DEFAULT_WIDGET_CONFIG.features,
277
+ ...config.features,
278
+ },
279
+ suggestionChips: config.suggestionChips ?? DEFAULT_WIDGET_CONFIG.suggestionChips,
280
+ suggestionChipsConfig: {
281
+ ...DEFAULT_WIDGET_CONFIG.suggestionChipsConfig,
282
+ ...config.suggestionChipsConfig,
283
+ },
284
+ layout: {
285
+ ...DEFAULT_WIDGET_CONFIG.layout,
286
+ ...config.layout,
287
+ header: {
288
+ ...DEFAULT_WIDGET_CONFIG.layout?.header,
289
+ ...config.layout?.header,
290
+ },
291
+ messages: {
292
+ ...DEFAULT_WIDGET_CONFIG.layout?.messages,
293
+ ...config.layout?.messages,
294
+ avatar: {
295
+ ...DEFAULT_WIDGET_CONFIG.layout?.messages?.avatar,
296
+ ...config.layout?.messages?.avatar,
297
+ },
298
+ timestamp: {
299
+ ...DEFAULT_WIDGET_CONFIG.layout?.messages?.timestamp,
300
+ ...config.layout?.messages?.timestamp,
301
+ },
302
+ },
303
+ slots: {
304
+ ...DEFAULT_WIDGET_CONFIG.layout?.slots,
305
+ ...config.layout?.slots,
306
+ },
307
+ },
308
+ markdown: {
309
+ ...DEFAULT_WIDGET_CONFIG.markdown,
310
+ ...config.markdown,
311
+ options: {
312
+ ...DEFAULT_WIDGET_CONFIG.markdown?.options,
313
+ ...config.markdown?.options,
314
+ },
315
+ },
316
+ messageActions: {
317
+ ...DEFAULT_WIDGET_CONFIG.messageActions,
318
+ ...config.messageActions,
319
+ },
320
+ };
321
+ }