@chatwidgetai/chat-widget 0.3.0 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat-widget.umd.js +1903 -437
- package/dist/ai-chat-widget.umd.js.map +1 -1
- package/dist/api/client.d.ts +11 -2
- package/dist/api/client.d.ts.map +1 -1
- package/dist/components/ChatWidget.d.ts +2 -1
- package/dist/components/ChatWidget.d.ts.map +1 -1
- package/dist/components/ChatWindow.d.ts +1 -1
- package/dist/components/ChatWindow.d.ts.map +1 -1
- package/dist/components/DataPolicyView.d.ts +14 -0
- package/dist/components/DataPolicyView.d.ts.map +1 -0
- package/dist/hooks/useChat/action-handler.d.ts +6 -0
- package/dist/hooks/useChat/action-handler.d.ts.map +1 -0
- package/dist/hooks/useChat/action-lifecycle.d.ts +19 -0
- package/dist/hooks/useChat/action-lifecycle.d.ts.map +1 -0
- package/dist/hooks/useChat/error-utils.d.ts +7 -0
- package/dist/hooks/useChat/error-utils.d.ts.map +1 -0
- package/dist/hooks/{useChat.d.ts → useChat/index.d.ts} +4 -2
- package/dist/hooks/useChat/index.d.ts.map +1 -0
- package/dist/hooks/useChat/message-hydration.d.ts +4 -0
- package/dist/hooks/useChat/message-hydration.d.ts.map +1 -0
- package/dist/hooks/useChat/stream-handlers.d.ts +27 -0
- package/dist/hooks/useChat/stream-handlers.d.ts.map +1 -0
- package/dist/hooks/useChat/stream-state.d.ts +8 -0
- package/dist/hooks/useChat/stream-state.d.ts.map +1 -0
- package/dist/hooks/useChat/types.d.ts +26 -0
- package/dist/hooks/useChat/types.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +1904 -438
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1904 -437
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +22 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/sse-parser.d.ts.map +1 -1
- package/package.json +7 -3
- package/dist/hooks/useChat.d.ts.map +0 -1
package/dist/index.esm.js
CHANGED
|
@@ -8,11 +8,14 @@ async function* parseSSEStream(response, validator) {
|
|
|
8
8
|
const reader = response.body.getReader();
|
|
9
9
|
const decoder = new TextDecoder();
|
|
10
10
|
let buffer = "";
|
|
11
|
+
let eventCount = 0;
|
|
11
12
|
try {
|
|
12
13
|
while (true) {
|
|
13
14
|
const { done, value } = await reader.read();
|
|
14
|
-
if (done)
|
|
15
|
+
if (done) {
|
|
16
|
+
console.log(`[SSE Parser] Stream ended normally after ${eventCount} events`);
|
|
15
17
|
break;
|
|
18
|
+
}
|
|
16
19
|
buffer += decoder.decode(value, { stream: true });
|
|
17
20
|
const chunks = buffer.split("\n\n");
|
|
18
21
|
buffer = chunks.pop() || "";
|
|
@@ -24,6 +27,7 @@ async function* parseSSEStream(response, validator) {
|
|
|
24
27
|
const data = JSON.parse(line.slice(6));
|
|
25
28
|
if (validator) {
|
|
26
29
|
if (validator(data)) {
|
|
30
|
+
eventCount++;
|
|
27
31
|
yield data;
|
|
28
32
|
}
|
|
29
33
|
else {
|
|
@@ -31,6 +35,7 @@ async function* parseSSEStream(response, validator) {
|
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
else {
|
|
38
|
+
eventCount++;
|
|
34
39
|
yield data;
|
|
35
40
|
}
|
|
36
41
|
}
|
|
@@ -43,6 +48,10 @@ async function* parseSSEStream(response, validator) {
|
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(`[SSE Parser] Stream error after ${eventCount} events:`, error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
46
55
|
finally {
|
|
47
56
|
reader.releaseLock();
|
|
48
57
|
}
|
|
@@ -176,7 +185,7 @@ class WidgetApiClient {
|
|
|
176
185
|
const result = await response.json();
|
|
177
186
|
return result.file;
|
|
178
187
|
}
|
|
179
|
-
async *sendAgentMessageStream(conversationId, message, fileIds) {
|
|
188
|
+
async *sendAgentMessageStream(conversationId, message, fileIds, signal) {
|
|
180
189
|
const headers = {
|
|
181
190
|
'Content-Type': 'application/json',
|
|
182
191
|
};
|
|
@@ -192,6 +201,7 @@ class WidgetApiClient {
|
|
|
192
201
|
fileIds,
|
|
193
202
|
timeZone: this.getTimeZone(),
|
|
194
203
|
}),
|
|
204
|
+
signal,
|
|
195
205
|
});
|
|
196
206
|
if (!response.ok) {
|
|
197
207
|
throw await buildApiError(response, 'Failed to send agent message');
|
|
@@ -200,7 +210,7 @@ class WidgetApiClient {
|
|
|
200
210
|
return typeof data === 'object' && data !== null && 'type' in data;
|
|
201
211
|
});
|
|
202
212
|
}
|
|
203
|
-
async *continueAgentMessageStream(conversationId, toolCallId, state) {
|
|
213
|
+
async *continueAgentMessageStream(conversationId, toolCallId, state, signal) {
|
|
204
214
|
const headers = {
|
|
205
215
|
'Content-Type': 'application/json',
|
|
206
216
|
};
|
|
@@ -216,6 +226,7 @@ class WidgetApiClient {
|
|
|
216
226
|
state,
|
|
217
227
|
timeZone: this.getTimeZone(),
|
|
218
228
|
}),
|
|
229
|
+
signal,
|
|
219
230
|
});
|
|
220
231
|
if (!response.ok) {
|
|
221
232
|
throw await buildApiError(response, 'Failed to continue agent');
|
|
@@ -224,6 +235,33 @@ class WidgetApiClient {
|
|
|
224
235
|
return typeof data === 'object' && data !== null && 'type' in data;
|
|
225
236
|
});
|
|
226
237
|
}
|
|
238
|
+
async *dismissAgentMessageStream(conversationId, toolCallId, signal) {
|
|
239
|
+
const headers = {
|
|
240
|
+
'Content-Type': 'application/json',
|
|
241
|
+
};
|
|
242
|
+
if (this.config.currentRoute) {
|
|
243
|
+
headers['X-Current-Route'] = this.config.currentRoute;
|
|
244
|
+
}
|
|
245
|
+
const response = await fetch(`${this.config.apiUrl}/api/widget/${this.config.widgetId}/agent/dismiss`, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers,
|
|
248
|
+
body: JSON.stringify({
|
|
249
|
+
conversationId: conversationId,
|
|
250
|
+
toolCallId,
|
|
251
|
+
reason: "user",
|
|
252
|
+
}),
|
|
253
|
+
signal,
|
|
254
|
+
});
|
|
255
|
+
if (response.status === 204) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
throw await buildApiError(response, 'Failed to dismiss action');
|
|
260
|
+
}
|
|
261
|
+
yield* parseSSEStream(response, (data) => {
|
|
262
|
+
return typeof data === 'object' && data !== null && 'type' in data;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
227
265
|
/**
|
|
228
266
|
* Submit feedback for a message
|
|
229
267
|
*/
|
|
@@ -286,6 +324,27 @@ class WidgetApiClient {
|
|
|
286
324
|
return [];
|
|
287
325
|
}
|
|
288
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Create a demo conversation with preset messages
|
|
329
|
+
* Used when demo animation completes to persist the demo conversation to the database
|
|
330
|
+
*/
|
|
331
|
+
async createDemoConversation(userMessage, assistantMessage) {
|
|
332
|
+
const response = await fetch(`${this.config.apiUrl}/api/widget/${this.config.widgetId}/demo-conversation`, {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: {
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
userMessage,
|
|
339
|
+
assistantMessage,
|
|
340
|
+
timeZone: this.getTimeZone(),
|
|
341
|
+
}),
|
|
342
|
+
});
|
|
343
|
+
if (!response.ok) {
|
|
344
|
+
throw await buildApiError(response, 'Failed to create demo conversation');
|
|
345
|
+
}
|
|
346
|
+
return response.json();
|
|
347
|
+
}
|
|
289
348
|
/**
|
|
290
349
|
* Validate widget access
|
|
291
350
|
*/
|
|
@@ -27116,22 +27175,19 @@ function ScrollButton({ onClick, visible, className = '' }) {
|
|
|
27116
27175
|
const formatToolName = (name) => {
|
|
27117
27176
|
return name
|
|
27118
27177
|
.replace(/^(action_|tool_)/, '')
|
|
27178
|
+
.replace(/-/g, '_')
|
|
27119
27179
|
.split('_')
|
|
27120
27180
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
27121
27181
|
.join(' ');
|
|
27122
27182
|
};
|
|
27123
|
-
const GearIcon = ({ spinning = false }) => (jsxs("svg", { className: `ai-chat-tool-gear ${spinning ? 'spinning' : ''}`, width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z" }), jsx("path", { d: "M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z" }), jsx("path", { d: "M12 2v2" }), jsx("path", { d: "M12 22v-2" }), jsx("path", { d: "m17 20.66-1-1.73" }), jsx("path", { d: "M11 10.27 7 3.34" }), jsx("path", { d: "m20.66 17-1.73-1" }), jsx("path", { d: "m3.34 7 1.73 1" }), jsx("path", { d: "M14 12h8" }), jsx("path", { d: "M2 12h2" }), jsx("path", { d: "m20.66 7-1.73 1" }), jsx("path", { d: "m3.34 17 1.73-1" }), jsx("path", { d: "m17 3.34-1 1.73" }), jsx("path", { d: "m11 13.73-4 6.93" })] }));
|
|
27124
|
-
const CheckIcon$2 = () => (jsx("svg", { className: "ai-chat-tool-check", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
27125
|
-
const ErrorIcon = () => (jsx("svg", { className: "ai-chat-tool-error", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }));
|
|
27126
27183
|
function ToolIndicator({ badges, className = '' }) {
|
|
27127
|
-
|
|
27128
|
-
return (jsxs("div", { className: `ai-chat-tool-row ${className}`, children: [jsx(GearIcon, { spinning: isAnyLoading }), jsx("div", { className: "ai-chat-tool-badges", children: badges.map((badge) => (jsxs("div", { className: `ai-chat-tool-badge ${badge.status}`, children: [badge.status !== 'loading' && (badge.status === 'error' ? jsx(ErrorIcon, {}) : jsx(CheckIcon$2, {})), jsx("span", { className: "tool-name", children: formatToolName(badge.name) })] }, badge.id))) })] }));
|
|
27184
|
+
return (jsx("div", { className: `ai-chat-tool-row ${className}`, children: jsx("div", { className: "ai-chat-tool-badges", children: badges.map((badge) => (jsx("div", { className: `ai-chat-tool-badge ${badge.status}`, children: jsx("span", { className: "tool-name", children: formatToolName(badge.name) }) }, badge.id))) }) }));
|
|
27129
27185
|
}
|
|
27130
27186
|
|
|
27131
27187
|
// SVG Icon components
|
|
27132
27188
|
const ThumbsUpIcon = () => (jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("path", { d: "M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" }) }));
|
|
27133
27189
|
const ThumbsDownIcon = () => (jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("path", { d: "M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" }) }));
|
|
27134
|
-
const CheckIcon$
|
|
27190
|
+
const CheckIcon$2 = () => (jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
27135
27191
|
const FeedbackButtons = ({ messageId, currentFeedback, onFeedback, }) => {
|
|
27136
27192
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
27137
27193
|
const [submitted, setSubmitted] = useState(false);
|
|
@@ -27152,10 +27208,10 @@ const FeedbackButtons = ({ messageId, currentFeedback, onFeedback, }) => {
|
|
|
27152
27208
|
setIsSubmitting(false);
|
|
27153
27209
|
}
|
|
27154
27210
|
};
|
|
27155
|
-
return (jsxs("div", { className: `ai-chat-feedback ${submitted ? 'submitted' : ''}`, children: [jsxs("div", { className: "ai-chat-feedback-buttons", children: [jsx("button", { className: `ai-chat-feedback-button ${currentFeedback === 'positive' ? 'active' : ''}`, onClick: () => handleFeedback('positive'), disabled: isDisabled, "aria-label": "Helpful", title: "This was helpful", children: jsx(ThumbsUpIcon, {}) }), jsx("button", { className: `ai-chat-feedback-button ${currentFeedback === 'negative' ? 'active' : ''}`, onClick: () => handleFeedback('negative'), disabled: isDisabled, "aria-label": "Not helpful", title: "This was not helpful", children: jsx(ThumbsDownIcon, {}) })] }), submitted && (jsxs("div", { className: "ai-chat-feedback-message", "aria-live": "polite", children: [jsx("span", { className: "ai-chat-feedback-checkmark", children: jsx(CheckIcon$
|
|
27211
|
+
return (jsxs("div", { className: `ai-chat-feedback ${submitted ? 'submitted' : ''}`, children: [jsxs("div", { className: "ai-chat-feedback-buttons", children: [jsx("button", { className: `ai-chat-feedback-button ${currentFeedback === 'positive' ? 'active' : ''}`, onClick: () => handleFeedback('positive'), disabled: isDisabled, "aria-label": "Helpful", title: "This was helpful", children: jsx(ThumbsUpIcon, {}) }), jsx("button", { className: `ai-chat-feedback-button ${currentFeedback === 'negative' ? 'active' : ''}`, onClick: () => handleFeedback('negative'), disabled: isDisabled, "aria-label": "Not helpful", title: "This was not helpful", children: jsx(ThumbsDownIcon, {}) })] }), submitted && (jsxs("div", { className: "ai-chat-feedback-message", "aria-live": "polite", children: [jsx("span", { className: "ai-chat-feedback-checkmark", children: jsx(CheckIcon$2, {}) }), jsx("span", { className: "ai-chat-feedback-text", children: "Thanks for feedback" })] }))] }));
|
|
27156
27212
|
};
|
|
27157
27213
|
|
|
27158
|
-
const Message = ({ message, showTimestamp = true, enableFeedback = true, onFeedback,
|
|
27214
|
+
const Message = ({ message, showTimestamp = true, enableFeedback = true, onFeedback, }) => {
|
|
27159
27215
|
const formatTime = (timestamp) => {
|
|
27160
27216
|
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
|
|
27161
27217
|
return date.toLocaleTimeString('en-US', {
|
|
@@ -27175,15 +27231,15 @@ const Message = ({ message, showTimestamp = true, enableFeedback = true, onFeedb
|
|
|
27175
27231
|
return null;
|
|
27176
27232
|
}
|
|
27177
27233
|
// AI message rendering
|
|
27234
|
+
// Note: Actions are rendered by ToolMessageGroup for tool messages, not here
|
|
27178
27235
|
if (isAssistant) {
|
|
27179
27236
|
const aiContent = message.message.content || '';
|
|
27180
27237
|
const hasContent = aiContent.trim().length > 0;
|
|
27181
27238
|
if (!hasContent)
|
|
27182
27239
|
return null;
|
|
27183
|
-
|
|
27184
|
-
|
|
27185
|
-
|
|
27186
|
-
return (jsxs("div", { className: `ai-chat-message assistant ${isError ? 'error' : ''}`, children: [jsxs("div", { className: "ai-chat-message-content", children: [isError && (jsxs("div", { className: "ai-chat-error-indicator", children: [jsx("span", { className: "error-icon", children: "\u26A0\uFE0F" }), jsx("span", { className: "error-text", children: "Error" })] })), jsx(Markdown, { remarkPlugins: [remarkGfm], children: aiContent })] }), actionRenderer && message.action && actionRenderer(message, accentColor), showTimestamp && (jsxs("div", { className: "ai-chat-message-meta", children: [jsx("span", { className: "ai-chat-message-timestamp", children: formatTime(message.timestamp) }), enableFeedback && onFeedback && (jsx(FeedbackButtons, { messageId: message.id, currentFeedback: message.feedback, onFeedback: onFeedback }))] }))] }));
|
|
27240
|
+
return (jsxs("div", { className: `ai-chat-message assistant ${isError ? 'error' : ''}`, children: [jsx("div", { className: "ai-chat-message-content", children: jsx(Markdown, { remarkPlugins: [remarkGfm], components: {
|
|
27241
|
+
table: ({ children, ...props }) => (jsx("div", { className: "table-wrapper", children: jsx("div", { className: "table-scroll", children: jsx("table", { ...props, children: children }) }) })),
|
|
27242
|
+
}, children: aiContent }) }), showTimestamp && (jsxs("div", { className: "ai-chat-message-meta", children: [jsx("span", { className: "ai-chat-message-timestamp", children: formatTime(message.timestamp) }), enableFeedback && onFeedback && (jsx(FeedbackButtons, { messageId: message.id, currentFeedback: message.feedback, onFeedback: onFeedback }))] }))] }));
|
|
27187
27243
|
}
|
|
27188
27244
|
// System message rendering
|
|
27189
27245
|
if (isSystem) {
|
|
@@ -27197,35 +27253,86 @@ const Message = ({ message, showTimestamp = true, enableFeedback = true, onFeedb
|
|
|
27197
27253
|
return null;
|
|
27198
27254
|
};
|
|
27199
27255
|
|
|
27200
|
-
|
|
27201
|
-
|
|
27256
|
+
// Centralized action state logic
|
|
27257
|
+
function isActionComplete(state) {
|
|
27258
|
+
if (!state)
|
|
27259
|
+
return false;
|
|
27260
|
+
const TERMINAL_STATUSES = [
|
|
27261
|
+
'completed', 'booked', 'scheduled', 'cancelled', 'failed', 'error',
|
|
27262
|
+
'displaying', 'clicked', 'contacted', 'submitted', 'sent'
|
|
27263
|
+
];
|
|
27264
|
+
const status = state.status;
|
|
27265
|
+
if (typeof status === 'string' && TERMINAL_STATUSES.includes(status)) {
|
|
27266
|
+
return true;
|
|
27267
|
+
}
|
|
27268
|
+
// For non-halting actions (query_contact_directory, etc.) with results/success
|
|
27269
|
+
if (!status && (state.success !== undefined || state.results !== undefined)) {
|
|
27270
|
+
return true;
|
|
27271
|
+
}
|
|
27272
|
+
return false;
|
|
27273
|
+
}
|
|
27274
|
+
function isActionLoading(message) {
|
|
27275
|
+
if (!message.action)
|
|
27276
|
+
return false;
|
|
27277
|
+
if (message.action.done)
|
|
27278
|
+
return false;
|
|
27279
|
+
if (message.isStreaming)
|
|
27280
|
+
return true;
|
|
27281
|
+
const state = message.action.state;
|
|
27282
|
+
return !isActionComplete(state);
|
|
27283
|
+
}
|
|
27284
|
+
const ToolMessageGroup = ({ messages, getActionRenderer, showToolIndicator = true, accentColor, variant, onActionDismiss, }) => {
|
|
27285
|
+
const visibleMessages = messages.filter(message => !message.action?.hidden);
|
|
27286
|
+
const actionMessages = visibleMessages.filter(message => message.action);
|
|
27287
|
+
// Debug logging
|
|
27288
|
+
console.log('[DEBUG ToolMessageGroup] ========================================');
|
|
27289
|
+
console.log('[DEBUG ToolMessageGroup] Total messages:', messages.length);
|
|
27290
|
+
console.log('[DEBUG ToolMessageGroup] Messages with action:', actionMessages.length);
|
|
27291
|
+
console.log('[DEBUG ToolMessageGroup] hasGetActionRenderer:', !!getActionRenderer);
|
|
27292
|
+
messages.forEach((msg, i) => {
|
|
27293
|
+
console.log(`[DEBUG ToolMessageGroup] Message ${i}:`, {
|
|
27294
|
+
id: msg.id,
|
|
27295
|
+
role: msg.message.role,
|
|
27296
|
+
hasAction: !!msg.action,
|
|
27297
|
+
actionImpl: msg.action?.implementation,
|
|
27298
|
+
toolExecuting: msg.toolExecuting,
|
|
27299
|
+
});
|
|
27300
|
+
});
|
|
27301
|
+
actionMessages.forEach((msg, i) => {
|
|
27302
|
+
const impl = msg.action?.implementation || '';
|
|
27303
|
+
const renderer = getActionRenderer?.(impl);
|
|
27304
|
+
console.log(`[DEBUG ToolMessageGroup] Action ${i}:`, {
|
|
27305
|
+
implementation: impl,
|
|
27306
|
+
hasRenderer: !!renderer,
|
|
27307
|
+
rendererType: renderer ? typeof renderer : 'undefined',
|
|
27308
|
+
state: msg.action?.state,
|
|
27309
|
+
});
|
|
27310
|
+
});
|
|
27202
27311
|
// If tool indicator is hidden AND there are no action cards to render, don't render anything
|
|
27203
|
-
// This prevents empty wrapper divs that cause layout jumping
|
|
27204
27312
|
if (!showToolIndicator && actionMessages.length === 0) {
|
|
27205
27313
|
return null;
|
|
27206
27314
|
}
|
|
27207
|
-
const badges =
|
|
27315
|
+
const badges = visibleMessages.map((message) => {
|
|
27208
27316
|
const toolName = message.toolExecuting || message.message.name || 'Tool';
|
|
27209
27317
|
const hasError = message.isError || false;
|
|
27210
|
-
const
|
|
27211
|
-
const actionStatus = actionState?.status;
|
|
27212
|
-
const terminalStatuses = ['completed', 'booked', 'scheduled', 'failed', 'cancelled', 'displaying', 'clicked'];
|
|
27213
|
-
const isTerminalStatus = actionStatus && terminalStatuses.includes(actionStatus);
|
|
27214
|
-
const isDone = message.action ? (message.action.done ?? isTerminalStatus ?? false) : !message.isStreaming;
|
|
27215
|
-
const isLoading = !isDone;
|
|
27318
|
+
const loading = isActionLoading(message);
|
|
27216
27319
|
return {
|
|
27217
27320
|
id: message.id,
|
|
27218
27321
|
name: toolName,
|
|
27219
|
-
status:
|
|
27322
|
+
status: loading ? 'loading' : hasError ? 'error' : 'completed',
|
|
27220
27323
|
};
|
|
27221
27324
|
});
|
|
27222
27325
|
return (jsxs("div", { className: "ai-chat-message tool", children: [showToolIndicator && jsx(ToolIndicator, { badges: badges }), actionMessages.map((message) => {
|
|
27223
|
-
if (!message.action || !getActionRenderer)
|
|
27326
|
+
if (!message.action || !getActionRenderer) {
|
|
27327
|
+
console.log('[ToolMessageGroup] Skipping - no action or renderer:', { hasAction: !!message.action, hasGetRenderer: !!getActionRenderer });
|
|
27224
27328
|
return null;
|
|
27329
|
+
}
|
|
27225
27330
|
const renderer = getActionRenderer(message.action.implementation);
|
|
27226
|
-
if (!renderer)
|
|
27331
|
+
if (!renderer) {
|
|
27332
|
+
console.log('[ToolMessageGroup] No renderer for:', message.action.implementation);
|
|
27227
27333
|
return null;
|
|
27228
|
-
|
|
27334
|
+
}
|
|
27335
|
+
return (jsx("div", { className: "ai-chat-tool-action", children: renderer(message, accentColor, variant, onActionDismiss) }, `action-${message.id}`));
|
|
27229
27336
|
})] }));
|
|
27230
27337
|
};
|
|
27231
27338
|
|
|
@@ -27287,12 +27394,17 @@ const FollowUpSuggestions = ({ suggestions, onQuestionClick, onActionClick, acce
|
|
|
27287
27394
|
};
|
|
27288
27395
|
|
|
27289
27396
|
const MessageList = (props) => {
|
|
27290
|
-
const { messages, isTyping = false, showTypingIndicator = true, showTimestamps = true, showToolCalls = false, enableFeedback = true, welcomeTitle, welcomeMessage, suggestedQuestions, accentColor, onSuggestedQuestionClick, onActionClick, onFeedback, onScrollStateChange, getActionRenderer, } = props;
|
|
27397
|
+
const { messages, isTyping = false, showTypingIndicator = true, showTimestamps = true, showToolCalls = false, enableFeedback = true, welcomeTitle, welcomeMessage, suggestedQuestions, accentColor, onSuggestedQuestionClick, onActionClick, onActionDismiss, onFeedback, onScrollStateChange, getActionRenderer, variant, } = props;
|
|
27291
27398
|
const containerRef = useRef(null);
|
|
27292
27399
|
const messagesEndRef = useRef(null);
|
|
27293
27400
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
27294
27401
|
const prevMessageCountRef = useRef(0);
|
|
27295
|
-
const
|
|
27402
|
+
const visibleMessages = useMemo(() => messages.filter((message) => !message.action?.hidden), [messages]);
|
|
27403
|
+
const hasActiveAction = useMemo(() => {
|
|
27404
|
+
// Find the last tool message and check if its action is still active (not done)
|
|
27405
|
+
const lastToolMsg = [...visibleMessages].reverse().find(msg => msg.message.role === 'tool');
|
|
27406
|
+
return lastToolMsg?.action && lastToolMsg.action.done !== true;
|
|
27407
|
+
}, [visibleMessages]);
|
|
27296
27408
|
const checkScrollPosition = useCallback(() => {
|
|
27297
27409
|
const c = containerRef.current;
|
|
27298
27410
|
if (!c)
|
|
@@ -27315,7 +27427,7 @@ const MessageList = (props) => {
|
|
|
27315
27427
|
const c = containerRef.current;
|
|
27316
27428
|
if (!c)
|
|
27317
27429
|
return;
|
|
27318
|
-
const count =
|
|
27430
|
+
const count = visibleMessages.length;
|
|
27319
27431
|
const isNew = count > prevMessageCountRef.current;
|
|
27320
27432
|
prevMessageCountRef.current = count;
|
|
27321
27433
|
if (count === 0) {
|
|
@@ -27326,17 +27438,33 @@ const MessageList = (props) => {
|
|
|
27326
27438
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
27327
27439
|
}
|
|
27328
27440
|
checkScrollPosition();
|
|
27329
|
-
}, [
|
|
27441
|
+
}, [visibleMessages, isTyping, checkScrollPosition]);
|
|
27330
27442
|
const groupedMessages = useMemo(() => {
|
|
27443
|
+
console.log('[DEBUG MessageList] ========================================');
|
|
27444
|
+
console.log('[DEBUG MessageList] Processing messages:', visibleMessages.length);
|
|
27445
|
+
visibleMessages.forEach((m, i) => {
|
|
27446
|
+
console.log(`[DEBUG MessageList] Message ${i}:`, {
|
|
27447
|
+
id: m.id,
|
|
27448
|
+
role: m.message.role,
|
|
27449
|
+
hasAction: !!m.action,
|
|
27450
|
+
actionImpl: m.action?.implementation,
|
|
27451
|
+
content: (m.message.content || '').substring(0, 50),
|
|
27452
|
+
});
|
|
27453
|
+
});
|
|
27331
27454
|
const result = [];
|
|
27332
27455
|
let toolGroup = [];
|
|
27333
|
-
const flush = () => {
|
|
27334
|
-
|
|
27335
|
-
|
|
27336
|
-
|
|
27337
|
-
|
|
27338
|
-
|
|
27456
|
+
const flush = () => {
|
|
27457
|
+
if (toolGroup.length) {
|
|
27458
|
+
console.log('[DEBUG MessageList] Flushing tool group with', toolGroup.length, 'messages');
|
|
27459
|
+
result.push({ type: 'tool-group', messages: [...toolGroup] });
|
|
27460
|
+
toolGroup = [];
|
|
27461
|
+
}
|
|
27462
|
+
};
|
|
27463
|
+
for (const m of visibleMessages) {
|
|
27464
|
+
if (m.message.role === 'tool') {
|
|
27465
|
+
console.log('[DEBUG MessageList] Adding to tool group:', m.id);
|
|
27339
27466
|
toolGroup.push(m);
|
|
27467
|
+
}
|
|
27340
27468
|
else if (m.message.role === 'user') {
|
|
27341
27469
|
flush();
|
|
27342
27470
|
result.push({ type: 'message', message: m });
|
|
@@ -27348,6 +27476,7 @@ const MessageList = (props) => {
|
|
|
27348
27476
|
flush();
|
|
27349
27477
|
result.push({ type: 'message', message: m });
|
|
27350
27478
|
}
|
|
27479
|
+
// Don't flush on empty assistant messages - let tools continue grouping
|
|
27351
27480
|
}
|
|
27352
27481
|
else {
|
|
27353
27482
|
flush();
|
|
@@ -27355,18 +27484,27 @@ const MessageList = (props) => {
|
|
|
27355
27484
|
}
|
|
27356
27485
|
}
|
|
27357
27486
|
flush();
|
|
27487
|
+
console.log('[DEBUG MessageList] Final grouped result:', result.length, 'items');
|
|
27488
|
+
result.forEach((item, i) => {
|
|
27489
|
+
if (item.type === 'tool-group') {
|
|
27490
|
+
console.log(`[DEBUG MessageList] Group ${i}: tool-group with ${item.messages.length} messages`);
|
|
27491
|
+
}
|
|
27492
|
+
else {
|
|
27493
|
+
console.log(`[DEBUG MessageList] Group ${i}: message (${item.message.message.role})`);
|
|
27494
|
+
}
|
|
27495
|
+
});
|
|
27358
27496
|
return result;
|
|
27359
|
-
}, [
|
|
27360
|
-
const hasSuggestions =
|
|
27497
|
+
}, [visibleMessages]);
|
|
27498
|
+
const hasSuggestions = visibleMessages.length === 0 && onSuggestedQuestionClick && suggestedQuestions?.length;
|
|
27361
27499
|
const showWelcome = welcomeTitle || welcomeMessage || hasSuggestions;
|
|
27362
27500
|
return (jsxs("div", { ref: containerRef, className: "ai-chat-messages", role: "log", "aria-live": "polite", children: [showWelcome && (jsxs("div", { className: "ai-chat-welcome", children: [welcomeTitle && jsx("div", { className: "ai-chat-welcome-title", children: welcomeTitle }), welcomeMessage && jsx("div", { className: "ai-chat-welcome-text", children: welcomeMessage }), hasSuggestions && jsx(SuggestedQuestions, { questions: suggestedQuestions, onQuestionClick: onSuggestedQuestionClick })] })), groupedMessages.map((item, i) => {
|
|
27363
27501
|
if (item.type === 'tool-group') {
|
|
27364
|
-
return jsx(ToolMessageGroup, { messages: item.messages, getActionRenderer: getActionRenderer, showToolIndicator: showToolCalls, accentColor: accentColor }, `tg-${i}`);
|
|
27502
|
+
return (jsx(ToolMessageGroup, { messages: item.messages, getActionRenderer: getActionRenderer, showToolIndicator: showToolCalls, accentColor: accentColor, variant: variant, onActionDismiss: onActionDismiss }, `tg-${i}`));
|
|
27365
27503
|
}
|
|
27366
27504
|
const isLast = i === groupedMessages.length - 1;
|
|
27367
27505
|
const hasFollowUp = item.message.message.role === 'assistant' && item.message.suggestions?.length && isLast && !isTyping;
|
|
27368
27506
|
return (jsxs(React.Fragment, { children: [jsx(Message, { message: item.message, showTimestamp: showTimestamps, onFeedback: onFeedback, getActionRenderer: getActionRenderer, accentColor: accentColor }), hasFollowUp && onSuggestedQuestionClick && jsx(FollowUpSuggestions, { suggestions: item.message.suggestions, onQuestionClick: onSuggestedQuestionClick, onActionClick: onActionClick, accentColor: accentColor })] }, item.message.id));
|
|
27369
|
-
}), isTyping && showTypingIndicator && !hasActiveAction &&
|
|
27507
|
+
}), isTyping && showTypingIndicator && !hasActiveAction && visibleMessages.length > 0 && jsx(TypingIndicator, {}), jsx("div", { ref: messagesEndRef })] }));
|
|
27370
27508
|
};
|
|
27371
27509
|
|
|
27372
27510
|
const ALLOWED_EXTENSIONS = ['.pdf', '.doc', '.docx', '.txt', '.md', '.csv'];
|
|
@@ -27380,7 +27518,7 @@ const formatFileSize = (bytes) => {
|
|
|
27380
27518
|
return (bytes / 1024).toFixed(1) + ' KB';
|
|
27381
27519
|
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
27382
27520
|
};
|
|
27383
|
-
const MessageInput = ({ onSend, placeholder = 'Type your message...', disabled = false, enableFileUpload = false, separateFromChat = true, }) => {
|
|
27521
|
+
const MessageInput = ({ onSend, placeholder = 'Type your message...', disabled = false, enableFileUpload = false, separateFromChat = true, showDataPolicy = true, onDataPolicyClick, }) => {
|
|
27384
27522
|
const [value, setValue] = useState('');
|
|
27385
27523
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
27386
27524
|
const textareaRef = useRef(null);
|
|
@@ -27415,10 +27553,14 @@ const MessageInput = ({ onSend, placeholder = 'Type your message...', disabled =
|
|
|
27415
27553
|
}
|
|
27416
27554
|
};
|
|
27417
27555
|
const canSend = value.trim() || selectedFiles.length > 0;
|
|
27418
|
-
return (jsxs("div", { className: `ai-chat-input-container ${separateFromChat ? 'separate' : 'integrated'}`, children: [selectedFiles.length > 0 && (jsx("div", { className: "ai-chat-file-list", children: selectedFiles.map((file, index) => (jsxs("div", { className: "ai-chat-file-item", children: [jsx("span", { className: "ai-chat-file-extension", children: getFileExtension(file.name) }), jsxs("div", { className: "ai-chat-file-info", children: [jsx("span", { className: "ai-chat-file-name", children: file.name }), jsx("span", { className: "ai-chat-file-size", children: formatFileSize(file.size) })] }), jsx("button", { className: "ai-chat-file-remove", onClick: () => handleRemoveFile(index), "aria-label": "Remove file", children: jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }, index))) })), jsxs("div", { className: "ai-chat-input-wrapper", children: [enableFileUpload && (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", onChange: handleFileSelect, style: { display: 'none' }, multiple: true, accept: ALLOWED_EXTENSIONS.join(',') }), jsx("button", { className: "ai-chat-file-button", onClick: () => fileInputRef.current?.click(), disabled: disabled, "aria-label": "Attach file", children: jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }) })] })), jsx("textarea", { ref: textareaRef, className: "ai-chat-input", value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, rows: 2, wrap: "soft", "aria-label": "Message input" }), jsx("button", { className: `ai-chat-send-button ${canSend ? 'active' : ''}`, onClick: handleSend, disabled: disabled || !canSend, "aria-label": "Send message", children: jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [jsx("path", { d: "M12 19V5" }), jsx("path", { d: "M5 12l7-7 7 7" })] }) })] })] }));
|
|
27556
|
+
return (jsxs("div", { className: `ai-chat-input-container ${separateFromChat ? 'separate' : 'integrated'}`, children: [selectedFiles.length > 0 && (jsx("div", { className: "ai-chat-file-list", children: selectedFiles.map((file, index) => (jsxs("div", { className: "ai-chat-file-item", children: [jsx("span", { className: "ai-chat-file-extension", children: getFileExtension(file.name) }), jsxs("div", { className: "ai-chat-file-info", children: [jsx("span", { className: "ai-chat-file-name", children: file.name }), jsx("span", { className: "ai-chat-file-size", children: formatFileSize(file.size) })] }), jsx("button", { className: "ai-chat-file-remove", onClick: () => handleRemoveFile(index), "aria-label": "Remove file", children: jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }, index))) })), jsxs("div", { className: "ai-chat-input-wrapper", children: [enableFileUpload && (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", onChange: handleFileSelect, style: { display: 'none' }, multiple: true, accept: ALLOWED_EXTENSIONS.join(',') }), jsx("button", { className: "ai-chat-file-button", onClick: () => fileInputRef.current?.click(), disabled: disabled, "aria-label": "Attach file", children: jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }) })] })), jsx("textarea", { ref: textareaRef, className: "ai-chat-input", value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, rows: 2, wrap: "soft", "aria-label": "Message input" }), jsx("button", { className: `ai-chat-send-button ${canSend ? 'active' : ''}`, onClick: handleSend, disabled: disabled || !canSend, "aria-label": "Send message", children: jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [jsx("path", { d: "M12 19V5" }), jsx("path", { d: "M5 12l7-7 7 7" })] }) })] }), showDataPolicy && (jsxs("div", { className: "ai-chat-data-policy", children: [jsx("span", { children: "AI-generated responses may be inaccurate." }), onDataPolicyClick && (jsxs(Fragment, { children: [' ', jsx("button", { type: "button", className: "ai-chat-data-policy-link", onClick: onDataPolicyClick, children: "Privacy Notice" })] }))] }))] }));
|
|
27419
27557
|
};
|
|
27420
27558
|
|
|
27421
|
-
|
|
27559
|
+
const CloseButton = ({ onClick, className = "", ariaLabel = "Close", }) => {
|
|
27560
|
+
return (jsx("button", { type: "button", className: `ai-chat-action-close-btn ${className}`, onClick: onClick, "aria-label": ariaLabel, children: jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M1 1L13 13M1 13L13 1" }) }) }));
|
|
27561
|
+
};
|
|
27562
|
+
|
|
27563
|
+
function groupSlotsByDate$1(slots) {
|
|
27422
27564
|
const grouped = new Map();
|
|
27423
27565
|
for (const slot of slots) {
|
|
27424
27566
|
if (!slot || typeof slot !== 'object' || !slot.startTime || typeof slot.startTime !== 'string') {
|
|
@@ -27432,7 +27574,7 @@ function groupSlotsByDate(slots) {
|
|
|
27432
27574
|
}
|
|
27433
27575
|
return grouped;
|
|
27434
27576
|
}
|
|
27435
|
-
function formatDate(dateStr) {
|
|
27577
|
+
function formatDate$1(dateStr) {
|
|
27436
27578
|
try {
|
|
27437
27579
|
const date = new Date(dateStr);
|
|
27438
27580
|
return new Intl.DateTimeFormat("en-US", {
|
|
@@ -27445,19 +27587,19 @@ function formatDate(dateStr) {
|
|
|
27445
27587
|
return dateStr;
|
|
27446
27588
|
}
|
|
27447
27589
|
}
|
|
27448
|
-
function CalendarIcon() {
|
|
27590
|
+
function CalendarIcon$1() {
|
|
27449
27591
|
return (jsx("svg", { className: "ai-chat-action-icon", viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", clipRule: "evenodd" }) }));
|
|
27450
27592
|
}
|
|
27451
|
-
function CheckIcon() {
|
|
27593
|
+
function CheckIcon$1() {
|
|
27452
27594
|
return (jsx("svg", { className: "ai-chat-action-icon-success", viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }));
|
|
27453
27595
|
}
|
|
27454
|
-
function ExternalLinkIcon$
|
|
27596
|
+
function ExternalLinkIcon$2() {
|
|
27455
27597
|
return (jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }), jsx("polyline", { points: "15 3 21 3 21 9" }), jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })] }));
|
|
27456
27598
|
}
|
|
27457
|
-
function Skeleton({ width, height, borderRadius = '4px' }) {
|
|
27599
|
+
function Skeleton$2({ width, height, borderRadius = '4px' }) {
|
|
27458
27600
|
return (jsx("div", { className: "ai-chat-action-skeleton-item", style: { width, height, borderRadius } }));
|
|
27459
27601
|
}
|
|
27460
|
-
function GoogleCalendarCard({ action, onComplete, accentColor, className = '' }) {
|
|
27602
|
+
function GoogleCalendarCard({ action, onComplete, onDismiss, accentColor, className = '' }) {
|
|
27461
27603
|
const state = action.state;
|
|
27462
27604
|
const rawSlots = state.availableSlots;
|
|
27463
27605
|
const availableSlots = Array.isArray(rawSlots)
|
|
@@ -27471,13 +27613,12 @@ function GoogleCalendarCard({ action, onComplete, accentColor, className = '' })
|
|
|
27471
27613
|
: [];
|
|
27472
27614
|
const allowTopic = state.allowTopic !== false;
|
|
27473
27615
|
const isBooked = state.status === "booked";
|
|
27474
|
-
const slotsByDate = groupSlotsByDate(availableSlots);
|
|
27616
|
+
const slotsByDate = groupSlotsByDate$1(availableSlots);
|
|
27475
27617
|
const dates = Array.from(slotsByDate.keys()).sort();
|
|
27476
27618
|
const [selectedDate, setSelectedDate] = useState(dates[0] ?? "");
|
|
27477
27619
|
const [selectedSlot, setSelectedSlot] = useState(null);
|
|
27478
27620
|
const [topic, setTopic] = useState("");
|
|
27479
27621
|
const [error, setError] = useState(null);
|
|
27480
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
27481
27622
|
const slotsForSelectedDate = selectedDate ? slotsByDate.get(selectedDate) ?? [] : [];
|
|
27482
27623
|
const accentStyle = accentColor ? { '--action-accent': accentColor } : {};
|
|
27483
27624
|
const onConfirm = () => {
|
|
@@ -27490,8 +27631,252 @@ function GoogleCalendarCard({ action, onComplete, accentColor, className = '' })
|
|
|
27490
27631
|
return;
|
|
27491
27632
|
}
|
|
27492
27633
|
setError(null);
|
|
27634
|
+
onComplete?.(action.toolCallId, {
|
|
27635
|
+
...action.state,
|
|
27636
|
+
selectedSlot: {
|
|
27637
|
+
startTime: selectedSlot.startTime,
|
|
27638
|
+
endTime: selectedSlot.endTime,
|
|
27639
|
+
},
|
|
27640
|
+
topic: allowTopic ? topic.trim() : null,
|
|
27641
|
+
});
|
|
27642
|
+
};
|
|
27643
|
+
const handleDismiss = () => {
|
|
27644
|
+
onDismiss?.(action.toolCallId);
|
|
27645
|
+
};
|
|
27646
|
+
// Booked state
|
|
27647
|
+
if (isBooked) {
|
|
27648
|
+
const bookedSlot = state.selectedSlot;
|
|
27649
|
+
const bookedTopic = state.topic;
|
|
27650
|
+
const eventLink = state.bookedEventLink;
|
|
27651
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-booked ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx("div", { className: "ai-chat-action-success-icon-wrapper", children: jsx(CheckIcon$1, {}) }), "Appointment Confirmed"] }), jsxs("div", { className: "ai-chat-action-body", children: [bookedTopic && (jsxs("div", { className: "ai-chat-action-detail-box", children: [jsx("span", { className: "ai-chat-action-label-small", children: "TOPIC" }), jsx("span", { className: "ai-chat-action-value-large", children: bookedTopic })] })), bookedSlot && bookedSlot.startTime && (jsxs("div", { className: "ai-chat-action-detail-box", children: [jsx("span", { className: "ai-chat-action-label-small", children: "TIME" }), jsx("span", { className: "ai-chat-action-value-large", children: bookedSlot.displayTime || new Date(bookedSlot.startTime).toLocaleString() })] })), eventLink && (jsxs("a", { href: eventLink, target: "_blank", rel: "noopener noreferrer", className: "ai-chat-action-link-button", children: ["View in Google Calendar", jsx(ExternalLinkIcon$2, {})] }))] })] }));
|
|
27652
|
+
}
|
|
27653
|
+
// Skeleton loading state - show when waiting for backend after user confirms
|
|
27654
|
+
const isWaitingForBackend = !action.done && state.selectedSlot && !isBooked;
|
|
27655
|
+
if (isWaitingForBackend) {
|
|
27656
|
+
return (jsx("div", { className: `ai-chat-action-card ai-chat-action-skeleton ${className}`, style: accentStyle, children: jsxs("div", { className: "ai-chat-action-skeleton-content", children: [jsxs("div", { className: "ai-chat-action-skeleton-header", children: [jsx(Skeleton$2, { width: "28px", height: "28px", borderRadius: "50%" }), jsx(Skeleton$2, { width: "180px", height: "20px", borderRadius: "4px" })] }), jsxs("div", { className: "ai-chat-action-skeleton-box", children: [jsx(Skeleton$2, { width: "60px", height: "12px", borderRadius: "4px" }), jsx(Skeleton$2, { width: "120px", height: "18px", borderRadius: "4px" })] }), jsxs("div", { className: "ai-chat-action-skeleton-box", children: [jsx(Skeleton$2, { width: "50px", height: "12px", borderRadius: "4px" }), jsx(Skeleton$2, { width: "200px", height: "18px", borderRadius: "4px" })] }), jsx(Skeleton$2, { width: "100%", height: "44px", borderRadius: "999px" })] }) }));
|
|
27657
|
+
}
|
|
27658
|
+
// Booking form
|
|
27659
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-card--closable ai-chat-google-calendar ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx(CalendarIcon$1, {}), "Schedule an Appointment", onDismiss && (jsx(CloseButton, { onClick: handleDismiss, ariaLabel: "Cancel appointment booking" }))] }), jsxs("div", { className: "ai-chat-action-body", children: [allowTopic && (jsxs("div", { className: "ai-chat-action-field", children: [jsx("label", { htmlFor: `topic-${action.toolCallId}`, className: "ai-chat-action-label", children: "Meeting Topic" }), jsx("input", { id: `topic-${action.toolCallId}`, type: "text", className: "ai-chat-action-input", placeholder: "e.g., Product Demo", value: topic, onChange: (e) => setTopic(e.target.value) })] })), jsxs("div", { className: "ai-chat-action-field", children: [jsx("label", { className: "ai-chat-action-label", children: "Select Date" }), jsx("div", { className: "ai-chat-action-date-grid", children: dates.slice(0, 7).map((date) => (jsx("button", { type: "button", className: `ai-chat-action-date-btn ${selectedDate === date ? "active" : ""}`, onClick: () => {
|
|
27660
|
+
setSelectedDate(date);
|
|
27661
|
+
setSelectedSlot(null);
|
|
27662
|
+
}, children: formatDate$1(date) }, date))) })] }), selectedDate && slotsForSelectedDate.length > 0 && (jsxs("div", { className: "ai-chat-action-field", children: [jsx("label", { className: "ai-chat-action-label", children: "Select Time" }), jsx("div", { className: "ai-chat-action-time-grid", children: slotsForSelectedDate.map((slot) => (jsx("button", { type: "button", className: `ai-chat-action-time-btn ${selectedSlot?.startTime === slot.startTime ? "active" : ""}`, onClick: () => setSelectedSlot(slot), children: slot.displayTime || new Date(slot.startTime).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) }, slot.startTime))) })] })), error && jsx("div", { className: "ai-chat-action-error", children: error }), jsx("button", { className: "ai-chat-action-button", type: "button", onClick: onConfirm, disabled: !selectedSlot, children: "Confirm Appointment" }), availableSlots.length === 0 && (jsx("div", { className: "ai-chat-action-hint", children: "No available time slots found." }))] })] }));
|
|
27663
|
+
}
|
|
27664
|
+
|
|
27665
|
+
function groupSlotsByDate(slots) {
|
|
27666
|
+
const grouped = new Map();
|
|
27667
|
+
for (const slot of slots) {
|
|
27668
|
+
if (!slot || typeof slot !== 'object' || !slot.startTime || typeof slot.startTime !== 'string') {
|
|
27669
|
+
continue;
|
|
27670
|
+
}
|
|
27671
|
+
const date = slot.startTime.slice(0, 10);
|
|
27672
|
+
if (!grouped.has(date)) {
|
|
27673
|
+
grouped.set(date, []);
|
|
27674
|
+
}
|
|
27675
|
+
grouped.get(date).push(slot);
|
|
27676
|
+
}
|
|
27677
|
+
return grouped;
|
|
27678
|
+
}
|
|
27679
|
+
function formatDate(dateStr) {
|
|
27680
|
+
try {
|
|
27681
|
+
const date = new Date(dateStr);
|
|
27682
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
27683
|
+
weekday: "short",
|
|
27684
|
+
month: "short",
|
|
27685
|
+
day: "numeric",
|
|
27686
|
+
}).format(date);
|
|
27687
|
+
}
|
|
27688
|
+
catch {
|
|
27689
|
+
return dateStr;
|
|
27690
|
+
}
|
|
27691
|
+
}
|
|
27692
|
+
function CalendarIcon() {
|
|
27693
|
+
return (jsx("svg", { className: "ai-chat-action-icon", viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", clipRule: "evenodd" }) }));
|
|
27694
|
+
}
|
|
27695
|
+
function MailIcon() {
|
|
27696
|
+
return (jsxs("svg", { className: "ai-chat-action-icon", viewBox: "0 0 20 20", fill: "currentColor", children: [jsx("path", { d: "M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" }), jsx("path", { d: "M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" })] }));
|
|
27697
|
+
}
|
|
27698
|
+
function CheckIcon() {
|
|
27699
|
+
return (jsx("svg", { className: "ai-chat-action-icon-success", viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }));
|
|
27700
|
+
}
|
|
27701
|
+
function ErrorIcon() {
|
|
27702
|
+
return (jsx("svg", { className: "ai-chat-action-icon-error", viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }));
|
|
27703
|
+
}
|
|
27704
|
+
function ExternalLinkIcon$1() {
|
|
27705
|
+
return (jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }), jsx("polyline", { points: "15 3 21 3 21 9" }), jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })] }));
|
|
27706
|
+
}
|
|
27707
|
+
function Skeleton$1({ width, height, borderRadius = '4px' }) {
|
|
27708
|
+
return (jsx("div", { className: "ai-chat-action-skeleton-item", style: { width, height, borderRadius } }));
|
|
27709
|
+
}
|
|
27710
|
+
function PinInputGroup$1({ values, onChange, disabled }) {
|
|
27711
|
+
const inputRefs = useRef([]);
|
|
27712
|
+
const handleChange = (index, value) => {
|
|
27713
|
+
// Only allow digits
|
|
27714
|
+
const digit = value.replace(/[^0-9]/g, '');
|
|
27715
|
+
const newValues = [...values];
|
|
27716
|
+
newValues[index] = digit.slice(-1);
|
|
27717
|
+
onChange(newValues);
|
|
27718
|
+
// Auto-focus next input
|
|
27719
|
+
if (digit && index < 5) {
|
|
27720
|
+
inputRefs.current[index + 1]?.focus();
|
|
27721
|
+
}
|
|
27722
|
+
};
|
|
27723
|
+
const handleKeyDown = (index, e) => {
|
|
27724
|
+
if (e.key === 'Backspace' && !values[index] && index > 0) {
|
|
27725
|
+
inputRefs.current[index - 1]?.focus();
|
|
27726
|
+
}
|
|
27727
|
+
};
|
|
27728
|
+
const handlePaste = (e) => {
|
|
27729
|
+
e.preventDefault();
|
|
27730
|
+
const pastedData = e.clipboardData.getData('text').replace(/[^0-9]/g, '').slice(0, 6);
|
|
27731
|
+
const newValues = pastedData.split('').concat(Array(6 - pastedData.length).fill(''));
|
|
27732
|
+
onChange(newValues);
|
|
27733
|
+
// Focus the next empty input or the last one
|
|
27734
|
+
const nextIndex = Math.min(pastedData.length, 5);
|
|
27735
|
+
inputRefs.current[nextIndex]?.focus();
|
|
27736
|
+
};
|
|
27737
|
+
return (jsx("div", { className: "ai-chat-pin-input-group", children: values.map((value, index) => (jsx("input", { ref: (el) => {
|
|
27738
|
+
inputRefs.current[index] = el;
|
|
27739
|
+
}, type: "text", inputMode: "numeric", maxLength: 1, className: "ai-chat-pin-input", value: value, onChange: (e) => handleChange(index, e.target.value), onKeyDown: (e) => handleKeyDown(index, e), onPaste: handlePaste, disabled: disabled, autoFocus: index === 0 }, index))) }));
|
|
27740
|
+
}
|
|
27741
|
+
function MicrosoftCalendarCard({ action, onComplete, onDismiss, accentColor, className = '' }) {
|
|
27742
|
+
const state = action.state;
|
|
27743
|
+
const phase = state.phase || "awaiting_email";
|
|
27744
|
+
const allowTopic = state.allowTopic !== false;
|
|
27745
|
+
const accentStyle = accentColor ? { '--action-accent': accentColor } : {};
|
|
27746
|
+
const handleDismiss = () => {
|
|
27747
|
+
onDismiss?.(action.toolCallId);
|
|
27748
|
+
};
|
|
27749
|
+
const showCloseButton = Boolean(onDismiss);
|
|
27750
|
+
// Debug: Log state changes
|
|
27751
|
+
const prevStateRef = useRef(null);
|
|
27752
|
+
useEffect(() => {
|
|
27753
|
+
if (JSON.stringify(prevStateRef.current) !== JSON.stringify(state)) {
|
|
27754
|
+
console.log('[MicrosoftCalendarCard] State updated:', {
|
|
27755
|
+
phase: state.phase,
|
|
27756
|
+
hasError: !!state.errorMessage,
|
|
27757
|
+
error: state.errorMessage,
|
|
27758
|
+
slotsCount: state.availableSlots?.length || 0
|
|
27759
|
+
});
|
|
27760
|
+
prevStateRef.current = state;
|
|
27761
|
+
}
|
|
27762
|
+
}, [state]);
|
|
27763
|
+
// Email phase state
|
|
27764
|
+
const [email, setEmail] = useState("");
|
|
27765
|
+
const [emailError, setEmailError] = useState(null);
|
|
27766
|
+
// OTP phase state
|
|
27767
|
+
const [otpValues, setOtpValues] = useState(Array(6).fill(''));
|
|
27768
|
+
const [otpError, setOtpError] = useState(null);
|
|
27769
|
+
// Loading states
|
|
27770
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
27771
|
+
// Reset loading state when phase changes (backend has responded)
|
|
27772
|
+
// Use useEffect for reliable re-rendering
|
|
27773
|
+
useEffect(() => {
|
|
27774
|
+
console.log('[MicrosoftCalendarCard] Phase changed to:', phase);
|
|
27775
|
+
setIsSubmitting(false);
|
|
27776
|
+
// Clear errors when transitioning to new phase
|
|
27777
|
+
if (phase === "awaiting_email") {
|
|
27778
|
+
setEmailError(null);
|
|
27779
|
+
setOtpError(null);
|
|
27780
|
+
setSelectionError(null);
|
|
27781
|
+
}
|
|
27782
|
+
else if (phase === "awaiting_otp") {
|
|
27783
|
+
setOtpError(state.errorMessage || null);
|
|
27784
|
+
setEmailError(null);
|
|
27785
|
+
setSelectionError(null);
|
|
27786
|
+
// Clear OTP input for fresh attempt
|
|
27787
|
+
setOtpValues(Array(6).fill(''));
|
|
27788
|
+
}
|
|
27789
|
+
else if (phase === "awaiting_options") {
|
|
27790
|
+
setEmailError(null);
|
|
27791
|
+
setOtpError(null);
|
|
27792
|
+
setSelectionError(null);
|
|
27793
|
+
}
|
|
27794
|
+
else if (phase === "awaiting_booking") {
|
|
27795
|
+
setSelectionError(state.errorMessage || null);
|
|
27796
|
+
setEmailError(null);
|
|
27797
|
+
setOtpError(null);
|
|
27798
|
+
}
|
|
27799
|
+
else if (phase === "booked" || phase === "cancelled" || phase === "error") {
|
|
27800
|
+
setEmailError(null);
|
|
27801
|
+
setOtpError(null);
|
|
27802
|
+
setSelectionError(null);
|
|
27803
|
+
setSelectedId(null); // Reset selection
|
|
27804
|
+
}
|
|
27805
|
+
}, [phase, state.errorMessage]);
|
|
27806
|
+
// Selection phase state
|
|
27807
|
+
const rawSlots = state.availableSlots;
|
|
27808
|
+
const availableSlots = Array.isArray(rawSlots)
|
|
27809
|
+
? rawSlots.filter((slot) => slot !== null &&
|
|
27810
|
+
slot !== undefined &&
|
|
27811
|
+
typeof slot === "object" &&
|
|
27812
|
+
"startTime" in slot &&
|
|
27813
|
+
"endTime" in slot &&
|
|
27814
|
+
typeof slot.startTime === "string" &&
|
|
27815
|
+
typeof slot.endTime === "string")
|
|
27816
|
+
: [];
|
|
27817
|
+
const slotsByDate = groupSlotsByDate(availableSlots);
|
|
27818
|
+
const dates = Array.from(slotsByDate.keys()).sort();
|
|
27819
|
+
const [selectedDate, setSelectedDate] = useState(dates[0] ?? "");
|
|
27820
|
+
const [selectedSlot, setSelectedSlot] = useState(null);
|
|
27821
|
+
const [topic, setTopic] = useState("");
|
|
27822
|
+
const [selectionError, setSelectionError] = useState(null);
|
|
27823
|
+
// Cancellation phase state
|
|
27824
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
27825
|
+
const slotsForSelectedDate = selectedDate ? slotsByDate.get(selectedDate) ?? [] : [];
|
|
27826
|
+
// Phase 1: Email Input
|
|
27827
|
+
const handleEmailSubmit = () => {
|
|
27828
|
+
const trimmedEmail = email.trim();
|
|
27829
|
+
if (!trimmedEmail) {
|
|
27830
|
+
setEmailError("Please enter your email address");
|
|
27831
|
+
return;
|
|
27832
|
+
}
|
|
27833
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail)) {
|
|
27834
|
+
setEmailError("Please enter a valid email address");
|
|
27835
|
+
return;
|
|
27836
|
+
}
|
|
27837
|
+
setEmailError(null);
|
|
27838
|
+
setIsSubmitting(true);
|
|
27839
|
+
console.log('[MicrosoftCalendarCard] Submitting email:', trimmedEmail);
|
|
27840
|
+
setTimeout(() => {
|
|
27841
|
+
onComplete?.(action.toolCallId, {
|
|
27842
|
+
...action.state,
|
|
27843
|
+
email: trimmedEmail,
|
|
27844
|
+
});
|
|
27845
|
+
}, 50);
|
|
27846
|
+
};
|
|
27847
|
+
// Phase 2: OTP Verification
|
|
27848
|
+
const handleOtpSubmit = () => {
|
|
27849
|
+
const otpCode = otpValues.join('');
|
|
27850
|
+
if (otpCode.length !== 6) {
|
|
27851
|
+
setOtpError("Please enter the 6-digit code");
|
|
27852
|
+
return;
|
|
27853
|
+
}
|
|
27854
|
+
setOtpError(null);
|
|
27855
|
+
setIsSubmitting(true);
|
|
27856
|
+
console.log('[MicrosoftCalendarCard] Submitting OTP code');
|
|
27857
|
+
setTimeout(() => {
|
|
27858
|
+
onComplete?.(action.toolCallId, {
|
|
27859
|
+
...action.state,
|
|
27860
|
+
otpCode,
|
|
27861
|
+
});
|
|
27862
|
+
}, 50);
|
|
27863
|
+
};
|
|
27864
|
+
// Phase 3: Appointment Selection
|
|
27865
|
+
const handleAppointmentConfirm = () => {
|
|
27866
|
+
if (!selectedSlot) {
|
|
27867
|
+
setSelectionError("Please select a time slot");
|
|
27868
|
+
return;
|
|
27869
|
+
}
|
|
27870
|
+
if (allowTopic && !topic.trim()) {
|
|
27871
|
+
setSelectionError("Please enter a meeting topic");
|
|
27872
|
+
return;
|
|
27873
|
+
}
|
|
27874
|
+
setSelectionError(null);
|
|
27493
27875
|
setIsSubmitting(true);
|
|
27494
|
-
|
|
27876
|
+
console.log('[MicrosoftCalendarCard] Confirming appointment:', {
|
|
27877
|
+
slot: selectedSlot,
|
|
27878
|
+
topic: topic.trim()
|
|
27879
|
+
});
|
|
27495
27880
|
setTimeout(() => {
|
|
27496
27881
|
onComplete?.(action.toolCallId, {
|
|
27497
27882
|
...action.state,
|
|
@@ -27503,22 +27888,149 @@ function GoogleCalendarCard({ action, onComplete, accentColor, className = '' })
|
|
|
27503
27888
|
});
|
|
27504
27889
|
}, 50);
|
|
27505
27890
|
};
|
|
27506
|
-
//
|
|
27507
|
-
|
|
27891
|
+
// Handle "Use different email" button
|
|
27892
|
+
const handleUseDifferentEmail = () => {
|
|
27893
|
+
setEmail("");
|
|
27894
|
+
setEmailError(null);
|
|
27895
|
+
setOtpValues(Array(6).fill(''));
|
|
27896
|
+
setOtpError(null);
|
|
27897
|
+
onComplete?.(action.toolCallId, {
|
|
27898
|
+
phase: "awaiting_email",
|
|
27899
|
+
email: null,
|
|
27900
|
+
otpVerified: false,
|
|
27901
|
+
otpAttempts: 0,
|
|
27902
|
+
availableSlots: [],
|
|
27903
|
+
selectedSlot: null,
|
|
27904
|
+
topic: null,
|
|
27905
|
+
bookedEventId: null,
|
|
27906
|
+
bookedEventLink: null,
|
|
27907
|
+
allowTopic,
|
|
27908
|
+
errorMessage: null,
|
|
27909
|
+
});
|
|
27910
|
+
};
|
|
27911
|
+
// Phase 5: Booked Confirmation
|
|
27912
|
+
if (phase === "booked") {
|
|
27508
27913
|
const bookedSlot = state.selectedSlot;
|
|
27509
27914
|
const bookedTopic = state.topic;
|
|
27510
27915
|
const eventLink = state.bookedEventLink;
|
|
27511
|
-
|
|
27512
|
-
|
|
27513
|
-
|
|
27916
|
+
const bookedEmail = state.email;
|
|
27917
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-booked ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx("div", { className: "ai-chat-action-success-icon-wrapper", children: jsx(CheckIcon, {}) }), "Appointment Confirmed"] }), jsxs("div", { className: "ai-chat-action-body", children: [bookedTopic && (jsxs("div", { className: "ai-chat-action-detail-box", children: [jsx("span", { className: "ai-chat-action-label-small", children: "TOPIC" }), jsx("span", { className: "ai-chat-action-value-large", children: bookedTopic })] })), bookedSlot && bookedSlot.startTime && (jsxs("div", { className: "ai-chat-action-detail-box", children: [jsx("span", { className: "ai-chat-action-label-small", children: "TIME" }), jsx("span", { className: "ai-chat-action-value-large", children: bookedSlot.displayTime || new Date(bookedSlot.startTime).toLocaleString() })] })), bookedEmail && (jsxs("div", { className: "ai-chat-action-hint", children: ["A calendar invitation has been sent to ", bookedEmail] })), eventLink && (jsxs("a", { href: eventLink, target: "_blank", rel: "noopener noreferrer", className: "ai-chat-action-link-button", children: ["View in Outlook Calendar", jsx(ExternalLinkIcon$1, {})] }))] })] }));
|
|
27918
|
+
}
|
|
27919
|
+
// Phase 6: Cancelled Confirmation
|
|
27920
|
+
if (phase === "cancelled") {
|
|
27921
|
+
const cancelledSubject = state.cancelledEventSubject;
|
|
27922
|
+
const userEmail = state.email;
|
|
27923
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-booked ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx("div", { className: "ai-chat-action-success-icon-wrapper", children: jsx(CheckIcon, {}) }), "Appointment Cancelled"] }), jsxs("div", { className: "ai-chat-action-body", children: [cancelledSubject && (jsxs("div", { className: "ai-chat-action-detail-box", children: [jsx("span", { className: "ai-chat-action-label-small", children: "CANCELLED" }), jsx("span", { className: "ai-chat-action-value-large", children: cancelledSubject })] })), userEmail && (jsxs("div", { className: "ai-chat-action-hint", children: ["A cancellation notice has been sent to ", userEmail] }))] })] }));
|
|
27924
|
+
}
|
|
27925
|
+
// Error State
|
|
27926
|
+
if (phase === "error") {
|
|
27927
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-error ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx(ErrorIcon, {}), "Error"] }), jsxs("div", { className: "ai-chat-action-body", children: [jsx("div", { className: "ai-chat-action-error-message", children: state.errorMessage || "An error occurred. Please try again." }), jsx("button", { className: "ai-chat-action-button-secondary", type: "button", onClick: () => {
|
|
27928
|
+
setEmail("");
|
|
27929
|
+
setEmailError(null);
|
|
27930
|
+
setOtpValues(Array(6).fill(''));
|
|
27931
|
+
setOtpError(null);
|
|
27932
|
+
onComplete?.(action.toolCallId, {
|
|
27933
|
+
phase: "awaiting_email",
|
|
27934
|
+
email: null,
|
|
27935
|
+
otpVerified: false,
|
|
27936
|
+
otpAttempts: 0,
|
|
27937
|
+
availableSlots: [],
|
|
27938
|
+
selectedSlot: null,
|
|
27939
|
+
topic: null,
|
|
27940
|
+
bookedEventId: null,
|
|
27941
|
+
bookedEventLink: null,
|
|
27942
|
+
allowTopic,
|
|
27943
|
+
errorMessage: null,
|
|
27944
|
+
});
|
|
27945
|
+
}, children: "Start Over" })] })] }));
|
|
27946
|
+
}
|
|
27947
|
+
// Loading State
|
|
27514
27948
|
if (isSubmitting) {
|
|
27515
|
-
return (jsx("div", { className: `ai-chat-action-card ai-chat-action-skeleton ${className}`, style: accentStyle, children: jsxs("div", { className: "ai-chat-action-skeleton-content", children: [jsxs("div", { className: "ai-chat-action-skeleton-header", children: [jsx(Skeleton, { width: "28px", height: "28px", borderRadius: "50%" }), jsx(Skeleton, { width: "180px", height: "20px", borderRadius: "4px" })] }), jsxs("div", { className: "ai-chat-action-skeleton-box", children: [jsx(Skeleton, { width: "60px", height: "12px", borderRadius: "4px" }), jsx(Skeleton, { width: "120px", height: "18px", borderRadius: "4px" })] }),
|
|
27516
|
-
}
|
|
27517
|
-
//
|
|
27518
|
-
|
|
27519
|
-
|
|
27520
|
-
|
|
27521
|
-
|
|
27949
|
+
return (jsx("div", { className: `ai-chat-action-card ai-chat-action-skeleton ${className}`, style: accentStyle, children: jsxs("div", { className: "ai-chat-action-skeleton-content", children: [jsxs("div", { className: "ai-chat-action-skeleton-header", children: [jsx(Skeleton$1, { width: "28px", height: "28px", borderRadius: "50%" }), jsx(Skeleton$1, { width: "180px", height: "20px", borderRadius: "4px" })] }), jsxs("div", { className: "ai-chat-action-skeleton-box", children: [jsx(Skeleton$1, { width: "60px", height: "12px", borderRadius: "4px" }), jsx(Skeleton$1, { width: "120px", height: "18px", borderRadius: "4px" })] }), jsx(Skeleton$1, { width: "100%", height: "44px", borderRadius: "999px" })] }) }));
|
|
27950
|
+
}
|
|
27951
|
+
// Phase 1: Email Input
|
|
27952
|
+
if (phase === "awaiting_email") {
|
|
27953
|
+
const displayError = state.errorMessage || emailError;
|
|
27954
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-card--closable ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx(CalendarIcon, {}), "Schedule an Appointment", showCloseButton && (jsx(CloseButton, { onClick: handleDismiss, ariaLabel: "Cancel appointment booking" }))] }), jsxs("div", { className: "ai-chat-action-body", children: [jsx("div", { className: "ai-chat-action-hint", children: "We'll send a verification code to your email" }), jsxs("div", { className: "ai-chat-action-field", children: [jsx("label", { htmlFor: `email-${action.toolCallId}`, className: "ai-chat-action-label", children: "Email Address" }), jsx("input", { id: `email-${action.toolCallId}`, type: "email", className: "ai-chat-action-input", placeholder: "your@email.com", value: email, onChange: (e) => {
|
|
27955
|
+
setEmail(e.target.value);
|
|
27956
|
+
setEmailError(null);
|
|
27957
|
+
}, onKeyPress: (e) => {
|
|
27958
|
+
if (e.key === 'Enter') {
|
|
27959
|
+
handleEmailSubmit();
|
|
27960
|
+
}
|
|
27961
|
+
}, autoFocus: true })] }), displayError && (jsx("div", { className: "ai-chat-action-error", children: displayError })), jsx("button", { className: "ai-chat-action-button", type: "button", onClick: handleEmailSubmit, children: "Continue" })] })] }));
|
|
27962
|
+
}
|
|
27963
|
+
// Phase 2: OTP Input
|
|
27964
|
+
if (phase === "awaiting_otp") {
|
|
27965
|
+
const displayError = state.errorMessage || otpError;
|
|
27966
|
+
const userEmail = state.email;
|
|
27967
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-card--closable ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx(MailIcon, {}), "Verify Your Email", showCloseButton && (jsx(CloseButton, { onClick: handleDismiss, ariaLabel: "Cancel appointment booking" }))] }), jsxs("div", { className: "ai-chat-action-body", children: [jsxs("div", { className: "ai-chat-action-hint", children: ["We sent a 6-digit code to ", jsx("strong", { children: userEmail })] }), jsx(PinInputGroup$1, { values: otpValues, onChange: (newValues) => {
|
|
27968
|
+
setOtpValues(newValues);
|
|
27969
|
+
setOtpError(null);
|
|
27970
|
+
// Auto-submit when all 6 digits are entered
|
|
27971
|
+
if (newValues.every(v => v.length === 1)) {
|
|
27972
|
+
console.log('[MicrosoftCalendarCard] Auto-submitting OTP (all 6 digits entered)');
|
|
27973
|
+
setIsSubmitting(true);
|
|
27974
|
+
const code = newValues.join('');
|
|
27975
|
+
setTimeout(() => {
|
|
27976
|
+
onComplete?.(action.toolCallId, {
|
|
27977
|
+
...action.state,
|
|
27978
|
+
otpCode: code,
|
|
27979
|
+
});
|
|
27980
|
+
}, 100);
|
|
27981
|
+
}
|
|
27982
|
+
}, disabled: isSubmitting }), displayError && (jsx("div", { className: "ai-chat-action-error", children: displayError })), jsx("button", { className: "ai-chat-action-button", type: "button", onClick: handleOtpSubmit, disabled: otpValues.join('').length !== 6, children: "Verify Code" }), jsx("button", { className: "ai-chat-action-button-secondary", type: "button", onClick: handleUseDifferentEmail, children: "Use different email" })] })] }));
|
|
27983
|
+
}
|
|
27984
|
+
// Phase 3: Options Menu (with inline cancel buttons and confirmation)
|
|
27985
|
+
if (phase === "awaiting_options") {
|
|
27986
|
+
const userAppointments = state.userAppointments || [];
|
|
27987
|
+
const maxAppointments = state.maxAppointmentsPerUser || 3;
|
|
27988
|
+
const appointmentCount = userAppointments.length;
|
|
27989
|
+
const canBook = appointmentCount < maxAppointments;
|
|
27990
|
+
const hasAppointments = appointmentCount > 0;
|
|
27991
|
+
const displayError = state.errorMessage || selectionError;
|
|
27992
|
+
// If confirming cancellation, show confirmation dialog
|
|
27993
|
+
if (selectedId) {
|
|
27994
|
+
const appointmentToCancel = userAppointments.find(appt => appt.id === selectedId);
|
|
27995
|
+
if (!appointmentToCancel) {
|
|
27996
|
+
setSelectedId(null); // Reset if appointment not found
|
|
27997
|
+
}
|
|
27998
|
+
else {
|
|
27999
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-card--closable ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx(CalendarIcon, {}), "Confirm Cancellation", showCloseButton && (jsx(CloseButton, { onClick: handleDismiss, ariaLabel: "Cancel appointment booking" }))] }), jsxs("div", { className: "ai-chat-action-body", children: [jsx("div", { className: "ai-chat-action-hint", children: "Are you sure you want to cancel this appointment?" }), jsx("div", { className: "ai-chat-action-appointment-item", style: { marginBottom: '16px' }, children: jsxs("div", { className: "ai-chat-action-appointment-info", children: [jsx("div", { className: "ai-chat-action-appointment-subject", children: appointmentToCancel.subject }), jsx("div", { className: "ai-chat-action-appointment-time", children: appointmentToCancel.displayTime })] }) }), displayError && (jsx("div", { className: "ai-chat-action-error", children: displayError })), jsxs("div", { className: "ai-chat-action-button-group", children: [jsx("button", { className: "ai-chat-action-button", type: "button", onClick: () => {
|
|
28000
|
+
setIsSubmitting(true);
|
|
28001
|
+
onComplete?.(action.toolCallId, {
|
|
28002
|
+
...action.state,
|
|
28003
|
+
selectedOption: "cancel",
|
|
28004
|
+
selectedAppointmentId: selectedId,
|
|
28005
|
+
});
|
|
28006
|
+
}, disabled: isSubmitting, children: isSubmitting ? 'Cancelling...' : 'Confirm Cancellation' }), jsx("button", { className: "ai-chat-action-button-secondary", type: "button", onClick: () => {
|
|
28007
|
+
setSelectedId(null);
|
|
28008
|
+
setSelectionError(null);
|
|
28009
|
+
}, disabled: isSubmitting, children: "Go Back" })] })] })] }));
|
|
28010
|
+
}
|
|
28011
|
+
}
|
|
28012
|
+
// Normal view with inline cancel buttons
|
|
28013
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-card--closable ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx(CalendarIcon, {}), "Manage Your Appointments", showCloseButton && (jsx(CloseButton, { onClick: handleDismiss, ariaLabel: "Cancel appointment booking" }))] }), jsxs("div", { className: "ai-chat-action-body", children: [jsxs("div", { className: "ai-chat-action-hint", children: ["You have ", appointmentCount, " active appointment", appointmentCount !== 1 ? 's' : '', canBook && ` (${maxAppointments - appointmentCount} slot${maxAppointments - appointmentCount !== 1 ? 's' : ''} remaining)`] }), hasAppointments && (jsxs("div", { className: "ai-chat-action-appointment-list", children: [jsx("div", { className: "ai-chat-action-label", children: "Your Upcoming Appointments" }), userAppointments.map((appt) => (jsxs("div", { className: "ai-chat-action-appointment-item", children: [jsxs("div", { className: "ai-chat-action-appointment-info", children: [jsx("div", { className: "ai-chat-action-appointment-subject", children: appt.subject }), jsx("div", { className: "ai-chat-action-appointment-time", children: appt.displayTime })] }), jsx("button", { className: "ai-chat-action-button-secondary", type: "button", onClick: () => {
|
|
28014
|
+
setSelectedId(appt.id);
|
|
28015
|
+
setSelectionError(null);
|
|
28016
|
+
}, children: "Cancel" })] }, appt.id)))] })), displayError && (jsx("div", { className: "ai-chat-action-error", children: displayError })), canBook && (jsx("button", { className: "ai-chat-action-button", type: "button", onClick: () => {
|
|
28017
|
+
setIsSubmitting(true);
|
|
28018
|
+
onComplete?.(action.toolCallId, {
|
|
28019
|
+
...action.state,
|
|
28020
|
+
selectedOption: "book",
|
|
28021
|
+
});
|
|
28022
|
+
}, disabled: isSubmitting, children: isSubmitting ? 'Loading...' : 'Book New Appointment' })), !canBook && !hasAppointments && (jsx("div", { className: "ai-chat-action-hint", children: "No appointments found." }))] })] }));
|
|
28023
|
+
}
|
|
28024
|
+
// Phase 4: Appointment Selection (Booking)
|
|
28025
|
+
if (phase === "awaiting_booking") {
|
|
28026
|
+
const displayError = state.errorMessage || selectionError;
|
|
28027
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-action-card--closable ${className}`, style: accentStyle, children: [jsxs("div", { className: "ai-chat-action-header", children: [jsx(CalendarIcon, {}), "Select Appointment Time", showCloseButton && (jsx(CloseButton, { onClick: handleDismiss, ariaLabel: "Cancel appointment booking" }))] }), jsxs("div", { className: "ai-chat-action-body", children: [allowTopic && (jsxs("div", { className: "ai-chat-action-field", children: [jsx("label", { htmlFor: `topic-${action.toolCallId}`, className: "ai-chat-action-label", children: "Meeting Topic" }), jsx("input", { id: `topic-${action.toolCallId}`, type: "text", className: "ai-chat-action-input", placeholder: "e.g., Product Demo", value: topic, onChange: (e) => setTopic(e.target.value) })] })), jsxs("div", { className: "ai-chat-action-field", children: [jsx("label", { className: "ai-chat-action-label", children: "Select Date" }), jsx("div", { className: "ai-chat-action-date-grid", children: dates.slice(0, 7).map((date) => (jsx("button", { type: "button", className: `ai-chat-action-date-btn ${selectedDate === date ? "active" : ""}`, onClick: () => {
|
|
28028
|
+
setSelectedDate(date);
|
|
28029
|
+
setSelectedSlot(null);
|
|
28030
|
+
}, children: formatDate(date) }, date))) })] }), selectedDate && slotsForSelectedDate.length > 0 && (jsxs("div", { className: "ai-chat-action-field", children: [jsx("label", { className: "ai-chat-action-label", children: "Select Time" }), jsx("div", { className: "ai-chat-action-time-grid", children: slotsForSelectedDate.map((slot) => (jsx("button", { type: "button", className: `ai-chat-action-time-btn ${selectedSlot?.startTime === slot.startTime ? "active" : ""}`, onClick: () => setSelectedSlot(slot), children: slot.displayTime || new Date(slot.startTime).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) }, slot.startTime))) })] })), displayError && (jsx("div", { className: "ai-chat-action-error", children: displayError })), jsx("button", { className: "ai-chat-action-button", type: "button", onClick: handleAppointmentConfirm, disabled: !selectedSlot, children: "Confirm Appointment" }), availableSlots.length === 0 && (jsx("div", { className: "ai-chat-action-hint", children: "No available time slots found." }))] })] }));
|
|
28031
|
+
}
|
|
28032
|
+
// Fallback
|
|
28033
|
+
return null;
|
|
27522
28034
|
}
|
|
27523
28035
|
|
|
27524
28036
|
function truncate(text, maxLength) {
|
|
@@ -27529,17 +28041,30 @@ function truncate(text, maxLength) {
|
|
|
27529
28041
|
function ExternalLinkIcon() {
|
|
27530
28042
|
return (jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }), jsx("polyline", { points: "15 3 21 3 21 9" }), jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })] }));
|
|
27531
28043
|
}
|
|
28044
|
+
function SingleLinkPreview({ link, onLinkClick, accentColor }) {
|
|
28045
|
+
const domain = (() => {
|
|
28046
|
+
if (!link.url)
|
|
28047
|
+
return '';
|
|
28048
|
+
try {
|
|
28049
|
+
return new URL(link.url).hostname.replace('www.', '');
|
|
28050
|
+
}
|
|
28051
|
+
catch {
|
|
28052
|
+
return link.url;
|
|
28053
|
+
}
|
|
28054
|
+
})();
|
|
28055
|
+
const style = accentColor ? { '--action-accent': accentColor } : undefined;
|
|
28056
|
+
return (jsxs("div", { className: "ai-chat-action-card ai-chat-link-preview", onClick: onLinkClick, style: style, role: "link", tabIndex: 0, onKeyDown: (e) => e.key === 'Enter' && onLinkClick(), children: [link.image && (jsx("div", { className: "ai-chat-link-preview__image", children: jsx("img", { src: link.image, alt: link.title, onError: (e) => {
|
|
28057
|
+
e.currentTarget.parentElement.style.display = 'none';
|
|
28058
|
+
} }) })), jsxs("div", { className: "ai-chat-link-preview__content", children: [jsxs("div", { className: "ai-chat-link-preview__site", children: [link.favicon && (jsx("img", { src: link.favicon, alt: "", className: "ai-chat-link-preview__favicon", onError: (e) => {
|
|
28059
|
+
e.currentTarget.style.display = 'none';
|
|
28060
|
+
} })), jsx("span", { className: "ai-chat-link-preview__domain", children: link.siteName || domain })] }), jsx("h4", { className: "ai-chat-link-preview__title", children: link.title }), link.description && (jsx("p", { className: "ai-chat-link-preview__description", children: truncate(link.description, 120) }))] }), jsx("div", { className: "ai-chat-link-preview__arrow", children: jsx(ExternalLinkIcon, {}) })] }));
|
|
28061
|
+
}
|
|
27532
28062
|
function LinkPreviewCard({ action, onComplete, accentColor }) {
|
|
27533
28063
|
const rawState = action.state;
|
|
27534
28064
|
const hasCompletedRef = useRef(false);
|
|
27535
28065
|
// Provide safe defaults if state is missing
|
|
27536
28066
|
const state = {
|
|
27537
|
-
|
|
27538
|
-
title: rawState?.title || 'Link',
|
|
27539
|
-
description: rawState?.description,
|
|
27540
|
-
image: rawState?.image,
|
|
27541
|
-
siteName: rawState?.siteName,
|
|
27542
|
-
favicon: rawState?.favicon,
|
|
28067
|
+
links: rawState?.links || [],
|
|
27543
28068
|
context: rawState?.context,
|
|
27544
28069
|
status: rawState?.status || 'displaying',
|
|
27545
28070
|
error: rawState?.error,
|
|
@@ -27547,34 +28072,29 @@ function LinkPreviewCard({ action, onComplete, accentColor }) {
|
|
|
27547
28072
|
const isError = state.status === 'error';
|
|
27548
28073
|
// Auto-complete on mount so AI can continue generating text response
|
|
27549
28074
|
useEffect(() => {
|
|
27550
|
-
if (!action.done && !hasCompletedRef.current && onComplete && state.
|
|
28075
|
+
if (!action.done && !hasCompletedRef.current && onComplete && state.links.length > 0) {
|
|
27551
28076
|
hasCompletedRef.current = true;
|
|
27552
28077
|
// Signal completion immediately - the card is displayed, AI can continue
|
|
27553
28078
|
onComplete(action.toolCallId, { ...state, status: 'displaying' });
|
|
27554
28079
|
}
|
|
27555
28080
|
}, [action.done, action.toolCallId, onComplete, state]);
|
|
27556
|
-
const
|
|
27557
|
-
if (
|
|
27558
|
-
window.open(
|
|
28081
|
+
const handleLinkClick = (url) => {
|
|
28082
|
+
if (url) {
|
|
28083
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
27559
28084
|
}
|
|
27560
28085
|
onComplete?.(action.toolCallId, { ...state, status: 'clicked' });
|
|
27561
28086
|
};
|
|
27562
|
-
|
|
27563
|
-
|
|
27564
|
-
|
|
27565
|
-
|
|
27566
|
-
|
|
27567
|
-
|
|
27568
|
-
|
|
27569
|
-
|
|
27570
|
-
|
|
27571
|
-
|
|
27572
|
-
|
|
27573
|
-
return (jsxs("div", { className: `ai-chat-link-preview ${isError ? 'ai-chat-link-preview--error' : ''}`, onClick: handleClick, style: style, role: "link", tabIndex: 0, onKeyDown: (e) => e.key === 'Enter' && handleClick(), children: [state.image && !isError && (jsx("div", { className: "ai-chat-link-preview__image", children: jsx("img", { src: state.image, alt: state.title, onError: (e) => {
|
|
27574
|
-
e.currentTarget.parentElement.style.display = 'none';
|
|
27575
|
-
} }) })), jsxs("div", { className: "ai-chat-link-preview__content", children: [jsxs("div", { className: "ai-chat-link-preview__site", children: [state.favicon && (jsx("img", { src: state.favicon, alt: "", className: "ai-chat-link-preview__favicon", onError: (e) => {
|
|
27576
|
-
e.currentTarget.style.display = 'none';
|
|
27577
|
-
} })), jsx("span", { className: "ai-chat-link-preview__domain", children: state.siteName || domain })] }), jsx("h4", { className: "ai-chat-link-preview__title", children: state.title }), state.description && (jsx("p", { className: "ai-chat-link-preview__description", children: truncate(state.description, 120) })), state.context && (jsx("p", { className: "ai-chat-link-preview__context", children: state.context })), isError && state.error && (jsx("p", { className: "ai-chat-link-preview__error-text", children: state.error }))] }), jsx("div", { className: "ai-chat-link-preview__arrow", children: jsx(ExternalLinkIcon, {}) })] }));
|
|
28087
|
+
if (isError) {
|
|
28088
|
+
return (jsx("div", { className: "ai-chat-action-card ai-chat-link-preview ai-chat-link-preview--error", children: jsx("div", { className: "ai-chat-link-preview__content", children: jsx("p", { className: "ai-chat-link-preview__error-text", children: state.error || 'Failed to load preview' }) }) }));
|
|
28089
|
+
}
|
|
28090
|
+
if (state.links.length === 0) {
|
|
28091
|
+
return null;
|
|
28092
|
+
}
|
|
28093
|
+
return (jsxs("div", { className: "ai-chat-link-preview-container", children: [state.context && (jsx("p", { className: "ai-chat-link-preview__context", style: { marginBottom: '8px', fontSize: '0.9em', color: 'var(--ai-chat-fg-muted)' }, children: state.context })), jsx("div", { className: "ai-chat-link-preview-grid", style: {
|
|
28094
|
+
display: 'grid',
|
|
28095
|
+
gridTemplateColumns: state.links.length === 1 ? '1fr' : state.links.length === 2 ? 'repeat(2, 1fr)' : 'repeat(3, 1fr)',
|
|
28096
|
+
gap: '12px',
|
|
28097
|
+
}, children: state.links.map((link, index) => (jsx(SingleLinkPreview, { link: link, onLinkClick: () => handleLinkClick(link.url), accentColor: accentColor }, index))) })] }));
|
|
27578
28098
|
}
|
|
27579
28099
|
|
|
27580
28100
|
function PlayIcon() {
|
|
@@ -27640,7 +28160,7 @@ function VideoPlayerCard({ action, onComplete, accentColor }) {
|
|
|
27640
28160
|
return src;
|
|
27641
28161
|
}
|
|
27642
28162
|
};
|
|
27643
|
-
return (jsxs("div", { className: "ai-chat-
|
|
28163
|
+
return (jsxs("div", { className: "ai-chat-action-card ai-chat-video-player", style: style, children: [jsx("div", { className: "ai-chat-video-player__container", children: isError ? (jsx("div", { className: "ai-chat-video-player__error", children: jsx("span", { children: state.error || 'Could not load video' }) })) : !isPlaying && state.thumbnailUrl ? (jsxs("div", { className: "ai-chat-video-player__thumbnail", onClick: handlePlay, children: [jsx("img", { src: state.thumbnailUrl, alt: state.title || 'Video thumbnail' }), jsx("button", { className: "ai-chat-video-player__play-btn", "aria-label": "Play video", children: jsx(PlayIcon, {}) }), jsx("div", { className: "ai-chat-video-player__provider-badge", children: getProviderLabel(state.provider) })] })) : !isPlaying ? (jsxs("div", { className: "ai-chat-video-player__placeholder", onClick: handlePlay, children: [jsx("button", { className: "ai-chat-video-player__play-btn", "aria-label": "Play video", children: jsx(PlayIcon, {}) }), jsx("span", { className: "ai-chat-video-player__click-text", children: "Click to play" }), jsx("div", { className: "ai-chat-video-player__provider-badge", children: getProviderLabel(state.provider) })] })) : state.provider === 'direct' ? (jsx("video", { src: state.embedUrl, controls: true, autoPlay: true, className: "ai-chat-video-player__video" })) : (jsx("iframe", { src: getEmbedSrc(), className: "ai-chat-video-player__iframe", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true, title: state.title || 'Video player' })) }), state.context && (jsx("div", { className: "ai-chat-video-player__context", children: state.context }))] }));
|
|
27644
28164
|
}
|
|
27645
28165
|
|
|
27646
28166
|
function MapPinIcon() {
|
|
@@ -27701,7 +28221,9 @@ function LocationItem({ location, settings, accentColor, onDirections, showMap =
|
|
|
27701
28221
|
}
|
|
27702
28222
|
};
|
|
27703
28223
|
const style = accentColor ? { '--action-accent': accentColor } : undefined;
|
|
27704
|
-
|
|
28224
|
+
// Use smaller map height in compact mode
|
|
28225
|
+
const effectiveMapHeight = compact ? Math.min(mapHeight, 140) : mapHeight;
|
|
28226
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-location-card ${compact ? 'ai-chat-location-card--compact' : ''}`, style: style, children: [showMap && settings.showMap !== false && (jsx("div", { className: "ai-chat-location-card__map", style: { height: effectiveMapHeight }, children: jsx("iframe", { src: getMapEmbedUrl(), allowFullScreen: true, loading: "lazy", referrerPolicy: "no-referrer-when-downgrade", title: `Map of ${location.name}` }) })), jsxs("div", { className: "ai-chat-location-card__content", children: [jsxs("div", { className: "ai-chat-location-card__header", children: [jsx("h4", { className: "ai-chat-location-card__name", children: location.name }), location.type && (jsx("span", { className: "ai-chat-location-card__type", children: location.type })), openStatus !== null && (jsx("span", { className: `ai-chat-location-card__status ai-chat-location-card__status--${openStatus ? 'open' : 'closed'}`, children: openStatus ? 'Open' : 'Closed' }))] }), jsxs("p", { className: "ai-chat-location-card__address", children: [jsx(MapPinIcon, {}), location.address] }), location.description && (jsx("p", { className: "ai-chat-location-card__description", children: location.description })), settings.showHours !== false && location.hours && location.hours.length > 0 && (jsx(HoursDisplay, { hours: location.hours, compact: compact })), settings.showPhone !== false && location.phone && (jsxs("button", { type: "button", className: "ai-chat-location-card__phone", onClick: handleCall, children: [jsx(PhoneIcon, {}), location.phone] })), jsxs("div", { className: "ai-chat-location-card__actions", children: [settings.showDirectionsButton !== false && (jsxs("button", { type: "button", className: "ai-chat-location-card__button", style: accentColor ? { backgroundColor: accentColor } : undefined, onClick: onDirections, children: [jsx(NavigationIcon, {}), compact ? 'Directions' : 'Get Directions'] })), !compact && location.website && (jsxs("a", { href: location.website, target: "_blank", rel: "noopener noreferrer", className: "ai-chat-location-card__link", children: [jsx(GlobeIcon, {}), "Website"] }))] })] })] }));
|
|
27705
28227
|
}
|
|
27706
28228
|
function LocationCard({ action, onComplete, accentColor, maxColumns = 3 }) {
|
|
27707
28229
|
const rawState = action.state;
|
|
@@ -27735,40 +28257,443 @@ function LocationCard({ action, onComplete, accentColor, maxColumns = 3 }) {
|
|
|
27735
28257
|
if (isSingleLocation) {
|
|
27736
28258
|
return (jsx(LocationItem, { location: locations[0], settings: settings, accentColor: accentColor, onDirections: () => handleDirections(locations[0]), showMap: true }));
|
|
27737
28259
|
}
|
|
27738
|
-
return (jsxs("div", { className: `ai-chat-location-card-list ai-chat-location-card-list--${layout}`, children: [jsxs("div", { className: "ai-chat-location-card-list__header", children: [jsx(MapPinIcon, {}), jsxs("span", { children: [locations.length, " Locations"] })] }), layout === 'stack' && (jsx("div", { className:
|
|
27739
|
-
gridTemplateColumns: `repeat(${stackColumns}, minmax(0, 1fr))`,
|
|
27740
|
-
}, children: locations.map((location) => (jsx(LocationItem, { location: location, settings: settings, accentColor: accentColor, onDirections: () => handleDirections(location), showMap: settings.showMap !== false, compact: locations.length > 2 }, location.id))) })), layout === 'grid' && (jsx("div", { className: "ai-chat-location-card-list__grid", children: locations.map((location) => (jsx(LocationItem, { location: location, settings: settings, accentColor: accentColor, onDirections: () => handleDirections(location), showMap: false, compact: true }, location.id))) })), layout === 'carousel' && (jsx("div", { className: "ai-chat-location-card-list__carousel", children: locations.map((location) => (jsx(LocationItem, { location: location, settings: settings, accentColor: accentColor, onDirections: () => handleDirections(location), showMap: true, compact: false }, location.id))) }))] }));
|
|
28260
|
+
return (jsxs("div", { className: `ai-chat-location-card-list ai-chat-location-card-list--${layout}`, children: [jsxs("div", { className: "ai-chat-location-card-list__header", children: [jsx(MapPinIcon, {}), jsxs("span", { children: [locations.length, " Locations"] })] }), layout === 'stack' && (jsx("div", { className: `ai-chat-location-card-list__stack ai-chat-location-card-list__stack--cols-${stackColumns}`, children: locations.map((location) => (jsx(LocationItem, { location: location, settings: settings, accentColor: accentColor, onDirections: () => handleDirections(location), showMap: settings.showMap !== false, compact: locations.length > 2 }, location.id))) })), layout === 'grid' && (jsx("div", { className: "ai-chat-location-card-list__grid", children: locations.map((location) => (jsx(LocationItem, { location: location, settings: settings, accentColor: accentColor, onDirections: () => handleDirections(location), showMap: false, compact: true }, location.id))) })), layout === 'carousel' && (jsx("div", { className: "ai-chat-location-card-list__carousel", children: locations.map((location) => (jsx(LocationItem, { location: location, settings: settings, accentColor: accentColor, onDirections: () => handleDirections(location), showMap: true, compact: false }, location.id))) }))] }));
|
|
27741
28261
|
}
|
|
27742
28262
|
|
|
27743
|
-
|
|
27744
|
-
|
|
27745
|
-
const frontendActionHandlers = {};
|
|
27746
|
-
const actionRenderers = {};
|
|
27747
|
-
function getFrontendActionHandler(implementation) {
|
|
27748
|
-
return frontendActionHandlers[implementation];
|
|
28263
|
+
function UsersIcon() {
|
|
28264
|
+
return (jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }), jsx("circle", { cx: "9", cy: "7", r: "4" }), jsx("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }), jsx("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })] }));
|
|
27749
28265
|
}
|
|
27750
|
-
|
|
27751
|
-
|
|
27752
|
-
}
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
|
|
28266
|
+
const AVATAR_COLORS = [
|
|
28267
|
+
{ bg: '#E8F5E9', text: '#2E7D32' }, // Green
|
|
28268
|
+
{ bg: '#E3F2FD', text: '#1565C0' }, // Blue
|
|
28269
|
+
{ bg: '#FFF3E0', text: '#E65100' }, // Orange
|
|
28270
|
+
{ bg: '#F3E5F5', text: '#7B1FA2' }, // Purple
|
|
28271
|
+
{ bg: '#FFEBEE', text: '#C62828' }, // Red
|
|
28272
|
+
{ bg: '#E0F7FA', text: '#00838F' }, // Cyan
|
|
28273
|
+
{ bg: '#FFF8E1', text: '#F9A825' }, // Amber
|
|
28274
|
+
{ bg: '#FCE4EC', text: '#AD1457' }, // Pink
|
|
28275
|
+
];
|
|
28276
|
+
function getInitials(name) {
|
|
28277
|
+
const parts = name.trim().split(/\s+/);
|
|
28278
|
+
if (parts.length === 1) {
|
|
28279
|
+
return parts[0].substring(0, 2).toUpperCase();
|
|
27756
28280
|
}
|
|
27757
|
-
return
|
|
27758
|
-
}
|
|
27759
|
-
function waitForActionState(toolCallId) {
|
|
27760
|
-
return new Promise((resolve) => {
|
|
27761
|
-
pendingResolvers.set(toolCallId, resolve);
|
|
27762
|
-
});
|
|
28281
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
27763
28282
|
}
|
|
27764
|
-
function
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
resolver(state);
|
|
27769
|
-
return;
|
|
28283
|
+
function getColorFromName(name) {
|
|
28284
|
+
let hash = 0;
|
|
28285
|
+
for (let i = 0; i < name.length; i++) {
|
|
28286
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
27770
28287
|
}
|
|
27771
|
-
|
|
28288
|
+
return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];
|
|
28289
|
+
}
|
|
28290
|
+
function ContactItem({ contact, settings, accentColor, onEmail, onPhone, compact = false, layout = 'vertical', }) {
|
|
28291
|
+
const style = accentColor ? { '--action-accent': accentColor } : undefined;
|
|
28292
|
+
const layoutClass = layout === 'horizontal'
|
|
28293
|
+
? 'ai-chat-contact-card--horizontal'
|
|
28294
|
+
: 'ai-chat-contact-card--vertical';
|
|
28295
|
+
return (jsxs("div", { className: `ai-chat-action-card ai-chat-contact-card ${layoutClass} ${compact ? 'ai-chat-contact-card--compact' : ''}`, style: style, children: [jsxs("div", { className: "ai-chat-contact-card__image-section", children: [contact.profilePictureUrl ? (jsx("img", { src: contact.profilePictureUrl, alt: contact.name, className: "ai-chat-contact-card__image", onError: (e) => {
|
|
28296
|
+
e.currentTarget.style.display = 'none';
|
|
28297
|
+
const placeholder = e.currentTarget.parentElement?.querySelector('.ai-chat-contact-card__initials');
|
|
28298
|
+
if (placeholder) {
|
|
28299
|
+
placeholder.style.display = 'flex';
|
|
28300
|
+
}
|
|
28301
|
+
} })) : null, (() => {
|
|
28302
|
+
const colors = getColorFromName(contact.name);
|
|
28303
|
+
return (jsx("div", { className: "ai-chat-contact-card__initials", style: {
|
|
28304
|
+
display: contact.profilePictureUrl ? 'none' : 'flex',
|
|
28305
|
+
backgroundColor: colors.bg,
|
|
28306
|
+
color: colors.text,
|
|
28307
|
+
}, children: getInitials(contact.name) }));
|
|
28308
|
+
})()] }), jsxs("div", { className: "ai-chat-contact-card__info", children: [jsx("h4", { className: "ai-chat-contact-card__name", children: contact.name }), settings.showRole !== false && contact.role && (jsx("p", { className: "ai-chat-contact-card__role", children: contact.role })), jsxs("div", { className: "ai-chat-contact-card__details", children: [settings.showEmail !== false && contact.email && (jsx("a", { href: `mailto:${contact.email}`, className: "ai-chat-contact-card__detail", onClick: onEmail, children: contact.email })), settings.showPhone !== false && contact.phone && (jsx("a", { href: `tel:${contact.phone}`, className: "ai-chat-contact-card__detail", onClick: onPhone, children: contact.phone }))] }), settings.showResponsibilities !== false && contact.responsibilities && contact.responsibilities.length > 0 && !compact && (jsxs("div", { className: "ai-chat-contact-card__responsibilities", children: [contact.responsibilities.slice(0, 3).map((resp, idx) => (jsx("span", { className: "ai-chat-contact-card__responsibility-tag", children: resp }, idx))), contact.responsibilities.length > 3 && (jsxs("span", { className: "ai-chat-contact-card__responsibility-more", children: ["+", contact.responsibilities.length - 3, " more"] }))] }))] })] }));
|
|
28309
|
+
}
|
|
28310
|
+
function ContactCard({ action, onComplete, accentColor, maxColumns = 3 }) {
|
|
28311
|
+
const rawState = action.state;
|
|
28312
|
+
const hasCompletedRef = useRef(false);
|
|
28313
|
+
const state = {
|
|
28314
|
+
contacts: rawState?.contacts || [],
|
|
28315
|
+
settings: rawState?.settings || {},
|
|
28316
|
+
query: rawState?.query,
|
|
28317
|
+
status: rawState?.status || 'displaying',
|
|
28318
|
+
};
|
|
28319
|
+
const { contacts, settings } = state;
|
|
28320
|
+
const isSingleContact = contacts.length === 1;
|
|
28321
|
+
const stackColumns = Math.min(Math.max(contacts.length, 1), maxColumns);
|
|
28322
|
+
useEffect(() => {
|
|
28323
|
+
if (!action.done && !hasCompletedRef.current && onComplete) {
|
|
28324
|
+
hasCompletedRef.current = true;
|
|
28325
|
+
onComplete(action.toolCallId, { ...state, status: 'displaying' });
|
|
28326
|
+
}
|
|
28327
|
+
}, [action.done, action.toolCallId, onComplete, state]);
|
|
28328
|
+
const handleContact = () => {
|
|
28329
|
+
onComplete?.(action.toolCallId, { ...state, status: 'contacted' });
|
|
28330
|
+
};
|
|
28331
|
+
if (contacts.length === 0) {
|
|
28332
|
+
return (jsxs("div", { className: "ai-chat-contact-card ai-chat-contact-card--empty", children: [jsx("div", { className: "ai-chat-contact-card__empty-icon", children: jsx(UsersIcon, {}) }), jsxs("p", { className: "ai-chat-contact-card__empty-text", children: ["No contacts found", state.query ? ` for "${state.query}"` : ''] })] }));
|
|
28333
|
+
}
|
|
28334
|
+
if (isSingleContact) {
|
|
28335
|
+
return (jsx(ContactItem, { contact: contacts[0], settings: settings, accentColor: accentColor, onEmail: handleContact, onPhone: handleContact, layout: settings.layout || 'horizontal' }));
|
|
28336
|
+
}
|
|
28337
|
+
const isWidget = maxColumns === 1;
|
|
28338
|
+
const stackClassName = isWidget
|
|
28339
|
+
? 'ai-chat-contact-card-list__stack ai-chat-contact-card-list__stack--widget'
|
|
28340
|
+
: 'ai-chat-contact-card-list__stack';
|
|
28341
|
+
return (jsxs("div", { className: "ai-chat-contact-card-list", style: { containerType: 'inline-size' }, children: [jsxs("div", { className: "ai-chat-contact-card-list__header", children: [jsx(UsersIcon, {}), jsxs("span", { children: [contacts.length, " Contacts"] })] }), jsx("div", { className: stackClassName, style: isWidget ? undefined : {
|
|
28342
|
+
gridTemplateColumns: `repeat(${stackColumns}, minmax(0, 1fr))`,
|
|
28343
|
+
}, children: contacts.map((contact) => (jsx(ContactItem, { contact: contact, settings: settings, accentColor: accentColor, onEmail: handleContact, onPhone: handleContact, compact: true, layout: settings.layout || 'vertical' }, contact.id))) })] }));
|
|
28344
|
+
}
|
|
28345
|
+
|
|
28346
|
+
function FormCard({ action, onComplete, onDismiss, accentColor }) {
|
|
28347
|
+
const state = action.state;
|
|
28348
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
28349
|
+
const [answers, setAnswers] = useState({});
|
|
28350
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
28351
|
+
const questions = state.questions || [];
|
|
28352
|
+
const currentQuestion = questions[currentStep];
|
|
28353
|
+
const totalQuestions = questions.length;
|
|
28354
|
+
const handleAnswerChange = (questionId, value) => {
|
|
28355
|
+
setAnswers((prev) => ({ ...prev, [questionId]: value }));
|
|
28356
|
+
};
|
|
28357
|
+
const handleNext = () => {
|
|
28358
|
+
if (currentStep < totalQuestions - 1) {
|
|
28359
|
+
setCurrentStep((prev) => prev + 1);
|
|
28360
|
+
}
|
|
28361
|
+
};
|
|
28362
|
+
const handlePrev = () => {
|
|
28363
|
+
if (currentStep > 0) {
|
|
28364
|
+
setCurrentStep((prev) => prev - 1);
|
|
28365
|
+
}
|
|
28366
|
+
};
|
|
28367
|
+
const handleSubmit = () => {
|
|
28368
|
+
if (!onComplete)
|
|
28369
|
+
return;
|
|
28370
|
+
setIsSubmitting(true);
|
|
28371
|
+
const formattedAnswers = Object.entries(answers).map(([questionId, value]) => ({
|
|
28372
|
+
questionId,
|
|
28373
|
+
value,
|
|
28374
|
+
}));
|
|
28375
|
+
onComplete(action.toolCallId, {
|
|
28376
|
+
...state,
|
|
28377
|
+
status: 'submitted',
|
|
28378
|
+
answers: formattedAnswers,
|
|
28379
|
+
});
|
|
28380
|
+
};
|
|
28381
|
+
const handleSkip = () => {
|
|
28382
|
+
if (!onComplete || !state.settings.allowSkip)
|
|
28383
|
+
return;
|
|
28384
|
+
onComplete(action.toolCallId, {
|
|
28385
|
+
...state,
|
|
28386
|
+
status: 'skipped',
|
|
28387
|
+
});
|
|
28388
|
+
};
|
|
28389
|
+
const isCurrentAnswered = () => {
|
|
28390
|
+
if (!currentQuestion)
|
|
28391
|
+
return false;
|
|
28392
|
+
const answer = answers[currentQuestion.id];
|
|
28393
|
+
if (!answer)
|
|
28394
|
+
return !currentQuestion.required;
|
|
28395
|
+
if (Array.isArray(answer))
|
|
28396
|
+
return answer.length > 0 || !currentQuestion.required;
|
|
28397
|
+
return answer.trim() !== '' || !currentQuestion.required;
|
|
28398
|
+
};
|
|
28399
|
+
const canSubmit = () => {
|
|
28400
|
+
return questions.every((q) => {
|
|
28401
|
+
const answer = answers[q.id];
|
|
28402
|
+
if (!q.required)
|
|
28403
|
+
return true;
|
|
28404
|
+
if (!answer)
|
|
28405
|
+
return false;
|
|
28406
|
+
if (Array.isArray(answer))
|
|
28407
|
+
return answer.length > 0;
|
|
28408
|
+
return answer.trim() !== '';
|
|
28409
|
+
});
|
|
28410
|
+
};
|
|
28411
|
+
const handleDismiss = () => {
|
|
28412
|
+
onDismiss?.(action.toolCallId);
|
|
28413
|
+
};
|
|
28414
|
+
// Error state
|
|
28415
|
+
if (state.status === 'error') {
|
|
28416
|
+
return (jsxs("div", { className: "ai-chat-form-card ai-chat-form-card--error", children: [jsxs("div", { className: "ai-chat-form-card__header", children: [jsx("span", { className: "ai-chat-form-card__icon", children: "\u26A0\uFE0F" }), jsx("span", { className: "ai-chat-form-card__title", children: "Form Error" })] }), jsx("p", { className: "ai-chat-form-card__error", children: state.error || 'Could not load form' })] }));
|
|
28417
|
+
}
|
|
28418
|
+
// Submitted state
|
|
28419
|
+
if (state.status === 'submitted' || action.done) {
|
|
28420
|
+
return (jsxs("div", { className: "ai-chat-form-card ai-chat-form-card--submitted", children: [jsxs("div", { className: "ai-chat-form-card__header", children: [jsx("span", { className: "ai-chat-form-card__icon", children: "\u2713" }), jsx("span", { className: "ai-chat-form-card__title", children: state.title })] }), jsx("p", { className: "ai-chat-form-card__success", children: state.settings.successMessage || 'Thank you for your response!' })] }));
|
|
28421
|
+
}
|
|
28422
|
+
// Skipped state
|
|
28423
|
+
if (state.status === 'skipped') {
|
|
28424
|
+
return (jsxs("div", { className: "ai-chat-form-card ai-chat-form-card--skipped", children: [jsxs("div", { className: "ai-chat-form-card__header", children: [jsx("span", { className: "ai-chat-form-card__icon", children: "\u21B7" }), jsx("span", { className: "ai-chat-form-card__title", children: state.title })] }), jsx("p", { className: "ai-chat-form-card__skipped-text", children: "Form skipped" })] }));
|
|
28425
|
+
}
|
|
28426
|
+
// No questions
|
|
28427
|
+
if (totalQuestions === 0) {
|
|
28428
|
+
return (jsxs("div", { className: "ai-chat-form-card ai-chat-form-card--empty", children: [jsxs("div", { className: "ai-chat-form-card__header", children: [jsx("span", { className: "ai-chat-form-card__icon", children: "\uD83D\uDCCB" }), jsx("span", { className: "ai-chat-form-card__title", children: state.title })] }), jsx("p", { className: "ai-chat-form-card__empty-text", children: "This form has no questions." })] }));
|
|
28429
|
+
}
|
|
28430
|
+
const showCloseButton = state.status === "displaying" && !action.done && Boolean(onDismiss);
|
|
28431
|
+
return (jsxs("div", { className: `ai-chat-form-card${showCloseButton ? " ai-chat-form-card--closable" : ""}`, children: [jsxs("div", { className: "ai-chat-form-card__header", children: [jsx("span", { className: "ai-chat-form-card__icon", children: "\uD83D\uDCCB" }), jsx("span", { className: "ai-chat-form-card__title", children: state.title }), showCloseButton && (jsx(CloseButton, { onClick: handleDismiss, ariaLabel: "Close form" }))] }), state.description && (jsx("p", { className: "ai-chat-form-card__description", children: state.description })), state.context && (jsx("p", { className: "ai-chat-form-card__context", children: state.context })), state.settings.showProgress && (jsxs("div", { className: "ai-chat-form-card__progress", children: [jsx("div", { className: "ai-chat-form-card__progress-bar", style: {
|
|
28432
|
+
width: `${((currentStep + 1) / totalQuestions) * 100}%`,
|
|
28433
|
+
backgroundColor: accentColor || 'var(--ai-chat-accent-color, #3b82f6)',
|
|
28434
|
+
} }), jsxs("span", { className: "ai-chat-form-card__progress-text", children: [currentStep + 1, " of ", totalQuestions] })] })), jsxs("div", { className: "ai-chat-form-card__question", children: [jsxs("p", { className: "ai-chat-form-card__question-text", children: [currentQuestion.text, currentQuestion.required && jsx("span", { className: "ai-chat-form-card__required", children: "*" })] }), jsxs("div", { className: "ai-chat-form-card__answer", children: [currentQuestion.type === 'text' && (jsx("textarea", { className: "ai-chat-form-card__textarea", placeholder: currentQuestion.placeholder || 'Type your answer...', value: answers[currentQuestion.id] || '', onChange: (e) => handleAnswerChange(currentQuestion.id, e.target.value), rows: 3 })), currentQuestion.type === 'single_choice' && currentQuestion.options && (jsx("div", { className: "ai-chat-form-card__options", children: currentQuestion.options.map((option) => (jsxs("label", { className: "ai-chat-form-card__option", children: [jsx("input", { type: "radio", name: currentQuestion.id, value: option.value, checked: answers[currentQuestion.id] === option.value, onChange: () => handleAnswerChange(currentQuestion.id, option.value) }), jsx("span", { className: "ai-chat-form-card__option-text", children: option.text })] }, option.id))) })), currentQuestion.type === 'multiple_choice' && currentQuestion.options && (jsx("div", { className: "ai-chat-form-card__options", children: currentQuestion.options.map((option) => {
|
|
28435
|
+
const currentAnswers = answers[currentQuestion.id] || [];
|
|
28436
|
+
const isChecked = currentAnswers.includes(option.value);
|
|
28437
|
+
return (jsxs("label", { className: "ai-chat-form-card__option", children: [jsx("input", { type: "checkbox", value: option.value, checked: isChecked, onChange: () => {
|
|
28438
|
+
const newAnswers = isChecked
|
|
28439
|
+
? currentAnswers.filter((v) => v !== option.value)
|
|
28440
|
+
: [...currentAnswers, option.value];
|
|
28441
|
+
handleAnswerChange(currentQuestion.id, newAnswers);
|
|
28442
|
+
} }), jsx("span", { className: "ai-chat-form-card__option-text", children: option.text })] }, option.id));
|
|
28443
|
+
}) })), currentQuestion.type === 'rating' && (jsx("div", { className: "ai-chat-form-card__rating", children: Array.from({ length: (currentQuestion.maxRating || 5) - (currentQuestion.minRating || 1) + 1 }, (_, i) => (currentQuestion.minRating || 1) + i).map((rating) => (jsx("button", { type: "button", className: `ai-chat-form-card__rating-btn ${answers[currentQuestion.id] === String(rating) ? 'ai-chat-form-card__rating-btn--selected' : ''}`, onClick: () => handleAnswerChange(currentQuestion.id, String(rating)), style: {
|
|
28444
|
+
borderColor: answers[currentQuestion.id] === String(rating)
|
|
28445
|
+
? (accentColor || 'var(--ai-chat-accent-color, #3b82f6)')
|
|
28446
|
+
: undefined,
|
|
28447
|
+
backgroundColor: answers[currentQuestion.id] === String(rating)
|
|
28448
|
+
? (accentColor || 'var(--ai-chat-accent-color, #3b82f6)')
|
|
28449
|
+
: undefined,
|
|
28450
|
+
}, children: rating }, rating))) }))] })] }), jsxs("div", { className: "ai-chat-form-card__actions", children: [currentStep > 0 && (jsx("button", { type: "button", className: "ai-chat-form-card__btn ai-chat-form-card__btn--secondary", onClick: handlePrev, children: "Back" })), state.settings.allowSkip && currentStep === 0 && (jsx("button", { type: "button", className: "ai-chat-form-card__btn ai-chat-form-card__btn--ghost", onClick: handleSkip, children: "Skip" })), jsx("div", { className: "ai-chat-form-card__actions-spacer" }), currentStep < totalQuestions - 1 ? (jsx("button", { type: "button", className: "ai-chat-form-card__btn ai-chat-form-card__btn--primary", onClick: handleNext, disabled: !isCurrentAnswered(), style: {
|
|
28451
|
+
backgroundColor: accentColor || 'var(--ai-chat-accent-color, #3b82f6)',
|
|
28452
|
+
}, children: "Next" })) : (jsx("button", { type: "button", className: "ai-chat-form-card__btn ai-chat-form-card__btn--primary", onClick: handleSubmit, disabled: !canSubmit() || isSubmitting, style: {
|
|
28453
|
+
backgroundColor: accentColor || 'var(--ai-chat-accent-color, #3b82f6)',
|
|
28454
|
+
}, children: isSubmitting ? 'Submitting...' : (state.settings.submitButtonText || 'Submit') }))] })] }));
|
|
28455
|
+
}
|
|
28456
|
+
|
|
28457
|
+
function Skeleton({ width, height, borderRadius = '4px' }) {
|
|
28458
|
+
return (jsx("div", { className: "ai-chat-action-skeleton-item", style: { width, height, borderRadius } }));
|
|
28459
|
+
}
|
|
28460
|
+
function PinInputGroup({ values, onChange, disabled }) {
|
|
28461
|
+
const inputRefs = useRef([]);
|
|
28462
|
+
const handleChange = (index, value) => {
|
|
28463
|
+
// Only allow digits
|
|
28464
|
+
const digit = value.replace(/[^0-9]/g, '');
|
|
28465
|
+
const newValues = [...values];
|
|
28466
|
+
newValues[index] = digit.slice(-1);
|
|
28467
|
+
onChange(newValues);
|
|
28468
|
+
// Auto-focus next input
|
|
28469
|
+
if (digit && index < 5) {
|
|
28470
|
+
inputRefs.current[index + 1]?.focus();
|
|
28471
|
+
}
|
|
28472
|
+
};
|
|
28473
|
+
const handleKeyDown = (index, e) => {
|
|
28474
|
+
if (e.key === 'Backspace' && !values[index] && index > 0) {
|
|
28475
|
+
inputRefs.current[index - 1]?.focus();
|
|
28476
|
+
}
|
|
28477
|
+
};
|
|
28478
|
+
const handlePaste = (e) => {
|
|
28479
|
+
e.preventDefault();
|
|
28480
|
+
const pastedData = e.clipboardData.getData('text').replace(/[^0-9]/g, '').slice(0, 6);
|
|
28481
|
+
const newValues = pastedData.split('').concat(Array(6 - pastedData.length).fill(''));
|
|
28482
|
+
onChange(newValues);
|
|
28483
|
+
// Focus the next empty input or the last one
|
|
28484
|
+
const nextIndex = Math.min(pastedData.length, 5);
|
|
28485
|
+
inputRefs.current[nextIndex]?.focus();
|
|
28486
|
+
};
|
|
28487
|
+
return (jsx("div", { className: "ai-chat-pin-input-group", children: values.map((value, index) => (jsx("input", { ref: (el) => {
|
|
28488
|
+
inputRefs.current[index] = el;
|
|
28489
|
+
}, type: "text", inputMode: "numeric", maxLength: 1, className: "ai-chat-pin-input", value: value, onChange: (e) => handleChange(index, e.target.value), onKeyDown: (e) => handleKeyDown(index, e), onPaste: handlePaste, disabled: disabled, autoFocus: index === 0 }, index))) }));
|
|
28490
|
+
}
|
|
28491
|
+
function BookContactAppointmentCard({ action, onComplete, accentColor }) {
|
|
28492
|
+
const state = action.state;
|
|
28493
|
+
const [emailInput, setEmailInput] = useState('');
|
|
28494
|
+
const [otpValues, setOtpValues] = useState(Array(6).fill(''));
|
|
28495
|
+
const [subjectInput, setSubjectInput] = useState(state.subject || '');
|
|
28496
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
28497
|
+
const [emailError, setEmailError] = useState(null);
|
|
28498
|
+
const [otpError, setOtpError] = useState(null);
|
|
28499
|
+
const phase = state.phase || 'awaiting_email';
|
|
28500
|
+
const handleSubmit = (newState, delay = 50) => {
|
|
28501
|
+
if (!onComplete)
|
|
28502
|
+
return;
|
|
28503
|
+
setIsSubmitting(true);
|
|
28504
|
+
setTimeout(() => {
|
|
28505
|
+
onComplete(action.toolCallId, { ...state, ...newState, errorMessage: null });
|
|
28506
|
+
}, delay);
|
|
28507
|
+
};
|
|
28508
|
+
useEffect(() => {
|
|
28509
|
+
setIsSubmitting(false);
|
|
28510
|
+
if (phase === 'awaiting_email') {
|
|
28511
|
+
setEmailError(null);
|
|
28512
|
+
setOtpError(null);
|
|
28513
|
+
}
|
|
28514
|
+
else if (phase === 'awaiting_otp') {
|
|
28515
|
+
setOtpError(state.errorMessage || null);
|
|
28516
|
+
setEmailError(null);
|
|
28517
|
+
setOtpValues(Array(6).fill(''));
|
|
28518
|
+
if (state.email) {
|
|
28519
|
+
setEmailInput(state.email);
|
|
28520
|
+
}
|
|
28521
|
+
}
|
|
28522
|
+
else {
|
|
28523
|
+
setEmailError(null);
|
|
28524
|
+
setOtpError(null);
|
|
28525
|
+
}
|
|
28526
|
+
}, [phase, state.errorMessage]);
|
|
28527
|
+
const isWaitingForBackend = !action.done && Boolean(state.confirmed) && phase === 'awaiting_confirmation';
|
|
28528
|
+
const renderSkeleton = () => (jsx("div", { className: "ai-chat-booking-card", children: jsxs("div", { className: "ai-chat-action-skeleton-content", children: [jsxs("div", { className: "ai-chat-action-skeleton-header", children: [jsx(Skeleton, { width: "28px", height: "28px", borderRadius: "50%" }), jsx(Skeleton, { width: "180px", height: "20px", borderRadius: "4px" })] }), jsxs("div", { className: "ai-chat-action-skeleton-box", children: [jsx(Skeleton, { width: "60px", height: "12px", borderRadius: "4px" }), jsx(Skeleton, { width: "120px", height: "18px", borderRadius: "4px" })] }), jsx(Skeleton, { width: "100%", height: "44px", borderRadius: "999px" })] }) }));
|
|
28529
|
+
const handleEmailSubmit = () => {
|
|
28530
|
+
const trimmedEmail = emailInput.trim();
|
|
28531
|
+
if (!trimmedEmail) {
|
|
28532
|
+
setEmailError('Please enter your email address');
|
|
28533
|
+
return;
|
|
28534
|
+
}
|
|
28535
|
+
setEmailError(null);
|
|
28536
|
+
setEmailInput(trimmedEmail);
|
|
28537
|
+
handleSubmit({ email: trimmedEmail });
|
|
28538
|
+
};
|
|
28539
|
+
const handleOtpSubmit = () => {
|
|
28540
|
+
const code = otpValues.join('');
|
|
28541
|
+
if (code.length !== 6) {
|
|
28542
|
+
setOtpError('Please enter the 6-digit code');
|
|
28543
|
+
return;
|
|
28544
|
+
}
|
|
28545
|
+
setOtpError(null);
|
|
28546
|
+
const resolvedEmail = state.email || emailInput.trim() || null;
|
|
28547
|
+
handleSubmit({ otpCode: code, email: resolvedEmail });
|
|
28548
|
+
};
|
|
28549
|
+
// ========================================
|
|
28550
|
+
// Terminal States
|
|
28551
|
+
// ========================================
|
|
28552
|
+
if (phase === 'booked') {
|
|
28553
|
+
return (jsxs("div", { className: "ai-chat-booking-card ai-chat-booking-card--success", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\u2713" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Appointment Booked" })] }), state.bookedTeamsLink && (jsxs("div", { className: "ai-chat-booking-card__content", children: [jsx("p", { className: "ai-chat-booking-card__success-text", children: "Your appointment has been confirmed. You can join via Microsoft Teams." }), jsx("a", { href: state.bookedTeamsLink, target: "_blank", rel: "noopener noreferrer", className: "ai-chat-booking-card__link", style: { color: accentColor }, children: "Join Teams Meeting \u2192" })] }))] }));
|
|
28554
|
+
}
|
|
28555
|
+
if (phase === 'pending_approval') {
|
|
28556
|
+
return (jsxs("div", { className: "ai-chat-booking-card ai-chat-booking-card--pending", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\u23F3" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Awaiting Approval" })] }), jsx("div", { className: "ai-chat-booking-card__content", children: jsx("p", { className: "ai-chat-booking-card__pending-text", children: "Your appointment request has been sent and is awaiting approval from the contact." }) })] }));
|
|
28557
|
+
}
|
|
28558
|
+
if (phase === 'cancelled') {
|
|
28559
|
+
return (jsx("div", { className: "ai-chat-booking-card ai-chat-booking-card--cancelled", children: jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\u2715" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Appointment Cancelled" })] }) }));
|
|
28560
|
+
}
|
|
28561
|
+
if (phase === 'error') {
|
|
28562
|
+
return (jsxs("div", { className: "ai-chat-booking-card ai-chat-booking-card--error", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\u26A0\uFE0F" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Booking Error" })] }), state.errorMessage && (jsx("p", { className: "ai-chat-booking-card__error", children: state.errorMessage }))] }));
|
|
28563
|
+
}
|
|
28564
|
+
if (isSubmitting || isWaitingForBackend) {
|
|
28565
|
+
return renderSkeleton();
|
|
28566
|
+
}
|
|
28567
|
+
// ========================================
|
|
28568
|
+
// Phase: Email Collection
|
|
28569
|
+
// ========================================
|
|
28570
|
+
if (phase === 'awaiting_email') {
|
|
28571
|
+
const displayError = emailError || state.errorMessage;
|
|
28572
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\uD83D\uDCE7" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Enter Your Email" })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [jsx("p", { className: "ai-chat-booking-card__description", children: "Please enter your email address to start the booking process." }), displayError && (jsx("p", { className: "ai-chat-booking-card__error", children: displayError })), jsx("input", { type: "email", className: "ai-chat-booking-card__input", placeholder: "your@email.com", value: emailInput, onChange: (e) => {
|
|
28573
|
+
setEmailInput(e.target.value);
|
|
28574
|
+
setEmailError(null);
|
|
28575
|
+
}, onKeyDown: (e) => {
|
|
28576
|
+
if (e.key === 'Enter') {
|
|
28577
|
+
handleEmailSubmit();
|
|
28578
|
+
}
|
|
28579
|
+
} }), jsx("button", { className: "ai-chat-booking-card__btn ai-chat-booking-card__btn--primary", onClick: handleEmailSubmit, disabled: !emailInput.trim(), style: {
|
|
28580
|
+
backgroundColor: accentColor || undefined,
|
|
28581
|
+
borderColor: accentColor || undefined,
|
|
28582
|
+
}, children: "Continue" })] })] }));
|
|
28583
|
+
}
|
|
28584
|
+
// ========================================
|
|
28585
|
+
// Phase: OTP Verification
|
|
28586
|
+
// ========================================
|
|
28587
|
+
if (phase === 'awaiting_otp') {
|
|
28588
|
+
const displayError = otpError || state.errorMessage;
|
|
28589
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\uD83D\uDD10" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Verify Your Email" })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [jsxs("p", { className: "ai-chat-booking-card__description", children: ["Enter the verification code sent to ", jsx("strong", { children: state.email })] }), jsx(PinInputGroup, { values: otpValues, onChange: (newValues) => {
|
|
28590
|
+
setOtpValues(newValues);
|
|
28591
|
+
setOtpError(null);
|
|
28592
|
+
if (newValues.every((value) => value.length === 1) && !isSubmitting) {
|
|
28593
|
+
const resolvedEmail = state.email || emailInput.trim() || null;
|
|
28594
|
+
handleSubmit({ otpCode: newValues.join(''), email: resolvedEmail }, 100);
|
|
28595
|
+
}
|
|
28596
|
+
}, disabled: isSubmitting }), displayError && (jsx("p", { className: "ai-chat-booking-card__error", children: displayError })), jsx("button", { className: "ai-chat-booking-card__btn ai-chat-booking-card__btn--primary", onClick: handleOtpSubmit, disabled: otpValues.join('').length !== 6, style: {
|
|
28597
|
+
backgroundColor: accentColor || undefined,
|
|
28598
|
+
borderColor: accentColor || undefined,
|
|
28599
|
+
}, children: "Verify" })] })] }));
|
|
28600
|
+
}
|
|
28601
|
+
// ========================================
|
|
28602
|
+
// Phase: Contact Selection
|
|
28603
|
+
// ========================================
|
|
28604
|
+
if (phase === 'awaiting_contact_selection') {
|
|
28605
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\uD83D\uDC65" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Select a Contact" })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [state.errorMessage && (jsx("p", { className: "ai-chat-booking-card__error", children: state.errorMessage })), state.bookableContacts.length === 0 ? (jsx("p", { className: "ai-chat-booking-card__empty", children: "No contacts available for booking." })) : (jsx("div", { className: "ai-chat-booking-card__grid", children: state.bookableContacts.map((contact) => (jsxs("button", { className: `ai-chat-booking-card__contact ${state.selectedContactId === contact.id ? 'ai-chat-booking-card__contact--selected' : ''}`, onClick: () => handleSubmit({ selectedContactId: contact.id }), style: {
|
|
28606
|
+
borderColor: state.selectedContactId === contact.id
|
|
28607
|
+
? accentColor || undefined
|
|
28608
|
+
: undefined,
|
|
28609
|
+
backgroundColor: state.selectedContactId === contact.id
|
|
28610
|
+
? `${accentColor || '#3b82f6'}15`
|
|
28611
|
+
: undefined,
|
|
28612
|
+
}, children: [jsx("div", { className: "ai-chat-booking-card__contact-name", children: contact.name }), contact.role && (jsx("div", { className: "ai-chat-booking-card__contact-role", children: contact.role }))] }, contact.id))) }))] })] }));
|
|
28613
|
+
}
|
|
28614
|
+
// ========================================
|
|
28615
|
+
// Phase: Options Selection
|
|
28616
|
+
// ========================================
|
|
28617
|
+
if (phase === 'awaiting_options') {
|
|
28618
|
+
const selectedContact = state.bookableContacts.find((c) => c.id === state.selectedContactId);
|
|
28619
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\uD83D\uDCC5" }), jsxs("span", { className: "ai-chat-booking-card__title", children: ["Book with ", selectedContact?.name] })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [state.errorMessage && (jsx("p", { className: "ai-chat-booking-card__error", children: state.errorMessage })), jsxs("div", { className: "ai-chat-booking-card__options", children: [jsxs("button", { className: "ai-chat-booking-card__option-btn", onClick: () => handleSubmit({ selectedOption: 'book_new' }), children: [jsx("span", { className: "ai-chat-booking-card__option-icon", children: "\u2795" }), jsx("span", { className: "ai-chat-booking-card__option-text", children: "Book New Appointment" })] }), state.userAppointments.length > 0 && (jsxs(Fragment, { children: [jsxs("button", { className: "ai-chat-booking-card__option-btn", onClick: () => handleSubmit({ selectedOption: 'view_existing' }), children: [jsx("span", { className: "ai-chat-booking-card__option-icon", children: "\uD83D\uDCCB" }), jsx("span", { className: "ai-chat-booking-card__option-text", children: "View Appointments" })] }), jsxs("button", { className: "ai-chat-booking-card__option-btn", onClick: () => handleSubmit({ selectedOption: 'cancel_existing' }), children: [jsx("span", { className: "ai-chat-booking-card__option-icon", children: "\u2715" }), jsx("span", { className: "ai-chat-booking-card__option-text", children: "Cancel Appointment" })] })] }))] })] })] }));
|
|
28620
|
+
}
|
|
28621
|
+
// ========================================
|
|
28622
|
+
// Phase: View Existing Appointments
|
|
28623
|
+
// ========================================
|
|
28624
|
+
if (state.phase === 'awaiting_options' && state.selectedOption === 'view_existing') {
|
|
28625
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\uD83D\uDCCB" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Your Appointments" })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [state.userAppointments.length === 0 ? (jsx("p", { className: "ai-chat-booking-card__empty", children: "You have no appointments yet." })) : (jsx("div", { className: "ai-chat-booking-card__appointments", children: state.userAppointments.map((apt) => (jsxs("div", { className: "ai-chat-booking-card__appointment", children: [jsxs("div", { className: "ai-chat-booking-card__appointment-header", children: [jsx("span", { className: "ai-chat-booking-card__appointment-subject", children: apt.subject }), jsx("span", { className: `ai-chat-booking-card__appointment-status ai-chat-booking-card__appointment-status--${apt.status}`, children: apt.status })] }), jsx("div", { className: "ai-chat-booking-card__appointment-time", children: apt.displayTime }), jsxs("div", { className: "ai-chat-booking-card__appointment-contact", children: ["with ", apt.contactName] }), apt.teamsLink && (jsx("a", { href: apt.teamsLink, target: "_blank", rel: "noopener noreferrer", className: "ai-chat-booking-card__link", style: { color: accentColor }, children: "Join Teams Meeting \u2192" }))] }, apt.id))) })), jsx("button", { className: "ai-chat-booking-card__btn ai-chat-booking-card__btn--secondary", onClick: () => handleSubmit({ selectedOption: undefined }), children: "Back" })] })] }));
|
|
28626
|
+
}
|
|
28627
|
+
// ========================================
|
|
28628
|
+
// Phase: Cancel Appointment
|
|
28629
|
+
// ========================================
|
|
28630
|
+
if (state.phase === 'awaiting_options' && state.selectedOption === 'cancel_existing') {
|
|
28631
|
+
const activeAppointments = state.userAppointments.filter((a) => a.status !== 'cancelled' && a.status !== 'declined');
|
|
28632
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\u2715" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Cancel Appointment" })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [state.errorMessage && (jsx("p", { className: "ai-chat-booking-card__error", children: state.errorMessage })), activeAppointments.length === 0 ? (jsxs(Fragment, { children: [jsx("p", { className: "ai-chat-booking-card__empty", children: "No active appointments to cancel." }), jsx("button", { className: "ai-chat-booking-card__btn ai-chat-booking-card__btn--secondary", onClick: () => handleSubmit({ selectedOption: undefined }), children: "Back" })] })) : (jsx("div", { className: "ai-chat-booking-card__appointments", children: activeAppointments.map((apt) => (jsxs("div", { className: "ai-chat-booking-card__appointment", children: [jsx("div", { className: "ai-chat-booking-card__appointment-header", children: jsx("span", { className: "ai-chat-booking-card__appointment-subject", children: apt.subject }) }), jsx("div", { className: "ai-chat-booking-card__appointment-time", children: apt.displayTime }), jsx("button", { className: "ai-chat-booking-card__btn ai-chat-booking-card__btn--danger", onClick: () => handleSubmit({ selectedAppointmentId: apt.id, confirmCancel: true }), children: "Cancel This Appointment" })] }, apt.id))) }))] })] }));
|
|
28633
|
+
}
|
|
28634
|
+
// ========================================
|
|
28635
|
+
// Phase: Slot Selection
|
|
28636
|
+
// ========================================
|
|
28637
|
+
if (phase === 'awaiting_slot_selection') {
|
|
28638
|
+
const groupedSlots = state.availableSlots.reduce((acc, slot) => {
|
|
28639
|
+
if (!acc[slot.displayDate]) {
|
|
28640
|
+
acc[slot.displayDate] = [];
|
|
28641
|
+
}
|
|
28642
|
+
acc[slot.displayDate].push(slot);
|
|
28643
|
+
return acc;
|
|
28644
|
+
}, {});
|
|
28645
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\uD83D\uDD50" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Select a Time Slot" })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [jsxs("p", { className: "ai-chat-booking-card__description", children: ["Available times in ", state.timeZone] }), state.errorMessage && (jsx("p", { className: "ai-chat-booking-card__error", children: state.errorMessage })), Object.entries(groupedSlots).map(([date, slots]) => (jsxs("div", { className: "ai-chat-booking-card__date-group", children: [jsx("div", { className: "ai-chat-booking-card__date-header", children: date }), jsx("div", { className: "ai-chat-booking-card__slots", children: slots.map((slot, idx) => {
|
|
28646
|
+
const isSelected = state.selectedSlot?.startTime === slot.startTime &&
|
|
28647
|
+
state.selectedSlot?.endTime === slot.endTime;
|
|
28648
|
+
return (jsx("button", { className: `ai-chat-booking-card__slot ${isSelected ? 'ai-chat-booking-card__slot--selected' : ''}`, onClick: () => handleSubmit({
|
|
28649
|
+
selectedSlot: { startTime: slot.startTime, endTime: slot.endTime },
|
|
28650
|
+
}), style: {
|
|
28651
|
+
borderColor: isSelected ? accentColor || undefined : undefined,
|
|
28652
|
+
backgroundColor: isSelected ? `${accentColor || '#3b82f6'}15` : undefined,
|
|
28653
|
+
}, children: slot.displayTime }, `${slot.startTime}-${idx}`));
|
|
28654
|
+
}) })] }, date))), state.availableSlots.length === 0 && (jsx("p", { className: "ai-chat-booking-card__empty", children: "No available time slots." }))] })] }));
|
|
28655
|
+
}
|
|
28656
|
+
// ========================================
|
|
28657
|
+
// Phase: Confirmation
|
|
28658
|
+
// ========================================
|
|
28659
|
+
if (phase === 'awaiting_confirmation') {
|
|
28660
|
+
const selectedContact = state.bookableContacts.find((c) => c.id === state.selectedContactId);
|
|
28661
|
+
const selectedSlot = state.availableSlots.find((s) => s.startTime === state.selectedSlot?.startTime && s.endTime === state.selectedSlot?.endTime);
|
|
28662
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\u2713" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Confirm Booking" })] }), jsxs("div", { className: "ai-chat-booking-card__content", children: [state.errorMessage && (jsx("p", { className: "ai-chat-booking-card__error", children: state.errorMessage })), jsxs("div", { className: "ai-chat-booking-card__summary", children: [jsxs("div", { className: "ai-chat-booking-card__summary-row", children: [jsx("span", { className: "ai-chat-booking-card__summary-label", children: "Contact:" }), jsx("span", { className: "ai-chat-booking-card__summary-value", children: selectedContact?.name })] }), jsxs("div", { className: "ai-chat-booking-card__summary-row", children: [jsx("span", { className: "ai-chat-booking-card__summary-label", children: "Date:" }), jsx("span", { className: "ai-chat-booking-card__summary-value", children: selectedSlot?.displayDate })] }), jsxs("div", { className: "ai-chat-booking-card__summary-row", children: [jsx("span", { className: "ai-chat-booking-card__summary-label", children: "Time:" }), jsx("span", { className: "ai-chat-booking-card__summary-value", children: selectedSlot?.displayTime })] })] }), state.allowCustomSubject && (jsxs(Fragment, { children: [jsx("label", { className: "ai-chat-booking-card__label", children: "Subject (optional):" }), jsx("input", { type: "text", className: "ai-chat-booking-card__input", placeholder: "Meeting subject", value: subjectInput, onChange: (e) => setSubjectInput(e.target.value) })] })), jsx("button", { className: "ai-chat-booking-card__btn ai-chat-booking-card__btn--primary", onClick: () => handleSubmit({
|
|
28663
|
+
subject: state.allowCustomSubject ? subjectInput || undefined : undefined,
|
|
28664
|
+
confirmed: true,
|
|
28665
|
+
}), style: {
|
|
28666
|
+
backgroundColor: accentColor || undefined,
|
|
28667
|
+
borderColor: accentColor || undefined,
|
|
28668
|
+
}, children: "Confirm Booking" })] })] }));
|
|
28669
|
+
}
|
|
28670
|
+
// Fallback for unknown states
|
|
28671
|
+
return (jsxs("div", { className: "ai-chat-booking-card", children: [jsxs("div", { className: "ai-chat-booking-card__header", children: [jsx("span", { className: "ai-chat-booking-card__icon", children: "\uD83D\uDCC5" }), jsx("span", { className: "ai-chat-booking-card__title", children: "Booking" })] }), jsx("div", { className: "ai-chat-booking-card__content", children: jsx("p", { className: "ai-chat-booking-card__description", children: "Loading booking options..." }) })] }));
|
|
28672
|
+
}
|
|
28673
|
+
|
|
28674
|
+
const pendingResolvers = new Map();
|
|
28675
|
+
const resumeCallbacks = new Map();
|
|
28676
|
+
const frontendActionHandlers = {};
|
|
28677
|
+
const actionRenderers = {};
|
|
28678
|
+
function getFrontendActionHandler(implementation) {
|
|
28679
|
+
return frontendActionHandlers[implementation];
|
|
28680
|
+
}
|
|
28681
|
+
function getActionRenderer(implementation) {
|
|
28682
|
+
return actionRenderers[implementation];
|
|
28683
|
+
}
|
|
28684
|
+
function waitForActionState(toolCallId) {
|
|
28685
|
+
return new Promise((resolve) => {
|
|
28686
|
+
pendingResolvers.set(toolCallId, resolve);
|
|
28687
|
+
});
|
|
28688
|
+
}
|
|
28689
|
+
function resolveActionState(toolCallId, state) {
|
|
28690
|
+
const resolver = pendingResolvers.get(toolCallId);
|
|
28691
|
+
if (resolver) {
|
|
28692
|
+
pendingResolvers.delete(toolCallId);
|
|
28693
|
+
resolver(state);
|
|
28694
|
+
return;
|
|
28695
|
+
}
|
|
28696
|
+
const resumeCallback = resumeCallbacks.get(toolCallId);
|
|
27772
28697
|
if (resumeCallback) {
|
|
27773
28698
|
resumeCallback(state).catch((error) => {
|
|
27774
28699
|
console.error("[Action] Failed to resume action:", error);
|
|
@@ -27796,13 +28721,19 @@ function registerGoogleCalendarAction() {
|
|
|
27796
28721
|
// Register the handler
|
|
27797
28722
|
registerGoogleCalendarHandler();
|
|
27798
28723
|
// Register the renderer
|
|
27799
|
-
actionRenderers["google-calendar-appointment"] = (message) => {
|
|
28724
|
+
actionRenderers["google-calendar-appointment"] = (message, accentColor, _variant, onActionDismiss) => {
|
|
27800
28725
|
const action = message.action;
|
|
27801
28726
|
if (!action)
|
|
27802
28727
|
return null;
|
|
27803
28728
|
const handleComplete = (toolCallId, newState) => {
|
|
27804
28729
|
resolveActionState(toolCallId, newState);
|
|
27805
28730
|
};
|
|
28731
|
+
const handleDismiss = onActionDismiss
|
|
28732
|
+
? (toolCallId) => {
|
|
28733
|
+
resolveActionState(toolCallId, { __dismissed: true });
|
|
28734
|
+
onActionDismiss(toolCallId);
|
|
28735
|
+
}
|
|
28736
|
+
: undefined;
|
|
27806
28737
|
return (jsx(GoogleCalendarCard, { action: {
|
|
27807
28738
|
implementation: action.implementation,
|
|
27808
28739
|
toolCallId: action.toolCallId,
|
|
@@ -27810,7 +28741,45 @@ function registerGoogleCalendarAction() {
|
|
|
27810
28741
|
input: action.input,
|
|
27811
28742
|
state: action.state,
|
|
27812
28743
|
done: action.done ?? false,
|
|
27813
|
-
}, onComplete: handleComplete }));
|
|
28744
|
+
}, onComplete: handleComplete, onDismiss: handleDismiss, accentColor: accentColor }));
|
|
28745
|
+
};
|
|
28746
|
+
}
|
|
28747
|
+
|
|
28748
|
+
function registerMicrosoftCalendarHandler() {
|
|
28749
|
+
frontendActionHandlers["microsoft-calendar-appointment"] = async (_input, _state, context) => {
|
|
28750
|
+
return waitForActionState(context.toolCallId);
|
|
28751
|
+
};
|
|
28752
|
+
}
|
|
28753
|
+
|
|
28754
|
+
/**
|
|
28755
|
+
* Register microsoft-calendar-appointment action handler and renderer.
|
|
28756
|
+
* Called by initializeActionHandlers to prevent tree-shaking.
|
|
28757
|
+
*/
|
|
28758
|
+
function registerMicrosoftCalendarAction() {
|
|
28759
|
+
// Register the handler
|
|
28760
|
+
registerMicrosoftCalendarHandler();
|
|
28761
|
+
// Register the renderer
|
|
28762
|
+
actionRenderers["microsoft-calendar-appointment"] = (message, accentColor, _variant, onActionDismiss) => {
|
|
28763
|
+
const action = message.action;
|
|
28764
|
+
if (!action)
|
|
28765
|
+
return null;
|
|
28766
|
+
const handleComplete = (toolCallId, newState) => {
|
|
28767
|
+
resolveActionState(toolCallId, newState);
|
|
28768
|
+
};
|
|
28769
|
+
const handleDismiss = onActionDismiss
|
|
28770
|
+
? (toolCallId) => {
|
|
28771
|
+
resolveActionState(toolCallId, { __dismissed: true });
|
|
28772
|
+
onActionDismiss(toolCallId);
|
|
28773
|
+
}
|
|
28774
|
+
: undefined;
|
|
28775
|
+
return (jsx(MicrosoftCalendarCard, { action: {
|
|
28776
|
+
implementation: action.implementation,
|
|
28777
|
+
toolCallId: action.toolCallId,
|
|
28778
|
+
actionId: action.actionId,
|
|
28779
|
+
input: action.input,
|
|
28780
|
+
state: action.state,
|
|
28781
|
+
done: action.done ?? false,
|
|
28782
|
+
}, onComplete: handleComplete, onDismiss: handleDismiss, accentColor: accentColor }));
|
|
27814
28783
|
};
|
|
27815
28784
|
}
|
|
27816
28785
|
|
|
@@ -27824,7 +28793,7 @@ function registerLinkPreviewAction() {
|
|
|
27824
28793
|
return { ...state, status: "displaying" };
|
|
27825
28794
|
};
|
|
27826
28795
|
// Renderer - displays the link preview card
|
|
27827
|
-
actionRenderers["link-preview"] = (message) => {
|
|
28796
|
+
actionRenderers["link-preview"] = (message, accentColor) => {
|
|
27828
28797
|
const action = message.action;
|
|
27829
28798
|
if (!action)
|
|
27830
28799
|
return null;
|
|
@@ -27842,7 +28811,7 @@ function registerLinkPreviewAction() {
|
|
|
27842
28811
|
input: action.input,
|
|
27843
28812
|
state: action.state,
|
|
27844
28813
|
done: isDone,
|
|
27845
|
-
}, onComplete: handleComplete }));
|
|
28814
|
+
}, onComplete: handleComplete, accentColor: accentColor }));
|
|
27846
28815
|
};
|
|
27847
28816
|
}
|
|
27848
28817
|
|
|
@@ -27856,7 +28825,7 @@ function registerVideoPlayerAction() {
|
|
|
27856
28825
|
return { ...state, status: "displaying" };
|
|
27857
28826
|
};
|
|
27858
28827
|
// Renderer - displays the embedded video player card
|
|
27859
|
-
actionRenderers["video-player"] = (message) => {
|
|
28828
|
+
actionRenderers["video-player"] = (message, accentColor) => {
|
|
27860
28829
|
const action = message.action;
|
|
27861
28830
|
if (!action)
|
|
27862
28831
|
return null;
|
|
@@ -27874,7 +28843,7 @@ function registerVideoPlayerAction() {
|
|
|
27874
28843
|
input: action.input,
|
|
27875
28844
|
state: action.state,
|
|
27876
28845
|
done: isDone,
|
|
27877
|
-
}, onComplete: handleComplete }));
|
|
28846
|
+
}, onComplete: handleComplete, accentColor: accentColor }));
|
|
27878
28847
|
};
|
|
27879
28848
|
}
|
|
27880
28849
|
|
|
@@ -27888,7 +28857,7 @@ function registerLocationCardAction() {
|
|
|
27888
28857
|
return { ...state, status: "displaying" };
|
|
27889
28858
|
};
|
|
27890
28859
|
// Renderer - displays the location card
|
|
27891
|
-
actionRenderers["location-card"] = (message, accentColor) => {
|
|
28860
|
+
actionRenderers["location-card"] = (message, accentColor, variant) => {
|
|
27892
28861
|
const action = message.action;
|
|
27893
28862
|
if (!action)
|
|
27894
28863
|
return null;
|
|
@@ -27906,7 +28875,134 @@ function registerLocationCardAction() {
|
|
|
27906
28875
|
input: action.input,
|
|
27907
28876
|
state: action.state,
|
|
27908
28877
|
done: isDone,
|
|
27909
|
-
}, onComplete: handleComplete, accentColor: accentColor, maxColumns: 1 }));
|
|
28878
|
+
}, onComplete: handleComplete, accentColor: accentColor, maxColumns: variant === 'fullpage' ? 3 : 1 }));
|
|
28879
|
+
};
|
|
28880
|
+
}
|
|
28881
|
+
|
|
28882
|
+
function registerContactCardAction() {
|
|
28883
|
+
// Handler - auto-completes immediately since no user input is needed
|
|
28884
|
+
frontendActionHandlers['contact-card'] = async (_input, state, _context) => {
|
|
28885
|
+
return { ...state, status: 'displaying' };
|
|
28886
|
+
};
|
|
28887
|
+
// Renderer - displays the contact card
|
|
28888
|
+
actionRenderers['contact-card'] = (message, accentColor, variant) => {
|
|
28889
|
+
const action = message.action;
|
|
28890
|
+
if (!action)
|
|
28891
|
+
return null;
|
|
28892
|
+
const handleComplete = (toolCallId, newState) => {
|
|
28893
|
+
resolveActionState(toolCallId, newState);
|
|
28894
|
+
};
|
|
28895
|
+
// Check if action state indicates it's already complete
|
|
28896
|
+
const state = action.state;
|
|
28897
|
+
const status = state?.status;
|
|
28898
|
+
const isDone = action.done || status === 'displaying' || status === 'contacted';
|
|
28899
|
+
return (jsx(ContactCard, { action: {
|
|
28900
|
+
implementation: action.implementation,
|
|
28901
|
+
toolCallId: action.toolCallId,
|
|
28902
|
+
actionId: action.actionId,
|
|
28903
|
+
input: action.input,
|
|
28904
|
+
state: action.state,
|
|
28905
|
+
done: isDone,
|
|
28906
|
+
}, onComplete: handleComplete, accentColor: accentColor, maxColumns: variant === 'fullpage' ? 3 : 1 }));
|
|
28907
|
+
};
|
|
28908
|
+
}
|
|
28909
|
+
|
|
28910
|
+
function registerQueryContactDirectoryAction() {
|
|
28911
|
+
// Handler - auto-completes immediately since no user input is needed
|
|
28912
|
+
frontendActionHandlers['query-contact-directory'] = async (_input, state, _context) => {
|
|
28913
|
+
return { ...state, status: 'displaying' };
|
|
28914
|
+
};
|
|
28915
|
+
// Renderer - displays the contact card with search results
|
|
28916
|
+
actionRenderers['query-contact-directory'] = (message, accentColor, variant) => {
|
|
28917
|
+
const action = message.action;
|
|
28918
|
+
if (!action)
|
|
28919
|
+
return null;
|
|
28920
|
+
// Handle completion - triggers agent to continue with text response
|
|
28921
|
+
const handleComplete = (toolCallId, newState) => {
|
|
28922
|
+
resolveActionState(toolCallId, newState);
|
|
28923
|
+
};
|
|
28924
|
+
// Check if action state indicates it's already complete
|
|
28925
|
+
const state = action.state;
|
|
28926
|
+
const status = state?.status;
|
|
28927
|
+
const isDone = action.done || status === 'displaying' || status === 'contacted';
|
|
28928
|
+
return (jsx(ContactCard, { action: {
|
|
28929
|
+
implementation: action.implementation,
|
|
28930
|
+
toolCallId: action.toolCallId,
|
|
28931
|
+
actionId: action.actionId,
|
|
28932
|
+
input: action.input,
|
|
28933
|
+
state: action.state,
|
|
28934
|
+
done: isDone,
|
|
28935
|
+
}, onComplete: handleComplete, accentColor: accentColor, maxColumns: variant === 'fullpage' ? 3 : 1 }));
|
|
28936
|
+
};
|
|
28937
|
+
}
|
|
28938
|
+
|
|
28939
|
+
function registerDisplayFormAction() {
|
|
28940
|
+
// Handler - handles form submission and state updates
|
|
28941
|
+
frontendActionHandlers['display-form'] = async (_input, _state, context) => {
|
|
28942
|
+
return waitForActionState(context.toolCallId);
|
|
28943
|
+
};
|
|
28944
|
+
// Renderer - displays the form card
|
|
28945
|
+
actionRenderers['display-form'] = (message, accentColor, variant, onActionDismiss) => {
|
|
28946
|
+
const action = message.action;
|
|
28947
|
+
if (!action)
|
|
28948
|
+
return null;
|
|
28949
|
+
const handleComplete = (toolCallId, newState) => {
|
|
28950
|
+
resolveActionState(toolCallId, newState);
|
|
28951
|
+
};
|
|
28952
|
+
const handleDismiss = onActionDismiss
|
|
28953
|
+
? (toolCallId) => {
|
|
28954
|
+
resolveActionState(toolCallId, { __dismissed: true });
|
|
28955
|
+
onActionDismiss(toolCallId);
|
|
28956
|
+
}
|
|
28957
|
+
: undefined;
|
|
28958
|
+
// Check if action state indicates it's already complete
|
|
28959
|
+
const state = action.state;
|
|
28960
|
+
const status = state?.status;
|
|
28961
|
+
const isDone = action.done || status === 'completed' || status === 'submitted';
|
|
28962
|
+
return (jsx(FormCard, { action: {
|
|
28963
|
+
implementation: action.implementation,
|
|
28964
|
+
toolCallId: action.toolCallId,
|
|
28965
|
+
actionId: action.actionId,
|
|
28966
|
+
input: action.input,
|
|
28967
|
+
state: action.state,
|
|
28968
|
+
done: isDone,
|
|
28969
|
+
}, onComplete: handleComplete, onDismiss: handleDismiss, accentColor: accentColor }));
|
|
28970
|
+
};
|
|
28971
|
+
}
|
|
28972
|
+
|
|
28973
|
+
/**
|
|
28974
|
+
* Book Contact Appointment Handler
|
|
28975
|
+
* Frontend action handler that waits for action completion
|
|
28976
|
+
*/
|
|
28977
|
+
function registerBookContactAppointmentHandler() {
|
|
28978
|
+
frontendActionHandlers["book-contact-appointment"] = async (_input, _state, context) => {
|
|
28979
|
+
return waitForActionState(context.toolCallId);
|
|
28980
|
+
};
|
|
28981
|
+
}
|
|
28982
|
+
|
|
28983
|
+
/**
|
|
28984
|
+
* Register book-contact-appointment action handler and renderer.
|
|
28985
|
+
* Called by initializeActionHandlers to prevent tree-shaking.
|
|
28986
|
+
*/
|
|
28987
|
+
function registerBookContactAppointmentAction() {
|
|
28988
|
+
// Register the handler
|
|
28989
|
+
registerBookContactAppointmentHandler();
|
|
28990
|
+
// Register the renderer
|
|
28991
|
+
actionRenderers['book-contact-appointment'] = (message, accentColor) => {
|
|
28992
|
+
const action = message.action;
|
|
28993
|
+
if (!action)
|
|
28994
|
+
return null;
|
|
28995
|
+
const handleComplete = (toolCallId, newState) => {
|
|
28996
|
+
resolveActionState(toolCallId, newState);
|
|
28997
|
+
};
|
|
28998
|
+
return (jsx(BookContactAppointmentCard, { action: {
|
|
28999
|
+
implementation: action.implementation,
|
|
29000
|
+
toolCallId: action.toolCallId,
|
|
29001
|
+
actionId: action.actionId,
|
|
29002
|
+
input: action.input,
|
|
29003
|
+
state: action.state,
|
|
29004
|
+
done: action.done ?? false,
|
|
29005
|
+
}, onComplete: handleComplete, accentColor: accentColor }));
|
|
27910
29006
|
};
|
|
27911
29007
|
}
|
|
27912
29008
|
|
|
@@ -27923,9 +29019,14 @@ function initializeActionHandlers() {
|
|
|
27923
29019
|
initialized = true;
|
|
27924
29020
|
// Explicitly call each registration function to prevent tree-shaking
|
|
27925
29021
|
registerGoogleCalendarAction();
|
|
29022
|
+
registerMicrosoftCalendarAction();
|
|
27926
29023
|
registerLinkPreviewAction();
|
|
27927
29024
|
registerVideoPlayerAction();
|
|
27928
29025
|
registerLocationCardAction();
|
|
29026
|
+
registerQueryContactDirectoryAction();
|
|
29027
|
+
registerContactCardAction();
|
|
29028
|
+
registerDisplayFormAction();
|
|
29029
|
+
registerBookContactAppointmentAction();
|
|
27929
29030
|
}
|
|
27930
29031
|
|
|
27931
29032
|
/**
|
|
@@ -28145,12 +29246,6 @@ function isStorageAvailable() {
|
|
|
28145
29246
|
}
|
|
28146
29247
|
}
|
|
28147
29248
|
|
|
28148
|
-
/**
|
|
28149
|
-
* useChat Hook
|
|
28150
|
-
* Main state management for chat functionality
|
|
28151
|
-
*/
|
|
28152
|
-
// Initialize action handlers immediately to prevent tree-shaking
|
|
28153
|
-
initializeActionHandlers();
|
|
28154
29249
|
function hydrateToolNames(messages) {
|
|
28155
29250
|
const toolCallNameById = new Map();
|
|
28156
29251
|
for (const entry of messages) {
|
|
@@ -28194,118 +29289,78 @@ function hydrateToolNames(messages) {
|
|
|
28194
29289
|
};
|
|
28195
29290
|
});
|
|
28196
29291
|
}
|
|
28197
|
-
function
|
|
28198
|
-
|
|
28199
|
-
|
|
28200
|
-
return entry;
|
|
28201
|
-
}
|
|
28202
|
-
const content = typeof entry.message.content === "string" ? entry.message.content : "";
|
|
28203
|
-
if (content.trim().length > 0) {
|
|
28204
|
-
return entry;
|
|
28205
|
-
}
|
|
28206
|
-
return {
|
|
28207
|
-
...entry,
|
|
28208
|
-
message: { ...entry.message, content: getActionPrompt(entry.action.implementation) },
|
|
28209
|
-
};
|
|
28210
|
-
});
|
|
29292
|
+
function hydrateMessages(messages) {
|
|
29293
|
+
const visibleMessages = messages.filter((message) => !message.action?.hidden);
|
|
29294
|
+
return hydrateToolNames(visibleMessages);
|
|
28211
29295
|
}
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
|
|
28215
|
-
|
|
28216
|
-
|
|
28217
|
-
|
|
28218
|
-
|
|
28219
|
-
|
|
29296
|
+
|
|
29297
|
+
function deriveErrorInfo(error) {
|
|
29298
|
+
if (error instanceof ApiError) {
|
|
29299
|
+
const retryAfterSeconds = typeof error.retryAfterMs === 'number'
|
|
29300
|
+
? Math.max(1, Math.ceil(error.retryAfterMs / 1000))
|
|
29301
|
+
: undefined;
|
|
29302
|
+
const lowerMessage = (error.message || '').toLowerCase();
|
|
29303
|
+
let message;
|
|
29304
|
+
switch (error.status) {
|
|
29305
|
+
case 429: {
|
|
29306
|
+
const isPerUser = lowerMessage.includes('user');
|
|
29307
|
+
const base = isPerUser
|
|
29308
|
+
? 'You have reached the per-user rate limit.'
|
|
29309
|
+
: 'This widget has received too many requests.';
|
|
29310
|
+
if (retryAfterSeconds) {
|
|
29311
|
+
message = `${base} Please wait ${retryAfterSeconds} second${retryAfterSeconds === 1 ? '' : 's'} before trying again.`;
|
|
29312
|
+
}
|
|
29313
|
+
else {
|
|
29314
|
+
message = `${base} Please wait a moment and try again.`;
|
|
29315
|
+
}
|
|
29316
|
+
break;
|
|
29317
|
+
}
|
|
29318
|
+
case 401:
|
|
29319
|
+
message = 'Authentication failed. Please refresh the page or verify your API key.';
|
|
29320
|
+
break;
|
|
29321
|
+
case 403:
|
|
29322
|
+
message = 'Access to this widget is restricted. Please contact the site owner if you believe this is an error.';
|
|
29323
|
+
break;
|
|
29324
|
+
case 404:
|
|
29325
|
+
if (lowerMessage.includes('not active')) {
|
|
29326
|
+
message = 'This widget is currently inactive. Please contact the site owner.';
|
|
29327
|
+
}
|
|
29328
|
+
else {
|
|
29329
|
+
message = 'We could not find this widget. It may have been removed.';
|
|
29330
|
+
}
|
|
29331
|
+
break;
|
|
29332
|
+
default:
|
|
29333
|
+
if (error.status >= 500) {
|
|
29334
|
+
message = 'The server encountered an error. Please try again shortly.';
|
|
29335
|
+
}
|
|
29336
|
+
else if (error.status > 0) {
|
|
29337
|
+
message = error.message || 'Something went wrong. Please try again.';
|
|
29338
|
+
}
|
|
29339
|
+
else {
|
|
29340
|
+
message = error.message || 'Unable to connect to the server. Please check your internet connection.';
|
|
29341
|
+
}
|
|
28220
29342
|
}
|
|
28221
|
-
|
|
28222
|
-
|
|
28223
|
-
|
|
28224
|
-
|
|
29343
|
+
return { message, retryAfterSeconds, status: error.status };
|
|
29344
|
+
}
|
|
29345
|
+
if (error instanceof Error) {
|
|
29346
|
+
const lower = error.message.toLowerCase();
|
|
29347
|
+
if (lower.includes('network')) {
|
|
29348
|
+
return { message: 'Unable to connect to the server. Please check your internet connection.' };
|
|
28225
29349
|
}
|
|
28226
|
-
|
|
28227
|
-
|
|
28228
|
-
const status = state.status;
|
|
28229
|
-
if (status === "displaying" || status === "clicked") {
|
|
28230
|
-
return {
|
|
28231
|
-
...entry,
|
|
28232
|
-
action: { ...entry.action, done: true },
|
|
28233
|
-
};
|
|
28234
|
-
}
|
|
29350
|
+
if (lower.includes('timeout')) {
|
|
29351
|
+
return { message: 'The request timed out. Please try again.' };
|
|
28235
29352
|
}
|
|
28236
|
-
|
|
28237
|
-
|
|
28238
|
-
const status = state.status;
|
|
28239
|
-
if (status === "booked" || status === "cancelled" || status === "error") {
|
|
28240
|
-
return {
|
|
28241
|
-
...entry,
|
|
28242
|
-
action: { ...entry.action, done: true },
|
|
28243
|
-
};
|
|
28244
|
-
}
|
|
29353
|
+
if (lower.includes('unauthorized') || lower.includes('401')) {
|
|
29354
|
+
return { message: 'Authentication failed. Please refresh the page or verify your API key.' };
|
|
28245
29355
|
}
|
|
28246
|
-
|
|
28247
|
-
|
|
28248
|
-
}
|
|
28249
|
-
function hydrateMessages(messages) {
|
|
28250
|
-
return hydrateActionDoneStatus(hydrateActionContent(hydrateToolNames(messages)));
|
|
28251
|
-
}
|
|
28252
|
-
function setupActionResumeCallbacks(messages, client, conversationId, setState, onMessageUpdate) {
|
|
28253
|
-
// Find all incomplete actions and register resume callbacks
|
|
28254
|
-
for (const message of messages) {
|
|
28255
|
-
if (message.action && !message.action.done) {
|
|
28256
|
-
const toolCallId = message.action.toolCallId;
|
|
28257
|
-
const toolName = message.message.name || message.toolExecuting || "tool";
|
|
28258
|
-
registerActionResumeCallback(toolCallId, async (newState) => {
|
|
28259
|
-
// When user interacts with the action after reload, continue the stream
|
|
28260
|
-
try {
|
|
28261
|
-
// Update the action message with the new state
|
|
28262
|
-
setState(prev => ({
|
|
28263
|
-
...prev,
|
|
28264
|
-
messages: prev.messages.map(m => m.action?.toolCallId === toolCallId
|
|
28265
|
-
? {
|
|
28266
|
-
...m,
|
|
28267
|
-
action: m.action ? { ...m.action, state: newState } : undefined,
|
|
28268
|
-
}
|
|
28269
|
-
: m),
|
|
28270
|
-
isTyping: true,
|
|
28271
|
-
}));
|
|
28272
|
-
const streamState = createStreamState();
|
|
28273
|
-
// Continue the agent stream with the new state
|
|
28274
|
-
for await (const event of client.continueAgentMessageStream(conversationId, toolCallId, newState)) {
|
|
28275
|
-
if (event.type === "done") {
|
|
28276
|
-
finalizeToolMessage(streamState, setState, toolCallId, toolName);
|
|
28277
|
-
streamState.sources = event.sources;
|
|
28278
|
-
streamState.toolCallToActionId = event.tool_call_to_action_id;
|
|
28279
|
-
finalizeStreamMessages(setState, streamState.newMessageIds, event.sources, event.tool_call_to_action_id, event.suggestions);
|
|
28280
|
-
continue;
|
|
28281
|
-
}
|
|
28282
|
-
if (event.type === "error") {
|
|
28283
|
-
const errorMessage = {
|
|
28284
|
-
id: generateMessageId(),
|
|
28285
|
-
message: {
|
|
28286
|
-
role: "assistant",
|
|
28287
|
-
content: "Sorry, an error occurred. Please try again later.",
|
|
28288
|
-
},
|
|
28289
|
-
timestamp: new Date().toISOString(),
|
|
28290
|
-
sources: [],
|
|
28291
|
-
isError: true,
|
|
28292
|
-
};
|
|
28293
|
-
upsertMessage(setState, errorMessage, false);
|
|
28294
|
-
setState(prev => ({ ...prev, isTyping: false }));
|
|
28295
|
-
return;
|
|
28296
|
-
}
|
|
28297
|
-
handleStreamEvent(event, streamState, onMessageUpdate, setState);
|
|
28298
|
-
}
|
|
28299
|
-
setState(prev => ({ ...prev, isTyping: false }));
|
|
28300
|
-
}
|
|
28301
|
-
catch (error) {
|
|
28302
|
-
console.error("[Action Resume] Failed to continue stream:", error);
|
|
28303
|
-
setState(prev => ({ ...prev, isTyping: false }));
|
|
28304
|
-
}
|
|
28305
|
-
});
|
|
29356
|
+
if (lower.includes('internal server error') || lower.includes('500')) {
|
|
29357
|
+
return { message: 'The server encountered an error. Please try again shortly.' };
|
|
28306
29358
|
}
|
|
29359
|
+
return { message: error.message || 'Something went wrong. Please try again.' };
|
|
28307
29360
|
}
|
|
29361
|
+
return { message: 'Something went wrong. Please try again.' };
|
|
28308
29362
|
}
|
|
29363
|
+
|
|
28309
29364
|
function createStreamState() {
|
|
28310
29365
|
return {
|
|
28311
29366
|
currentContent: "",
|
|
@@ -28314,6 +29369,7 @@ function createStreamState() {
|
|
|
28314
29369
|
newMessageIds: new Set(),
|
|
28315
29370
|
sources: [],
|
|
28316
29371
|
toolCallToActionId: {},
|
|
29372
|
+
requestId: generateMessageId(),
|
|
28317
29373
|
};
|
|
28318
29374
|
}
|
|
28319
29375
|
function upsertMessage(setState, message, isTyping) {
|
|
@@ -28349,15 +29405,40 @@ function finalizeStreamMessages(setState, messageIds, sources, toolCallToActionI
|
|
|
28349
29405
|
return msg;
|
|
28350
29406
|
}
|
|
28351
29407
|
// Attach suggestions only to the last assistant message
|
|
28352
|
-
|
|
28353
|
-
|
|
28354
|
-
|
|
28355
|
-
return { ...msg, sources, toolCallToActionId };
|
|
29408
|
+
const withSuggestions = index === lastAssistantIndex && suggestions && suggestions.length > 0
|
|
29409
|
+
? { suggestions }
|
|
29410
|
+
: {};
|
|
29411
|
+
return { ...msg, sources, toolCallToActionId, ...withSuggestions };
|
|
28356
29412
|
}),
|
|
28357
29413
|
isTyping: false,
|
|
28358
29414
|
};
|
|
28359
29415
|
});
|
|
28360
29416
|
}
|
|
29417
|
+
function finalizeToolMessage(streamState, setState, toolCallId, toolName) {
|
|
29418
|
+
setState(prev => {
|
|
29419
|
+
const messages = prev.messages.map((entry) => {
|
|
29420
|
+
const matchesToolCall = entry.message.role === "tool" && entry.message.tool_call_id === toolCallId;
|
|
29421
|
+
if (!matchesToolCall) {
|
|
29422
|
+
return entry;
|
|
29423
|
+
}
|
|
29424
|
+
const existingName = entry.message.name || toolName;
|
|
29425
|
+
return {
|
|
29426
|
+
...entry,
|
|
29427
|
+
message: {
|
|
29428
|
+
role: "tool",
|
|
29429
|
+
content: typeof entry.message.content === "string" ? entry.message.content : "",
|
|
29430
|
+
tool_call_id: toolCallId,
|
|
29431
|
+
name: existingName,
|
|
29432
|
+
},
|
|
29433
|
+
isStreaming: false,
|
|
29434
|
+
toolExecuting: existingName,
|
|
29435
|
+
};
|
|
29436
|
+
});
|
|
29437
|
+
return { ...prev, messages, isTyping: false, isLoading: false };
|
|
29438
|
+
});
|
|
29439
|
+
streamState.activeToolCallCount = Math.max(0, streamState.activeToolCallCount - 1);
|
|
29440
|
+
}
|
|
29441
|
+
|
|
28361
29442
|
function handleContentEvent(event, streamState, onMessageUpdate, setState) {
|
|
28362
29443
|
streamState.currentContent += event.content;
|
|
28363
29444
|
const assistantMessage = {
|
|
@@ -28404,8 +29485,6 @@ function handleToolStartEvent(event, streamState, onMessageUpdate, setState) {
|
|
|
28404
29485
|
}
|
|
28405
29486
|
function handleToolEndEvent(event, streamState, _onMessageUpdate, setState) {
|
|
28406
29487
|
streamState.activeToolCallCount = Math.max(0, streamState.activeToolCallCount - 1);
|
|
28407
|
-
// Update state and mark action as done in a single setState call
|
|
28408
|
-
// Keep isTyping: true because the agent may continue generating content after tool completion
|
|
28409
29488
|
setState(prev => {
|
|
28410
29489
|
const messages = prev.messages.map((msg) => {
|
|
28411
29490
|
const matchesToolCall = msg.message.role === "tool" && msg.message.tool_call_id === event.tool_call_id;
|
|
@@ -28413,7 +29492,27 @@ function handleToolEndEvent(event, streamState, _onMessageUpdate, setState) {
|
|
|
28413
29492
|
return msg;
|
|
28414
29493
|
}
|
|
28415
29494
|
const existingName = msg.message.name || event.tool_name;
|
|
28416
|
-
|
|
29495
|
+
let action = msg.action;
|
|
29496
|
+
if (event.action_id && event.implementation) {
|
|
29497
|
+
action = {
|
|
29498
|
+
...action,
|
|
29499
|
+
implementation: event.implementation,
|
|
29500
|
+
toolCallId: event.tool_call_id,
|
|
29501
|
+
actionId: event.action_id,
|
|
29502
|
+
input: (event.input || {}),
|
|
29503
|
+
state: (event.state || {}),
|
|
29504
|
+
done: event.done,
|
|
29505
|
+
};
|
|
29506
|
+
}
|
|
29507
|
+
else if (action) {
|
|
29508
|
+
action = {
|
|
29509
|
+
...action,
|
|
29510
|
+
input: event.input ? event.input : action.input,
|
|
29511
|
+
state: event.state ? event.state : action.state,
|
|
29512
|
+
done: event.done,
|
|
29513
|
+
};
|
|
29514
|
+
}
|
|
29515
|
+
const updatedMsg = {
|
|
28417
29516
|
...msg,
|
|
28418
29517
|
message: {
|
|
28419
29518
|
role: "tool",
|
|
@@ -28423,14 +29522,10 @@ function handleToolEndEvent(event, streamState, _onMessageUpdate, setState) {
|
|
|
28423
29522
|
},
|
|
28424
29523
|
isStreaming: false,
|
|
28425
29524
|
toolExecuting: existingName,
|
|
28426
|
-
action
|
|
28427
|
-
...msg.action,
|
|
28428
|
-
state: event.state || msg.action.state,
|
|
28429
|
-
done: true, // Mark action as completed
|
|
28430
|
-
} : undefined,
|
|
29525
|
+
action,
|
|
28431
29526
|
};
|
|
29527
|
+
return updatedMsg;
|
|
28432
29528
|
});
|
|
28433
|
-
// Keep typing indicator visible - it will be hidden by done/finalizeStreamMessages
|
|
28434
29529
|
return { ...prev, messages, isTyping: true, isLoading: false };
|
|
28435
29530
|
});
|
|
28436
29531
|
}
|
|
@@ -28462,34 +29557,6 @@ function handleToolErrorEvent(event, streamState, _onMessageUpdate, setState) {
|
|
|
28462
29557
|
return { ...prev, messages, isTyping: true, isLoading: false };
|
|
28463
29558
|
});
|
|
28464
29559
|
}
|
|
28465
|
-
function finalizeToolMessage(streamState, setState, toolCallId, toolName) {
|
|
28466
|
-
setState(prev => {
|
|
28467
|
-
const messages = prev.messages.map((entry) => {
|
|
28468
|
-
const matchesToolCall = entry.message.role === "tool" && entry.message.tool_call_id === toolCallId;
|
|
28469
|
-
if (!matchesToolCall) {
|
|
28470
|
-
return entry;
|
|
28471
|
-
}
|
|
28472
|
-
const existingName = entry.message.name || toolName;
|
|
28473
|
-
return {
|
|
28474
|
-
...entry,
|
|
28475
|
-
message: {
|
|
28476
|
-
role: "tool",
|
|
28477
|
-
content: typeof entry.message.content === "string" ? entry.message.content : "",
|
|
28478
|
-
tool_call_id: toolCallId,
|
|
28479
|
-
name: existingName,
|
|
28480
|
-
},
|
|
28481
|
-
isStreaming: false,
|
|
28482
|
-
toolExecuting: existingName,
|
|
28483
|
-
action: entry.action ? {
|
|
28484
|
-
...entry.action,
|
|
28485
|
-
done: true, // Mark action as completed
|
|
28486
|
-
} : undefined,
|
|
28487
|
-
};
|
|
28488
|
-
});
|
|
28489
|
-
return { ...prev, messages, isTyping: false, isLoading: false };
|
|
28490
|
-
});
|
|
28491
|
-
streamState.activeToolCallCount = Math.max(0, streamState.activeToolCallCount - 1);
|
|
28492
|
-
}
|
|
28493
29560
|
function handleDoneEvent(event, streamState, _onMessageUpdate, setState) {
|
|
28494
29561
|
streamState.sources = event.sources;
|
|
28495
29562
|
streamState.toolCallToActionId = event.tool_call_to_action_id;
|
|
@@ -28548,6 +29615,10 @@ function handleStreamEvent(event, streamState, onMessageUpdate, setState) {
|
|
|
28548
29615
|
console.warn('[Chat] Unknown event type:', event.type);
|
|
28549
29616
|
}
|
|
28550
29617
|
}
|
|
29618
|
+
|
|
29619
|
+
function isDismissedState(state) {
|
|
29620
|
+
return Boolean(state.__dismissed);
|
|
29621
|
+
}
|
|
28551
29622
|
async function handleActionLoop(client, initialEvent, streamState, onMessageUpdate, setState, widgetId, conversationId, getMessages) {
|
|
28552
29623
|
let pendingEvent = initialEvent;
|
|
28553
29624
|
while (pendingEvent) {
|
|
@@ -28574,7 +29645,7 @@ async function handleActionLoop(client, initialEvent, streamState, onMessageUpda
|
|
|
28574
29645
|
actionId: pendingEvent.action_id,
|
|
28575
29646
|
input: pendingEvent.input,
|
|
28576
29647
|
state: pendingEvent.state,
|
|
28577
|
-
done: false,
|
|
29648
|
+
done: pendingEvent.done ?? false,
|
|
28578
29649
|
},
|
|
28579
29650
|
};
|
|
28580
29651
|
if (streamState.activeToolCallCount === 0) {
|
|
@@ -28588,7 +29659,7 @@ async function handleActionLoop(client, initialEvent, streamState, onMessageUpda
|
|
|
28588
29659
|
id: generateMessageId(),
|
|
28589
29660
|
message: {
|
|
28590
29661
|
role: "assistant",
|
|
28591
|
-
content: "Sorry, an error occurred.
|
|
29662
|
+
content: "Sorry, an error occurred.",
|
|
28592
29663
|
},
|
|
28593
29664
|
timestamp: new Date().toISOString(),
|
|
28594
29665
|
sources: [],
|
|
@@ -28612,7 +29683,7 @@ async function handleActionLoop(client, initialEvent, streamState, onMessageUpda
|
|
|
28612
29683
|
console.error("[Widget] Frontend action failed:", error);
|
|
28613
29684
|
const errorMessageEntry = {
|
|
28614
29685
|
id: generateMessageId(),
|
|
28615
|
-
message: { role: "assistant", content: "Sorry, an error occurred.
|
|
29686
|
+
message: { role: "assistant", content: "Sorry, an error occurred." },
|
|
28616
29687
|
timestamp: new Date().toISOString(),
|
|
28617
29688
|
sources: [],
|
|
28618
29689
|
isError: true,
|
|
@@ -28622,16 +29693,17 @@ async function handleActionLoop(client, initialEvent, streamState, onMessageUpda
|
|
|
28622
29693
|
return;
|
|
28623
29694
|
}
|
|
28624
29695
|
pendingEvent = null;
|
|
28625
|
-
const
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
|
|
28629
|
-
|
|
28630
|
-
|
|
28631
|
-
|
|
28632
|
-
|
|
28633
|
-
|
|
28634
|
-
|
|
29696
|
+
const dismissed = isDismissedState(nextState);
|
|
29697
|
+
if (!dismissed) {
|
|
29698
|
+
const updatedToolMessage = {
|
|
29699
|
+
...toolMessage,
|
|
29700
|
+
action: toolMessage.action ? { ...toolMessage.action, state: nextState } : toolMessage.action,
|
|
29701
|
+
};
|
|
29702
|
+
upsertMessage(setState, updatedToolMessage, true);
|
|
29703
|
+
}
|
|
29704
|
+
if (dismissed) {
|
|
29705
|
+
return;
|
|
29706
|
+
}
|
|
28635
29707
|
let streamEnded = false;
|
|
28636
29708
|
for await (const event of client.continueAgentMessageStream(conversationId, resumeToolCallId, nextState)) {
|
|
28637
29709
|
if (event.type === "action_request") {
|
|
@@ -28639,22 +29711,20 @@ async function handleActionLoop(client, initialEvent, streamState, onMessageUpda
|
|
|
28639
29711
|
break;
|
|
28640
29712
|
}
|
|
28641
29713
|
if (event.type === "done") {
|
|
28642
|
-
//
|
|
28643
|
-
// updated by tool_end event or by the user's frontend action.
|
|
29714
|
+
// Finalize tool message and stream messages
|
|
28644
29715
|
finalizeToolMessage(streamState, setState, resumeToolCallId, toolName);
|
|
28645
|
-
// Handle the done event but skip the tool finalization part since we already did it
|
|
28646
29716
|
streamState.sources = event.sources;
|
|
28647
29717
|
streamState.toolCallToActionId = event.tool_call_to_action_id;
|
|
28648
29718
|
finalizeStreamMessages(setState, streamState.newMessageIds, event.sources, event.tool_call_to_action_id, event.suggestions);
|
|
28649
29719
|
streamEnded = true;
|
|
28650
|
-
continue;
|
|
29720
|
+
continue;
|
|
28651
29721
|
}
|
|
28652
29722
|
if (event.type === "error") {
|
|
28653
29723
|
const errorMessage = {
|
|
28654
29724
|
id: generateMessageId(),
|
|
28655
29725
|
message: {
|
|
28656
29726
|
role: "assistant",
|
|
28657
|
-
content: "Sorry, an error occurred.
|
|
29727
|
+
content: "Sorry, an error occurred.",
|
|
28658
29728
|
},
|
|
28659
29729
|
timestamp: new Date().toISOString(),
|
|
28660
29730
|
sources: [],
|
|
@@ -28665,73 +29735,83 @@ async function handleActionLoop(client, initialEvent, streamState, onMessageUpda
|
|
|
28665
29735
|
}
|
|
28666
29736
|
handleStreamEvent(event, streamState, onMessageUpdate, setState);
|
|
28667
29737
|
}
|
|
28668
|
-
// If stream ended without a done event
|
|
29738
|
+
// If stream ended without a done event, finalize the tool message
|
|
28669
29739
|
if (!streamEnded && !pendingEvent) {
|
|
28670
29740
|
finalizeToolMessage(streamState, setState, resumeToolCallId, toolName);
|
|
28671
29741
|
}
|
|
28672
29742
|
}
|
|
28673
29743
|
}
|
|
28674
|
-
function
|
|
28675
|
-
|
|
28676
|
-
|
|
28677
|
-
|
|
28678
|
-
|
|
28679
|
-
|
|
28680
|
-
|
|
28681
|
-
|
|
28682
|
-
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
|
|
28690
|
-
|
|
28691
|
-
|
|
28692
|
-
|
|
28693
|
-
|
|
28694
|
-
|
|
28695
|
-
|
|
28696
|
-
|
|
28697
|
-
|
|
28698
|
-
|
|
28699
|
-
|
|
28700
|
-
|
|
28701
|
-
|
|
28702
|
-
|
|
28703
|
-
|
|
28704
|
-
|
|
28705
|
-
|
|
28706
|
-
|
|
28707
|
-
|
|
28708
|
-
|
|
28709
|
-
|
|
29744
|
+
function setupActionResumeCallbacks(messages, client, conversationId, setState, onMessageUpdate, createStreamState, registerCallback) {
|
|
29745
|
+
// Find all incomplete actions and register resume callbacks
|
|
29746
|
+
for (const message of messages) {
|
|
29747
|
+
if (message.action && !message.action.done && !message.action.hidden) {
|
|
29748
|
+
const toolCallId = message.action.toolCallId;
|
|
29749
|
+
const toolName = message.message.name || message.toolExecuting || "tool";
|
|
29750
|
+
registerCallback(toolCallId, async (newState) => {
|
|
29751
|
+
// When user interacts with the action after reload, continue the stream
|
|
29752
|
+
try {
|
|
29753
|
+
// Update the action message with the new state and check completion
|
|
29754
|
+
if (isDismissedState(newState)) {
|
|
29755
|
+
setState(prev => ({ ...prev, isTyping: false }));
|
|
29756
|
+
return;
|
|
29757
|
+
}
|
|
29758
|
+
setState(prev => ({
|
|
29759
|
+
...prev,
|
|
29760
|
+
messages: prev.messages.map(m => {
|
|
29761
|
+
if (m.action?.toolCallId !== toolCallId) {
|
|
29762
|
+
return m;
|
|
29763
|
+
}
|
|
29764
|
+
if (!m.action) {
|
|
29765
|
+
return m;
|
|
29766
|
+
}
|
|
29767
|
+
return { ...m, action: { ...m.action, state: newState } };
|
|
29768
|
+
}),
|
|
29769
|
+
isTyping: true,
|
|
29770
|
+
}));
|
|
29771
|
+
const streamState = createStreamState();
|
|
29772
|
+
// Continue the agent stream with the new state
|
|
29773
|
+
for await (const event of client.continueAgentMessageStream(conversationId, toolCallId, newState)) {
|
|
29774
|
+
if (event.type === "done") {
|
|
29775
|
+
finalizeToolMessage(streamState, setState, toolCallId, toolName);
|
|
29776
|
+
streamState.sources = event.sources;
|
|
29777
|
+
streamState.toolCallToActionId = event.tool_call_to_action_id;
|
|
29778
|
+
finalizeStreamMessages(setState, streamState.newMessageIds, event.sources, event.tool_call_to_action_id, event.suggestions);
|
|
29779
|
+
continue;
|
|
29780
|
+
}
|
|
29781
|
+
if (event.type === "error") {
|
|
29782
|
+
const errorMessage = {
|
|
29783
|
+
id: generateMessageId(),
|
|
29784
|
+
message: {
|
|
29785
|
+
role: "assistant",
|
|
29786
|
+
content: "Sorry, an error occurred. Please try again later.",
|
|
29787
|
+
},
|
|
29788
|
+
timestamp: new Date().toISOString(),
|
|
29789
|
+
sources: [],
|
|
29790
|
+
isError: true,
|
|
29791
|
+
};
|
|
29792
|
+
upsertMessage(setState, errorMessage, false);
|
|
29793
|
+
setState(prev => ({ ...prev, isTyping: false }));
|
|
29794
|
+
return;
|
|
29795
|
+
}
|
|
29796
|
+
handleStreamEvent(event, streamState, onMessageUpdate, setState);
|
|
29797
|
+
}
|
|
29798
|
+
setState(prev => ({ ...prev, isTyping: false }));
|
|
28710
29799
|
}
|
|
28711
|
-
|
|
28712
|
-
|
|
29800
|
+
catch (error) {
|
|
29801
|
+
console.error("[Action Resume] Failed to continue stream:", error);
|
|
29802
|
+
setState(prev => ({ ...prev, isTyping: false }));
|
|
28713
29803
|
}
|
|
29804
|
+
});
|
|
28714
29805
|
}
|
|
28715
|
-
return { message, retryAfterSeconds, status: error.status };
|
|
28716
|
-
}
|
|
28717
|
-
if (error instanceof Error) {
|
|
28718
|
-
const lower = error.message.toLowerCase();
|
|
28719
|
-
if (lower.includes('network')) {
|
|
28720
|
-
return { message: 'Unable to connect to the server. Please check your internet connection.' };
|
|
28721
|
-
}
|
|
28722
|
-
if (lower.includes('timeout')) {
|
|
28723
|
-
return { message: 'The request timed out. Please try again.' };
|
|
28724
|
-
}
|
|
28725
|
-
if (lower.includes('unauthorized') || lower.includes('401')) {
|
|
28726
|
-
return { message: 'Authentication failed. Please refresh the page or verify your API key.' };
|
|
28727
|
-
}
|
|
28728
|
-
if (lower.includes('internal server error') || lower.includes('500')) {
|
|
28729
|
-
return { message: 'The server encountered an error. Please try again shortly.' };
|
|
28730
|
-
}
|
|
28731
|
-
return { message: error.message || 'Something went wrong. Please try again.' };
|
|
28732
29806
|
}
|
|
28733
|
-
return { message: 'Something went wrong. Please try again.' };
|
|
28734
29807
|
}
|
|
29808
|
+
|
|
29809
|
+
/**
|
|
29810
|
+
* useChat Hook
|
|
29811
|
+
* Main state management for chat functionality
|
|
29812
|
+
*/
|
|
29813
|
+
// Initialize action handlers immediately to prevent tree-shaking
|
|
29814
|
+
initializeActionHandlers();
|
|
28735
29815
|
function useChat(options) {
|
|
28736
29816
|
const { widgetId, apiUrl, currentRoute, onMessage, onError, skipInitialization = false, } = options;
|
|
28737
29817
|
const [state, setState] = useState({
|
|
@@ -28740,27 +29820,24 @@ function useChat(options) {
|
|
|
28740
29820
|
isLoading: false,
|
|
28741
29821
|
isTyping: false,
|
|
28742
29822
|
error: null,
|
|
28743
|
-
conversationId: '',
|
|
29823
|
+
conversationId: '',
|
|
28744
29824
|
config: null,
|
|
28745
29825
|
});
|
|
28746
29826
|
const stateRef = useRef(state);
|
|
28747
29827
|
useEffect(() => {
|
|
28748
29828
|
stateRef.current = state;
|
|
28749
29829
|
}, [state]);
|
|
28750
|
-
// Chat history state
|
|
28751
29830
|
const [conversations, setConversations] = useState([]);
|
|
28752
|
-
// Stream cancellation and rate limiting
|
|
28753
29831
|
const abortControllerRef = useRef(null);
|
|
29832
|
+
const currentRequestIdRef = useRef(null);
|
|
28754
29833
|
const lastNewChatTimeRef = useRef(0);
|
|
28755
|
-
const NEW_CHAT_COOLDOWN_MS = 5000;
|
|
29834
|
+
const NEW_CHAT_COOLDOWN_MS = 5000;
|
|
28756
29835
|
const apiClient = useRef(new WidgetApiClient({ widgetId, apiUrl, currentRoute }));
|
|
28757
|
-
// Update API client when currentRoute changes
|
|
28758
29836
|
useEffect(() => {
|
|
28759
29837
|
apiClient.current = new WidgetApiClient({ widgetId, apiUrl, currentRoute });
|
|
28760
29838
|
}, [widgetId, apiUrl, currentRoute]);
|
|
28761
29839
|
// Load configuration on mount and hydrate with existing conversation if available
|
|
28762
29840
|
useEffect(() => {
|
|
28763
|
-
// Skip initialization in preview mode
|
|
28764
29841
|
if (skipInitialization) {
|
|
28765
29842
|
return;
|
|
28766
29843
|
}
|
|
@@ -28797,7 +29874,7 @@ function useChat(options) {
|
|
|
28797
29874
|
}));
|
|
28798
29875
|
// Setup resume callbacks for incomplete actions
|
|
28799
29876
|
if (conversationId) {
|
|
28800
|
-
setupActionResumeCallbacks(hydratedMessages, apiClient.current, conversationId, setState, onMessage ?? (() => { }));
|
|
29877
|
+
setupActionResumeCallbacks(hydratedMessages, apiClient.current, conversationId, setState, onMessage ?? (() => { }), createStreamState, registerActionResumeCallback);
|
|
28801
29878
|
}
|
|
28802
29879
|
}
|
|
28803
29880
|
catch (error) {
|
|
@@ -28814,14 +29891,13 @@ function useChat(options) {
|
|
|
28814
29891
|
initialize();
|
|
28815
29892
|
return () => {
|
|
28816
29893
|
isMounted = false;
|
|
28817
|
-
// Cleanup resume callbacks
|
|
28818
29894
|
state.messages.forEach(message => {
|
|
28819
29895
|
if (message.action?.toolCallId) {
|
|
28820
29896
|
unregisterActionResumeCallback(message.action.toolCallId);
|
|
28821
29897
|
}
|
|
28822
29898
|
});
|
|
28823
29899
|
};
|
|
28824
|
-
}, [widgetId, apiUrl, onError]);
|
|
29900
|
+
}, [widgetId, apiUrl, onError, skipInitialization]);
|
|
28825
29901
|
// Save conversation when messages change
|
|
28826
29902
|
useEffect(() => {
|
|
28827
29903
|
const persistConversation = state.config?.settings.persistConversation ?? true;
|
|
@@ -28837,19 +29913,15 @@ function useChat(options) {
|
|
|
28837
29913
|
const hasFiles = !!files && files.length > 0;
|
|
28838
29914
|
if (!trimmedContent && !hasFiles)
|
|
28839
29915
|
return;
|
|
28840
|
-
// Block parallel streams - don't allow sending while already streaming
|
|
28841
29916
|
if (stateRef.current.isTyping) {
|
|
28842
29917
|
console.warn('[Widget] Cannot send message while streaming is in progress');
|
|
28843
29918
|
return;
|
|
28844
29919
|
}
|
|
28845
|
-
// Cancel any existing stream before starting new one
|
|
28846
29920
|
if (abortControllerRef.current) {
|
|
28847
29921
|
abortControllerRef.current.abort();
|
|
28848
29922
|
abortControllerRef.current = null;
|
|
28849
29923
|
}
|
|
28850
|
-
// Create new abort controller for this stream
|
|
28851
29924
|
abortControllerRef.current = new AbortController();
|
|
28852
|
-
// Strip [EXECUTE_ACTION:...] prefix from displayed message (but keep for API)
|
|
28853
29925
|
const displayContent = trimmedContent.replace(/^\[EXECUTE_ACTION:[^\]]+\]\s*/, '');
|
|
28854
29926
|
const userMessage = {
|
|
28855
29927
|
id: generateMessageId(),
|
|
@@ -28860,12 +29932,11 @@ function useChat(options) {
|
|
|
28860
29932
|
timestamp: new Date().toISOString(),
|
|
28861
29933
|
sources: [],
|
|
28862
29934
|
};
|
|
28863
|
-
// Add user message immediately
|
|
28864
29935
|
setState(prev => ({
|
|
28865
29936
|
...prev,
|
|
28866
29937
|
messages: [...prev.messages, userMessage],
|
|
28867
|
-
isLoading: false,
|
|
28868
|
-
isTyping: true,
|
|
29938
|
+
isLoading: false,
|
|
29939
|
+
isTyping: true,
|
|
28869
29940
|
error: null,
|
|
28870
29941
|
}));
|
|
28871
29942
|
onMessage?.(userMessage);
|
|
@@ -28905,26 +29976,27 @@ function useChat(options) {
|
|
|
28905
29976
|
}
|
|
28906
29977
|
catch (uploadError) {
|
|
28907
29978
|
console.error('Failed to upload file:', uploadError);
|
|
28908
|
-
// Continue with other files
|
|
28909
29979
|
}
|
|
28910
29980
|
}
|
|
28911
29981
|
}
|
|
28912
|
-
// Stream the response
|
|
28913
29982
|
let lastStreamedMessage = null;
|
|
28914
29983
|
const streamState = createStreamState();
|
|
28915
|
-
|
|
29984
|
+
currentRequestIdRef.current = streamState.requestId;
|
|
28916
29985
|
const currentAbortController = abortControllerRef.current;
|
|
28917
29986
|
const streamConversationId = conversationId;
|
|
28918
|
-
const
|
|
29987
|
+
const streamRequestId = streamState.requestId;
|
|
29988
|
+
const stream = apiClient.current.sendAgentMessageStream(conversationId, trimmedContent, fileIds, currentAbortController?.signal);
|
|
28919
29989
|
for await (const event of stream) {
|
|
28920
|
-
|
|
28921
|
-
|
|
28922
|
-
|
|
29990
|
+
if (currentAbortController?.signal.aborted ||
|
|
29991
|
+
stateRef.current.conversationId !== streamConversationId ||
|
|
29992
|
+
currentRequestIdRef.current !== streamRequestId) {
|
|
29993
|
+
console.log('[Widget] Stream aborted, conversation changed, or superseded by new request');
|
|
28923
29994
|
break;
|
|
28924
29995
|
}
|
|
28925
29996
|
if (event.type === "action_request") {
|
|
28926
|
-
|
|
28927
|
-
|
|
29997
|
+
if (currentAbortController?.signal.aborted ||
|
|
29998
|
+
stateRef.current.conversationId !== streamConversationId ||
|
|
29999
|
+
currentRequestIdRef.current !== streamRequestId) {
|
|
28928
30000
|
break;
|
|
28929
30001
|
}
|
|
28930
30002
|
await handleActionLoop(apiClient.current, event, streamState, (message) => {
|
|
@@ -28936,30 +30008,26 @@ function useChat(options) {
|
|
|
28936
30008
|
lastStreamedMessage = message;
|
|
28937
30009
|
}, setState);
|
|
28938
30010
|
}
|
|
28939
|
-
|
|
28940
|
-
|
|
28941
|
-
|
|
30011
|
+
if (currentAbortController?.signal.aborted ||
|
|
30012
|
+
stateRef.current.conversationId !== streamConversationId ||
|
|
30013
|
+
currentRequestIdRef.current !== streamRequestId) {
|
|
30014
|
+
console.log('[Widget] Stream was aborted or superseded, skipping finalization');
|
|
28942
30015
|
return;
|
|
28943
30016
|
}
|
|
28944
|
-
// Stream completed - finalize state
|
|
28945
30017
|
setState(prev => ({
|
|
28946
30018
|
...prev,
|
|
28947
30019
|
isLoading: false,
|
|
28948
30020
|
isTyping: false,
|
|
28949
30021
|
}));
|
|
28950
|
-
// Notify about final message
|
|
28951
30022
|
if (lastStreamedMessage) {
|
|
28952
30023
|
onMessage?.(lastStreamedMessage);
|
|
28953
30024
|
}
|
|
28954
|
-
// Generate follow-up suggestions asynchronously
|
|
28955
30025
|
const enableFollowUps = state.config?.settings.enableFollowUpSuggestions !== false;
|
|
28956
30026
|
const actionIds = state.config?.actions || [];
|
|
28957
30027
|
if (enableFollowUps) {
|
|
28958
|
-
// Don't await - let it run in background
|
|
28959
30028
|
apiClient.current.generateFollowUps(stateRef.current.messages, actionIds)
|
|
28960
30029
|
.then(suggestions => {
|
|
28961
30030
|
if (suggestions.length > 0) {
|
|
28962
|
-
// Attach suggestions to the last assistant message
|
|
28963
30031
|
setState(prev => {
|
|
28964
30032
|
const messages = [...prev.messages];
|
|
28965
30033
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -28988,7 +30056,7 @@ function useChat(options) {
|
|
|
28988
30056
|
},
|
|
28989
30057
|
timestamp: new Date().toISOString(),
|
|
28990
30058
|
sources: [],
|
|
28991
|
-
isError: !fallbackMessage,
|
|
30059
|
+
isError: !fallbackMessage,
|
|
28992
30060
|
};
|
|
28993
30061
|
setState(prev => ({
|
|
28994
30062
|
...prev,
|
|
@@ -29000,9 +30068,6 @@ function useChat(options) {
|
|
|
29000
30068
|
onError?.(err);
|
|
29001
30069
|
}
|
|
29002
30070
|
}, [state.conversationId, state.config, state.messages, onMessage, onError]);
|
|
29003
|
-
/**
|
|
29004
|
-
* Clear all messages
|
|
29005
|
-
*/
|
|
29006
30071
|
const clearMessages = useCallback(() => {
|
|
29007
30072
|
setState(prev => ({
|
|
29008
30073
|
...prev,
|
|
@@ -29015,9 +30080,6 @@ function useChat(options) {
|
|
|
29015
30080
|
clearConversation(widgetId);
|
|
29016
30081
|
}
|
|
29017
30082
|
}, [widgetId, state.config?.settings.persistConversation]);
|
|
29018
|
-
/**
|
|
29019
|
-
* Submit feedback for a message
|
|
29020
|
-
*/
|
|
29021
30083
|
const submitFeedback = useCallback(async (messageId, feedback) => {
|
|
29022
30084
|
try {
|
|
29023
30085
|
const message = state.messages.find(msg => msg.id === messageId);
|
|
@@ -29027,7 +30089,6 @@ function useChat(options) {
|
|
|
29027
30089
|
: undefined;
|
|
29028
30090
|
console.log('Submitting feedback:', { conversationId: state.conversationId, messageId, feedback });
|
|
29029
30091
|
await apiClient.current.submitFeedback(state.conversationId, messageId, feedback, meta);
|
|
29030
|
-
// Update message with feedback
|
|
29031
30092
|
setState(prev => ({
|
|
29032
30093
|
...prev,
|
|
29033
30094
|
messages: prev.messages.map(msg => msg.id === messageId
|
|
@@ -29042,9 +30103,51 @@ function useChat(options) {
|
|
|
29042
30103
|
onError?.(err);
|
|
29043
30104
|
}
|
|
29044
30105
|
}, [state.conversationId, onError]);
|
|
29045
|
-
|
|
29046
|
-
|
|
29047
|
-
|
|
30106
|
+
const dismissAction = useCallback(async (toolCallId) => {
|
|
30107
|
+
if (!toolCallId)
|
|
30108
|
+
return;
|
|
30109
|
+
const dismissedAt = new Date().toISOString();
|
|
30110
|
+
setState(prev => ({
|
|
30111
|
+
...prev,
|
|
30112
|
+
messages: prev.messages.map((message) => {
|
|
30113
|
+
if (message.action?.toolCallId !== toolCallId) {
|
|
30114
|
+
return message;
|
|
30115
|
+
}
|
|
30116
|
+
if (!message.action) {
|
|
30117
|
+
return message;
|
|
30118
|
+
}
|
|
30119
|
+
return {
|
|
30120
|
+
...message,
|
|
30121
|
+
action: {
|
|
30122
|
+
...message.action,
|
|
30123
|
+
hidden: true,
|
|
30124
|
+
dismissedAt,
|
|
30125
|
+
dismissedBy: "user",
|
|
30126
|
+
done: true,
|
|
30127
|
+
},
|
|
30128
|
+
};
|
|
30129
|
+
}),
|
|
30130
|
+
isTyping: true,
|
|
30131
|
+
isLoading: false,
|
|
30132
|
+
}));
|
|
30133
|
+
unregisterActionResumeCallback(toolCallId);
|
|
30134
|
+
const conversationId = stateRef.current.conversationId;
|
|
30135
|
+
if (!conversationId) {
|
|
30136
|
+
setState(prev => ({ ...prev, isTyping: false }));
|
|
30137
|
+
return;
|
|
30138
|
+
}
|
|
30139
|
+
try {
|
|
30140
|
+
const streamState = createStreamState();
|
|
30141
|
+
for await (const event of apiClient.current.dismissAgentMessageStream(conversationId, toolCallId)) {
|
|
30142
|
+
handleStreamEvent(event, streamState, onMessage ?? (() => { }), setState);
|
|
30143
|
+
}
|
|
30144
|
+
setState(prev => ({ ...prev, isTyping: false, isLoading: false }));
|
|
30145
|
+
}
|
|
30146
|
+
catch (error) {
|
|
30147
|
+
console.error("[Widget] dismissAction error:", error);
|
|
30148
|
+
setState(prev => ({ ...prev, isTyping: false, isLoading: false }));
|
|
30149
|
+
}
|
|
30150
|
+
}, [onMessage]);
|
|
29048
30151
|
const loadConversations = useCallback(() => {
|
|
29049
30152
|
const persistConversation = state.config?.settings.persistConversation ?? true;
|
|
29050
30153
|
if (!persistConversation || !isStorageAvailable()) {
|
|
@@ -29061,13 +30164,11 @@ function useChat(options) {
|
|
|
29061
30164
|
})));
|
|
29062
30165
|
}, [widgetId, state.config?.settings.persistConversation]);
|
|
29063
30166
|
const switchConversation = useCallback(async (conversationId) => {
|
|
29064
|
-
// Cancel any active stream before switching conversations
|
|
29065
30167
|
if (abortControllerRef.current) {
|
|
29066
30168
|
abortControllerRef.current.abort();
|
|
29067
30169
|
abortControllerRef.current = null;
|
|
29068
30170
|
}
|
|
29069
30171
|
const persistConversation = state.config?.settings.persistConversation ?? true;
|
|
29070
|
-
// First try to load from localStorage
|
|
29071
30172
|
if (persistConversation && isStorageAvailable()) {
|
|
29072
30173
|
const stored = loadConversationById(widgetId, conversationId);
|
|
29073
30174
|
if (stored) {
|
|
@@ -29086,7 +30187,6 @@ function useChat(options) {
|
|
|
29086
30187
|
try {
|
|
29087
30188
|
const conversation = await apiClient.current.getOrCreateConversation(conversationId);
|
|
29088
30189
|
const hydratedMessages = hydrateMessages(conversation.messages);
|
|
29089
|
-
// Clear old resume callbacks
|
|
29090
30190
|
state.messages.forEach(message => {
|
|
29091
30191
|
if (message.action?.toolCallId) {
|
|
29092
30192
|
unregisterActionResumeCallback(message.action.toolCallId);
|
|
@@ -29098,9 +30198,7 @@ function useChat(options) {
|
|
|
29098
30198
|
messages: hydratedMessages,
|
|
29099
30199
|
isLoading: false,
|
|
29100
30200
|
}));
|
|
29101
|
-
|
|
29102
|
-
setupActionResumeCallbacks(hydratedMessages, apiClient.current, conversation.id, setState, onMessage ?? (() => { }));
|
|
29103
|
-
// Save to local storage
|
|
30201
|
+
setupActionResumeCallbacks(hydratedMessages, apiClient.current, conversation.id, setState, onMessage ?? (() => { }), createStreamState, registerActionResumeCallback);
|
|
29104
30202
|
if (persistConversation && isStorageAvailable()) {
|
|
29105
30203
|
saveConversation(widgetId, conversation.id, hydratedMessages);
|
|
29106
30204
|
}
|
|
@@ -29111,19 +30209,16 @@ function useChat(options) {
|
|
|
29111
30209
|
}
|
|
29112
30210
|
}, [widgetId, state.config?.settings.persistConversation]);
|
|
29113
30211
|
const startNewConversation = useCallback(() => {
|
|
29114
|
-
// Rate limiting - prevent spamming new chats
|
|
29115
30212
|
const now = Date.now();
|
|
29116
30213
|
if (now - lastNewChatTimeRef.current < NEW_CHAT_COOLDOWN_MS) {
|
|
29117
30214
|
console.warn('[Widget] New chat rate limited - please wait');
|
|
29118
30215
|
return;
|
|
29119
30216
|
}
|
|
29120
30217
|
lastNewChatTimeRef.current = now;
|
|
29121
|
-
// Cancel any active stream before starting new conversation
|
|
29122
30218
|
if (abortControllerRef.current) {
|
|
29123
30219
|
abortControllerRef.current.abort();
|
|
29124
30220
|
abortControllerRef.current = null;
|
|
29125
30221
|
}
|
|
29126
|
-
// Reset typing state if stream was active
|
|
29127
30222
|
setState(prev => ({
|
|
29128
30223
|
...prev,
|
|
29129
30224
|
messages: [],
|
|
@@ -29142,11 +30237,8 @@ function useChat(options) {
|
|
|
29142
30237
|
if (!persistConversation || !isStorageAvailable()) {
|
|
29143
30238
|
return;
|
|
29144
30239
|
}
|
|
29145
|
-
// Delete from storage
|
|
29146
30240
|
deleteConversation(widgetId, conversationId);
|
|
29147
|
-
// Update local state
|
|
29148
30241
|
setConversations(prev => prev.filter(c => c.id !== conversationId));
|
|
29149
|
-
// If we deleted the current conversation, clear it
|
|
29150
30242
|
if (state.conversationId === conversationId) {
|
|
29151
30243
|
setState(prev => ({
|
|
29152
30244
|
...prev,
|
|
@@ -29156,6 +30248,43 @@ function useChat(options) {
|
|
|
29156
30248
|
}));
|
|
29157
30249
|
}
|
|
29158
30250
|
}, [widgetId, state.config?.settings.persistConversation, state.conversationId]);
|
|
30251
|
+
const createDemoConversation = useCallback(async (userMessage, assistantMessage) => {
|
|
30252
|
+
try {
|
|
30253
|
+
const result = await apiClient.current.createDemoConversation(userMessage, assistantMessage);
|
|
30254
|
+
if (result.success && result.id) {
|
|
30255
|
+
// Update state with the new conversation ID
|
|
30256
|
+
setState(prev => ({
|
|
30257
|
+
...prev,
|
|
30258
|
+
conversationId: result.id,
|
|
30259
|
+
}));
|
|
30260
|
+
// Save to local storage if persistence is enabled
|
|
30261
|
+
const persistConversation = state.config?.settings.persistConversation ?? true;
|
|
30262
|
+
if (persistConversation && isStorageAvailable()) {
|
|
30263
|
+
const demoMessages = [
|
|
30264
|
+
{
|
|
30265
|
+
id: generateMessageId(),
|
|
30266
|
+
message: { role: 'user', content: userMessage },
|
|
30267
|
+
timestamp: new Date().toISOString(),
|
|
30268
|
+
sources: [],
|
|
30269
|
+
},
|
|
30270
|
+
{
|
|
30271
|
+
id: generateMessageId(),
|
|
30272
|
+
message: { role: 'assistant', content: assistantMessage },
|
|
30273
|
+
timestamp: new Date(Date.now() + 1000).toISOString(),
|
|
30274
|
+
sources: [],
|
|
30275
|
+
},
|
|
30276
|
+
];
|
|
30277
|
+
saveConversation(widgetId, result.id, demoMessages);
|
|
30278
|
+
}
|
|
30279
|
+
return result.id;
|
|
30280
|
+
}
|
|
30281
|
+
return null;
|
|
30282
|
+
}
|
|
30283
|
+
catch (error) {
|
|
30284
|
+
console.error('[useChat] Failed to create demo conversation:', error);
|
|
30285
|
+
return null;
|
|
30286
|
+
}
|
|
30287
|
+
}, [widgetId, state.config?.settings.persistConversation]);
|
|
29159
30288
|
return {
|
|
29160
30289
|
messages: state.messages,
|
|
29161
30290
|
isLoading: state.isLoading,
|
|
@@ -29166,20 +30295,29 @@ function useChat(options) {
|
|
|
29166
30295
|
sendMessage,
|
|
29167
30296
|
clearMessages,
|
|
29168
30297
|
submitFeedback,
|
|
29169
|
-
|
|
30298
|
+
dismissAction,
|
|
29170
30299
|
conversations,
|
|
29171
30300
|
loadConversations,
|
|
29172
30301
|
switchConversation,
|
|
29173
30302
|
startNewConversation,
|
|
29174
30303
|
deleteConversation: deleteConversation$1,
|
|
30304
|
+
createDemoConversation,
|
|
29175
30305
|
};
|
|
29176
30306
|
}
|
|
29177
30307
|
|
|
30308
|
+
const DataPolicyView = ({ config, widgetName, }) => {
|
|
30309
|
+
const headerTitle = widgetName || config?.appearance?.headerTitle || 'AI Assistant';
|
|
30310
|
+
const hasFileUpload = config?.settings?.enableFileUpload ?? false;
|
|
30311
|
+
const persistsConversation = config?.settings?.persistConversation ?? true;
|
|
30312
|
+
return (jsx("div", { className: "ai-chat-data-policy-view", children: jsxs("div", { className: "ai-chat-data-policy-content", children: [jsx("div", { className: "ai-chat-data-policy-intro", children: jsxs("p", { children: ["This privacy notice informs you pursuant to Art. 13 GDPR about how ", jsx("strong", { children: headerTitle }), " processes data when you use this chat. Please note that the specific scope of processing depends on the operator of this website/application (the controller) and on the respective activated functions."] }) }), jsxs("div", { className: "ai-chat-data-policy-section", children: [jsx("h3", { children: "Controller / Contact" }), jsx("p", { children: "The controller within the meaning of Art. 4 No. 7 GDPR is the operator of this website/application. The contact details (and, if applicable, the contact details of a data protection officer) can be found in the privacy policy or in the legal notice of the website into which this chat widget is embedded." })] }), jsxs("div", { className: "ai-chat-data-policy-section", children: [jsx("h3", { children: "Processed Data" }), jsxs("p", { children: ["The chat processes the following categories of data. ", jsx("strong", { children: "Chat Content" }), " comprises messages (text) and, if applicable, context information that you provide in the chat. This content is processed to generate responses and provide the conversation."] }), hasFileUpload && (jsxs("p", { children: [jsx("strong", { children: "Uploaded Files" }), " that you transmit to the chat are processed to handle your request. Processing may include extracting text or information."] })), jsxs("p", { children: [jsx("strong", { children: "Technical Usage Data" }), " includes timestamps, session or request information, as well as technical metadata required for operation, security (abuse prevention), and error analysis."] }), jsx("p", { children: "Please do not enter special categories of personal data (e.g., health data), passwords, credit card or bank data, or confidential business secrets in the chat. AI-generated responses may be inaccurate and should be checked independently before use." })] }), jsxs("div", { className: "ai-chat-data-policy-section", children: [jsx("h3", { children: "Purposes and Legal Bases" }), jsxs("p", { children: [jsx("strong", { children: "Provision of the chat and answering of inquiries" }), " is based on Art. 6 Para. 1 lit. b GDPR, insofar as contractual or pre-contractual measures apply; otherwise on Art. 6 Para. 1 lit. f GDPR (legitimate interest in efficient communication and support)."] }), jsxs("p", { children: [jsx("strong", { children: "Quality assurance, operation and security" }), " are based on Art. 6 Para. 1 lit. f GDPR, for example for stability, abuse detection, and troubleshooting."] }), jsxs("p", { children: [jsx("strong", { children: "Consent-based processing" }), " may occur if the operator provides for this (Art. 6 Para. 1 lit. a GDPR)."] })] }), jsxs("div", { className: "ai-chat-data-policy-section", children: [jsx("h3", { children: "Recipients and Processors" }), jsxs("p", { children: [jsx("strong", { children: "Hosting/IT Service Providers" }), " may be used by the operator for hosting, logging, monitoring, and infrastructure."] }), jsxs("p", { children: [jsx("strong", { children: "AI Service Providers" }), " may receive chat content to generate responses. Where required, this is done on the basis of a data processing agreement (Art. 28 GDPR)."] }), jsxs("p", { children: [jsx("strong", { children: "Third-Country Transfer" }), " may occur if recipients are located outside the EU/EEA. In this case, appropriate safeguards (e.g., EU Standard Contractual Clauses) are used where required."] })] }), jsxs("div", { className: "ai-chat-data-policy-section", children: [jsx("h3", { children: "Retention Period" }), persistsConversation ? (jsxs("p", { children: [jsx("strong", { children: "Chat History" }), " may be stored to continue the conversation across multiple sessions."] })) : (jsxs("p", { children: [jsx("strong", { children: "Chat History" }), " is not permanently stored and ends when the chat is closed."] })), hasFileUpload && (jsxs("p", { children: [jsx("strong", { children: "Files" }), " are processed only as long as necessary to handle the request and are then deleted, unless longer retention is legally required."] })), jsxs("p", { children: [jsx("strong", { children: "Technical Logs" }), " may be stored for a limited period to ensure secure operation."] })] }), jsxs("div", { className: "ai-chat-data-policy-section", children: [jsx("h3", { children: "Your Rights (Data Subject Rights)" }), jsx("p", { children: "You have the following rights under the GDPR: Right to Information (Art. 15), Right to Rectification (Art. 16), Right to Erasure (Art. 17) and Restriction of Processing (Art. 18), Right to Data Portability (Art. 20), Right to Objection to processing based on legitimate interests (Art. 21), and Right to Complain to a supervisory authority (Art. 77)." }), jsx("p", { children: "Note: Without clear identification features, the operator may not be able to assign individual chat histories to a person. For inquiries, please contact the operator of this website/application." })] })] }) }));
|
|
30313
|
+
};
|
|
30314
|
+
|
|
29178
30315
|
const MenuIcon = () => (jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "4", y1: "10", x2: "20", y2: "10" }), jsx("line", { x1: "10", y1: "14", x2: "20", y2: "14" })] }));
|
|
29179
30316
|
const PlusIcon = () => (jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })] }));
|
|
29180
30317
|
const TrashIcon = () => (jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M3 6h18" }), jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" })] }));
|
|
29181
30318
|
const CloseIcon = () => (jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
|
|
29182
|
-
const
|
|
30319
|
+
const BackIcon = () => (jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M19 12H5" }), jsx("path", { d: "M12 19l-7-7 7-7" })] }));
|
|
30320
|
+
const ChatWindow = ({ messages, isLoading, isTyping, config, onSendMessage, onClose: _onClose, onFeedback, onActionClick, onActionDismiss,
|
|
29183
30321
|
// Chat history props (only active when persistConversation is true)
|
|
29184
30322
|
conversations = [], onLoadConversations, onSwitchConversation, onStartNewConversation, onDeleteConversation, currentConversationId,
|
|
29185
30323
|
// Override props for live preview
|
|
@@ -29196,6 +30334,8 @@ headerTitleOverride, welcomeTitleOverride, welcomeMessageOverride, placeholderOv
|
|
|
29196
30334
|
const inputPlaceholder = placeholderOverride ?? appearance?.placeholder ?? 'Ask me anything...';
|
|
29197
30335
|
// Track if history panel is open
|
|
29198
30336
|
const [showHistory, setShowHistory] = useState(false);
|
|
30337
|
+
// Track if data policy view is open
|
|
30338
|
+
const [showDataPolicy, setShowDataPolicy] = useState(false);
|
|
29199
30339
|
// Scroll button state (managed by MessageList)
|
|
29200
30340
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
29201
30341
|
const [scrollToBottom, setScrollToBottom] = useState(null);
|
|
@@ -29205,6 +30345,13 @@ headerTitleOverride, welcomeTitleOverride, welcomeMessageOverride, placeholderOv
|
|
|
29205
30345
|
}, []);
|
|
29206
30346
|
// History exit animation when starting a new chat from overview
|
|
29207
30347
|
const [isHistoryExiting, setIsHistoryExiting] = useState(false);
|
|
30348
|
+
// Handle data policy click
|
|
30349
|
+
const handleDataPolicyClick = useCallback(() => {
|
|
30350
|
+
setShowDataPolicy(true);
|
|
30351
|
+
}, []);
|
|
30352
|
+
const handleDataPolicyBack = useCallback(() => {
|
|
30353
|
+
setShowDataPolicy(false);
|
|
30354
|
+
}, []);
|
|
29208
30355
|
// Load conversations when history panel opens
|
|
29209
30356
|
const handleOpenHistory = () => {
|
|
29210
30357
|
setShowHistory(true);
|
|
@@ -29252,10 +30399,22 @@ headerTitleOverride, welcomeTitleOverride, welcomeMessageOverride, placeholderOv
|
|
|
29252
30399
|
// The backend will detect and trigger the action based on the message
|
|
29253
30400
|
onSendMessage(question);
|
|
29254
30401
|
};
|
|
29255
|
-
return (jsxs("div", { className: `ai-chat-window size-${size}`, role: "dialog", "aria-label": "Chat window", children: [jsx("div", { className: `ai-chat-header ${showHistory ? 'is-history' : ''}`, children: showHistory ? (jsxs(Fragment, { children: [jsx("div", { className: "ai-chat-title", children: headerTitle }), jsx("button", { className: "ai-chat-header-button", onClick: handleNewConversation, "aria-label": "New chat", children: jsx(PlusIcon, {}) })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "ai-chat-header-content", children: [appearance?.logo && (jsx("img", { src: appearance.logo, alt: "Logo", className: "ai-chat-logo" })), jsx("div", { className: "ai-chat-title", children: headerTitle })] }), jsxs("div", { className: "ai-chat-header-actions", children: [canShowHistory && (jsx("button", { className: "ai-chat-header-button", onClick: handleOpenHistory, "aria-label": "Chat overview", children: jsx(MenuIcon, {}) })), jsx("button", { className: "ai-chat-close-button header-close-button", onClick: _onClose, "aria-label": "Close chat", children: jsx(CloseIcon, {}) })] })] })) }),
|
|
30402
|
+
return (jsxs("div", { className: `ai-chat-window size-${size}`, role: "dialog", "aria-label": "Chat window", children: [jsx("div", { className: `ai-chat-header ${showHistory ? 'is-history' : ''} ${showDataPolicy ? 'is-data-policy' : ''}`, children: showDataPolicy ? (jsxs(Fragment, { children: [jsx("button", { className: "ai-chat-header-button", onClick: handleDataPolicyBack, "aria-label": "Back to chat", children: jsx(BackIcon, {}) }), jsx("div", { className: "ai-chat-title", children: "Privacy Notice" })] })) : showHistory ? (jsxs(Fragment, { children: [jsx("div", { className: "ai-chat-title", children: headerTitle }), jsx("button", { className: "ai-chat-header-button", onClick: handleNewConversation, "aria-label": "New chat", children: jsx(PlusIcon, {}) })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "ai-chat-header-content", children: [appearance?.logo && (jsx("img", { src: appearance.logo, alt: "Logo", className: "ai-chat-logo" })), jsx("div", { className: "ai-chat-title", children: headerTitle })] }), jsxs("div", { className: "ai-chat-header-actions", children: [canShowHistory && (jsx("button", { className: "ai-chat-header-button", onClick: handleOpenHistory, "aria-label": "Chat overview", children: jsx(MenuIcon, {}) })), jsx("button", { className: "ai-chat-close-button header-close-button", onClick: _onClose, "aria-label": "Close chat", children: jsx(CloseIcon, {}) })] })] })) }), showDataPolicy ? (jsx(DataPolicyView, { config: config, onBack: handleDataPolicyBack, widgetName: headerTitle })) : showHistory ? (
|
|
30403
|
+
/* History Panel */
|
|
30404
|
+
jsxs("div", { className: "ai-chat-history-panel", children: [conversations.length === 0 ? (jsx("div", { className: "ai-chat-history-empty", children: "No previous conversations" })) : (jsx("div", { className: `ai-chat-history-list ${isHistoryExiting ? 'exiting' : ''}`, children: conversations.map((conv) => (jsx("div", { className: `ai-chat-history-item ${conv.id === currentConversationId ? 'active' : ''}`, onClick: () => handleSelectConversation(conv.id), children: jsxs("div", { className: "ai-chat-history-item-content", children: [jsx("div", { className: "ai-chat-history-item-preview", children: conv.preview }), onDeleteConversation && (jsx("button", { className: "ai-chat-history-item-delete", onClick: (e) => {
|
|
29256
30405
|
e.stopPropagation();
|
|
29257
30406
|
onDeleteConversation(conv.id);
|
|
29258
|
-
}, "aria-label": "Delete conversation", children: jsx(TrashIcon, {}) }))] }) }, conv.id))) })), jsx(MessageInput, { onSend: (text) => handleSendFromOverview(text), placeholder: inputPlaceholder, disabled: isLoading, enableFileUpload: settings?.enableFileUpload })] })) : (jsxs(Fragment, { children: [
|
|
30407
|
+
}, "aria-label": "Delete conversation", children: jsx(TrashIcon, {}) }))] }) }, conv.id))) })), jsx(MessageInput, { onSend: (text) => handleSendFromOverview(text), placeholder: inputPlaceholder, disabled: isLoading, enableFileUpload: settings?.enableFileUpload, showDataPolicy: settings?.showDataPolicy ?? true, onDataPolicyClick: handleDataPolicyClick })] })) : (jsxs(Fragment, { children: [maxMessages && userMessageCount >= maxMessages - 2 && !isLimitReached && (jsxs("div", { className: "ai-chat-warning", role: "alert", children: [maxMessages - userMessageCount, " message", maxMessages - userMessageCount !== 1 ? 's' : '', " remaining"] })), isLimitReached && (jsx("div", { className: "ai-chat-error", role: "alert", children: "Message limit reached. Please start a new conversation." })), (() => {
|
|
30408
|
+
console.log('[DEBUG ChatWindow] Rendering MessageList with', messages.length, 'messages');
|
|
30409
|
+
messages.forEach((m, i) => {
|
|
30410
|
+
console.log(`[DEBUG ChatWindow] msg ${i}:`, { role: m.message.role, hasAction: !!m.action, impl: m.action?.implementation });
|
|
30411
|
+
});
|
|
30412
|
+
console.log('[DEBUG ChatWindow] getActionRenderer available:', !!getActionRenderer);
|
|
30413
|
+
if (getActionRenderer) {
|
|
30414
|
+
console.log('[DEBUG ChatWindow] Testing renderer for query-contact-directory:', !!getActionRenderer('query-contact-directory'));
|
|
30415
|
+
}
|
|
30416
|
+
return null;
|
|
30417
|
+
})(), jsx(MessageList, { messages: messages, isTyping: isTyping, showTypingIndicator: settings?.showTypingIndicator, showTimestamps: settings?.showTimestamps, showToolCalls: settings?.showToolCalls, enableFeedback: settings?.enableFeedback, welcomeTitle: welcomeTitle || 'Welcome Message', welcomeMessage: welcomeMessage, suggestedQuestions: suggestedQuestionsOverride ?? settings?.suggestedQuestions, accentColor: appearance?.primaryColor, onSuggestedQuestionClick: handleQuestionClick, onActionClick: onActionClick, onActionDismiss: onActionDismiss, onFeedback: onFeedback, onScrollStateChange: handleScrollStateChange, getActionRenderer: getActionRenderer }), settings?.showDataPolicy && (jsxs("div", { className: "ai-chat-page-disclaimer", children: [jsx("span", { children: "AI-generated responses may be inaccurate." }), jsx("button", { type: "button", className: "ai-chat-page-disclaimer-link", onClick: handleDataPolicyClick, children: "Privacy Notice" })] })), jsx(ScrollButton, { onClick: () => scrollToBottom?.(), visible: showScrollButton }), jsx(MessageInput, { onSend: onSendMessage, placeholder: isLimitReached ? 'Message limit reached' : (isTyping ? 'Waiting for response...' : inputPlaceholder), disabled: isLoading || isTyping || isLimitReached, enableFileUpload: settings?.enableFileUpload, showDataPolicy: settings?.showDataPolicy ?? true, onDataPolicyClick: handleDataPolicyClick })] }))] }));
|
|
29259
30418
|
};
|
|
29260
30419
|
|
|
29261
30420
|
/**
|
|
@@ -29661,7 +30820,7 @@ function styleInject(css, ref) {
|
|
|
29661
30820
|
if ( ref === void 0 ) ref = {};
|
|
29662
30821
|
var insertAt = ref.insertAt;
|
|
29663
30822
|
|
|
29664
|
-
if (typeof document === 'undefined') { return; }
|
|
30823
|
+
if (!css || typeof document === 'undefined') { return; }
|
|
29665
30824
|
|
|
29666
30825
|
var head = document.head || document.getElementsByTagName('head')[0];
|
|
29667
30826
|
var style = document.createElement('style');
|
|
@@ -29684,7 +30843,10 @@ function styleInject(css, ref) {
|
|
|
29684
30843
|
}
|
|
29685
30844
|
}
|
|
29686
30845
|
|
|
29687
|
-
var css_248z = ".ai-chat-message{animation:ai-chat-message-appear .2s var(--chat-ease-bounce);max-width:85%}.ai-chat-message-content{border-radius:var(--chat-radius-bubble,14px);font-size:var(--chat-text-md,15px);line-height:var(--chat-line-relaxed,1.6);padding:var(--chat-space-sm,8px) var(--chat-space-md,16px)}.ai-chat-message.user .ai-chat-message-content{background:var(--chat-user-bg,#f4f3f0);border-bottom-right-radius:var(--chat-radius-sm,4px);color:var(--chat-user-text,#000)}.ai-chat-message.assistant .ai-chat-message-content{background:var(--chat-assistant-bg,transparent);color:var(--chat-assistant-text,#000)}.ai-chat-message-timestamp{color:var(--chat-text-muted,#71717a);font-size:var(--chat-text-xs,12px);margin-top:var(--chat-space-xs,4px);padding:0 var(--chat-space-xs,4px)}.ai-chat-message.streaming .ai-chat-message-content:after{animation:ai-chat-cursor-blink .8s infinite;content:\"▋\";margin-left:2px;opacity:.7}@keyframes ai-chat-message-appear{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes ai-chat-cursor-blink{0%,50%{opacity:1}51%,to{opacity:0}}.ai-chat-message.fullpage .ai-chat-message-content{font-size:var(--chat-text-lg,18px);padding:var(--chat-space-md,16px) var(--chat-space-lg,24px)}.ai-chat-typing{gap:var(--chat-space-xs,4px);padding:var(--chat-space-sm,8px) var(--chat-space-md,16px)}.ai-chat-typing-dot{background:var(--chat-text-muted,#71717a)}.ai-chat-tool-gear{color:var(--text-primary,#3e3e3e)}.ai-chat-tool-gear.spinning{animation:ai-chat-spin 1.5s linear infinite}.ai-chat-tool-badge{border-radius:8px}.ai-chat-tool-badge.loading{background:#e5e7eb;border:1px solid #d1d5db;color:#1f2937}.ai-chat-tool-badge.error{background:rgba(239,68,68,.1);color:#ef4444}.ai-chat-tool-check{color:#22c55e;flex-shrink:0}.ai-chat-tool-error{color:#ef4444;flex-shrink:0}.ai-chat-tool-badge .tool-name{max-width:150px;overflow:hidden;text-overflow:ellipsis}.ai-chat-widget.dark .ai-chat-tool-gear,.ai-chat-widget[data-theme=dark] .ai-chat-tool-gear,.dark .ai-chat-tool-gear,[data-theme=dark] .ai-chat-tool-gear{color:#fff}.ai-chat-widget.dark .ai-chat-tool-badge,.ai-chat-widget[data-theme=dark] .ai-chat-tool-badge,.dark .ai-chat-tool-badge,[data-theme=dark] .ai-chat-tool-badge{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);color:hsla(0,0%,100%,.9)}.ai-chat-widget.dark .ai-chat-tool-badge.error,.ai-chat-widget[data-theme=dark] .ai-chat-tool-badge.error,.dark .ai-chat-tool-badge.error,[data-theme=dark] .ai-chat-tool-badge.error{background:rgba(239,68,68,.2);color:#f87171}.chakra-ui-dark .ai-chat-tool-gear,html.dark .ai-chat-tool-gear{color:#fff}.chakra-ui-dark .ai-chat-tool-badge,html.dark .ai-chat-tool-badge{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);color:hsla(0,0%,100%,.9)}.chakra-ui-dark .ai-chat-tool-badge.error,html.dark .ai-chat-tool-badge.error{background:rgba(239,68,68,.2);color:#f87171}@keyframes ai-chat-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.ai-chat-action-skeleton-item{animation:ai-chat-skeleton-pulse 1.5s ease-in-out infinite;background:var(--bg-secondary,#e5e7eb)}.ai-chat-widget.dark .ai-chat-action-skeleton-item,.chakra-ui-dark .ai-chat-action-skeleton-item,.dark .ai-chat-action-skeleton-item,[data-theme=dark] .ai-chat-action-skeleton-item{background:hsla(0,0%,100%,.1)}.ai-chat-action-skeleton-content{display:flex;flex-direction:column;gap:16px}.ai-chat-action-skeleton-header{align-items:center;display:flex;gap:10px}.ai-chat-action-skeleton-box{background:rgba(0,0,0,.08);border-radius:10px;display:flex;flex-direction:column;gap:8px;padding:14px}.ai-chat-widget.dark .ai-chat-action-skeleton-box,.chakra-ui-dark .ai-chat-action-skeleton-box,.dark .ai-chat-action-skeleton-box,[data-theme=dark] .ai-chat-action-skeleton-box{background:rgba(0,0,0,.25)}.ai-chat-action-card{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f4f4f4);border:none;border-radius:var(--radius-lg,12px);box-sizing:border-box;margin-top:var(--space-sm,8px);max-width:100%;padding:var(--space-md,16px);transition:all var(--duration-normal,.25s) ease;width:100%}.ai-chat-widget.dark .ai-chat-action-card,.chakra-ui-dark .ai-chat-action-card,.dark .ai-chat-action-card,[data-theme=dark] .ai-chat-action-card{background:var(--bg-secondary,#3a3a3a)}.ai-chat-action-booked{background:var(--bg-secondary,#f4f4f4);border:none}.ai-chat-widget.dark .ai-chat-action-booked,.chakra-ui-dark .ai-chat-action-booked,.dark .ai-chat-action-booked,[data-theme=dark] .ai-chat-action-booked{background:var(--bg-secondary,#3a3a3a)}.ai-chat-action-header{align-items:center;color:var(--text-primary,#3e3e3e);display:flex;font-size:var(--text-md,15px);font-weight:var(--font-weight-semibold,600);gap:var(--space-sm,8px);margin-bottom:var(--space-md,16px)}.ai-chat-widget.dark .ai-chat-action-header,.chakra-ui-dark .ai-chat-action-header,.dark .ai-chat-action-header,[data-theme=dark] .ai-chat-action-header{color:var(--text-primary,#fff)}.ai-chat-action-icon{color:var(--action-accent,var(--primary-color,#3b82f6));flex-shrink:0;height:20px;width:20px}.ai-chat-action-success-icon-wrapper{align-items:center;background:var(--action-accent,var(--primary-color,#22c55e));border-radius:50%;color:#fff;display:flex;flex-shrink:0;height:24px;justify-content:center;width:24px}.ai-chat-action-icon-success{color:currentColor;height:14px;width:14px}.ai-chat-action-detail-box{background:var(--bg-primary,#fff);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:var(--radius-md,8px);display:flex;flex-direction:column;gap:4px;padding:12px 16px}.ai-chat-widget.dark .ai-chat-action-detail-box,.chakra-ui-dark .ai-chat-action-detail-box,.dark .ai-chat-action-detail-box,[data-theme=dark] .ai-chat-action-detail-box{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.05)}.ai-chat-action-label-small{color:var(--text-muted,#71717a);font-size:11px;font-weight:600;letter-spacing:.5px;text-transform:uppercase}.ai-chat-action-value-large{color:var(--text-primary,#3e3e3e);font-size:15px;font-weight:500}.ai-chat-widget.dark .ai-chat-action-value-large,.chakra-ui-dark .ai-chat-action-value-large,.dark .ai-chat-action-value-large,[data-theme=dark] .ai-chat-action-value-large{color:#fff}.ai-chat-action-link-button{align-items:center;background:var(--action-accent,var(--primary-color,#3b82f6));border:none;border-radius:9999px;box-sizing:border-box;color:#fff;display:flex;font-size:14px;font-weight:600;gap:6px;justify-content:center;margin-top:8px;padding:12px;text-decoration:none;transition:all .2s ease;width:100%}.ai-chat-action-link-button:hover{opacity:.9;transform:translateY(-1px)}.ai-chat-action-body{display:flex;flex-direction:column;gap:var(--space-md,16px)}.ai-chat-action-field{display:flex;flex-direction:column;gap:var(--space-xs,6px)}.ai-chat-action-label{color:var(--text-secondary,#6b7280);font-size:var(--text-sm,13px);font-weight:var(--font-weight-medium,500)}.ai-chat-widget.dark .ai-chat-action-label,.chakra-ui-dark .ai-chat-action-label,.dark .ai-chat-action-label,[data-theme=dark] .ai-chat-action-label{color:var(--text-secondary,#a1a1aa)}.ai-chat-action-input{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);color:var(--text-primary,#3e3e3e);font-size:var(--text-sm,13px);outline:none;padding:10px 12px;transition:border-color .2s ease,box-shadow .2s ease}.ai-chat-action-input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.1)}.ai-chat-action-input::placeholder{color:var(--text-muted,#9ca3af)}.ai-chat-widget.dark .ai-chat-action-input,.chakra-ui-dark .ai-chat-action-input,.dark .ai-chat-action-input,[data-theme=dark] .ai-chat-action-input{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-action-input:focus,.chakra-ui-dark .ai-chat-action-input:focus,.dark .ai-chat-action-input:focus,[data-theme=dark] .ai-chat-action-input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.2)}.ai-chat-action-date-grid{display:grid;gap:var(--space-xs,6px);grid-template-columns:repeat(auto-fill,minmax(90px,1fr))}.ai-chat-action-date-btn{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);color:var(--text-primary,#3e3e3e);cursor:pointer;font-size:var(--text-xs,12px);font-weight:var(--font-weight-medium,500);padding:8px 10px;text-align:center;transition:all .15s ease}.ai-chat-action-date-btn:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-action-date-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-widget.dark .ai-chat-action-date-btn,.chakra-ui-dark .ai-chat-action-date-btn,.dark .ai-chat-action-date-btn,[data-theme=dark] .ai-chat-action-date-btn{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-action-date-btn:hover,.chakra-ui-dark .ai-chat-action-date-btn:hover,.dark .ai-chat-action-date-btn:hover,[data-theme=dark] .ai-chat-action-date-btn:hover{background:rgba(59,130,246,.15);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-action-date-btn.active,.chakra-ui-dark .ai-chat-action-date-btn.active,.dark .ai-chat-action-date-btn.active,[data-theme=dark] .ai-chat-action-date-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-action-time-grid{display:grid;gap:var(--space-xs,6px);grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.ai-chat-action-time-btn{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);color:var(--text-primary,#3e3e3e);cursor:pointer;font-size:var(--text-xs,12px);font-weight:var(--font-weight-medium,500);padding:8px 10px;text-align:center;transition:all .15s ease}.ai-chat-action-time-btn:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-action-time-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-widget.dark .ai-chat-action-time-btn,.chakra-ui-dark .ai-chat-action-time-btn,.dark .ai-chat-action-time-btn,[data-theme=dark] .ai-chat-action-time-btn{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-action-time-btn:hover,.chakra-ui-dark .ai-chat-action-time-btn:hover,.dark .ai-chat-action-time-btn:hover,[data-theme=dark] .ai-chat-action-time-btn:hover{background:rgba(59,130,246,.15);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-action-time-btn.active,.chakra-ui-dark .ai-chat-action-time-btn.active,.dark .ai-chat-action-time-btn.active,[data-theme=dark] .ai-chat-action-time-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-action-button{background:var(--action-accent,var(--primary-color,#3b82f6));border:none;border-radius:9999px;color:#fff;cursor:pointer;font-size:var(--text-sm,13px);font-weight:var(--font-weight-semibold,600);padding:12px 16px;transition:all .2s ease;width:100%}.ai-chat-action-button:hover:not(:disabled){opacity:.9;transform:translateY(-1px)}.ai-chat-action-button:disabled{cursor:not-allowed;opacity:.5}.ai-chat-action-error{background:rgba(239,68,68,.1);border-radius:var(--radius-md,8px);color:#dc2626;font-size:var(--text-sm,13px);padding:10px 12px}.ai-chat-widget.dark .ai-chat-action-error,.chakra-ui-dark .ai-chat-action-error,.dark .ai-chat-action-error,[data-theme=dark] .ai-chat-action-error{background:rgba(239,68,68,.2);color:#fca5a5}.ai-chat-action-hint{color:var(--text-muted,#9ca3af);font-size:var(--text-sm,13px);padding:var(--space-sm,8px);text-align:center}.ai-chat-link-preview{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f4f4f4);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:var(--radius-lg,12px);cursor:pointer;display:flex;flex-direction:column;margin-top:var(--space-sm,8px);overflow:hidden;position:relative;transition:border-color .2s,box-shadow .2s,transform .2s}.ai-chat-link-preview:hover{border-color:var(--action-accent);box-shadow:0 2px 8px rgba(0,0,0,.1);transform:translateY(-1px)}.ai-chat-link-preview:focus{border-color:var(--action-accent);box-shadow:0 0 0 2px rgba(59,130,246,.2);outline:none}.ai-chat-widget.dark .ai-chat-link-preview,.chakra-ui-dark .ai-chat-link-preview,.dark .ai-chat-link-preview,[data-theme=dark] .ai-chat-link-preview{background:var(--bg-secondary,#3a3a3a);border-color:hsla(0,0%,100%,.08)}.ai-chat-widget.dark .ai-chat-link-preview:hover,.chakra-ui-dark .ai-chat-link-preview:hover,.dark .ai-chat-link-preview:hover,[data-theme=dark] .ai-chat-link-preview:hover{border-color:var(--action-accent);box-shadow:0 2px 12px rgba(0,0,0,.3)}.ai-chat-link-preview__image{aspect-ratio:1.91/1;background:var(--bg-muted,#e5e7eb);overflow:hidden;width:100%}.ai-chat-widget.dark .ai-chat-link-preview__image,.chakra-ui-dark .ai-chat-link-preview__image,.dark .ai-chat-link-preview__image,[data-theme=dark] .ai-chat-link-preview__image{background:hsla(0,0%,100%,.05)}.ai-chat-link-preview__image img{height:100%;object-fit:cover;width:100%}.ai-chat-link-preview__content{flex:1;padding:12px}.ai-chat-link-preview__site{align-items:center;display:flex;gap:6px;margin-bottom:6px}.ai-chat-link-preview__favicon{border-radius:2px;flex-shrink:0;height:16px;width:16px}.ai-chat-link-preview__domain{color:var(--text-muted,#71717a);font-size:12px;letter-spacing:.5px;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}.ai-chat-link-preview__title{-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;color:var(--text-primary,#3e3e3e);display:-webkit-box;font-size:15px;font-weight:600;line-height:1.3;margin:0 0 4px;overflow:hidden}.ai-chat-widget.dark .ai-chat-link-preview__title,.chakra-ui-dark .ai-chat-link-preview__title,.dark .ai-chat-link-preview__title,[data-theme=dark] .ai-chat-link-preview__title{color:#fff}.ai-chat-link-preview__description{-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;color:var(--text-muted,#71717a);display:-webkit-box;font-size:13px;line-height:1.4;margin:0;overflow:hidden}.ai-chat-link-preview__context{border-top:1px solid var(--border-subtle,rgba(0,0,0,.08));color:var(--text-muted,#71717a);font-size:12px;font-style:italic;margin:8px 0 0;padding-top:8px}.ai-chat-widget.dark .ai-chat-link-preview__context,.chakra-ui-dark .ai-chat-link-preview__context,.dark .ai-chat-link-preview__context,[data-theme=dark] .ai-chat-link-preview__context{border-color:hsla(0,0%,100%,.08)}.ai-chat-link-preview__arrow{align-items:center;background:var(--bg-primary,#fff);border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.1);color:var(--text-muted,#71717a);display:flex;height:28px;justify-content:center;opacity:0;position:absolute;right:12px;top:12px;transition:opacity .2s,background .2s;width:28px}.ai-chat-link-preview:hover .ai-chat-link-preview__arrow{opacity:1}.ai-chat-widget.dark .ai-chat-link-preview__arrow,.chakra-ui-dark .ai-chat-link-preview__arrow,.dark .ai-chat-link-preview__arrow,[data-theme=dark] .ai-chat-link-preview__arrow{background:hsla(0,0%,100%,.1);color:#fff}.ai-chat-link-preview--error{border-color:rgba(239,68,68,.3)}.ai-chat-link-preview--error:hover{border-color:rgba(239,68,68,.5)}.ai-chat-link-preview__error-text{color:#dc2626;font-size:12px;margin:4px 0 0}.ai-chat-widget.dark .ai-chat-link-preview__error-text,.chakra-ui-dark .ai-chat-link-preview__error-text,.dark .ai-chat-link-preview__error-text,[data-theme=dark] .ai-chat-link-preview__error-text{color:#fca5a5}.ai-chat-video-player{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f4f4f4);border-radius:var(--radius-lg,12px);display:flex;flex-direction:column;margin-top:var(--space-sm,8px);overflow:hidden}.ai-chat-widget.dark .ai-chat-video-player,.chakra-ui-dark .ai-chat-video-player,.dark .ai-chat-video-player,[data-theme=dark] .ai-chat-video-player{background:var(--bg-secondary,#3a3a3a)}.ai-chat-video-player__header{align-items:center;color:var(--action-accent,var(--primary-color,#3b82f6));display:flex;gap:8px;padding:12px 12px 8px}.ai-chat-video-player__title{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:600}.ai-chat-widget.dark .ai-chat-video-player__title,.chakra-ui-dark .ai-chat-video-player__title,.dark .ai-chat-video-player__title,[data-theme=dark] .ai-chat-video-player__title{color:#fff}.ai-chat-video-player__container{aspect-ratio:16/9;background:#000;position:relative;width:100%}.ai-chat-video-player__thumbnail{cursor:pointer;height:100%;position:relative;width:100%}.ai-chat-video-player__thumbnail img{height:100%;object-fit:cover;width:100%}.ai-chat-video-player__placeholder{align-items:center;background:linear-gradient(135deg,#1a1a2e,#16213e);cursor:pointer;display:flex;flex-direction:column;gap:8px;height:100%;justify-content:center;position:relative;width:100%}.ai-chat-video-player__click-text{color:hsla(0,0%,100%,.7);font-size:13px}.ai-chat-video-player__play-btn{align-items:center;background:rgba(0,0,0,.7);border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;height:64px;justify-content:center;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);transition:background .2s,transform .2s;width:64px}.ai-chat-video-player__placeholder .ai-chat-video-player__play-btn{left:auto;position:relative;top:auto;transform:none}.ai-chat-video-player__play-btn:hover{background:rgba(0,0,0,.9);transform:translate(-50%,-50%) scale(1.05)}.ai-chat-video-player__placeholder .ai-chat-video-player__play-btn:hover{transform:scale(1.05)}.ai-chat-video-player__provider-badge{background:rgba(0,0,0,.8);border-radius:4px;bottom:8px;color:#fff;font-size:11px;font-weight:600;letter-spacing:.5px;padding:4px 8px;position:absolute;right:8px;text-transform:uppercase}.ai-chat-video-player__iframe,.ai-chat-video-player__video{border:none;height:100%;left:0;position:absolute;top:0;width:100%}.ai-chat-video-player__error{align-items:center;background:rgba(239,68,68,.1);color:#dc2626;display:flex;font-size:13px;height:100%;justify-content:center;padding:16px;text-align:center;width:100%}.ai-chat-widget.dark .ai-chat-video-player__error,.chakra-ui-dark .ai-chat-video-player__error,.dark .ai-chat-video-player__error,[data-theme=dark] .ai-chat-video-player__error{background:rgba(239,68,68,.2);color:#fca5a5}.ai-chat-video-player__context{border-top:1px solid var(--border-subtle,rgba(0,0,0,.08));color:var(--text-muted,#71717a);font-size:12px;font-style:italic;padding:10px 12px}.ai-chat-widget.dark .ai-chat-video-player__context,.chakra-ui-dark .ai-chat-video-player__context,.dark .ai-chat-video-player__context,[data-theme=dark] .ai-chat-video-player__context{border-color:hsla(0,0%,100%,.08)}.ai-chat-location-card{background:var(--bg-secondary,#fff);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:12px;overflow:hidden}.ai-chat-widget.dark .ai-chat-location-card,.chakra-ui-dark .ai-chat-location-card,.dark .ai-chat-location-card,[data-theme=dark] .ai-chat-location-card{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1)}.ai-chat-location-card--compact{border-radius:10px}.ai-chat-location-card--error{color:var(--text-muted,#71717a);padding:16px;text-align:center}.ai-chat-location-card__map{background:var(--bg-muted,#f4f4f5);position:relative;width:100%}.ai-chat-widget.dark .ai-chat-location-card__map,.chakra-ui-dark .ai-chat-location-card__map,.dark .ai-chat-location-card__map,[data-theme=dark] .ai-chat-location-card__map{background:rgba(0,0,0,.2)}.ai-chat-location-card__map iframe{border:none;display:block;height:100%;width:100%}.ai-chat-location-card__content{padding:14px}.ai-chat-location-card--compact .ai-chat-location-card__content{padding:12px}.ai-chat-location-card__header{align-items:center;display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}.ai-chat-location-card__name{color:var(--text-primary,#18181b);flex:1;font-size:16px;font-weight:600;margin:0;min-width:0}.ai-chat-widget.dark .ai-chat-location-card__name,.chakra-ui-dark .ai-chat-location-card__name,.dark .ai-chat-location-card__name,[data-theme=dark] .ai-chat-location-card__name{color:#fff}.ai-chat-location-card--compact .ai-chat-location-card__name{font-size:14px}.ai-chat-location-card__type{background:var(--bg-muted,#f4f4f5);border-radius:10px;color:var(--text-muted,#71717a);font-size:11px;font-weight:500;letter-spacing:.5px;padding:2px 8px;text-transform:uppercase}.ai-chat-widget.dark .ai-chat-location-card__type,.chakra-ui-dark .ai-chat-location-card__type,.dark .ai-chat-location-card__type,[data-theme=dark] .ai-chat-location-card__type{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.7)}.ai-chat-location-card__status{border-radius:12px;font-size:12px;font-weight:500;padding:2px 8px}.ai-chat-location-card__status--open{background:#dcfce7;color:#16a34a}.ai-chat-location-card__status--closed{background:#fef2f2;color:#dc2626}.ai-chat-widget.dark .ai-chat-location-card__status--open,.chakra-ui-dark .ai-chat-location-card__status--open,.dark .ai-chat-location-card__status--open,[data-theme=dark] .ai-chat-location-card__status--open{background:rgba(34,197,94,.2);color:#4ade80}.ai-chat-widget.dark .ai-chat-location-card__status--closed,.chakra-ui-dark .ai-chat-location-card__status--closed,.dark .ai-chat-location-card__status--closed,[data-theme=dark] .ai-chat-location-card__status--closed{background:rgba(239,68,68,.2);color:#fca5a5}.ai-chat-location-card__address{align-items:flex-start;color:var(--text-muted,#71717a);display:flex;font-size:13px;gap:6px;line-height:1.4;margin:0 0 8px}.ai-chat-location-card__address svg{flex-shrink:0;margin-top:2px}.ai-chat-location-card__description{color:var(--text-muted,#71717a);font-size:13px;line-height:1.4;margin:0 0 8px}.ai-chat-location-card__hours{align-items:flex-start;color:var(--text-muted,#71717a);display:flex;font-size:13px;gap:6px;margin-bottom:8px}.ai-chat-location-card__hours svg{flex-shrink:0;margin-top:2px}.ai-chat-location-card__hours-list{flex:1}.ai-chat-location-card__hours-toggle{align-items:center;background:none;border:none;color:inherit;cursor:pointer;display:flex;font:inherit;gap:4px;padding:0}.ai-chat-location-card__hours-toggle:hover{text-decoration:underline}.ai-chat-location-card__hours-full{list-style:none;margin:8px 0 0;padding:0}.ai-chat-location-card__hours-full li{display:flex;font-size:12px;justify-content:space-between;padding:4px 0}.ai-chat-location-card__hours-today{color:var(--text-primary,#18181b);font-weight:600}.ai-chat-widget.dark .ai-chat-location-card__hours-today,.chakra-ui-dark .ai-chat-location-card__hours-today,.dark .ai-chat-location-card__hours-today,[data-theme=dark] .ai-chat-location-card__hours-today{color:#fff}.ai-chat-location-card__phone{align-items:center;background:none;border:none;color:var(--action-accent,#3b82f6);cursor:pointer;display:flex;font-size:13px;gap:6px;margin-bottom:12px;padding:0}.ai-chat-location-card__phone:hover{text-decoration:underline}.ai-chat-location-card__actions{display:flex;gap:8px;justify-content:flex-start;width:100%}.ai-chat-location-card__button{align-items:center;background:var(--action-accent,#3b82f6);border:none;border-radius:20px;color:#fff;cursor:pointer;display:flex;flex:1;font-size:13px;font-weight:500;gap:6px;justify-content:center;padding:10px 16px;transition:opacity .2s}.ai-chat-location-card__button:hover{opacity:.9}.ai-chat-location-card--compact .ai-chat-location-card__button{font-size:12px;padding:8px 12px}.ai-chat-location-card__link{align-items:center;background:var(--bg-secondary,#fff);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:20px;color:var(--text-primary,#18181b);display:flex;font-size:13px;gap:6px;padding:10px 16px;text-decoration:none;transition:border-color .2s}.ai-chat-widget.dark .ai-chat-location-card__link,.chakra-ui-dark .ai-chat-location-card__link,.dark .ai-chat-location-card__link,[data-theme=dark] .ai-chat-location-card__link{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-location-card__link:hover{border-color:var(--action-accent,#3b82f6)}.ai-chat-location-card-list{display:flex;flex-direction:column;gap:8px}.ai-chat-location-card-list__header{align-items:center;color:var(--text-muted,#71717a);display:flex;font-size:13px;font-weight:500;gap:6px;margin-bottom:4px;padding:0 4px}.ai-chat-location-card-list__stack{display:grid;gap:12px;grid-template-columns:repeat(3,minmax(0,1fr))}@media (max-width:1200px){.ai-chat-location-card-list__stack{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width:768px){.ai-chat-location-card-list__stack{grid-template-columns:1fr}}@media (max-width:520px){.ai-chat-location-card-list__stack{grid-template-columns:1fr!important}}@media (min-width:521px) and (max-width:900px){.ai-chat-location-card-list__stack{grid-template-columns:repeat(2,minmax(0,1fr))!important}}.ai-chat-location-card-list__grid{display:grid;gap:8px;grid-template-columns:repeat(2,1fr)}@media (max-width:400px){.ai-chat-location-card-list__grid{grid-template-columns:1fr}}.ai-chat-location-card-list__carousel{-webkit-overflow-scrolling:touch;-ms-overflow-style:none;display:flex;gap:8px;overflow-x:auto;padding-bottom:4px;scroll-snap-type:x mandatory;scrollbar-width:none}.ai-chat-location-card-list__carousel::-webkit-scrollbar{display:none}.ai-chat-location-card-list__carousel>.ai-chat-location-card{flex:0 0 280px;scroll-snap-align:start}.ai-chat-widget,.chat-ui{--radius-sm:4px;--radius-md:8px;--radius-lg:12px;--radius-xl:16px;--radius-2xl:18px;--radius-pill:9999px;--radius-window-top:22px;--radius-window-bottom:44px;--radius-window-gutter:16px;--radius-chat-bubble:14px;--radius-preset-badge:13px;--radius-history-item:14px;--radius-action-badge:8px;--radius-input:62px;--space-xs:4px;--space-sm:8px;--space-md:16px;--space-lg:24px;--space-xl:32px;--text-xs:12px;--text-sm:14px;--text-md:15px;--text-lg:18px;--text-xl:22px;--text-2xl:28px;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--line-height-tight:1.3;--line-height-normal:1.4;--line-height-relaxed:1.6;--bg-primary:#fff;--bg-secondary:#f4f4f4;--bg-tertiary:#e5e7eb;--bg-hover:#e5e7eb;--text-primary:#3e3e3e;--text-secondary:#000;--text-muted:#71717a;--text-placeholder:#a1a1aa;--border-default:#d3d3d3;--border-subtle:#e5e7eb;--border-muted:#f4f4f5;--user-bg:#f4f3f0;--user-text:#000;--user-bg-hover:#e8e7e4;--agent-bg:transparent;--agent-text:#000;--input-bg:#f4f4f4;--input-border:#d3d3d3;--input-text:#000;--btn-primary-bg:#151515;--btn-primary-text:#f4f4f4;--btn-secondary-bg:transparent;--btn-secondary-text:#71717a;--spring-bounce:cubic-bezier(0.34,1.56,0.64,1);--spring-smooth:cubic-bezier(0.4,0,0.2,1);--spring-snappy:cubic-bezier(0.2,0,0,1);--duration-fast:0.15s;--duration-normal:0.25s;--duration-slow:0.35s;--shadow-sm:0 1px 2px rgba(0,0,0,.05);--shadow-md:0 2px 8px rgba(0,0,0,.1);--shadow-lg:0 4px 16px rgba(0,0,0,.12);--shadow-window:0px 0px 15px 9px rgba(0,0,0,.1);--shadow-button:0px 0px 15px 9px rgba(0,0,0,.03)}.ai-chat-widget.dark,.chat-ui.dark{--bg-primary:#282625;--bg-secondary:#4a4846;--bg-tertiary:#484848;--bg-hover:#484848;--text-primary:#fff;--text-secondary:#fff;--text-muted:#a1a1aa;--text-placeholder:#71717a;--border-default:#5d5b5b;--border-subtle:#5d5b5b;--border-muted:#5d5b5b;--user-bg:#484848;--user-text:#fff;--user-bg-hover:#5a5a5a;--agent-bg:transparent;--agent-text:#fff;--input-bg:#4a4846;--input-border:#5d5b5b;--input-text:#fff;--btn-primary-bg:#fff;--btn-primary-text:#312f2d;--btn-secondary-bg:transparent;--btn-secondary-text:#a1a1aa;--shadow-window:0px 0px 15px 9px rgba(0,0,0,.2);--shadow-button:0px 0px 15px 9px rgba(0,0,0,.03);--shadow-input:0px 0px 10px rgba(0,0,0,.15)}.ai-chat-widget,.chat-ui{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.ai-chat-widget-container{font-size:var(--text-sm);line-height:1.5;position:fixed;z-index:9999}.ai-chat-widget-container.bottom-right{bottom:20px;right:20px}.ai-chat-widget-container.bottom-left{bottom:20px;left:20px}.ai-chat-widget-container.top-right{right:20px;top:20px}.ai-chat-widget-container.top-left{left:20px;top:20px}@keyframes ai-chat-window-open{0%{opacity:0;transform:scale(.9) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes ai-chat-window-close{0%{opacity:1;transform:scale(1) translateY(0)}to{opacity:0;transform:scale(.9) translateY(20px)}}@keyframes ai-chat-message-slide-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes ai-chat-welcome-fade-in{0%{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}@keyframes ai-chat-typing-pulse{0%,60%,to{opacity:.4;transform:translateY(0) scale(1)}30%{opacity:1;transform:translateY(-4px) scale(1.1)}}@keyframes ai-chat-gear-spin{to{transform:rotate(1turn)}}@keyframes ai-chat-tool-active{0%,to{background:var(--bg-secondary);opacity:1}50%{background:var(--bg-tertiary);opacity:1}}@keyframes ai-chat-feedback-morph{0%{opacity:.5;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@keyframes ai-chat-checkmark-pop{0%{opacity:0;transform:scale(0) rotate(-45deg)}50%{transform:scale(1.3) rotate(0deg)}to{opacity:1;transform:scale(1) rotate(0deg)}}@keyframes ai-chat-history-exit{to{opacity:0;transform:translateY(-18px)}}@keyframes ai-chat-tool-gradient{0%{background-position:200% 0}to{background-position:-200% 0}}@media (max-width:480px){.ai-chat-widget-container.is-open{height:100vh!important;inset:0!important;width:100vw!important}.ai-chat-widget-container.is-open .ai-chat-window{animation:none!important;border-radius:0!important;height:100%!important;inset:0!important;max-height:100%!important;max-width:100%!important;position:absolute!important;width:100%!important}.ai-chat-widget-container.is-open .ai-chat-button{display:none!important;visibility:hidden!important}.ai-chat-header{padding-top:max(16px,env(safe-area-inset-top))}.ai-chat-input-container{padding-bottom:max(20px,env(safe-area-inset-bottom))}}.ai-chat-window{animation:ai-chat-window-open var(--duration-slow,.35s) var(--spring-bounce);background:var(--bg-primary,#fff);border:1px solid var(--border-default,#d3d3d3);border-radius:var(--radius-window-top,22px) var(--radius-window-top,22px) var(--radius-window-bottom,44px) var(--radius-window-bottom,44px);box-shadow:var(--shadow-window,0 0 15px 5px rgba(0,0,0,.08));display:flex;flex-direction:column;overflow:hidden;position:absolute;transform-origin:bottom right;z-index:2}.ai-chat-widget.dark .ai-chat-window{background:var(--bg-primary,#282625);border-color:var(--border-default,#5d5b5b);border-width:.7px}.ai-chat-window.closing{animation:ai-chat-window-close var(--duration-normal) var(--spring-smooth) forwards}.ai-chat-window.size-small{height:500px;width:380px}.ai-chat-window.size-medium,.ai-chat-window.size-small{max-height:calc(100vh - 100px);max-width:calc(100vw - 40px)}.ai-chat-window.size-medium{height:600px;width:420px}.ai-chat-window.size-large{height:700px;max-height:calc(100vh - 100px);max-width:calc(100vw - 40px);width:480px}.ai-chat-widget-container.bottom-right .ai-chat-window{bottom:calc(var(--button-size, 56px) + 8px);right:0}.ai-chat-widget-container.bottom-left .ai-chat-window{bottom:calc(var(--button-size, 56px) + 8px);left:0}.ai-chat-widget-container.top-right .ai-chat-window{right:0;top:calc(var(--button-size, 56px) + 8px)}.ai-chat-widget-container.top-left .ai-chat-window{left:0;top:calc(var(--button-size, 56px) + 8px)}.ai-chat-header{align-items:center;background:var(--bg-primary,#fff);border-bottom:1px solid var(--border-default,#d3d3d3);border-radius:var(--radius-window-top,22px) var(--radius-window-top,22px) 0 0;display:flex;justify-content:space-between;padding:18px var(--space-md,16px);position:relative;z-index:10}.ai-chat-widget.dark .ai-chat-header{background:var(--bg-primary,#282625);border-bottom-color:var(--border-default,#5d5b5b);border-bottom-width:.7px}.ai-chat-header.is-history{padding-left:var(--space-md)}.ai-chat-header.is-history .ai-chat-title{flex:1;min-width:0;overflow:hidden;padding-right:var(--space-lg);text-overflow:ellipsis;white-space:nowrap}.ai-chat-header-content{align-items:center;display:flex;flex:1;gap:var(--space-lg)}.ai-chat-header-actions{align-items:center;display:flex;gap:var(--space-sm)}.ai-chat-logo{border-radius:10px;height:36px;object-fit:cover;width:36px}.ai-chat-title{color:var(--text-primary,#3e3e3e);font-size:var(--text-xl,22px);font-weight:var(--font-weight-bold,700);letter-spacing:-.02em}.ai-chat-widget.dark .ai-chat-title{color:var(--text-primary,#fff)}.ai-chat-close-button,.ai-chat-header-button{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:color var(--duration-fast) ease;width:32px}.ai-chat-close-button:hover,.ai-chat-header-button:hover{color:var(--text-primary)}.ai-chat-close-button:active,.ai-chat-header-button:active{transform:scale(.95)}.ai-chat-close-button svg,.ai-chat-header-button svg{height:22px;width:22px}.ai-chat-button{align-items:center;background:var(--button-color,var(--btn-primary-bg));border:1px solid var(--border-default,#d3d3d3);border-radius:50%;box-shadow:var(--shadow-button,0 0 15px 9px rgba(0,0,0,.03));color:var(--button-icon-color,var(--btn-primary-text));cursor:pointer;display:flex;height:var(--button-size,56px);justify-content:center;overflow:hidden;position:relative;transition:opacity var(--duration-fast) ease;width:var(--button-size,56px);z-index:1}.ai-chat-button:hover{opacity:.9}.ai-chat-button:active{opacity:.8}.ai-chat-button-svg{height:50%;min-height:24px;min-width:24px;transition:transform var(--duration-fast) ease;width:50%}.ai-chat-button.is-open .ai-chat-button-svg{transform:rotate(0deg)}.ai-chat-button-icon{font-size:1.5em;line-height:1}.ai-chat-input-container{background:linear-gradient(to bottom,transparent 0,var(--bg-primary,#fff) 50%,var(--bg-primary,#fff) 100%);bottom:0;left:0;padding-top:30px;position:absolute;right:0;z-index:10}.ai-chat-widget.dark .ai-chat-input-container{background:linear-gradient(to bottom,transparent 0,var(--bg-primary,#282625) 50%,var(--bg-primary,#282625) 100%)}.ai-chat-input-container.separate{padding:0 var(--radius-window-gutter,16px) var(--radius-window-gutter,16px);padding-top:30px}.ai-chat-input-wrapper{align-items:flex-end;background:var(--input-bg,#f4f4f4);border:1px solid var(--input-border,#d3d3d3);border-radius:var(--radius-input,62px);box-sizing:border-box;display:flex;gap:0;height:52px;overflow:hidden;padding:6px 6px 6px 16px;position:relative;transition:all var(--duration-fast,.15s) ease;z-index:5}.ai-chat-input-wrapper.multiline{border-radius:14px!important;min-height:64px;padding:10px 10px 10px 14px}.ai-chat-widget.dark .ai-chat-input-wrapper{background:var(--input-bg,#4a4846);border-color:var(--input-border,#5d5b5b);border-width:.7px;box-shadow:var(--shadow-input,0 0 10px rgba(0,0,0,.15))}.ai-chat-input-wrapper:focus-within{border-color:var(--text-muted,#a1a1aa)}.ai-chat-input{word-wrap:break-word!important;background:transparent!important;border:none!important;border-radius:0!important;box-shadow:none!important;box-sizing:border-box!important;color:var(--input-text,#000)!important;flex:1!important;font-family:inherit!important;font-size:var(--text-md,15px)!important;height:40px!important;line-height:20px!important;margin:0!important;max-height:40px!important;min-height:40px!important;min-width:0!important;outline:none!important;overflow-wrap:anywhere!important;overflow-x:hidden!important;overflow-y:auto!important;padding:10px var(--space-sm,8px)!important;resize:none!important;white-space:pre-wrap!important;width:0!important;word-break:break-word!important}.ai-chat-widget.dark .ai-chat-input{color:var(--input-text,#fff)}.ai-chat-input::placeholder{color:var(--text-placeholder,#a1a1aa)}.ai-chat-widget.dark .ai-chat-input::placeholder{color:var(--text-placeholder,#52525b)}.ai-chat-file-button{align-items:center;align-self:center;background:transparent;border:none;color:var(--text-placeholder);cursor:pointer;display:flex;flex-shrink:0;height:28px;justify-content:center;padding:0;transition:color var(--duration-fast) ease;width:28px}.ai-chat-file-button:hover{color:var(--text-secondary)}.ai-chat-file-button:disabled{cursor:not-allowed;opacity:.5}.ai-chat-send-button{align-items:center;align-self:center;background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#151515))));border:none;border-radius:50%;color:var(--button-icon-color,var(--btn-primary-text,#f4f4f4));cursor:pointer;display:flex;flex-shrink:0;height:40px;justify-content:center;min-height:40px;min-width:40px;padding:0;transition:all var(--duration-fast,.15s) ease;width:40px}.ai-chat-widget.dark .ai-chat-send-button{background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#fff))));color:var(--button-icon-color,var(--btn-primary-text,#312f2d))}.ai-chat-send-button.active{background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#151515))));color:var(--button-icon-color,var(--btn-primary-text,#f4f4f4))}.ai-chat-widget.dark .ai-chat-send-button.active{background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#fff))));color:var(--button-icon-color,var(--btn-primary-text,#312f2d))}.ai-chat-send-button:hover:not(:disabled){opacity:.8}.ai-chat-send-button:active:not(:disabled){transform:scale(.95)}.ai-chat-send-button:disabled{cursor:not-allowed;opacity:.3}.ai-chat-file-list{display:flex;flex-wrap:wrap;gap:var(--space-sm);padding:var(--space-sm) var(--space-sm)}.ai-chat-file-item{align-items:center;background:rgba(0,0,0,.05);border-radius:6px;display:flex;font-size:var(--text-xs);gap:var(--space-sm);padding:6px 10px}.ai-chat-file-extension{background:var(--btn-primary-bg);border-radius:3px;color:var(--btn-primary-text);display:inline-block;font-size:10px;font-weight:var(--font-weight-semibold);min-width:40px;padding:2px 6px;text-align:center;text-transform:uppercase}.ai-chat-file-info{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.ai-chat-file-name{font-weight:var(--font-weight-medium);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-file-size{color:var(--text-muted);font-size:10px;opacity:.7}.ai-chat-file-remove{align-items:center;background:none;border:none;color:inherit;cursor:pointer;display:flex;justify-content:center;opacity:.5;padding:var(--space-xs);transition:opacity var(--duration-fast) ease}.ai-chat-file-remove:hover{opacity:1}.ai-chat-messages{-webkit-overflow-scrolling:touch;-ms-overflow-style:none;align-items:stretch;background:var(--bg-primary,#fff);display:flex;flex:1;flex-direction:column;gap:var(--space-md,16px);justify-content:flex-start;overflow-x:hidden;overflow-y:auto;padding:var(--space-lg,24px) var(--space-md,16px) 100px;position:relative;scroll-behavior:smooth;scrollbar-width:none}.ai-chat-widget.dark .ai-chat-messages{background:var(--bg-primary,#18181b)}.ai-chat-messages::-webkit-scrollbar{display:none}.ai-chat-message{animation:ai-chat-message-slide-in .2s var(--spring-bounce);display:flex;flex-direction:column;max-width:90%}.ai-chat-message.user{align-items:flex-end;align-self:flex-end}.ai-chat-message.assistant{align-items:flex-start;align-self:flex-start;max-width:100%}.ai-chat-message.tool{align-self:flex-start;margin:0 -16px;max-width:100%;padding:0;width:calc(100% + 32px)}.ai-chat-message-content{word-wrap:break-word;border-radius:18px;font-size:var(--text-md,15px);line-height:var(--line-height-relaxed,1.6);overflow-wrap:break-word;padding:8px 14px}.ai-chat-message.user .ai-chat-message-content{background:var(--user-bg,#f4f3f0);border-radius:18px;color:var(--user-text,#000)}.ai-chat-widget.dark .ai-chat-message.user .ai-chat-message-content{background:var(--user-bg,#484848);color:var(--user-text,#fff)}.ai-chat-message.assistant .ai-chat-message-content{background:var(--agent-bg,transparent);color:var(--agent-text,#000);padding:0}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content{color:var(--agent-text,#fff)}.ai-chat-message-timestamp{color:var(--text-muted,#71717a);font-size:var(--text-xs,12px);margin-top:var(--space-xs,4px);padding:0 var(--space-xs,4px)}.ai-chat-welcome{animation:ai-chat-welcome-fade-in .3s var(--spring-smooth);display:flex;flex-direction:column;gap:var(--space-md,16px);padding:var(--space-lg,24px) 0}.ai-chat-welcome-title{color:var(--text-primary,#3e3e3e);font-size:var(--text-2xl,28px);font-weight:var(--font-weight-bold,700);line-height:var(--line-height-tight,1.3)}.ai-chat-widget.dark .ai-chat-welcome-title{color:var(--text-primary,#fff)}.ai-chat-welcome-text{color:var(--text-secondary,#000);font-size:var(--text-md,15px);line-height:var(--line-height-relaxed,1.6);max-width:100%}.ai-chat-widget.dark .ai-chat-welcome-text{color:var(--text-secondary,#fff)}.ai-chat-typing{align-items:center;display:flex;gap:var(--space-xs,4px);padding:var(--space-sm,8px) var(--space-md,16px)}.ai-chat-typing-dot{animation:ai-chat-typing-bounce 1.4s ease-in-out infinite both;background:var(--text-muted,#71717a);border-radius:50%;height:8px;width:8px}.ai-chat-typing-dot:first-child{animation-delay:-.32s}.ai-chat-typing-dot:nth-child(2){animation-delay:-.16s}.ai-chat-typing-dot:nth-child(3){animation-delay:0s}@keyframes ai-chat-typing-bounce{0%,80%,to{opacity:.4;transform:scale(.6)}40%{opacity:1;transform:scale(1)}}.ai-chat-scroll-button{align-items:center;background:var(--bg-secondary,#f4f4f5);border:1px solid var(--border-subtle,rgba(0,0,0,.1));border-radius:50%;bottom:80px;box-shadow:0 2px 8px rgba(0,0,0,.1);color:var(--text-secondary,#71717a);cursor:pointer;display:flex;height:36px;justify-content:center;left:50%;opacity:0;pointer-events:none;position:absolute;transform:translateX(-50%);transition:background .15s ease,box-shadow .15s ease,opacity .15s ease,visibility .15s ease;visibility:hidden;width:36px;z-index:15}.ai-chat-scroll-button.visible{opacity:1;pointer-events:auto;visibility:visible}.ai-chat-scroll-button:hover{background:var(--bg-tertiary,#e4e4e7);box-shadow:0 4px 12px rgba(0,0,0,.15)}.ai-chat-scroll-button:active{background:var(--bg-tertiary,#d4d4d8)}.ai-chat-widget.dark .ai-chat-scroll-button{background:var(--bg-secondary,#3f3f46);border-color:var(--border-subtle,hsla(0,0%,100%,.1));box-shadow:0 2px 8px rgba(0,0,0,.3);color:var(--text-secondary,#a1a1aa)}.ai-chat-widget.dark .ai-chat-scroll-button:hover{background:var(--bg-tertiary,#52525b);box-shadow:0 4px 12px rgba(0,0,0,.4)}.ai-chat-error{background:var(--bg-secondary);border-radius:var(--radius-chat-bubble);color:var(--text-primary);font-size:var(--text-md);margin:0 auto;padding:10px var(--space-md)}.ai-chat-message.assistant .ai-chat-message-content p{margin:0 0 var(--space-sm) 0}.ai-chat-message.assistant .ai-chat-message-content p:last-child{margin-bottom:0}.ai-chat-message.assistant .ai-chat-message-content ol,.ai-chat-message.assistant .ai-chat-message-content ul{margin:var(--space-sm) 0;padding-left:var(--space-lg)}.ai-chat-message.assistant .ai-chat-message-content li{margin-bottom:var(--space-xs)}.ai-chat-message.assistant .ai-chat-message-content code{background:rgba(0,0,0,.05);border-radius:var(--radius-sm);font-family:SF Mono,Monaco,Cascadia Code,monospace;font-size:.9em;padding:2px 6px}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content code{background:hsla(0,0%,100%,.1)}.ai-chat-message.assistant .ai-chat-message-content pre{background:rgba(0,0,0,.05);border-radius:var(--radius-md);margin:var(--space-sm) 0;overflow-x:auto;padding:var(--space-sm)}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content pre{background:hsla(0,0%,100%,.05)}.ai-chat-message.assistant .ai-chat-message-content pre code{background:transparent;padding:0}.ai-chat-message.assistant .ai-chat-message-content a{color:var(--btn-primary-bg);text-decoration:underline}.ai-chat-message.assistant .ai-chat-message-content strong{font-weight:var(--font-weight-semibold)}.ai-chat-message.assistant .ai-chat-message-content blockquote{border-left:3px solid var(--border-default);color:var(--text-muted);margin:var(--space-sm) 0;padding-left:var(--space-md)}.ai-chat-message.assistant .ai-chat-message-content hr{border:none;border-top:1px solid var(--border-subtle,rgba(0,0,0,.1));margin:var(--space-lg,24px) 0}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content hr{border-top-color:var(--border-subtle,hsla(0,0%,100%,.1))}.ai-chat-message.assistant .ai-chat-message-content table{border-collapse:collapse;font-size:var(--text-sm);margin:var(--space-sm) 0;width:100%}.ai-chat-message.assistant .ai-chat-message-content td,.ai-chat-message.assistant .ai-chat-message-content th{border:1px solid var(--border-subtle);padding:var(--space-sm);text-align:left}.ai-chat-message.assistant .ai-chat-message-content th{font-weight:var(--font-weight-semibold);white-space:nowrap}.ai-chat-message.assistant .ai-chat-message-content tbody tr:nth-child(2n){background:rgba(0,0,0,.02)}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content tbody tr:nth-child(2n){background:hsla(0,0%,100%,.03)}.ai-chat-suggested-questions{align-self:flex-end;margin:0;padding:16px 0 0;width:100%}.ai-chat-suggested-questions-list{align-items:center;display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.ai-chat-suggested-question{align-items:center;background:transparent;border:1px solid var(--border-default,#d4d4d8);border-radius:var(--radius-preset-badge,18px);color:var(--text-primary,#18181b);cursor:pointer;display:inline-flex;font-size:14px;font-weight:400;gap:6px;justify-content:center;line-height:1.3;max-width:100%;overflow:hidden;padding:8px 14px;text-overflow:ellipsis;transition:background .15s ease,border-color .15s ease,transform .1s ease;white-space:nowrap}.ai-chat-widget.dark .ai-chat-suggested-question{background:transparent;border-color:var(--border-subtle,#52525b);color:var(--text-primary,#fafafa)}.ai-chat-suggested-question-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-suggested-question:hover{background:var(--bg-hover,#f4f4f5);border-color:var(--border-default,#d4d4d8)}.ai-chat-widget.dark .ai-chat-suggested-question:hover{background:var(--bg-hover,#3f3f46);border-color:var(--border-subtle,#52525b)}.ai-chat-suggested-question:active{transform:scale(.98)}.ai-chat-suggested-question.action-type{border:none}.ai-chat-suggested-question.action-type,.ai-chat-widget.dark .ai-chat-suggested-question.action-type{background:var(--primary-color,var(--button-color,#ef4444));color:var(--button-icon-color,#fff)}.ai-chat-suggested-question.action-type:hover{background:var(--primary-color,var(--button-color,#ef4444));opacity:.9}.ai-chat-suggested-question-icon{align-items:center;display:flex;flex-shrink:0;justify-content:center}.ai-chat-suggested-question:not(.action-type) .ai-chat-suggested-question-icon{display:none}.ai-chat-follow-up-suggestions{box-sizing:border-box;margin:0;padding:8px 16px 0;width:100%}.ai-chat-follow-up-list{align-items:flex-end;display:flex;flex-direction:column;gap:6px}.ai-chat-follow-up-item{align-items:center;border:none;border-radius:var(--radius-preset-badge,18px);cursor:pointer;display:inline-flex;font-size:14px;font-weight:400;gap:6px;justify-content:center;line-height:1.3;max-width:100%;overflow:hidden;padding:8px 14px;text-overflow:ellipsis;transition:opacity .15s ease,transform .1s ease;white-space:nowrap}.ai-chat-follow-up-item,.ai-chat-widget.dark .ai-chat-follow-up-item{background:var(--primary-color,var(--button-color,#07f));color:var(--button-icon-color,#fff)}.ai-chat-follow-up-item:hover{opacity:.9}.ai-chat-follow-up-item:active{transform:scale(.98)}.ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-default,#d4d4d8);color:var(--text-primary,#18181b)}.ai-chat-widget.dark .ai-chat-follow-up-item.question-type,.dark .ai-chat-follow-up-item.question-type,[data-theme=dark] .ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-subtle,#52525b);color:var(--text-primary,#fafafa)}@media (prefers-color-scheme:dark){.ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-subtle,#52525b);color:var(--text-primary,#fafafa)}}.ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#f4f4f5);opacity:1}.ai-chat-widget.dark .ai-chat-follow-up-item.question-type:hover,.dark .ai-chat-follow-up-item.question-type:hover,[data-theme=dark] .ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#3f3f46);opacity:1}@media (prefers-color-scheme:dark){.ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#3f3f46);opacity:1}}.ai-chat-follow-up-item.action-type{background:var(--primary-color,var(--button-color,#07f));border:none;color:var(--button-icon-color,#fff)}.ai-chat-follow-up-icon{align-items:center;display:flex;flex-shrink:0;justify-content:center}.ai-chat-follow-up-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-feedback-buttons{align-items:center;display:flex;gap:var(--space-xs)}.ai-chat-feedback{align-items:center;display:inline-flex;gap:0;height:20px}.ai-chat-feedback-button{align-items:center;background:transparent!important;border:none;border-radius:var(--radius-sm);color:var(--text-placeholder);cursor:pointer;display:flex;font-size:var(--text-sm);height:20px;justify-content:center;padding:var(--space-xs);transition:all var(--duration-fast) var(--spring-bounce)}.ai-chat-feedback-button:hover:not(:disabled){background:none!important;color:var(--text-secondary)}.ai-chat-feedback-button:active:not(:disabled){transform:scale(.9)}.ai-chat-feedback-button:disabled{cursor:not-allowed;opacity:.4}.ai-chat-feedback-button.active{background:none!important;color:var(--text-primary)}.ai-chat-feedback.submitted{align-items:center;animation:ai-chat-feedback-morph .3s var(--spring-bounce);gap:var(--space-xs)}.ai-chat-feedback-message{align-items:center;display:flex;gap:4px;margin-left:var(--space-xxs)}.ai-chat-feedback-checkmark{animation:ai-chat-checkmark-pop .3s var(--spring-bounce);color:#10b981;font-size:var(--text-md);font-weight:700}.ai-chat-feedback-text{color:#10b981;font-size:var(--text-xs);font-weight:var(--font-weight-medium)}.ai-chat-history-panel{background:var(--bg-primary,#fff);display:flex;flex:1;flex-direction:column;overflow:hidden}.ai-chat-widget.dark .ai-chat-history-panel{background:var(--bg-primary,#18181b)}.ai-chat-history-empty,.ai-chat-history-loading{align-items:center;color:var(--text-muted);display:flex;flex:1;font-size:var(--text-sm);justify-content:center;padding:var(--space-lg);text-align:center}.ai-chat-history-list{-ms-overflow-style:none;display:flex;flex:1;flex-direction:column;gap:var(--space-sm);overflow-y:auto;padding:var(--space-xs) var(--space-md) 120px;scrollbar-width:none}.ai-chat-history-list::-webkit-scrollbar{display:none}.ai-chat-history-list.exiting{animation:ai-chat-history-exit .22s var(--spring-smooth) forwards}.ai-chat-history-item{align-items:center;background:var(--user-bg,#f4f4f5);border-radius:var(--radius-history-item,15px);display:flex;flex:0 0 auto;flex-direction:row;height:var(--history-item-height,36px);margin:0;overflow:hidden;transition:background var(--duration-fast,.15s) ease;width:100%}.ai-chat-history-item-content{align-items:center;background:transparent;border:none;cursor:pointer;display:flex;flex:1;flex-direction:row;height:100%;min-width:0;padding:0 var(--space-xs,4px) 0 var(--space-md,16px);text-align:left}.ai-chat-widget.dark .ai-chat-history-item{background:var(--user-bg,#27272a)}.ai-chat-history-item:hover{background:var(--user-bg-hover,#e5e7eb)}.ai-chat-widget.dark .ai-chat-history-item:hover{background:var(--user-bg-hover,#3f3f46)}.ai-chat-history-item.active{background:var(--user-bg-hover,#e5e7eb)}.ai-chat-widget.dark .ai-chat-history-item.active{background:var(--user-bg-hover,#3f3f46)}.ai-chat-history-item-preview{color:var(--text-primary,#18181b);flex:1;font-size:var(--text-sm,14px);font-weight:var(--font-weight-medium,500);line-height:var(--line-height-normal,1.4);margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-widget.dark .ai-chat-history-item-preview{color:var(--text-primary,#fafafa)}.ai-chat-history-item.active .ai-chat-history-item-preview{font-weight:var(--font-weight-medium)}.ai-chat-history-item-meta{display:none}.ai-chat-history-item-delete{align-items:center;background:transparent;border:none;border-radius:var(--radius-sm,6px);color:var(--text-muted,#71717a);cursor:pointer;display:flex;flex-shrink:0;height:24px;justify-content:center;margin-left:auto;margin-right:var(--space-xs,4px);opacity:0;transition:opacity var(--duration-fast,.15s) ease,background var(--duration-fast,.15s) ease,color var(--duration-fast,.15s) ease;width:24px}.ai-chat-history-item-delete svg{height:14px;width:14px}.ai-chat-history-item:hover .ai-chat-history-item-delete{opacity:1}.ai-chat-history-item-delete:hover{background:rgba(239,68,68,.1);color:#ef4444}.ai-chat-widget.dark .ai-chat-history-item-delete:hover{background:rgba(239,68,68,.2);color:#f87171}.ai-chat-tool-row{align-items:center;display:flex;gap:10px;margin:2px 0;padding:0 16px}.ai-chat-tool-gear{color:#1f2937;flex-shrink:0;height:20px;width:20px}.ai-chat-tool-gear.spinning{animation:ai-chat-gear-spin 1.5s linear infinite}.ai-chat-tool-badges{align-items:center;display:flex;flex-wrap:wrap;gap:8px}.ai-chat-tool-badge{align-items:center;background:#e5e7eb;border:1px solid #d1d5db;border-radius:var(--radius-action-badge,8px);color:#1f2937;display:inline-flex;font-size:12px;font-weight:500;gap:4px;line-height:1.2;padding:5px 12px;transition:all .2s ease;white-space:nowrap}.ai-chat-tool-badge.loading{animation:ai-chat-tool-gradient 2s linear infinite;background:linear-gradient(90deg,var(--tool-loading-bg-1,#e0e0e0) 0,var(--tool-loading-bg-2,#f0f0f0) 25%,var(--tool-loading-bg-3,#fff) 50%,var(--tool-loading-bg-2,#f0f0f0) 75%,var(--tool-loading-bg-1,#e0e0e0) 100%);background-size:200% 100%;color:var(--tool-loading-text,#1a1a1a);position:relative}.ai-chat-widget:not(.dark) .ai-chat-tool-badge.loading{--tool-loading-bg-1:#2a2a2a;--tool-loading-bg-2:#3a3a3a;--tool-loading-bg-3:#4a4a4a;--tool-loading-text:#fff}.ai-chat-tool-badge.completed{background:#e5e7eb;border:1px solid #d1d5db;color:#1f2937}.ai-chat-widget.dark .ai-chat-tool-badge,.ai-chat-widget[data-theme=dark] .ai-chat-tool-badge,.chakra-ui-dark .ai-chat-tool-badge,.dark .ai-chat-tool-badge,[data-theme=dark] .ai-chat-tool-badge,html.dark .ai-chat-tool-badge{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);color:hsla(0,0%,100%,.9)}.ai-chat-widget.dark .ai-chat-tool-gear,.ai-chat-widget[data-theme=dark] .ai-chat-tool-gear,.chakra-ui-dark .ai-chat-tool-gear,.dark .ai-chat-tool-gear,[data-theme=dark] .ai-chat-tool-gear,html.dark .ai-chat-tool-gear{color:#fff}.ai-chat-tool-badge.error{background:var(--tool-error-bg,rgba(239,68,68,.15));color:var(--tool-error-text,#ef4444)}.ai-chat-tool-badge .ai-chat-tool-check{color:#fff;flex-shrink:0}.ai-chat-tool-badge .ai-chat-tool-error{color:#ef4444;flex-shrink:0}.tool-name{font-weight:500;line-height:1.2;white-space:nowrap}.ai-chat-tool-action{box-sizing:border-box;padding:0 16px;width:100%}";
|
|
30846
|
+
var css_248z$1 = ".ai-chat-message{animation:ai-chat-message-appear .2s var(--chat-ease-bounce);max-width:85%}.ai-chat-message-content{border-radius:var(--chat-radius-bubble,14px);font-size:var(--chat-text-md,15px);line-height:var(--chat-line-relaxed,1.6);padding:var(--chat-space-sm,8px) var(--chat-space-md,16px)}.ai-chat-message.user .ai-chat-message-content{background:var(--chat-user-bg,#f4f3f0);border-bottom-right-radius:var(--chat-radius-sm,4px);color:var(--chat-user-text,#000)}.ai-chat-message.assistant .ai-chat-message-content{background:var(--chat-assistant-bg,transparent);color:var(--chat-assistant-text,#000)}.ai-chat-message-timestamp{color:var(--chat-text-muted,#71717a);font-size:var(--chat-text-xs,12px);margin-top:var(--chat-space-xs,4px);padding:0 var(--chat-space-xs,4px)}.ai-chat-message.streaming .ai-chat-message-content:after{animation:ai-chat-cursor-blink .8s infinite;content:\"▋\";margin-left:2px;opacity:.7}@keyframes ai-chat-message-appear{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes ai-chat-cursor-blink{0%,50%{opacity:1}51%,to{opacity:0}}.ai-chat-message.fullpage .ai-chat-message-content{font-size:var(--chat-text-lg,18px);padding:var(--chat-space-md,16px) var(--chat-space-lg,24px)}.ai-chat-typing{gap:var(--chat-space-xs,4px);padding:var(--chat-space-sm,8px) var(--chat-space-md,16px)}.ai-chat-typing-dot{background:var(--chat-text-muted,#71717a)}.ai-chat-tool-row{padding:0 16px}.ai-chat-tool-gear{color:var(--text-primary,#3e3e3e)}.ai-chat-tool-badge{border-radius:8px}.ai-chat-tool-badge.loading{background:#e5e7eb;border:1px solid #d1d5db;color:#1f2937}.ai-chat-tool-badge.error{background:rgba(239,68,68,.1);color:#ef4444}.ai-chat-tool-check{color:#22c55e;flex-shrink:0}.ai-chat-tool-error{color:#ef4444;flex-shrink:0}.ai-chat-tool-badge .tool-name{max-width:150px;overflow:hidden;text-overflow:ellipsis}.ai-chat-widget.dark .ai-chat-tool-gear,.ai-chat-widget[data-theme=dark] .ai-chat-tool-gear,.dark .ai-chat-tool-gear,[data-theme=dark] .ai-chat-tool-gear{color:#fff}.ai-chat-widget.dark .ai-chat-tool-badge,.ai-chat-widget[data-theme=dark] .ai-chat-tool-badge,.dark .ai-chat-tool-badge,[data-theme=dark] .ai-chat-tool-badge{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);color:hsla(0,0%,100%,.9)}.ai-chat-widget.dark .ai-chat-tool-badge.error,.ai-chat-widget[data-theme=dark] .ai-chat-tool-badge.error,.dark .ai-chat-tool-badge.error,[data-theme=dark] .ai-chat-tool-badge.error{background:rgba(239,68,68,.2);color:#f87171}.chakra-ui-dark .ai-chat-tool-gear,html.dark .ai-chat-tool-gear{color:#fff}.chakra-ui-dark .ai-chat-tool-badge,html.dark .ai-chat-tool-badge{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);color:hsla(0,0%,100%,.9)}.chakra-ui-dark .ai-chat-tool-badge.error,html.dark .ai-chat-tool-badge.error{background:rgba(239,68,68,.2);color:#f87171}.ai-chat-pin-input-group{align-items:center;display:flex;flex-wrap:nowrap;gap:8px;justify-content:center;margin:4px 0 8px}.ai-chat-pin-input{align-items:center;appearance:none;background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);box-sizing:border-box;color:var(--text-primary,#3e3e3e);display:inline-flex;flex:0 0 42px;font-family:inherit;font-size:18px;font-weight:600;height:46px;justify-content:center;line-height:1;max-width:42px;min-width:42px;outline:none;padding:0;text-align:center;transition:border-color .2s ease,box-shadow .2s ease;width:42px}.ai-chat-pin-input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.1)}.ai-chat-pin-input:disabled{cursor:not-allowed;opacity:.6}.ai-chat-widget.dark .ai-chat-pin-input,.chakra-ui-dark .ai-chat-pin-input,.dark .ai-chat-pin-input,[data-theme=dark] .ai-chat-pin-input{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-pin-input:focus,.chakra-ui-dark .ai-chat-pin-input:focus,.dark .ai-chat-pin-input:focus,[data-theme=dark] .ai-chat-pin-input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.2)}.ai-chat-action-button-secondary{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:9999px;color:var(--text-secondary,#6b7280);cursor:pointer;font-size:var(--text-sm,13px);font-weight:var(--font-weight-medium,500);padding:10px 16px;transition:all .2s ease;width:100%}.ai-chat-action-button-secondary:hover:not(:disabled){background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6));color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-action-button-secondary:disabled{cursor:not-allowed;opacity:.5}.ai-chat-widget.dark .ai-chat-action-button-secondary,.chakra-ui-dark .ai-chat-action-button-secondary,.dark .ai-chat-action-button-secondary,[data-theme=dark] .ai-chat-action-button-secondary{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#e5e7eb}.ai-chat-widget.dark .ai-chat-action-button-secondary:hover:not(:disabled),.chakra-ui-dark .ai-chat-action-button-secondary:hover:not(:disabled),.dark .ai-chat-action-button-secondary:hover:not(:disabled),[data-theme=dark] .ai-chat-action-button-secondary:hover:not(:disabled){background:rgba(59,130,246,.2);border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-action-appointment-list,.ai-chat-action-button-group{display:flex;flex-direction:column;gap:8px}.ai-chat-action-appointment-item{align-items:center;background:var(--bg-primary,#fff);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:var(--radius-md,8px);display:flex;gap:12px;justify-content:space-between;padding:10px 12px}.ai-chat-widget.dark .ai-chat-action-appointment-item,.chakra-ui-dark .ai-chat-action-appointment-item,.dark .ai-chat-action-appointment-item,[data-theme=dark] .ai-chat-action-appointment-item{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.08)}.ai-chat-action-appointment-info{display:flex;flex-direction:column;gap:2px;min-width:0}.ai-chat-action-appointment-subject{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-widget.dark .ai-chat-action-appointment-subject,.chakra-ui-dark .ai-chat-action-appointment-subject,.dark .ai-chat-action-appointment-subject,[data-theme=dark] .ai-chat-action-appointment-subject{color:#fff}.ai-chat-action-appointment-time{color:var(--text-muted,#71717a);font-size:12px}.ai-chat-action-appointment-item .ai-chat-action-button-secondary{font-size:12px;padding:6px 12px;width:auto}.ai-chat-action-error-message{background:rgba(239,68,68,.1);border-radius:var(--radius-md,8px);color:#dc2626;font-size:var(--text-sm,13px);padding:12px}.ai-chat-widget.dark .ai-chat-action-error-message,.chakra-ui-dark .ai-chat-action-error-message,.dark .ai-chat-action-error-message,[data-theme=dark] .ai-chat-action-error-message{background:rgba(239,68,68,.2);color:#fca5a5}.ai-chat-widget,.chat-ui{--radius-sm:4px;--radius-md:8px;--radius-lg:12px;--radius-xl:16px;--radius-2xl:18px;--radius-pill:9999px;--radius-window-top:22px;--radius-window-bottom:44px;--radius-window-gutter:16px;--radius-chat-bubble:14px;--radius-preset-badge:13px;--radius-history-item:14px;--radius-action-badge:8px;--radius-input:62px;--space-xs:4px;--space-sm:8px;--space-md:16px;--space-lg:24px;--space-xl:32px;--text-xs:12px;--text-sm:14px;--text-md:15px;--text-lg:18px;--text-xl:22px;--text-2xl:28px;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--line-height-tight:1.3;--line-height-normal:1.4;--line-height-relaxed:1.6;--bg-primary:#fff;--bg-secondary:#f4f4f4;--bg-tertiary:#e5e7eb;--bg-hover:#e5e7eb;--text-primary:#3e3e3e;--text-secondary:#000;--text-muted:#71717a;--text-placeholder:#a1a1aa;--border-default:#d3d3d3;--border-subtle:#e5e7eb;--border-muted:#f4f4f5;--user-bg:#f4f3f0;--user-text:#000;--user-bg-hover:#e8e7e4;--agent-bg:transparent;--agent-text:#000;--input-bg:#f4f4f4;--input-border:#d3d3d3;--input-text:#000;--btn-primary-bg:#151515;--btn-primary-text:#f4f4f4;--btn-secondary-bg:transparent;--btn-secondary-text:#71717a;--spring-bounce:cubic-bezier(0.34,1.56,0.64,1);--spring-smooth:cubic-bezier(0.4,0,0.2,1);--spring-snappy:cubic-bezier(0.2,0,0,1);--duration-fast:0.15s;--duration-normal:0.25s;--duration-slow:0.35s;--shadow-sm:0 1px 2px rgba(0,0,0,.05);--shadow-md:0 2px 8px rgba(0,0,0,.1);--shadow-lg:0 4px 16px rgba(0,0,0,.12);--shadow-window:0px 0px 15px 9px rgba(0,0,0,.1);--shadow-button:0px 0px 15px 9px rgba(0,0,0,.03)}.ai-chat-widget.dark,.chat-ui.dark{--bg-primary:#282625;--bg-secondary:#4a4846;--bg-tertiary:#484848;--bg-hover:#484848;--text-primary:#fff;--text-secondary:#fff;--text-muted:#a1a1aa;--text-placeholder:#71717a;--border-default:#5d5b5b;--border-subtle:#5d5b5b;--border-muted:#5d5b5b;--user-bg:#484848;--user-text:#fff;--user-bg-hover:#5a5a5a;--agent-bg:transparent;--agent-text:#fff;--input-bg:#4a4846;--input-border:#5d5b5b;--input-text:#fff;--btn-primary-bg:#fff;--btn-primary-text:#312f2d;--btn-secondary-bg:transparent;--btn-secondary-text:#a1a1aa;--shadow-window:0px 0px 15px 9px rgba(0,0,0,.2);--shadow-button:0px 0px 15px 9px rgba(0,0,0,.03);--shadow-input:0px 0px 10px rgba(0,0,0,.15)}.ai-chat-widget,.chat-ui{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.ai-chat-widget-container{font-size:var(--text-sm);line-height:1.5;position:fixed;z-index:var(--widget-z-index,2147483647)}.ai-chat-widget-container.bottom-right{bottom:20px;right:20px}.ai-chat-widget-container.bottom-left{bottom:20px;left:20px}.ai-chat-widget-container.top-right{right:20px;top:20px}.ai-chat-widget-container.top-left{left:20px;top:20px}.ai-chat-widget-container.container-mode{position:absolute}@keyframes ai-chat-window-open{0%{opacity:0;transform:scale(.9) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes ai-chat-window-close{0%{opacity:1;transform:scale(1) translateY(0)}to{opacity:0;transform:scale(.9) translateY(20px)}}@keyframes ai-chat-message-slide-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes ai-chat-welcome-fade-in{0%{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}@keyframes ai-chat-typing-pulse{0%,60%,to{opacity:.4;transform:translateY(0) scale(1)}30%{opacity:1;transform:translateY(-4px) scale(1.1)}}@keyframes ai-chat-gear-spin{to{transform:rotate(1turn)}}@keyframes ai-chat-tool-active{0%,to{background:var(--bg-secondary);opacity:1}50%{background:var(--bg-tertiary);opacity:1}}@keyframes ai-chat-feedback-morph{0%{opacity:.5;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@keyframes ai-chat-checkmark-pop{0%{opacity:0;transform:scale(0) rotate(-45deg)}50%{transform:scale(1.3) rotate(0deg)}to{opacity:1;transform:scale(1) rotate(0deg)}}@keyframes ai-chat-history-exit{to{opacity:0;transform:translateY(-18px)}}@keyframes ai-chat-tool-gradient{0%{background-position:200% 0}to{background-position:-200% 0}}.ai-chat-window{animation:ai-chat-window-open var(--duration-slow,.35s) var(--spring-bounce);background:var(--bg-primary,#fff);border:1px solid var(--border-default,#d3d3d3);border-radius:var(--radius-window-top,22px) var(--radius-window-top,22px) var(--radius-window-bottom,44px) var(--radius-window-bottom,44px);box-shadow:var(--shadow-window,0 0 15px 5px rgba(0,0,0,.08));display:flex;flex-direction:column;overflow:hidden;position:absolute;transform-origin:bottom right;z-index:2}.ai-chat-widget.dark .ai-chat-window{background:var(--bg-primary,#282625);border-color:var(--border-default,#5d5b5b);border-width:.7px}.ai-chat-window.closing{animation:ai-chat-window-close var(--duration-normal) var(--spring-smooth) forwards}.ai-chat-window.size-small{height:500px;width:380px}.ai-chat-window.size-medium,.ai-chat-window.size-small{max-height:calc(100vh - 100px);max-width:calc(100vw - 40px)}.ai-chat-window.size-medium{height:600px;width:420px}.ai-chat-window.size-large{height:700px;max-height:calc(100vh - 100px);max-width:calc(100vw - 40px);width:480px}.ai-chat-widget-container.bottom-right .ai-chat-window{bottom:calc(var(--button-size, 56px) + 8px);right:0}.ai-chat-widget-container.bottom-left .ai-chat-window{bottom:calc(var(--button-size, 56px) + 8px);left:0}.ai-chat-widget-container.top-right .ai-chat-window{right:0;top:calc(var(--button-size, 56px) + 8px)}.ai-chat-widget-container.top-left .ai-chat-window{left:0;top:calc(var(--button-size, 56px) + 8px)}.ai-chat-header{align-items:center;background:var(--bg-primary,#fff);border-bottom:1px solid var(--border-default,#d3d3d3);border-radius:var(--radius-window-top,22px) var(--radius-window-top,22px) 0 0;display:flex;justify-content:space-between;padding:12px var(--space-md,16px);position:relative;z-index:10}.ai-chat-widget.dark .ai-chat-header{background:var(--bg-primary,#282625);border-bottom-color:var(--border-default,#5d5b5b);border-bottom-width:.7px}.ai-chat-header.is-history{padding-left:var(--space-md)}.ai-chat-header.is-history .ai-chat-title{flex:1;min-width:0;overflow:hidden;padding-right:var(--space-lg);text-overflow:ellipsis;white-space:nowrap}.ai-chat-header-content{align-items:center;display:flex;flex:1;gap:var(--space-lg)}.ai-chat-header-actions{align-items:center;display:flex;gap:var(--space-sm)}.ai-chat-logo{border-radius:10px;height:36px;object-fit:cover;width:36px}.ai-chat-title{color:var(--text-primary,#3e3e3e);font-size:var(--text-xl,22px);font-weight:var(--font-weight-bold,700);letter-spacing:-.02em}.ai-chat-widget.dark .ai-chat-title{color:var(--text-primary,#fff)}.ai-chat-close-button,.ai-chat-header-button{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:color var(--duration-fast) ease;width:32px}.ai-chat-close-button:hover,.ai-chat-header-button:hover{color:var(--text-primary)}.ai-chat-close-button:active,.ai-chat-header-button:active{transform:scale(.95)}.ai-chat-close-button svg,.ai-chat-header-button svg{height:22px;width:22px}.ai-chat-button{align-items:center;background:var(--button-color,var(--btn-primary-bg));border:1px solid var(--border-default,#d3d3d3);border-radius:50%;box-shadow:var(--shadow-button,0 0 15px 9px rgba(0,0,0,.03));color:var(--button-icon-color,var(--btn-primary-text));cursor:pointer;display:flex;height:var(--button-size,56px);justify-content:center;overflow:hidden;position:relative;transition:transform var(--duration-fast) ease;width:var(--button-size,56px);z-index:1}.ai-chat-button:hover{transform:scale(1.05)}.ai-chat-button:active{transform:scale(.98)}.ai-chat-button-svg{height:50%;min-height:24px;min-width:24px;transition:transform var(--duration-fast) ease;width:50%}.ai-chat-button.is-open .ai-chat-button-svg{transform:rotate(0deg)}.ai-chat-button-icon{font-size:1.5em;line-height:1}.ai-chat-welcome-bubble{animation:ai-chat-bubble-fade-in .3s ease-out;background:var(--button-color,var(--btn-primary-bg,#07f));border:none;border-radius:16px;box-shadow:none;box-sizing:border-box;color:var(--button-icon-color,var(--btn-primary-text,#fff));cursor:pointer;font-size:13px;font-weight:500;line-height:1.5;max-width:min(420px,90vw);padding:12px 32px 12px 16px;position:absolute;text-align:left;white-space:normal;width:auto;z-index:0}.ai-chat-welcome-bubble-close{align-items:center;background:transparent;border:none;border-radius:50%;color:inherit;cursor:pointer;display:flex;height:20px;justify-content:center;opacity:.8;padding:0;position:absolute;right:8px;top:8px;transition:opacity .15s ease,background .15s ease;width:20px}.ai-chat-welcome-bubble-close:hover{background:hsla(0,0%,100%,.2);opacity:1}.ai-chat-welcome-bubble-close svg{height:12px;width:12px}.ai-chat-welcome-bubble-arrow{border-bottom:8px solid transparent;border-top:8px solid transparent;height:0;position:absolute;width:0}.ai-chat-widget-container.bottom-right .ai-chat-welcome-bubble{bottom:50%;right:calc(100% + 12px);text-align:left;transform:translateY(50%)}.ai-chat-widget-container.bottom-left .ai-chat-welcome-bubble{bottom:50%;left:calc(100% + 12px);text-align:left;transform:translateY(50%)}.ai-chat-widget-container.top-right .ai-chat-welcome-bubble{right:calc(100% + 12px);text-align:left;top:50%;transform:translateY(-50%)}.ai-chat-widget-container.top-left .ai-chat-welcome-bubble{left:calc(100% + 12px);text-align:left;top:50%;transform:translateY(-50%)}.ai-chat-widget-container.bottom-right .ai-chat-welcome-bubble-arrow{border-left:8px solid var(--button-color,var(--btn-primary-bg,#07f));right:-8px;top:50%;transform:translateY(-50%)}.ai-chat-widget-container.bottom-left .ai-chat-welcome-bubble-arrow{border-right:8px solid var(--button-color,var(--btn-primary-bg,#07f));left:-8px;top:50%;transform:translateY(-50%)}.ai-chat-widget-container.top-right .ai-chat-welcome-bubble-arrow{border-left:8px solid var(--button-color,var(--btn-primary-bg,#07f));right:-8px;top:50%;transform:translateY(-50%)}.ai-chat-widget-container.top-left .ai-chat-welcome-bubble-arrow{border-right:8px solid var(--button-color,var(--btn-primary-bg,#07f));left:-8px;top:50%;transform:translateY(-50%)}.ai-chat-welcome-bubble:hover{filter:brightness(1.1)}.ai-chat-widget-container.bottom-right .ai-chat-welcome-bubble{animation-name:ai-chat-bubble-fade-in-bottom-right}@keyframes ai-chat-bubble-fade-in-bottom-right{0%{opacity:0;transform:translateY(50%) translateX(8px)}to{opacity:1;transform:translateY(50%) translateX(0)}}.ai-chat-widget-container.bottom-left .ai-chat-welcome-bubble{animation-name:ai-chat-bubble-fade-in-bottom-left}@keyframes ai-chat-bubble-fade-in-bottom-left{0%{opacity:0;transform:translateY(50%) translateX(-8px)}to{opacity:1;transform:translateY(50%) translateX(0)}}.ai-chat-widget-container.top-right .ai-chat-welcome-bubble{animation-name:ai-chat-bubble-fade-in-top-right}@keyframes ai-chat-bubble-fade-in-top-right{0%{opacity:0;transform:translateY(-50%) translateX(8px)}to{opacity:1;transform:translateY(-50%) translateX(0)}}.ai-chat-widget-container.top-left .ai-chat-welcome-bubble{animation-name:ai-chat-bubble-fade-in-top-left}@keyframes ai-chat-bubble-fade-in-top-left{0%{opacity:0;transform:translateY(-50%) translateX(-8px)}to{opacity:1;transform:translateY(-50%) translateX(0)}}.ai-chat-trigger-pill{align-items:center;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);background:hsla(0,0%,100%,.08);border:1px solid hsla(0,0%,100%,.15);border-radius:9999px;color:var(--text-primary,#fff);cursor:pointer;display:flex;font-size:13px;font-weight:500;gap:8px;height:40px;justify-content:center;padding:0 20px;position:relative;transition:all .3s ease;white-space:nowrap;z-index:1}.ai-chat-trigger-pill.is-open{background:var(--button-color,var(--btn-primary-bg));border-color:var(--border-default,#d3d3d3);box-shadow:var(--shadow-button,0 0 15px 9px rgba(0,0,0,.03));height:56px;padding:0;width:56px}.ai-chat-trigger-pill.is-open .ai-chat-trigger-pill-icon{height:24px;width:24px}.ai-chat-trigger-pill:hover:not(.is-open){background:hsla(0,0%,100%,.12);border-color:hsla(0,0%,100%,.25)}.ai-chat-trigger-pill.is-open:hover{transform:scale(1.05)}.ai-chat-trigger-pill:active{transform:scale(.98)}.ai-chat-trigger-pill-icon{flex-shrink:0;height:16px;transition:all .3s ease;width:16px}.ai-chat-widget.light .ai-chat-trigger-pill{background:rgba(0,0,0,.04);border-color:rgba(0,0,0,.12);color:var(--text-primary,#1a1a1a)}.ai-chat-widget.light .ai-chat-trigger-pill:hover:not(.is-open){background:rgba(0,0,0,.08);border-color:rgba(0,0,0,.2)}.ai-chat-trigger-input-container{align-items:flex-end;display:flex;flex-direction:column;gap:8px}.ai-chat-widget-container.bottom-left .ai-chat-trigger-input-container,.ai-chat-widget-container.top-left .ai-chat-trigger-input-container{align-items:flex-start}.ai-chat-trigger-input-expand{align-items:center;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);background:hsla(0,0%,100%,.1);border:1px solid hsla(0,0%,100%,.15);border-radius:50%;color:hsla(0,0%,100%,.7);cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:all .2s ease;width:32px}.ai-chat-trigger-input-expand:hover{background:hsla(0,0%,100%,.15);color:hsla(0,0%,100%,.9);transform:translateY(-2px)}.ai-chat-trigger-input-expand:active{transform:translateY(0)}.ai-chat-trigger-input-expand svg{height:16px;width:16px}.ai-chat-widget.light .ai-chat-trigger-input-expand{background:rgba(0,0,0,.05);border:1px solid rgba(0,0,0,.1);color:rgba(0,0,0,.5)}.ai-chat-widget.light .ai-chat-trigger-input-expand:hover{background:rgba(0,0,0,.1);color:rgba(0,0,0,.7)}.ai-chat-trigger-input-wrapper{max-width:calc(100vw - 40px);position:relative;z-index:1}.ai-chat-widget-container.trigger-input-bar .ai-chat-trigger-input-wrapper{width:348px}.ai-chat-widget-container.trigger-input-bar.size-medium .ai-chat-trigger-input-wrapper{width:388px}.ai-chat-widget-container.trigger-input-bar.size-large .ai-chat-trigger-input-wrapper{width:448px}.ai-chat-trigger-input{backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);background:hsla(0,0%,100%,.08);border:1px solid hsla(0,0%,100%,.12);border-radius:var(--radius-input,62px);box-shadow:0 4px 24px rgba(0,0,0,.15);box-sizing:border-box;color:var(--input-text,#fff);font-size:var(--text-md,15px);height:52px;outline:none;padding:6px 52px 6px 16px;transition:all .2s ease;width:100%}.ai-chat-trigger-input::placeholder{color:var(--text-placeholder,hsla(0,0%,100%,.5))}.ai-chat-trigger-input:focus{background:hsla(0,0%,100%,.12);border-color:hsla(0,0%,100%,.25)}.ai-chat-trigger-input-btn{align-items:center;background:var(--primary-color,var(--button-color,#07f));border:none;border-radius:50%;box-shadow:0 2px 8px rgba(0,119,255,.3);color:#fff;cursor:pointer;display:flex;height:40px;justify-content:center;position:absolute;right:6px;top:50%;transform:translateY(-50%);transition:all .2s ease;width:40px}.ai-chat-trigger-input-btn:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,119,255,.4);transform:translateY(-50%) scale(1.05)}.ai-chat-trigger-input-btn:active:not(:disabled){transform:translateY(-50%) scale(.95)}.ai-chat-trigger-input-btn:disabled{box-shadow:none;cursor:not-allowed;opacity:.4}.ai-chat-trigger-input-btn svg{height:18px;width:18px}.ai-chat-widget.light .ai-chat-trigger-input{backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);background:hsla(0,0%,100%,.7);border:1px solid rgba(0,0,0,.1);box-shadow:0 4px 24px rgba(0,0,0,.08);color:var(--input-text,#1a1a1a)}.ai-chat-widget.light .ai-chat-trigger-input::placeholder{color:var(--text-placeholder,rgba(0,0,0,.4))}.ai-chat-widget.light .ai-chat-trigger-input:focus{background:hsla(0,0%,100%,.85);border-color:rgba(0,0,0,.2)}.ai-chat-widget.light .ai-chat-trigger-input-btn{background:var(--primary-color,var(--button-color,#07f));box-shadow:0 2px 8px rgba(0,119,255,.25);color:#fff}.ai-chat-widget.light .ai-chat-trigger-input-btn:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,119,255,.35)}.ai-chat-widget-container.trigger-input-bar{align-items:flex-end;display:flex;flex-direction:column;gap:12px}.ai-chat-widget-container.trigger-input-bar.bottom-left,.ai-chat-widget-container.trigger-input-bar.top-left{align-items:flex-start}.ai-chat-widget-container.trigger-input-bar .ai-chat-window{bottom:auto;left:auto;order:-1;position:relative;right:auto;top:auto;width:100%}.ai-chat-widget-container.trigger-input-bar .ai-chat-window.size-small{max-width:calc(100vw - 40px);width:380px}.ai-chat-widget-container.trigger-input-bar .ai-chat-window.size-medium{max-width:calc(100vw - 40px);width:420px}.ai-chat-widget-container.trigger-input-bar .ai-chat-window.size-large{max-width:calc(100vw - 40px);width:480px}.ai-chat-widget-container.trigger-input-bar .ai-chat-button,.ai-chat-widget-container.trigger-pill-text .ai-chat-button{display:none}.ai-chat-widget-container.trigger-pill-text.is-open{gap:8px}.ai-chat-input-container{background:var(--bg-primary,#fff);bottom:0;left:0;padding:8px 0 16px;position:absolute;right:0;z-index:10}.ai-chat-widget.dark .ai-chat-input-container{background:var(--bg-primary,#282625)}.ai-chat-input-container.separate{padding:0 var(--radius-window-gutter,16px) var(--radius-window-gutter,16px)}.ai-chat-input-wrapper{align-items:flex-end;background:var(--input-bg,#f4f4f4);border:1px solid var(--input-border,#d3d3d3);border-radius:var(--radius-input,62px);box-sizing:border-box;display:flex;gap:0;height:52px;overflow:hidden;padding:6px 6px 6px 16px;position:relative;transition:all var(--duration-fast,.15s) ease;z-index:5}.ai-chat-input-wrapper.multiline{border-radius:14px!important;min-height:64px;padding:10px 10px 10px 14px}.ai-chat-widget.dark .ai-chat-input-wrapper{background:var(--input-bg,#4a4846);border-color:var(--input-border,#5d5b5b);border-width:.7px;box-shadow:var(--shadow-input,0 0 10px rgba(0,0,0,.15))}.ai-chat-input-wrapper:focus-within{border-color:var(--text-muted,#a1a1aa)}.ai-chat-input{word-wrap:break-word!important;background:transparent!important;border:none!important;border-radius:0!important;box-shadow:none!important;box-sizing:border-box!important;color:var(--input-text,#000)!important;flex:1!important;font-family:inherit!important;font-size:var(--text-md,15px)!important;height:40px!important;line-height:20px!important;margin:0!important;max-height:40px!important;min-height:40px!important;min-width:0!important;outline:none!important;overflow-wrap:anywhere!important;overflow-x:hidden!important;overflow-y:auto!important;padding:10px var(--space-sm,8px)!important;resize:none!important;white-space:pre-wrap!important;width:0!important;word-break:break-word!important}.ai-chat-widget.dark .ai-chat-input{color:var(--input-text,#fff)}.ai-chat-input::placeholder{color:var(--text-placeholder,#a1a1aa)}.ai-chat-widget.dark .ai-chat-input::placeholder{color:var(--text-placeholder,#52525b)}.ai-chat-file-button{align-items:center;align-self:center;background:transparent;border:none;color:var(--text-placeholder);cursor:pointer;display:flex;flex-shrink:0;height:28px;justify-content:center;padding:0;transition:color var(--duration-fast) ease;width:28px}.ai-chat-file-button:hover{color:var(--text-secondary)}.ai-chat-file-button:disabled{cursor:not-allowed;opacity:.5}.ai-chat-send-button{align-items:center;align-self:center;background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#151515))));border:none;border-radius:50%;color:var(--button-icon-color,var(--btn-primary-text,#f4f4f4));cursor:pointer;display:flex;flex-shrink:0;height:40px;justify-content:center;min-height:40px;min-width:40px;padding:0;transition:all var(--duration-fast,.15s) ease;width:40px}.ai-chat-widget.dark .ai-chat-send-button{background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#fff))));color:var(--button-icon-color,var(--btn-primary-text,#312f2d))}.ai-chat-send-button.active{background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#151515))));color:var(--button-icon-color,var(--btn-primary-text,#f4f4f4))}.ai-chat-widget.dark .ai-chat-send-button.active{background:var(--send-button-background,var(--primary-color,var(--button-color,var(--btn-primary-bg,#fff))));color:var(--button-icon-color,var(--btn-primary-text,#312f2d))}.ai-chat-send-button:hover:not(:disabled){opacity:.8}.ai-chat-send-button:active:not(:disabled){transform:scale(.95)}.ai-chat-send-button:disabled{cursor:not-allowed;opacity:.3}.ai-chat-file-list{display:flex;flex-wrap:wrap;gap:var(--space-sm);padding:var(--space-sm) var(--space-sm)}.ai-chat-file-item{align-items:center;background:rgba(0,0,0,.05);border-radius:6px;display:flex;font-size:var(--text-xs);gap:var(--space-sm);padding:6px 10px}.ai-chat-file-extension{background:var(--btn-primary-bg);border-radius:3px;color:var(--btn-primary-text);display:inline-block;font-size:10px;font-weight:var(--font-weight-semibold);min-width:40px;padding:2px 6px;text-align:center;text-transform:uppercase}.ai-chat-file-info{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.ai-chat-file-name{font-weight:var(--font-weight-medium);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-file-size{color:var(--text-muted);font-size:10px;opacity:.7}.ai-chat-file-remove{align-items:center;background:none;border:none;color:inherit;cursor:pointer;display:flex;justify-content:center;opacity:.5;padding:var(--space-xs);transition:opacity var(--duration-fast) ease}.ai-chat-file-remove:hover{opacity:1}.ai-chat-data-policy{bottom:2px;color:var(--text-muted,#71717a);font-size:9px;left:0;line-height:1.4;opacity:.5;pointer-events:auto;position:absolute;right:0;text-align:center}.ai-chat-widget.dark .ai-chat-data-policy{color:var(--text-muted,#a1a1aa)}.ai-chat-data-policy-link{background:none;border:none;color:var(--text-muted,#71717a);cursor:pointer;font-family:inherit;font-size:inherit;margin:0;padding:0;text-decoration:underline;text-underline-offset:2px;transition:color .15s ease}.ai-chat-data-policy-link:hover{color:var(--text-secondary,#52525b)}.ai-chat-widget.dark .ai-chat-data-policy-link{color:var(--text-muted,#a1a1aa)}.ai-chat-widget.dark .ai-chat-data-policy-link:hover{color:var(--text-secondary,#d4d4d8)}.ai-chat-page-disclaimer{align-items:center;color:var(--text-muted,#71717a);display:flex;font-size:10px;gap:4px;justify-content:center;line-height:1.4;opacity:.7;padding:8px 16px;text-align:center}.ai-chat-widget.dark .ai-chat-page-disclaimer{color:var(--text-muted,#a1a1aa)}.ai-chat-page-disclaimer-link{background:none;border:none;color:var(--text-muted,#71717a);cursor:pointer;font-family:inherit;font-size:inherit;margin:0;padding:0;text-decoration:underline;text-underline-offset:2px;transition:color .15s ease}.ai-chat-page-disclaimer-link:hover{color:var(--text-secondary,#52525b)}.ai-chat-widget.dark .ai-chat-page-disclaimer-link{color:var(--text-muted,#a1a1aa)}.ai-chat-widget.dark .ai-chat-page-disclaimer-link:hover{color:var(--text-secondary,#d4d4d8)}.ai-chat-messages{-webkit-overflow-scrolling:touch;-ms-overflow-style:none;align-items:stretch;background:var(--bg-primary,#fff);display:flex;flex:1;flex-direction:column;gap:var(--space-md,16px);justify-content:flex-start;overflow-x:hidden;overflow-y:auto;padding:0 var(--space-md,16px) 100px;position:relative;scroll-behavior:smooth;scrollbar-width:none}.ai-chat-widget.dark .ai-chat-messages{background:var(--bg-primary,#18181b)}.ai-chat-messages::-webkit-scrollbar{display:none}.ai-chat-message{animation:ai-chat-message-slide-in .2s var(--spring-bounce);display:flex;flex-direction:column;max-width:90%}.ai-chat-message.user{align-items:flex-end;align-self:flex-end}.ai-chat-message.assistant{align-items:flex-start;align-self:flex-start;max-width:100%;width:100%}.ai-chat-message.tool{align-self:stretch;max-width:none;padding:0}.ai-chat-message-content{word-wrap:break-word;border-radius:18px;font-size:var(--text-md,15px);line-height:var(--line-height-relaxed,1.6);overflow-wrap:break-word;padding:8px 14px}.ai-chat-message.user .ai-chat-message-content{background:var(--user-bg,#f4f3f0);border-radius:18px;color:var(--user-text,#000)}.ai-chat-widget.dark .ai-chat-message.user .ai-chat-message-content{background:var(--user-bg,#484848);color:var(--user-text,#fff)}.ai-chat-message.assistant .ai-chat-message-content{background:var(--agent-bg,transparent);box-sizing:border-box;color:var(--agent-text,#000);padding:0;width:100%}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content{color:var(--agent-text,#fff)}.ai-chat-message-timestamp{color:var(--text-muted,#71717a);font-size:var(--text-xs,12px);margin-top:var(--space-xs,4px);padding:0 var(--space-xs,4px)}.ai-chat-welcome{animation:ai-chat-welcome-fade-in .3s var(--spring-smooth);display:flex;flex-direction:column;gap:var(--space-md,16px);padding:var(--space-lg,24px) 0}.ai-chat-welcome-title{color:var(--text-primary,#3e3e3e);font-size:var(--text-2xl,28px);font-weight:var(--font-weight-bold,700);line-height:var(--line-height-tight,1.3)}.ai-chat-widget.dark .ai-chat-welcome-title{color:var(--text-primary,#fff)}.ai-chat-welcome-text{color:var(--text-secondary,#000);font-size:var(--text-md,15px);line-height:var(--line-height-relaxed,1.6);max-width:100%}.ai-chat-widget.dark .ai-chat-welcome-text{color:var(--text-secondary,#fff)}.ai-chat-typing{align-items:center;display:flex;gap:var(--space-xs,4px);padding:var(--space-sm,8px) var(--space-md,16px)}.ai-chat-typing-dot{animation:ai-chat-typing-bounce 1.4s ease-in-out infinite both;background:var(--text-muted,#71717a);border-radius:50%;height:8px;width:8px}.ai-chat-typing-dot:first-child{animation-delay:-.32s}.ai-chat-typing-dot:nth-child(2){animation-delay:-.16s}.ai-chat-typing-dot:nth-child(3){animation-delay:0s}@keyframes ai-chat-typing-bounce{0%,80%,to{opacity:.4;transform:scale(.6)}40%{opacity:1;transform:scale(1)}}.ai-chat-scroll-button{align-items:center;background:var(--bg-secondary,#f4f4f5);border:1px solid var(--border-subtle,rgba(0,0,0,.1));border-radius:50%;bottom:80px;box-shadow:0 2px 8px rgba(0,0,0,.1);color:var(--text-secondary,#71717a);cursor:pointer;display:flex;height:36px;justify-content:center;left:50%;opacity:0;pointer-events:none;position:absolute;transform:translateX(-50%);transition:background .15s ease,box-shadow .15s ease,opacity .15s ease,visibility .15s ease;visibility:hidden;width:36px;z-index:15}.ai-chat-scroll-button.visible{opacity:1;pointer-events:auto;visibility:visible}.ai-chat-scroll-button:hover{background:var(--bg-tertiary,#e4e4e7);box-shadow:0 4px 12px rgba(0,0,0,.15)}.ai-chat-scroll-button:active{background:var(--bg-tertiary,#d4d4d8)}.ai-chat-widget.dark .ai-chat-scroll-button{background:var(--bg-secondary,#3f3f46);border-color:var(--border-subtle,hsla(0,0%,100%,.1));box-shadow:0 2px 8px rgba(0,0,0,.3);color:var(--text-secondary,#a1a1aa)}.ai-chat-widget.dark .ai-chat-scroll-button:hover{background:var(--bg-tertiary,#52525b);box-shadow:0 4px 12px rgba(0,0,0,.4)}.ai-chat-error{background:var(--bg-secondary);border-radius:var(--radius-chat-bubble);color:var(--text-primary);font-size:var(--text-md);margin:0 auto;padding:10px var(--space-md)}.ai-chat-message.assistant .ai-chat-message-content p{margin:0 0 var(--space-sm) 0}.ai-chat-message.assistant .ai-chat-message-content p:last-child{margin-bottom:0}.ai-chat-message.assistant .ai-chat-message-content h1{font-size:1.5em}.ai-chat-message.assistant .ai-chat-message-content h1,.ai-chat-message.assistant .ai-chat-message-content h2{color:var(--text-primary,#3e3e3e);font-weight:var(--font-weight-bold,700);line-height:var(--line-height-tight,1.3);margin:var(--space-md,16px) 0 var(--space-sm,8px) 0}.ai-chat-message.assistant .ai-chat-message-content h2{font-size:1.3em}.ai-chat-message.assistant .ai-chat-message-content h3{color:var(--text-primary,#3e3e3e);font-size:1.15em;font-weight:var(--font-weight-semibold,600);line-height:var(--line-height-tight,1.3);margin:var(--space-sm,8px) 0 var(--space-xs,4px) 0}.ai-chat-message.assistant .ai-chat-message-content h4,.ai-chat-message.assistant .ai-chat-message-content h5,.ai-chat-message.assistant .ai-chat-message-content h6{color:var(--text-primary,#3e3e3e);font-size:1em;font-weight:var(--font-weight-semibold,600);line-height:var(--line-height-tight,1.3);margin:var(--space-sm,8px) 0 var(--space-xs,4px) 0}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content h1,.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content h2,.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content h3,.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content h4,.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content h5,.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content h6{color:var(--text-primary,#fff)}.ai-chat-message.assistant .ai-chat-message-content>h1:first-child,.ai-chat-message.assistant .ai-chat-message-content>h2:first-child,.ai-chat-message.assistant .ai-chat-message-content>h3:first-child,.ai-chat-message.assistant .ai-chat-message-content>h4:first-child,.ai-chat-message.assistant .ai-chat-message-content>h5:first-child,.ai-chat-message.assistant .ai-chat-message-content>h6:first-child{margin-top:0}.ai-chat-message.assistant .ai-chat-message-content ul{list-style-type:disc;margin:var(--space-sm,8px) 0;padding-left:var(--space-lg,24px)}.ai-chat-message.assistant .ai-chat-message-content ul ul{list-style-type:circle;margin:var(--space-xs,4px) 0}.ai-chat-message.assistant .ai-chat-message-content ul ul ul{list-style-type:square}.ai-chat-message.assistant .ai-chat-message-content ol{list-style-type:decimal;margin:var(--space-sm,8px) 0;padding-left:var(--space-lg,24px)}.ai-chat-message.assistant .ai-chat-message-content ol ol{list-style-type:lower-alpha;margin:var(--space-xs,4px) 0}.ai-chat-message.assistant .ai-chat-message-content ol ol ol{list-style-type:lower-roman}.ai-chat-message.assistant .ai-chat-message-content li{margin-bottom:var(--space-xs,4px);padding-left:var(--space-xs,4px)}.ai-chat-message.assistant .ai-chat-message-content li:last-child{margin-bottom:0}.ai-chat-message.assistant .ai-chat-message-content li>ol,.ai-chat-message.assistant .ai-chat-message-content li>ul{margin-top:var(--space-xs,4px)}.ai-chat-message.assistant .ai-chat-message-content code{background:rgba(0,0,0,.05);border-radius:var(--radius-sm);font-family:SF Mono,Monaco,Cascadia Code,monospace;font-size:.9em;padding:2px 6px}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content code{background:hsla(0,0%,100%,.1)}.ai-chat-message.assistant .ai-chat-message-content pre{background:rgba(0,0,0,.05);border-radius:var(--radius-md);margin:var(--space-sm) 0;overflow-x:auto;padding:var(--space-sm)}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content pre{background:hsla(0,0%,100%,.05)}.ai-chat-message.assistant .ai-chat-message-content pre code{background:transparent;padding:0}.ai-chat-message.assistant .ai-chat-message-content a{color:var(--btn-primary-bg);text-decoration:underline}.ai-chat-message.assistant .ai-chat-message-content strong{font-weight:var(--font-weight-semibold)}.ai-chat-message.assistant .ai-chat-message-content blockquote{border-left:3px solid var(--border-default);color:var(--text-muted);margin:var(--space-sm) 0;padding-left:var(--space-md)}.ai-chat-message.assistant .ai-chat-message-content hr{border:none;border-top:1px solid var(--border-subtle,rgba(0,0,0,.1));margin:var(--space-lg,24px) 0}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content hr{border-top-color:var(--border-subtle,hsla(0,0%,100%,.1))}.ai-chat-message.assistant .ai-chat-message-content .table-wrapper{border:1px solid var(--border-subtle,rgba(0,0,0,.1));border-radius:var(--radius-md,8px);box-sizing:border-box;display:block;margin:var(--space-sm) var(--space-sm);max-width:100%;overflow:hidden;width:auto}.ai-chat-message.assistant .ai-chat-message-content .table-scroll{max-width:100%;overflow-x:auto;overflow-y:hidden;width:100%}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content .table-wrapper{border-color:var(--border-subtle,hsla(0,0%,100%,.1))}.ai-chat-message.assistant .ai-chat-message-content table{border-collapse:collapse;font-size:var(--text-sm);min-width:100%;width:max-content}.ai-chat-message.assistant .ai-chat-message-content td,.ai-chat-message.assistant .ai-chat-message-content th{border-bottom:1px solid var(--border-subtle,rgba(0,0,0,.1));border-right:1px solid var(--border-subtle,rgba(0,0,0,.1));padding:var(--space-sm);text-align:left}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content td,.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content th{border-color:var(--border-subtle,hsla(0,0%,100%,.1))}.ai-chat-message.assistant .ai-chat-message-content td:last-child,.ai-chat-message.assistant .ai-chat-message-content th:last-child{border-right:none}.ai-chat-message.assistant .ai-chat-message-content tr:last-child td{border-bottom:none}.ai-chat-message.assistant .ai-chat-message-content th{background:rgba(0,0,0,.03);font-weight:var(--font-weight-semibold);white-space:nowrap}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content th{background:hsla(0,0%,100%,.05)}.ai-chat-message.assistant .ai-chat-message-content tbody tr:nth-child(2n){background:rgba(0,0,0,.02)}.ai-chat-widget.dark .ai-chat-message.assistant .ai-chat-message-content tbody tr:nth-child(2n){background:hsla(0,0%,100%,.03)}.ai-chat-suggested-questions{align-self:flex-end;margin:0;padding:16px 0 0;width:100%}.ai-chat-suggested-questions-list{align-items:center;display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.ai-chat-suggested-question{align-items:center;background:transparent;border:1px solid var(--border-default,#d4d4d8);border-radius:var(--radius-preset-badge,18px);color:var(--text-primary,#18181b);cursor:pointer;display:inline-flex;font-size:14px;font-weight:400;gap:6px;justify-content:center;line-height:1.3;max-width:100%;overflow:hidden;padding:8px 14px;text-overflow:ellipsis;transition:background .15s ease,border-color .15s ease,transform .1s ease;white-space:nowrap}.ai-chat-widget.dark .ai-chat-suggested-question{background:transparent;border-color:var(--border-subtle,#52525b);color:var(--text-primary,#fafafa)}.ai-chat-suggested-question-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-suggested-question:hover{background:var(--bg-hover,#f4f4f5);border-color:var(--border-default,#d4d4d8)}.ai-chat-widget.dark .ai-chat-suggested-question:hover{background:var(--bg-hover,#3f3f46);border-color:var(--border-subtle,#52525b)}.ai-chat-suggested-question:active{transform:scale(.98)}.ai-chat-suggested-question.action-type{border:none}.ai-chat-suggested-question.action-type,.ai-chat-widget.dark .ai-chat-suggested-question.action-type{background:var(--primary-color,var(--button-color,#ef4444));color:var(--button-icon-color,#fff)}.ai-chat-suggested-question.action-type:hover{background:var(--primary-color,var(--button-color,#ef4444));opacity:.9}.ai-chat-suggested-question-icon{align-items:center;display:flex;flex-shrink:0;justify-content:center}.ai-chat-suggested-question:not(.action-type) .ai-chat-suggested-question-icon{display:none}.ai-chat-follow-up-suggestions{box-sizing:border-box;margin:0;padding:8px 16px 0;width:100%}.ai-chat-follow-up-list{align-items:flex-end;display:flex;flex-direction:column;gap:6px}.ai-chat-follow-up-item{align-items:center;border:none;border-radius:var(--radius-preset-badge,18px);cursor:pointer;display:inline-flex;font-size:14px;font-weight:400;gap:6px;justify-content:center;line-height:1.3;max-width:100%;overflow:hidden;padding:8px 14px;text-overflow:ellipsis;transition:opacity .15s ease,transform .1s ease;white-space:nowrap}.ai-chat-follow-up-item,.ai-chat-widget.dark .ai-chat-follow-up-item{background:var(--primary-color,var(--button-color,#07f));color:var(--button-icon-color,#fff)}.ai-chat-follow-up-item:hover{opacity:.9}.ai-chat-follow-up-item:active{transform:scale(.98)}.ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-default,#d4d4d8);color:var(--text-primary,#18181b)}.ai-chat-widget.dark .ai-chat-follow-up-item.question-type,.dark .ai-chat-follow-up-item.question-type,[data-color-mode=dark] .ai-chat-follow-up-item.question-type,[data-theme=dark] .ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-subtle,#52525b);color:var(--text-primary,#fafafa)}@media (prefers-color-scheme:dark){.ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-subtle,#52525b);color:var(--text-primary,#fafafa)}}.ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#f4f4f5);opacity:1}.ai-chat-widget.dark .ai-chat-follow-up-item.question-type:hover,.dark .ai-chat-follow-up-item.question-type:hover,[data-color-mode=dark] .ai-chat-follow-up-item.question-type:hover,[data-theme=dark] .ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#3f3f46);opacity:1}@media (prefers-color-scheme:dark){.ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#3f3f46);opacity:1}}.ai-chat-follow-up-item.action-type{background:var(--primary-color,var(--button-color,#07f));border:none;color:var(--button-icon-color,#fff)}.ai-chat-follow-up-icon{align-items:center;display:flex;flex-shrink:0;justify-content:center}.ai-chat-follow-up-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-feedback-buttons{align-items:center;display:flex;gap:var(--space-xs)}.ai-chat-feedback{align-items:center;display:inline-flex;gap:0;height:20px}.ai-chat-feedback-button{align-items:center;background:transparent!important;border:none;border-radius:var(--radius-sm);color:var(--text-placeholder);cursor:pointer;display:flex;font-size:var(--text-sm);height:20px;justify-content:center;padding:var(--space-xs);transition:all var(--duration-fast) var(--spring-bounce)}.ai-chat-feedback-button:hover:not(:disabled){background:none!important;color:var(--text-secondary)}.ai-chat-feedback-button:active:not(:disabled){transform:scale(.9)}.ai-chat-feedback-button:disabled{cursor:not-allowed;opacity:.4}.ai-chat-feedback-button.active{background:none!important;color:var(--text-primary)}.ai-chat-feedback.submitted{align-items:center;animation:ai-chat-feedback-morph .3s var(--spring-bounce);gap:var(--space-xs)}.ai-chat-feedback-message{align-items:center;display:flex;gap:4px;margin-left:var(--space-xxs)}.ai-chat-feedback-checkmark{animation:ai-chat-checkmark-pop .3s var(--spring-bounce);color:#10b981;font-size:var(--text-md);font-weight:700}.ai-chat-feedback-text{color:#10b981;font-size:var(--text-xs);font-weight:var(--font-weight-medium)}.ai-chat-history-panel{background:var(--bg-primary,#fff);display:flex;flex:1;flex-direction:column;overflow:hidden}.ai-chat-widget.dark .ai-chat-history-panel{background:var(--bg-primary,#18181b)}.ai-chat-history-empty,.ai-chat-history-loading{align-items:center;color:var(--text-muted);display:flex;flex:1;font-size:var(--text-sm);justify-content:center;padding:var(--space-lg);text-align:center}.ai-chat-history-list{-ms-overflow-style:none;display:flex;flex:1;flex-direction:column;gap:var(--space-sm);overflow-y:auto;padding:var(--space-xs) var(--space-md) 120px;scrollbar-width:none}.ai-chat-history-list::-webkit-scrollbar{display:none}.ai-chat-history-list.exiting{animation:ai-chat-history-exit .22s var(--spring-smooth) forwards}.ai-chat-history-item{align-items:center;background:var(--user-bg,#f4f4f5);border-radius:var(--radius-history-item,15px);display:flex;flex:0 0 auto;flex-direction:row;height:var(--history-item-height,36px);margin:0;overflow:hidden;transition:background var(--duration-fast,.15s) ease;width:100%}.ai-chat-history-item-content{align-items:center;background:transparent;border:none;cursor:pointer;display:flex;flex:1;flex-direction:row;height:100%;min-width:0;padding:0 var(--space-xs,4px) 0 var(--space-md,16px);text-align:left}.ai-chat-widget.dark .ai-chat-history-item{background:var(--user-bg,#27272a)}.ai-chat-history-item:hover{background:var(--user-bg-hover,#e5e7eb)}.ai-chat-widget.dark .ai-chat-history-item:hover{background:var(--user-bg-hover,#3f3f46)}.ai-chat-history-item.active{background:var(--user-bg-hover,#e5e7eb)}.ai-chat-widget.dark .ai-chat-history-item.active{background:var(--user-bg-hover,#3f3f46)}.ai-chat-history-item-preview{color:var(--text-primary,#18181b);flex:1;font-size:var(--text-sm,14px);font-weight:var(--font-weight-medium,500);line-height:var(--line-height-normal,1.4);margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-chat-widget.dark .ai-chat-history-item-preview{color:var(--text-primary,#fafafa)}.ai-chat-history-item.active .ai-chat-history-item-preview{font-weight:var(--font-weight-medium)}.ai-chat-history-item-meta{display:none}.ai-chat-history-item-delete{align-items:center;background:transparent;border:none;border-radius:var(--radius-sm,6px);color:var(--text-muted,#71717a);cursor:pointer;display:flex;flex-shrink:0;height:24px;justify-content:center;margin-left:auto;margin-right:var(--space-xs,4px);opacity:0;transition:opacity var(--duration-fast,.15s) ease,background var(--duration-fast,.15s) ease,color var(--duration-fast,.15s) ease;width:24px}.ai-chat-history-item-delete svg{height:14px;width:14px}.ai-chat-history-item:hover .ai-chat-history-item-delete{opacity:1}.ai-chat-history-item-delete:hover{background:rgba(239,68,68,.1);color:#ef4444}.ai-chat-widget.dark .ai-chat-history-item-delete:hover{background:rgba(239,68,68,.2);color:#f87171}.ai-chat-tool-row{align-items:center;display:flex;gap:10px;margin:2px 0;padding:0}.ai-chat-tool-gear{color:#1f2937;flex-shrink:0;height:20px;width:20px}.ai-chat-tool-gear.spinning{animation:ai-chat-gear-spin 1.5s linear infinite}.ai-chat-tool-badges{align-items:center;display:flex;flex-wrap:wrap;gap:8px}.ai-chat-tool-badge{align-items:center;background:#e5e7eb;border:1px solid #d1d5db;border-radius:var(--radius-action-badge,8px);color:#1f2937;display:inline-flex;font-size:12px;font-weight:500;gap:4px;line-height:1.2;padding:5px 12px;transition:all .2s ease;white-space:nowrap}.ai-chat-tool-badge.loading{animation:ai-chat-tool-gradient 2s linear infinite;background:linear-gradient(90deg,var(--tool-loading-bg-1,#e0e0e0) 0,var(--tool-loading-bg-2,#f0f0f0) 25%,var(--tool-loading-bg-3,#fff) 50%,var(--tool-loading-bg-2,#f0f0f0) 75%,var(--tool-loading-bg-1,#e0e0e0) 100%);background-size:200% 100%;color:var(--tool-loading-text,#1a1a1a);position:relative}.ai-chat-widget:not(.dark) .ai-chat-tool-badge.loading{--tool-loading-bg-1:#2a2a2a;--tool-loading-bg-2:#3a3a3a;--tool-loading-bg-3:#4a4a4a;--tool-loading-text:#fff}.ai-chat-tool-badge.completed{background:#e5e7eb;border:1px solid #d1d5db;color:#1f2937}.ai-chat-widget.dark .ai-chat-tool-badge,.ai-chat-widget[data-theme=dark] .ai-chat-tool-badge,.chakra-ui-dark .ai-chat-tool-badge,.dark .ai-chat-tool-badge,[data-theme=dark] .ai-chat-tool-badge,html.dark .ai-chat-tool-badge{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);color:hsla(0,0%,100%,.9)}.ai-chat-widget.dark .ai-chat-tool-gear,.ai-chat-widget[data-theme=dark] .ai-chat-tool-gear,.chakra-ui-dark .ai-chat-tool-gear,.dark .ai-chat-tool-gear,[data-theme=dark] .ai-chat-tool-gear,html.dark .ai-chat-tool-gear{color:#fff}.ai-chat-tool-badge.error{background:var(--tool-error-bg,rgba(239,68,68,.15));color:var(--tool-error-text,#ef4444)}.ai-chat-tool-badge .ai-chat-tool-check{color:#fff;flex-shrink:0}.ai-chat-tool-badge .ai-chat-tool-error{color:#ef4444;flex-shrink:0}.tool-name{font-weight:500;line-height:1.2;white-space:nowrap}.ai-chat-tool-action{box-sizing:border-box;padding:0;width:100%}@keyframes ai-chat-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.ai-chat-action-skeleton-item{animation:ai-chat-skeleton-pulse 1.5s ease-in-out infinite;background:var(--bg-secondary,#e5e7eb)}.ai-chat-widget.dark .ai-chat-action-skeleton-item,.chakra-ui-dark .ai-chat-action-skeleton-item,.dark .ai-chat-action-skeleton-item,[data-theme=dark] .ai-chat-action-skeleton-item{background:hsla(0,0%,100%,.1)}.ai-chat-action-skeleton-content{display:flex;flex-direction:column;gap:16px}.ai-chat-action-skeleton-header{align-items:center;display:flex;gap:10px}.ai-chat-action-skeleton-box{background:rgba(0,0,0,.08);border-radius:10px;display:flex;flex-direction:column;gap:8px;padding:14px}.ai-chat-widget.dark .ai-chat-action-skeleton-box,.chakra-ui-dark .ai-chat-action-skeleton-box,.dark .ai-chat-action-skeleton-box,[data-theme=dark] .ai-chat-action-skeleton-box{background:rgba(0,0,0,.25)}.ai-chat-action-card{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f9fafb);border:1px solid var(--border-subtle,rgba(0,0,0,.06));border-radius:12px;box-sizing:border-box;margin-top:4px;padding:16px;transition:all .2s ease;width:100%}.ai-chat-widget.dark .ai-chat-action-card,.chakra-ui-dark .ai-chat-action-card,.dark .ai-chat-action-card,[data-theme=dark] .ai-chat-action-card{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1)}.ai-chat-action-booked{background:var(--bg-secondary,#f9fafb);border:1px solid var(--border-subtle,rgba(0,0,0,.06))}.ai-chat-widget.dark .ai-chat-action-booked,.chakra-ui-dark .ai-chat-action-booked,.dark .ai-chat-action-booked,[data-theme=dark] .ai-chat-action-booked{background:var(--bg-secondary,#3a3a3a)}.ai-chat-action-header{align-items:center;color:var(--text-primary,#3e3e3e);display:flex;font-size:var(--text-md,15px);font-weight:var(--font-weight-semibold,600);gap:var(--space-sm,8px);margin-bottom:var(--space-md,16px)}.ai-chat-widget.dark .ai-chat-action-header,.chakra-ui-dark .ai-chat-action-header,.dark .ai-chat-action-header,[data-theme=dark] .ai-chat-action-header{color:var(--text-primary,#fff)}.ai-chat-action-icon{color:var(--action-accent,var(--primary-color,#3b82f6));flex-shrink:0;height:20px;width:20px}.ai-chat-action-success-icon-wrapper{align-items:center;background:var(--action-accent,var(--primary-color,#22c55e));border-radius:50%;color:#fff;display:flex;flex-shrink:0;height:24px;justify-content:center;width:24px}.ai-chat-action-icon-success{color:currentColor;height:14px;width:14px}.ai-chat-action-detail-box{background:var(--bg-primary,#fff);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:var(--radius-md,8px);display:flex;flex-direction:column;gap:4px;padding:12px 16px}.ai-chat-widget.dark .ai-chat-action-detail-box,.chakra-ui-dark .ai-chat-action-detail-box,.dark .ai-chat-action-detail-box,[data-theme=dark] .ai-chat-action-detail-box{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.05)}.ai-chat-action-label-small{color:var(--text-muted,#71717a);font-size:11px;font-weight:600;letter-spacing:.5px;text-transform:uppercase}.ai-chat-action-value-large{color:var(--text-primary,#3e3e3e);font-size:15px;font-weight:500}.ai-chat-widget.dark .ai-chat-action-value-large,.chakra-ui-dark .ai-chat-action-value-large,.dark .ai-chat-action-value-large,[data-theme=dark] .ai-chat-action-value-large{color:#fff}.ai-chat-action-body{display:flex;flex-direction:column;gap:var(--space-md,16px)}.ai-chat-action-close-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--ai-chat-fg-muted,#6b7280);cursor:pointer;display:flex;height:28px;justify-content:center;padding:0;position:absolute;right:12px;top:12px;transition:all .15s ease;width:28px;z-index:10}.ai-chat-action-close-btn:hover{background:var(--ai-chat-bg-muted,#f3f4f6);color:var(--ai-chat-fg,#1f2937)}.ai-chat-action-close-btn:active{transform:scale(.95)}.ai-chat-action-close-btn:focus-visible{outline:2px solid var(--ai-chat-accent,#2563eb);outline-offset:2px}.ai-chat-action-card--closable,.ai-chat-form-card--closable{position:relative}.ai-chat-action-card--closable .ai-chat-action-header,.ai-chat-form-card--closable .ai-chat-form-card__header{padding-right:40px}.ai-chat-widget.dark .ai-chat-action-close-btn:hover,.chakra-ui-dark .ai-chat-action-close-btn:hover,.dark .ai-chat-action-close-btn:hover,[data-theme=dark] .ai-chat-action-close-btn:hover{background:hsla(0,0%,100%,.1);color:var(--ai-chat-fg,#f3f4f6)}.ai-chat-action-field{display:flex;flex-direction:column;gap:var(--space-xs,6px)}.ai-chat-action-label{color:var(--text-secondary,#6b7280);font-size:var(--text-sm,13px);font-weight:var(--font-weight-medium,500)}.ai-chat-widget.dark .ai-chat-action-label,.chakra-ui-dark .ai-chat-action-label,.dark .ai-chat-action-label,[data-theme=dark] .ai-chat-action-label{color:var(--text-secondary,#a1a1aa)}.ai-chat-action-input{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);color:var(--text-primary,#3e3e3e);font-size:var(--text-sm,13px);outline:none;padding:10px 12px;transition:border-color .2s ease,box-shadow .2s ease}.ai-chat-action-input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.1)}.ai-chat-action-input::placeholder{color:var(--text-muted,#9ca3af)}.ai-chat-widget.dark .ai-chat-action-input,.chakra-ui-dark .ai-chat-action-input,.dark .ai-chat-action-input,[data-theme=dark] .ai-chat-action-input{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-action-input:focus,.chakra-ui-dark .ai-chat-action-input:focus,.dark .ai-chat-action-input:focus,[data-theme=dark] .ai-chat-action-input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.2)}.ai-chat-action-button{background:var(--action-accent,var(--primary-color,#3b82f6));border:none;border-radius:9999px;color:#fff;cursor:pointer;font-size:var(--text-sm,13px);font-weight:var(--font-weight-semibold,600);padding:12px 16px;transition:all .2s ease;width:100%}.ai-chat-action-button:hover:not(:disabled){opacity:.9;transform:translateY(-1px)}.ai-chat-action-button:disabled{cursor:not-allowed;opacity:.5}.ai-chat-action-link-button{align-items:center;background:var(--action-accent,var(--primary-color,#3b82f6));border:none;border-radius:9999px;box-sizing:border-box;color:#fff;display:flex;font-size:14px;font-weight:600;gap:6px;justify-content:center;margin-top:8px;padding:12px;text-decoration:none;transition:all .2s ease;width:100%}.ai-chat-action-link-button:hover{opacity:.9;transform:translateY(-1px)}.ai-chat-action-error{background:rgba(239,68,68,.1);border-radius:var(--radius-md,8px);color:#dc2626;font-size:var(--text-sm,13px);padding:10px 12px}.ai-chat-widget.dark .ai-chat-action-error,.chakra-ui-dark .ai-chat-action-error,.dark .ai-chat-action-error,[data-theme=dark] .ai-chat-action-error{background:rgba(239,68,68,.2);color:#fca5a5}.ai-chat-action-hint{color:var(--text-muted,#9ca3af);font-size:var(--text-sm,13px);padding:var(--space-sm,8px);text-align:center}.ai-chat-action-date-grid{display:grid;gap:var(--space-xs,6px);grid-template-columns:repeat(auto-fill,minmax(90px,1fr))}.ai-chat-action-date-btn{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);color:var(--text-primary,#3e3e3e);cursor:pointer;font-size:var(--text-xs,12px);font-weight:var(--font-weight-medium,500);padding:8px 10px;text-align:center;transition:all .15s ease}.ai-chat-action-date-btn:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-action-date-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-widget.dark .ai-chat-action-date-btn,.chakra-ui-dark .ai-chat-action-date-btn,.dark .ai-chat-action-date-btn,[data-theme=dark] .ai-chat-action-date-btn{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-action-date-btn:hover,.chakra-ui-dark .ai-chat-action-date-btn:hover,.dark .ai-chat-action-date-btn:hover,[data-theme=dark] .ai-chat-action-date-btn:hover{background:rgba(59,130,246,.15);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-action-date-btn.active,.chakra-ui-dark .ai-chat-action-date-btn.active,.dark .ai-chat-action-date-btn.active,[data-theme=dark] .ai-chat-action-date-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-action-time-grid{display:grid;gap:var(--space-xs,6px);grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.ai-chat-action-time-btn{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);color:var(--text-primary,#3e3e3e);cursor:pointer;font-size:var(--text-xs,12px);font-weight:var(--font-weight-medium,500);padding:8px 10px;text-align:center;transition:all .15s ease}.ai-chat-action-time-btn:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-action-time-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-widget.dark .ai-chat-action-time-btn,.chakra-ui-dark .ai-chat-action-time-btn,.dark .ai-chat-action-time-btn,[data-theme=dark] .ai-chat-action-time-btn{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-action-time-btn:hover,.chakra-ui-dark .ai-chat-action-time-btn:hover,.dark .ai-chat-action-time-btn:hover,[data-theme=dark] .ai-chat-action-time-btn:hover{background:rgba(59,130,246,.15);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-action-time-btn.active,.chakra-ui-dark .ai-chat-action-time-btn.active,.dark .ai-chat-action-time-btn.active,[data-theme=dark] .ai-chat-action-time-btn.active{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-link-preview{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f4f4f4);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:var(--radius-lg,12px);cursor:pointer;display:flex;flex-direction:column;margin-top:4px;overflow:hidden;padding:0!important;position:relative;transition:border-color .2s,box-shadow .2s,transform .2s}.ai-chat-link-preview:hover{border-color:var(--action-accent);box-shadow:0 2px 8px rgba(0,0,0,.1);transform:translateY(-1px)}.ai-chat-link-preview:focus{border-color:var(--action-accent);box-shadow:0 0 0 2px rgba(59,130,246,.2);outline:none}.ai-chat-widget.dark .ai-chat-link-preview,.chakra-ui-dark .ai-chat-link-preview,.dark .ai-chat-link-preview,[data-theme=dark] .ai-chat-link-preview{background:var(--bg-secondary,#3a3a3a);border-color:hsla(0,0%,100%,.08)}.ai-chat-widget.dark .ai-chat-link-preview:hover,.chakra-ui-dark .ai-chat-link-preview:hover,.dark .ai-chat-link-preview:hover,[data-theme=dark] .ai-chat-link-preview:hover{border-color:var(--action-accent);box-shadow:0 2px 12px rgba(0,0,0,.3)}.ai-chat-link-preview__image{aspect-ratio:1.91/1;background:var(--bg-muted,#e5e7eb);overflow:hidden;width:100%}.ai-chat-widget.dark .ai-chat-link-preview__image,.chakra-ui-dark .ai-chat-link-preview__image,.dark .ai-chat-link-preview__image,[data-theme=dark] .ai-chat-link-preview__image{background:hsla(0,0%,100%,.05)}.ai-chat-link-preview__image img{height:100%;object-fit:cover;width:100%}.ai-chat-link-preview__content{flex:1;padding:8px 10px}.ai-chat-link-preview__site{align-items:center;display:flex;gap:6px;margin-bottom:6px}.ai-chat-link-preview__favicon{border-radius:2px;flex-shrink:0;height:16px;width:16px}.ai-chat-link-preview__domain{color:var(--text-muted,#71717a);font-size:12px;letter-spacing:.5px;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}.ai-chat-link-preview__title{-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;color:var(--text-primary,#3e3e3e);display:-webkit-box;font-size:15px;font-weight:600;line-height:1.3;margin:0 0 4px;overflow:hidden}.ai-chat-widget.dark .ai-chat-link-preview__title,.chakra-ui-dark .ai-chat-link-preview__title,.dark .ai-chat-link-preview__title,[data-theme=dark] .ai-chat-link-preview__title{color:#fff}.ai-chat-link-preview__description{-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;color:var(--text-muted,#71717a);display:-webkit-box;font-size:13px;line-height:1.4;margin:0;overflow:hidden}.ai-chat-link-preview__context{border-top:1px solid var(--border-subtle,rgba(0,0,0,.08));color:var(--text-muted,#71717a);font-size:12px;font-style:italic;margin:8px 0 0;padding-top:8px}.ai-chat-widget.dark .ai-chat-link-preview__context,.chakra-ui-dark .ai-chat-link-preview__context,.dark .ai-chat-link-preview__context,[data-theme=dark] .ai-chat-link-preview__context{border-color:hsla(0,0%,100%,.08)}.ai-chat-link-preview__arrow{align-items:center;background:var(--bg-primary,#fff);border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.1);color:var(--text-muted,#71717a);display:flex;height:28px;justify-content:center;opacity:0;position:absolute;right:12px;top:12px;transition:opacity .2s,background .2s;width:28px}.ai-chat-link-preview:hover .ai-chat-link-preview__arrow{opacity:1}.ai-chat-widget.dark .ai-chat-link-preview__arrow,.chakra-ui-dark .ai-chat-link-preview__arrow,.dark .ai-chat-link-preview__arrow,[data-theme=dark] .ai-chat-link-preview__arrow{background:hsla(0,0%,100%,.1);color:#fff}.ai-chat-link-preview--error{border-color:rgba(239,68,68,.3)}.ai-chat-link-preview--error:hover{border-color:rgba(239,68,68,.5)}.ai-chat-link-preview__error-text{color:#dc2626;font-size:12px;margin:4px 0 0}.ai-chat-widget.dark .ai-chat-link-preview__error-text,.chakra-ui-dark .ai-chat-link-preview__error-text,.dark .ai-chat-link-preview__error-text,[data-theme=dark] .ai-chat-link-preview__error-text{color:#fca5a5}.ai-chat-video-player{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f4f4f4);border-radius:var(--radius-lg,12px);display:flex;flex-direction:column;gap:0;margin-top:4px;overflow:hidden;padding:0!important}.ai-chat-widget.dark .ai-chat-video-player,.chakra-ui-dark .ai-chat-video-player,.dark .ai-chat-video-player,[data-theme=dark] .ai-chat-video-player{background:var(--bg-secondary,#3a3a3a)}.ai-chat-video-player__header{align-items:center;color:var(--action-accent,var(--primary-color,#3b82f6));display:flex;gap:8px}.ai-chat-video-player__title{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:600}.ai-chat-widget.dark .ai-chat-video-player__title,.chakra-ui-dark .ai-chat-video-player__title,.dark .ai-chat-video-player__title,[data-theme=dark] .ai-chat-video-player__title{color:#fff}.ai-chat-video-player__container{aspect-ratio:16/9;background:#000;border-radius:8px;overflow:hidden;position:relative;width:100%}.ai-chat-video-player__thumbnail{cursor:pointer;height:100%;position:relative;width:100%}.ai-chat-video-player__thumbnail img{height:100%;object-fit:cover;width:100%}.ai-chat-video-player__placeholder{align-items:center;background:linear-gradient(135deg,#1a1a2e,#16213e);cursor:pointer;display:flex;flex-direction:column;gap:8px;height:100%;justify-content:center;position:relative;width:100%}.ai-chat-video-player__click-text{color:hsla(0,0%,100%,.7);font-size:13px}.ai-chat-video-player__play-btn{align-items:center;background:rgba(0,0,0,.7);border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;height:64px;justify-content:center;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);transition:background .2s,transform .2s;width:64px}.ai-chat-video-player__placeholder .ai-chat-video-player__play-btn{left:auto;position:relative;top:auto;transform:none}.ai-chat-video-player__play-btn:hover{background:rgba(0,0,0,.9);transform:translate(-50%,-50%) scale(1.05)}.ai-chat-video-player__placeholder .ai-chat-video-player__play-btn:hover{transform:scale(1.05)}.ai-chat-video-player__provider-badge{background:rgba(0,0,0,.8);border-radius:4px;bottom:8px;color:#fff;font-size:11px;font-weight:600;letter-spacing:.5px;padding:4px 8px;position:absolute;right:8px;text-transform:uppercase}.ai-chat-video-player__iframe,.ai-chat-video-player__video{border:none;height:100%;left:0;position:absolute;top:0;width:100%}.ai-chat-video-player__error{align-items:center;background:rgba(239,68,68,.1);color:#dc2626;display:flex;font-size:13px;height:100%;justify-content:center;padding:16px;text-align:center;width:100%}.ai-chat-widget.dark .ai-chat-video-player__error,.chakra-ui-dark .ai-chat-video-player__error,.dark .ai-chat-video-player__error,[data-theme=dark] .ai-chat-video-player__error{background:rgba(239,68,68,.2);color:#fca5a5}.ai-chat-video-player__context{color:var(--text-muted,#71717a);font-size:12px;font-style:italic;margin-top:4px;padding:8px 12px 12px}.ai-chat-location-card{background:var(--bg-secondary,#fff);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:12px;overflow:hidden;padding:0}.ai-chat-widget.dark .ai-chat-location-card,.chakra-ui-dark .ai-chat-location-card,.dark .ai-chat-location-card,[data-theme=dark] .ai-chat-location-card{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1)}.ai-chat-location-card--compact{border-radius:10px}.ai-chat-location-card--error{color:var(--text-muted,#71717a);padding:16px;text-align:center}.ai-chat-location-card__map{background:var(--bg-muted,#f4f4f5);position:relative;width:100%}.ai-chat-widget.dark .ai-chat-location-card__map,.chakra-ui-dark .ai-chat-location-card__map,.dark .ai-chat-location-card__map,[data-theme=dark] .ai-chat-location-card__map{background:rgba(0,0,0,.2)}.ai-chat-location-card__map iframe{border:none;display:block;height:100%;width:100%}.ai-chat-location-card__content{padding:12px}.ai-chat-location-card--compact .ai-chat-location-card__content{padding:10px}.ai-chat-location-card__header{align-items:center;display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}.ai-chat-location-card__name{color:var(--text-primary,#18181b);flex:1;font-size:16px;font-weight:600;margin:0;min-width:0}.ai-chat-widget.dark .ai-chat-location-card__name,.chakra-ui-dark .ai-chat-location-card__name,.dark .ai-chat-location-card__name,[data-theme=dark] .ai-chat-location-card__name{color:#fff}.ai-chat-location-card--compact .ai-chat-location-card__name{font-size:14px}.ai-chat-location-card__type{background:var(--bg-muted,#f4f4f5);border-radius:10px;color:var(--text-muted,#71717a);font-size:11px;font-weight:500;letter-spacing:.5px;padding:2px 8px;text-transform:uppercase}.ai-chat-widget.dark .ai-chat-location-card__type,.chakra-ui-dark .ai-chat-location-card__type,.dark .ai-chat-location-card__type,[data-theme=dark] .ai-chat-location-card__type{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.7)}.ai-chat-location-card__status{border-radius:12px;font-size:12px;font-weight:500;padding:2px 8px}.ai-chat-location-card__status--open{background:#dcfce7;color:#16a34a}.ai-chat-location-card__status--closed{background:#fef2f2;color:#dc2626}.ai-chat-widget.dark .ai-chat-location-card__status--open,.chakra-ui-dark .ai-chat-location-card__status--open,.dark .ai-chat-location-card__status--open,[data-theme=dark] .ai-chat-location-card__status--open{background:rgba(34,197,94,.2);color:#4ade80}.ai-chat-widget.dark .ai-chat-location-card__status--closed,.chakra-ui-dark .ai-chat-location-card__status--closed,.dark .ai-chat-location-card__status--closed,[data-theme=dark] .ai-chat-location-card__status--closed{background:rgba(239,68,68,.2);color:#fca5a5}.ai-chat-location-card__address{align-items:flex-start;color:var(--text-muted,#71717a);display:flex;font-size:13px;gap:6px;line-height:1.4;margin:0 0 8px}.ai-chat-location-card__address svg{flex-shrink:0;margin-top:2px}.ai-chat-location-card__description{color:var(--text-muted,#71717a);font-size:13px;line-height:1.4;margin:0 0 8px}.ai-chat-location-card__hours{align-items:flex-start;color:var(--text-muted,#71717a);display:flex;font-size:13px;gap:6px;margin-bottom:8px}.ai-chat-location-card__hours svg{flex-shrink:0;margin-top:2px}.ai-chat-location-card__hours-list{flex:1}.ai-chat-location-card__hours-toggle{align-items:center;background:none;border:none;color:inherit;cursor:pointer;display:flex;font:inherit;gap:4px;padding:0}.ai-chat-location-card__hours-toggle:hover{text-decoration:underline}.ai-chat-location-card__hours-full{list-style:none;margin:8px 0 0;padding:0}.ai-chat-location-card__hours-full li{display:flex;font-size:12px;justify-content:space-between;padding:4px 0}.ai-chat-location-card__hours-today{color:var(--text-primary,#18181b);font-weight:600}.ai-chat-widget.dark .ai-chat-location-card__hours-today,.chakra-ui-dark .ai-chat-location-card__hours-today,.dark .ai-chat-location-card__hours-today,[data-theme=dark] .ai-chat-location-card__hours-today{color:#fff}.ai-chat-location-card__phone{align-items:center;background:none;border:none;color:var(--action-accent,#3b82f6);cursor:pointer;display:flex;font-size:13px;gap:6px;margin-bottom:12px;padding:0}.ai-chat-location-card__phone:hover{text-decoration:underline}.ai-chat-location-card__actions{display:flex;gap:8px;justify-content:flex-start;width:100%}.ai-chat-location-card__button{align-items:center;background:var(--action-accent,#3b82f6);border:none;border-radius:20px;color:#fff;cursor:pointer;display:flex;flex:1;font-size:13px;font-weight:500;gap:6px;justify-content:center;padding:10px 16px;transition:opacity .2s}.ai-chat-location-card__button:hover{opacity:.9}.ai-chat-location-card--compact .ai-chat-location-card__button{font-size:12px;padding:8px 12px}.ai-chat-location-card__link{align-items:center;background:var(--bg-secondary,#fff);border:1px solid var(--border-subtle,rgba(0,0,0,.08));border-radius:20px;color:var(--text-primary,#18181b);display:flex;font-size:13px;gap:6px;padding:10px 16px;text-decoration:none;transition:border-color .2s}.ai-chat-widget.dark .ai-chat-location-card__link,.chakra-ui-dark .ai-chat-location-card__link,.dark .ai-chat-location-card__link,[data-theme=dark] .ai-chat-location-card__link{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-location-card__link:hover{border-color:var(--action-accent,#3b82f6)}.ai-chat-location-card-list{display:flex;flex-direction:column;gap:8px}.ai-chat-location-card-list__header{align-items:center;color:var(--text-muted,#71717a);display:flex;font-size:13px;font-weight:500;gap:6px;margin-bottom:4px;padding:0 4px}.ai-chat-location-card-list__stack{display:grid;gap:12px;grid-template-columns:1fr}.ai-chat-location-card-list__stack--cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.ai-chat-location-card-list__stack--cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}@media (max-width:1000px){.ai-chat-location-card-list__stack--cols-3{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width:640px){.ai-chat-location-card-list__stack--cols-2,.ai-chat-location-card-list__stack--cols-3{grid-template-columns:1fr}}.ai-chat-location-card-list__grid{display:grid;gap:8px;grid-template-columns:repeat(2,1fr)}@media (max-width:400px){.ai-chat-location-card-list__grid{grid-template-columns:1fr}}.ai-chat-location-card-list__carousel{-webkit-overflow-scrolling:touch;-ms-overflow-style:none;display:flex;gap:8px;overflow-x:auto;padding-bottom:4px;scroll-snap-type:x mandatory;scrollbar-width:none}.ai-chat-location-card-list__carousel::-webkit-scrollbar{display:none}.ai-chat-location-card-list__carousel>.ai-chat-location-card{flex:0 0 280px;scroll-snap-align:start}.ai-chat-contact-card{background:#fff;border:1px solid rgba(0,0,0,.08);border-radius:16px;overflow:hidden;padding:0;position:relative}.ai-chat-widget.dark .ai-chat-contact-card,.ai-chat-widget[data-theme=dark] .ai-chat-contact-card,.chakra-ui-dark .ai-chat-contact-card,.dark .ai-chat-contact-card,[data-theme=dark] .ai-chat-contact-card,html.dark .ai-chat-contact-card{background:#4a4a4a;border-color:hsla(0,0%,100%,.08)}.ai-chat-contact-card-list{gap:12px;width:100%}.ai-chat-contact-card--compact{border-radius:12px}.ai-chat-contact-card--empty{align-items:center;background:var(--bg-secondary,#f4f4f5);display:flex;flex-direction:column;gap:8px;justify-content:center;padding:24px 16px;text-align:center}.ai-chat-widget.dark .ai-chat-contact-card--empty,.chakra-ui-dark .ai-chat-contact-card--empty,.dark .ai-chat-contact-card--empty,[data-theme=dark] .ai-chat-contact-card--empty{background:#3a3a3a}.ai-chat-contact-card__empty-icon{color:var(--text-muted,#71717a);opacity:.6}.ai-chat-contact-card__empty-text{color:var(--text-muted,#71717a);font-size:14px;margin:0}.ai-chat-contact-card--vertical{display:flex;flex-direction:column}.ai-chat-contact-card--vertical .ai-chat-contact-card__image-section{aspect-ratio:3/2;overflow:hidden;width:100%}.ai-chat-contact-card--vertical .ai-chat-contact-card__image{height:100%;object-fit:cover;width:100%}.ai-chat-contact-card--vertical .ai-chat-contact-card__image-placeholder{align-items:center;background:linear-gradient(135deg,#5a5a5a,#3a3a3a);color:hsla(0,0%,100%,.5);display:flex;height:100%;justify-content:center;width:100%}.ai-chat-contact-card--vertical .ai-chat-contact-card__image-placeholder svg{height:48px;width:48px}.ai-chat-contact-card--vertical .ai-chat-contact-card__info{padding:16px;text-align:center}.ai-chat-contact-card--horizontal{display:flex;flex-direction:row}.ai-chat-contact-card--horizontal .ai-chat-contact-card__image-section{height:160px;min-width:140px;overflow:hidden;width:140px}.ai-chat-contact-card--horizontal.ai-chat-contact-card--compact .ai-chat-contact-card__image-section{height:120px;min-width:100px;width:100px}.ai-chat-contact-card--horizontal .ai-chat-contact-card__image{height:100%;object-fit:cover;width:100%}.ai-chat-contact-card--horizontal .ai-chat-contact-card__image-placeholder{align-items:center;background:linear-gradient(135deg,#5a5a5a,#3a3a3a);color:hsla(0,0%,100%,.5);display:flex;height:100%;justify-content:center;width:100%}.ai-chat-contact-card--horizontal .ai-chat-contact-card__image-placeholder svg{height:36px;width:36px}.ai-chat-contact-card--horizontal .ai-chat-contact-card__info{display:flex;flex:1;flex-direction:column;justify-content:center;padding:16px}.ai-chat-contact-card__name{color:var(--action-accent,#ef4444);font-size:18px;font-weight:600;line-height:1.3;margin:0}.ai-chat-contact-card--compact .ai-chat-contact-card__name{font-size:15px}.ai-chat-contact-card__role{color:rgba(0,0,0,.7);font-size:14px;font-weight:400;margin:2px 0 0}.ai-chat-widget.dark .ai-chat-contact-card__role,.ai-chat-widget[data-theme=dark] .ai-chat-contact-card__role,.chakra-ui-dark .ai-chat-contact-card__role,.dark .ai-chat-contact-card__role,[data-theme=dark] .ai-chat-contact-card__role,html.dark .ai-chat-contact-card__role{color:hsla(0,0%,100%,.9)}.ai-chat-contact-card--compact .ai-chat-contact-card__role{font-size:13px}.ai-chat-contact-card__details{display:flex;flex-direction:column;gap:2px;margin-top:12px}.ai-chat-contact-card__detail{color:rgba(0,0,0,.6);display:block;font-size:14px;line-height:1.5;margin:0;text-decoration:none}.ai-chat-contact-card__detail:hover{color:#000;text-decoration:underline}.ai-chat-widget.dark .ai-chat-contact-card__detail,.ai-chat-widget[data-theme=dark] .ai-chat-contact-card__detail,.chakra-ui-dark .ai-chat-contact-card__detail,.dark .ai-chat-contact-card__detail,[data-theme=dark] .ai-chat-contact-card__detail,html.dark .ai-chat-contact-card__detail{color:hsla(0,0%,100%,.7)}.ai-chat-widget.dark .ai-chat-contact-card__detail:hover,.ai-chat-widget[data-theme=dark] .ai-chat-contact-card__detail:hover,.chakra-ui-dark .ai-chat-contact-card__detail:hover,.dark .ai-chat-contact-card__detail:hover,[data-theme=dark] .ai-chat-contact-card__detail:hover,html.dark .ai-chat-contact-card__detail:hover{color:#fff}.ai-chat-contact-card--compact .ai-chat-contact-card__detail{font-size:13px}.ai-chat-contact-card__responsibilities{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.ai-chat-contact-card__responsibility-tag{background:rgba(0,0,0,.08);border-radius:10px;color:rgba(0,0,0,.8);font-size:11px;font-weight:500;padding:3px 10px}.ai-chat-widget.dark .ai-chat-contact-card__responsibility-tag,.ai-chat-widget[data-theme=dark] .ai-chat-contact-card__responsibility-tag,.chakra-ui-dark .ai-chat-contact-card__responsibility-tag,.dark .ai-chat-contact-card__responsibility-tag,[data-theme=dark] .ai-chat-contact-card__responsibility-tag,html.dark .ai-chat-contact-card__responsibility-tag{background:hsla(0,0%,100%,.15);color:hsla(0,0%,100%,.9)}.ai-chat-contact-card__responsibility-more{color:rgba(0,0,0,.5);font-size:11px;padding:3px 4px}.ai-chat-widget.dark .ai-chat-contact-card__responsibility-more,.ai-chat-widget[data-theme=dark] .ai-chat-contact-card__responsibility-more,.chakra-ui-dark .ai-chat-contact-card__responsibility-more,.dark .ai-chat-contact-card__responsibility-more,[data-theme=dark] .ai-chat-contact-card__responsibility-more,html.dark .ai-chat-contact-card__responsibility-more{color:hsla(0,0%,100%,.5)}.ai-chat-contact-card__actions{display:flex;gap:8px;padding:0 12px 12px}.ai-chat-contact-card--compact .ai-chat-contact-card__actions{gap:6px;padding:0 10px 10px}.ai-chat-contact-card__button{align-items:center;border:none;border-radius:9999px;cursor:pointer;display:flex;font-size:14px;font-weight:600;gap:8px;justify-content:center;padding:12px 20px;transition:all .15s ease;white-space:nowrap}.ai-chat-contact-card--compact .ai-chat-contact-card__button{font-size:13px;padding:10px 16px}.ai-chat-contact-card__button:hover{box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.ai-chat-contact-card__button:active{transform:translateY(0)}.ai-chat-contact-card__button--primary{background:var(--action-accent,#3b82f6);color:#fff;flex:1}.ai-chat-contact-card__button--primary:hover{background:color-mix(in srgb,var(--action-accent,#3b82f6) 90%,#000)}.ai-chat-contact-card__button--secondary{background:var(--bg-muted,#f4f4f5);border:1px solid var(--border-subtle,rgba(0,0,0,.08));color:var(--text-primary,#18181b);flex:1}.ai-chat-contact-card__button--secondary:hover{background:var(--bg-hover,#e4e4e7)}.ai-chat-widget.dark .ai-chat-contact-card__button--secondary,.chakra-ui-dark .ai-chat-contact-card__button--secondary,.dark .ai-chat-contact-card__button--secondary,[data-theme=dark] .ai-chat-contact-card__button--secondary{background:hsla(0,0%,100%,.1);border-color:hsla(0,0%,100%,.15);color:#fff}.ai-chat-widget.dark .ai-chat-contact-card__button--secondary:hover,.chakra-ui-dark .ai-chat-contact-card__button--secondary:hover,.dark .ai-chat-contact-card__button--secondary:hover,[data-theme=dark] .ai-chat-contact-card__button--secondary:hover{background:hsla(0,0%,100%,.15)}.ai-chat-contact-card-list{display:flex;flex-direction:column;gap:8px}.ai-chat-contact-card-list__header{align-items:center;color:var(--text-muted,#71717a);display:flex;font-size:13px;font-weight:500;gap:6px;margin-bottom:2px;margin-top:8px;padding:0 4px}.ai-chat-contact-card-list__stack{display:grid;gap:12px;grid-template-columns:repeat(3,minmax(0,1fr))}@media (max-width:900px){.ai-chat-contact-card-list__stack{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width:600px){.ai-chat-contact-card-list__stack{grid-template-columns:1fr}}.ai-chat-contact-card-list__stack--widget{grid-template-columns:1fr}@container (min-width: 380px){.ai-chat-contact-card-list__stack--widget{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width:520px){.ai-chat-contact-card-list__stack{grid-template-columns:1fr!important}.ai-chat-contact-card--horizontal{flex-direction:column}.ai-chat-contact-card--horizontal .ai-chat-contact-card__image-section{aspect-ratio:3/2;height:auto;min-width:100%;width:100%}}.ai-chat-contact-card__initials{align-items:center;display:flex;font-size:48px;font-weight:600;height:100%;justify-content:center;letter-spacing:.05em;text-transform:uppercase;width:100%}.ai-chat-contact-card--compact .ai-chat-contact-card__initials{font-size:32px}.ai-chat-contact-card--horizontal .ai-chat-contact-card__initials{font-size:28px}.ai-chat-contact-card--horizontal.ai-chat-contact-card--compact .ai-chat-contact-card__initials{font-size:22px}.ai-chat-form-card{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f9fafb);border:1px solid var(--border-subtle,rgba(0,0,0,.06));border-radius:12px;box-sizing:border-box;margin:6px 0;overflow:hidden;padding:16px;width:100%}.ai-chat-widget.dark .ai-chat-form-card,.chakra-ui-dark .ai-chat-form-card,.dark .ai-chat-form-card,[data-theme=dark] .ai-chat-form-card{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1)}.ai-chat-form-card--empty,.ai-chat-form-card--error,.ai-chat-form-card--skipped,.ai-chat-form-card--submitted{padding:12px 16px}.ai-chat-form-card__header{align-items:center;display:flex;gap:8px;margin-bottom:12px}.ai-chat-form-card__icon{font-size:18px}.ai-chat-form-card__title{color:var(--text-primary,#3e3e3e);font-size:15px;font-weight:600}.ai-chat-widget.dark .ai-chat-form-card__title,.chakra-ui-dark .ai-chat-form-card__title,.dark .ai-chat-form-card__title,[data-theme=dark] .ai-chat-form-card__title{color:#fff}.ai-chat-form-card__description{color:var(--text-muted,#71717a);font-size:13px;line-height:1.4;margin:0 0 12px}.ai-chat-form-card__context{color:var(--text-muted,#71717a);font-size:12px;font-style:italic;margin:0 0 12px}.ai-chat-form-card__error{color:#dc2626;font-size:13px;margin:0}.ai-chat-widget.dark .ai-chat-form-card__error,.chakra-ui-dark .ai-chat-form-card__error,.dark .ai-chat-form-card__error,[data-theme=dark] .ai-chat-form-card__error{color:#fca5a5}.ai-chat-form-card__success{color:#16a34a;font-size:13px;margin:0}.ai-chat-widget.dark .ai-chat-form-card__success,.chakra-ui-dark .ai-chat-form-card__success,.dark .ai-chat-form-card__success,[data-theme=dark] .ai-chat-form-card__success{color:#4ade80}.ai-chat-form-card__empty-text,.ai-chat-form-card__skipped-text{color:var(--text-muted,#71717a);font-size:13px;margin:0}.ai-chat-form-card__progress{align-items:center;display:flex;gap:12px;margin-bottom:16px}.ai-chat-form-card__progress-bar{background:var(--action-accent,var(--primary-color,#3b82f6));border-radius:2px;flex:1;height:4px;transition:width .3s ease}.ai-chat-form-card__progress-text{color:var(--text-muted,#71717a);font-size:12px;white-space:nowrap}.ai-chat-form-card__question{margin-bottom:16px}.ai-chat-form-card__question-text{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:500;line-height:1.4;margin:0 0 12px}.ai-chat-widget.dark .ai-chat-form-card__question-text,.chakra-ui-dark .ai-chat-form-card__question-text,.dark .ai-chat-form-card__question-text,[data-theme=dark] .ai-chat-form-card__question-text{color:#fff}.ai-chat-form-card__required{color:#dc2626;margin-left:2px}.ai-chat-form-card__answer{margin-top:8px}.ai-chat-form-card__textarea{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);box-sizing:border-box;color:var(--text-primary,#3e3e3e);font-family:inherit;font-size:14px;min-height:80px;outline:none;padding:10px 12px;resize:vertical;transition:border-color .2s ease,box-shadow .2s ease;width:100%}.ai-chat-form-card__textarea:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.1)}.ai-chat-form-card__textarea::placeholder{color:var(--text-muted,#9ca3af)}.ai-chat-widget.dark .ai-chat-form-card__textarea,.chakra-ui-dark .ai-chat-form-card__textarea,.dark .ai-chat-form-card__textarea,[data-theme=dark] .ai-chat-form-card__textarea{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-form-card__textarea:focus,.chakra-ui-dark .ai-chat-form-card__textarea:focus,.dark .ai-chat-form-card__textarea:focus,[data-theme=dark] .ai-chat-form-card__textarea:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.2)}.ai-chat-form-card__options{display:flex;flex-direction:column;gap:8px}.ai-chat-form-card__option{align-items:center;background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);cursor:pointer;display:flex;gap:10px;padding:10px 12px;transition:border-color .15s,background .15s}.ai-chat-form-card__option:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-form-card__option,.chakra-ui-dark .ai-chat-form-card__option,.dark .ai-chat-form-card__option,[data-theme=dark] .ai-chat-form-card__option{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1)}.ai-chat-widget.dark .ai-chat-form-card__option:hover,.chakra-ui-dark .ai-chat-form-card__option:hover,.dark .ai-chat-form-card__option:hover,[data-theme=dark] .ai-chat-form-card__option:hover{background:rgba(59,130,246,.15);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-form-card__option input{accent-color:var(--action-accent,var(--primary-color,#3b82f6));margin:0}.ai-chat-form-card__option-text{color:var(--text-primary,#3e3e3e);font-size:14px}.ai-chat-widget.dark .ai-chat-form-card__option-text,.chakra-ui-dark .ai-chat-form-card__option-text,.dark .ai-chat-form-card__option-text,[data-theme=dark] .ai-chat-form-card__option-text{color:#fff}.ai-chat-form-card__rating{display:flex;flex-wrap:wrap;gap:8px}.ai-chat-form-card__rating-btn{align-items:center;background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:var(--radius-md,8px);color:var(--text-primary,#3e3e3e);cursor:pointer;display:flex;font-size:14px;font-weight:500;height:40px;justify-content:center;transition:all .15s ease;width:40px}.ai-chat-form-card__rating-btn--selected,.ai-chat-form-card__rating-btn:hover{border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-form-card__rating-btn--selected{background:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-widget.dark .ai-chat-form-card__rating-btn,.chakra-ui-dark .ai-chat-form-card__rating-btn,.dark .ai-chat-form-card__rating-btn,[data-theme=dark] .ai-chat-form-card__rating-btn{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-form-card__rating-btn:hover,.chakra-ui-dark .ai-chat-form-card__rating-btn:hover,.dark .ai-chat-form-card__rating-btn:hover,[data-theme=dark] .ai-chat-form-card__rating-btn:hover{border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-form-card__rating-btn--selected,.chakra-ui-dark .ai-chat-form-card__rating-btn--selected,.dark .ai-chat-form-card__rating-btn--selected,[data-theme=dark] .ai-chat-form-card__rating-btn--selected{background:var(--action-accent,var(--primary-color,#3b82f6));border-color:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-form-card__actions{align-items:center;border-top:1px solid var(--border-subtle,rgba(0,0,0,.08));display:flex;gap:8px;margin-top:16px;padding-top:16px}.ai-chat-widget.dark .ai-chat-form-card__actions,.chakra-ui-dark .ai-chat-form-card__actions,.dark .ai-chat-form-card__actions,[data-theme=dark] .ai-chat-form-card__actions{border-color:hsla(0,0%,100%,.08)}.ai-chat-form-card__actions-spacer{flex:1}.ai-chat-form-card__btn{border:none;border-radius:9999px;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;padding:8px 16px;transition:all .2s ease}.ai-chat-form-card__btn:disabled{cursor:not-allowed;opacity:.5}.ai-chat-form-card__btn--primary{background:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-form-card__btn--primary:hover:not(:disabled){opacity:.9;transform:translateY(-1px)}.ai-chat-form-card__btn--secondary{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);color:var(--text-primary,#3e3e3e)}.ai-chat-form-card__btn--secondary:hover:not(:disabled){border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-form-card__btn--secondary,.chakra-ui-dark .ai-chat-form-card__btn--secondary,.dark .ai-chat-form-card__btn--secondary,[data-theme=dark] .ai-chat-form-card__btn--secondary{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-form-card__btn--ghost{background:transparent;color:var(--text-muted,#71717a)}.ai-chat-form-card__btn--ghost:hover:not(:disabled){background:rgba(0,0,0,.05);color:var(--text-primary,#3e3e3e)}.ai-chat-widget.dark .ai-chat-form-card__btn--ghost:hover:not(:disabled),.chakra-ui-dark .ai-chat-form-card__btn--ghost:hover:not(:disabled),.dark .ai-chat-form-card__btn--ghost:hover:not(:disabled),[data-theme=dark] .ai-chat-form-card__btn--ghost:hover:not(:disabled){background:hsla(0,0%,100%,.05);color:#fff}.ai-chat-booking-card{--action-accent:var(--primary-color,#3b82f6);background:var(--bg-secondary,#f9fafb);border:1px solid var(--border-subtle,rgba(0,0,0,.06));border-radius:12px;box-sizing:border-box;margin:6px 0;padding:16px;width:100%}.ai-chat-widget.dark .ai-chat-booking-card,.chakra-ui-dark .ai-chat-booking-card,.dark .ai-chat-booking-card,[data-theme=dark] .ai-chat-booking-card{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1)}.ai-chat-booking-card__header{align-items:center;display:flex;gap:8px;margin-bottom:12px}.ai-chat-booking-card__icon{flex-shrink:0;font-size:18px}.ai-chat-booking-card__title{color:var(--text-primary,#3e3e3e);font-size:15px;font-weight:600}.ai-chat-widget.dark .ai-chat-booking-card__title,.chakra-ui-dark .ai-chat-booking-card__title,.dark .ai-chat-booking-card__title,[data-theme=dark] .ai-chat-booking-card__title{color:#fff}.ai-chat-booking-card__content{display:flex;flex-direction:column;gap:12px}.ai-chat-booking-card__description{color:var(--text-muted,#71717a);font-size:13px;line-height:1.4;margin:0}.ai-chat-booking-card__empty{color:var(--text-muted,#71717a);font-size:13px;padding:16px;text-align:center}.ai-chat-booking-card__input{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:8px;box-sizing:border-box;color:var(--text-primary,#3e3e3e);font-family:inherit;font-size:14px;outline:none;padding:10px 12px;transition:border-color .2s ease,box-shadow .2s ease;width:100%}.ai-chat-booking-card__input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.1)}.ai-chat-booking-card__input::placeholder{color:var(--text-muted,#9ca3af)}.ai-chat-widget.dark .ai-chat-booking-card__input,.chakra-ui-dark .ai-chat-booking-card__input,.dark .ai-chat-booking-card__input,[data-theme=dark] .ai-chat-booking-card__input{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-booking-card__input:focus,.chakra-ui-dark .ai-chat-booking-card__input:focus,.dark .ai-chat-booking-card__input:focus,[data-theme=dark] .ai-chat-booking-card__input:focus{border-color:var(--action-accent,var(--primary-color,#3b82f6));box-shadow:0 0 0 3px rgba(59,130,246,.2)}.ai-chat-booking-card__label{color:var(--text-secondary,#6b7280);display:block;font-size:13px;font-weight:500;margin-bottom:4px}.ai-chat-widget.dark .ai-chat-booking-card__label,.chakra-ui-dark .ai-chat-booking-card__label,.dark .ai-chat-booking-card__label,[data-theme=dark] .ai-chat-booking-card__label{color:var(--text-secondary,#a1a1aa)}.ai-chat-booking-card__btn{border:none;border-radius:9999px;box-sizing:border-box;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;padding:10px 16px;transition:all .2s ease;width:100%}.ai-chat-booking-card__btn:disabled{cursor:not-allowed;opacity:.5}.ai-chat-booking-card__btn--primary{background:var(--action-accent,var(--primary-color,#3b82f6));color:#fff}.ai-chat-booking-card__btn--primary:hover:not(:disabled){opacity:.9;transform:translateY(-1px)}.ai-chat-booking-card__btn--secondary{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);color:var(--text-primary,#3e3e3e)}.ai-chat-booking-card__btn--secondary:hover:not(:disabled){border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-booking-card__btn--secondary,.chakra-ui-dark .ai-chat-booking-card__btn--secondary,.dark .ai-chat-booking-card__btn--secondary,[data-theme=dark] .ai-chat-booking-card__btn--secondary{background:hsla(0,0%,100%,.05);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-booking-card__btn--danger{background:rgba(220,38,38,.1);border:1px solid rgba(220,38,38,.2);color:#dc2626}.ai-chat-booking-card__btn--danger:hover:not(:disabled){background:rgba(220,38,38,.15)}.ai-chat-widget.dark .ai-chat-booking-card__btn--danger,.chakra-ui-dark .ai-chat-booking-card__btn--danger,.dark .ai-chat-booking-card__btn--danger,[data-theme=dark] .ai-chat-booking-card__btn--danger{background:rgba(239,68,68,.2);border-color:rgba(239,68,68,.3);color:#fca5a5}.ai-chat-booking-card__grid{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}.ai-chat-booking-card__contact{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:8px;cursor:pointer;display:flex;flex-direction:column;font-family:inherit;padding:12px;text-align:left;transition:all .2s ease}.ai-chat-booking-card__contact:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-booking-card__contact--selected{border-width:2px}.ai-chat-widget.dark .ai-chat-booking-card__contact,.chakra-ui-dark .ai-chat-booking-card__contact,.dark .ai-chat-booking-card__contact,[data-theme=dark] .ai-chat-booking-card__contact{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1)}.ai-chat-widget.dark .ai-chat-booking-card__contact--selected,.chakra-ui-dark .ai-chat-booking-card__contact--selected,.dark .ai-chat-booking-card__contact--selected,[data-theme=dark] .ai-chat-booking-card__contact--selected{background:rgba(59,130,246,.15)}.ai-chat-booking-card__contact-name{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:500}.ai-chat-widget.dark .ai-chat-booking-card__contact-name,.chakra-ui-dark .ai-chat-booking-card__contact-name,.dark .ai-chat-booking-card__contact-name,[data-theme=dark] .ai-chat-booking-card__contact-name{color:#fff}.ai-chat-booking-card__contact-role{color:var(--text-muted,#71717a);font-size:12px;margin-top:2px}.ai-chat-booking-card__options{display:flex;flex-direction:column;gap:8px}.ai-chat-booking-card__option-btn{align-items:center;background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:8px;box-sizing:border-box;cursor:pointer;display:flex;font-family:inherit;gap:12px;padding:12px;text-align:left;transition:all .2s ease;width:100%}.ai-chat-booking-card__option-btn:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-widget.dark .ai-chat-booking-card__option-btn,.chakra-ui-dark .ai-chat-booking-card__option-btn,.dark .ai-chat-booking-card__option-btn,[data-theme=dark] .ai-chat-booking-card__option-btn{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1)}.ai-chat-booking-card__option-icon{flex-shrink:0;font-size:18px}.ai-chat-booking-card__option-text{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:500}.ai-chat-widget.dark .ai-chat-booking-card__option-text,.chakra-ui-dark .ai-chat-booking-card__option-text,.dark .ai-chat-booking-card__option-text,[data-theme=dark] .ai-chat-booking-card__option-text{color:#fff}.ai-chat-booking-card__date-group{margin-bottom:12px}.ai-chat-booking-card__date-header{color:var(--text-muted,#71717a);font-size:12px;font-weight:600;letter-spacing:.5px;margin-bottom:8px;text-transform:uppercase}.ai-chat-booking-card__slots{display:grid;gap:8px;grid-template-columns:repeat(auto-fill,minmax(120px,1fr))}.ai-chat-booking-card__slot{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:8px;color:var(--text-primary,#3e3e3e);cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;padding:10px 12px;transition:all .2s ease}.ai-chat-booking-card__slot:hover{background:rgba(59,130,246,.05);border-color:var(--action-accent,var(--primary-color,#3b82f6))}.ai-chat-booking-card__slot--selected{border-width:2px}.ai-chat-widget.dark .ai-chat-booking-card__slot,.chakra-ui-dark .ai-chat-booking-card__slot,.dark .ai-chat-booking-card__slot,[data-theme=dark] .ai-chat-booking-card__slot{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1);color:#fff}.ai-chat-widget.dark .ai-chat-booking-card__slot--selected,.chakra-ui-dark .ai-chat-booking-card__slot--selected,.dark .ai-chat-booking-card__slot--selected,[data-theme=dark] .ai-chat-booking-card__slot--selected{background:rgba(59,130,246,.15)}.ai-chat-booking-card__appointments{display:flex;flex-direction:column;gap:10px}.ai-chat-booking-card__appointment{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:8px;padding:12px}.ai-chat-widget.dark .ai-chat-booking-card__appointment,.chakra-ui-dark .ai-chat-booking-card__appointment,.dark .ai-chat-booking-card__appointment,[data-theme=dark] .ai-chat-booking-card__appointment{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1)}.ai-chat-booking-card__appointment-header{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-bottom:6px}.ai-chat-booking-card__appointment-subject{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:500}.ai-chat-widget.dark .ai-chat-booking-card__appointment-subject,.chakra-ui-dark .ai-chat-booking-card__appointment-subject,.dark .ai-chat-booking-card__appointment-subject,[data-theme=dark] .ai-chat-booking-card__appointment-subject{color:#fff}.ai-chat-booking-card__appointment-status{border-radius:4px;font-size:11px;font-weight:600;padding:2px 6px;text-transform:uppercase}.ai-chat-booking-card__appointment-status--confirmed,.ai-chat-booking-card__appointment-status--pending{background:rgba(34,197,94,.1);color:#16a34a}.ai-chat-widget.dark .ai-chat-booking-card__appointment-status--confirmed,.ai-chat-widget.dark .ai-chat-booking-card__appointment-status--pending,.chakra-ui-dark .ai-chat-booking-card__appointment-status--confirmed,.chakra-ui-dark .ai-chat-booking-card__appointment-status--pending,.dark .ai-chat-booking-card__appointment-status--confirmed,.dark .ai-chat-booking-card__appointment-status--pending,[data-theme=dark] .ai-chat-booking-card__appointment-status--confirmed,[data-theme=dark] .ai-chat-booking-card__appointment-status--pending{background:rgba(34,197,94,.2);color:#4ade80}.ai-chat-booking-card__appointment-status--cancelled{background:hsla(220,9%,46%,.1);color:#6b7280}.ai-chat-booking-card__appointment-time{color:var(--text-secondary,#6b7280);font-size:13px;margin-bottom:4px}.ai-chat-widget.dark .ai-chat-booking-card__appointment-time,.chakra-ui-dark .ai-chat-booking-card__appointment-time,.dark .ai-chat-booking-card__appointment-time,[data-theme=dark] .ai-chat-booking-card__appointment-time{color:var(--text-secondary,#a1a1aa)}.ai-chat-booking-card__appointment-contact{color:var(--text-muted,#71717a);font-size:12px}.ai-chat-booking-card__summary{background:var(--bg-primary,#fff);border:1px solid var(--border-default,#e5e7eb);border-radius:8px;margin-bottom:12px;padding:12px}.ai-chat-widget.dark .ai-chat-booking-card__summary,.chakra-ui-dark .ai-chat-booking-card__summary,.dark .ai-chat-booking-card__summary,[data-theme=dark] .ai-chat-booking-card__summary{background:rgba(0,0,0,.2);border-color:hsla(0,0%,100%,.1)}.ai-chat-booking-card__summary-row{align-items:center;display:flex;justify-content:space-between;padding:6px 0}.ai-chat-booking-card__summary-row:not(:last-child){border-bottom:1px solid var(--border-subtle,rgba(0,0,0,.06))}.ai-chat-widget.dark .ai-chat-booking-card__summary-row:not(:last-child),.chakra-ui-dark .ai-chat-booking-card__summary-row:not(:last-child),.dark .ai-chat-booking-card__summary-row:not(:last-child),[data-theme=dark] .ai-chat-booking-card__summary-row:not(:last-child){border-bottom-color:hsla(0,0%,100%,.08)}.ai-chat-booking-card__summary-label{color:var(--text-muted,#71717a);font-size:12px;font-weight:600;letter-spacing:.5px;text-transform:uppercase}.ai-chat-booking-card__summary-value{color:var(--text-primary,#3e3e3e);font-size:14px;font-weight:500;text-align:right}.ai-chat-widget.dark .ai-chat-booking-card__summary-value,.chakra-ui-dark .ai-chat-booking-card__summary-value,.dark .ai-chat-booking-card__summary-value,[data-theme=dark] .ai-chat-booking-card__summary-value{color:#fff}.ai-chat-booking-card__link{display:inline-block;font-size:13px;font-weight:500;margin-top:8px;text-decoration:none;transition:opacity .2s ease}.ai-chat-booking-card__link:hover{opacity:.8;text-decoration:underline}.ai-chat-booking-card--success{background:rgba(34,197,94,.05);border-color:rgba(34,197,94,.2)}.ai-chat-widget.dark .ai-chat-booking-card--success,.chakra-ui-dark .ai-chat-booking-card--success,.dark .ai-chat-booking-card--success,[data-theme=dark] .ai-chat-booking-card--success{background:rgba(34,197,94,.1);border-color:rgba(34,197,94,.3)}.ai-chat-booking-card--pending{background:rgba(234,179,8,.05);border-color:rgba(234,179,8,.2)}.ai-chat-widget.dark .ai-chat-booking-card--pending,.chakra-ui-dark .ai-chat-booking-card--pending,.dark .ai-chat-booking-card--pending,[data-theme=dark] .ai-chat-booking-card--pending{background:rgba(234,179,8,.1);border-color:rgba(234,179,8,.3)}.ai-chat-booking-card--cancelled{background:hsla(220,9%,46%,.05);border-color:hsla(220,9%,46%,.2)}.ai-chat-widget.dark .ai-chat-booking-card--cancelled,.chakra-ui-dark .ai-chat-booking-card--cancelled,.dark .ai-chat-booking-card--cancelled,[data-theme=dark] .ai-chat-booking-card--cancelled{background:hsla(220,9%,46%,.1);border-color:hsla(220,9%,46%,.3)}.ai-chat-booking-card--error{background:rgba(239,68,68,.05);border-color:rgba(239,68,68,.2)}.ai-chat-widget.dark .ai-chat-booking-card--error,.chakra-ui-dark .ai-chat-booking-card--error,.dark .ai-chat-booking-card--error,[data-theme=dark] .ai-chat-booking-card--error{background:rgba(239,68,68,.1);border-color:rgba(239,68,68,.3)}.ai-chat-booking-card__pending-text,.ai-chat-booking-card__success-text{color:var(--text-secondary,#6b7280);font-size:13px;margin:0}.ai-chat-widget.dark .ai-chat-booking-card__pending-text,.ai-chat-widget.dark .ai-chat-booking-card__success-text,.chakra-ui-dark .ai-chat-booking-card__pending-text,.chakra-ui-dark .ai-chat-booking-card__success-text,.dark .ai-chat-booking-card__pending-text,.dark .ai-chat-booking-card__success-text,[data-theme=dark] .ai-chat-booking-card__pending-text,[data-theme=dark] .ai-chat-booking-card__success-text{color:var(--text-secondary,#a1a1aa)}.ai-chat-booking-card__error{color:#dc2626;font-size:13px;margin:0}.ai-chat-widget.dark .ai-chat-booking-card__error,.chakra-ui-dark .ai-chat-booking-card__error,.dark .ai-chat-booking-card__error,[data-theme=dark] .ai-chat-booking-card__error{color:#fca5a5}.chat-fullpage{--fp-max-width:800px;--fp-padding-x:16px;--fp-padding-top-mobile:64px;--fp-padding-top-desktop:16px;--fp-padding-bottom:200px;--fp-input-bottom:0}.chat-fullpage .ai-chat-messages{background:transparent;height:100%;margin:0 auto;max-width:var(--fp-max-width);padding:var(--fp-padding-top-desktop) var(--fp-padding-x) var(--fp-padding-bottom)}@media (max-width:768px){.chat-fullpage .ai-chat-messages{padding-top:var(--fp-padding-top-mobile)}}.chat-fullpage .ai-chat-message{animation:none}.chat-fullpage .ai-chat-message.user{max-width:85%}.chat-fullpage .ai-chat-message.user .ai-chat-message-content{background:var(--bg-muted,#f4f4f5);border-radius:24px;color:#000;padding:8px 16px}.chat-fullpage.dark .ai-chat-message.user .ai-chat-message-content{background:var(--bg-muted,#27272a);color:#fff}.chat-fullpage .ai-chat-message.assistant{width:100%}.chat-fullpage .ai-chat-message.assistant .ai-chat-message-content{color:#000;padding:8px 16px}.chat-fullpage.dark .ai-chat-message.assistant .ai-chat-message-content{color:#fff}.chat-fullpage .ai-chat-message.tool{margin:0;padding:0;width:100%}.chat-fullpage .ai-chat-welcome{align-items:center;display:flex;flex-direction:column;gap:24px;justify-content:center;min-height:60vh;padding:24px;text-align:center}.chat-fullpage .ai-chat-welcome-title{font-size:32px;font-weight:600}.chat-fullpage .ai-chat-welcome-text{color:var(--text-muted,#71717a);font-size:18px;max-width:400px}.chat-fullpage .ai-chat-input-container{background:transparent;bottom:0;left:0;padding:16px 16px calc(16px + env(safe-area-inset-bottom));position:fixed;right:0;z-index:20}.chat-fullpage .ai-chat-input-container:after{background:var(--bg-primary,#fff);bottom:0;content:\"\";height:calc(40% + 16px);left:0;pointer-events:none;position:absolute;right:0;z-index:-1}.chat-fullpage.dark .ai-chat-input-container:after{background:var(--bg-primary,#18181b)}@media (min-width:769px){.chat-fullpage .ai-chat-input-container{background:transparent;bottom:var(--fp-input-bottom);left:50%;max-width:var(--fp-max-width);padding:0;position:absolute;right:auto;transform:translateX(-50%);width:100%}}.chat-fullpage .ai-chat-input-wrapper{background:var(--bg-muted,#f4f4f5);border:1px solid var(--border-muted,#e4e4e7);border-radius:24px;box-shadow:0 1px 8px rgba(0,0,0,.06);margin:0 auto;max-width:var(--fp-max-width)}.chat-fullpage.dark .ai-chat-input-wrapper{background:var(--bg-muted,#27272a);border-color:var(--border-muted,#3f3f46);box-shadow:0 1px 12px rgba(0,0,0,.25)}.chat-fullpage .ai-chat-scroll-button{bottom:100px}@media (min-width:769px){.chat-fullpage .ai-chat-scroll-button{bottom:90px}}.chat-fullpage .ai-chat-suggested-questions{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;max-width:600px}.chat-fullpage .ai-chat-suggested-question{border-radius:9999px;font-size:14px;padding:8px 16px}.chat-fullpage .ai-chat-follow-up-suggestions{margin-top:12px}.chat-fullpage .ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-default,#d4d4d8);color:#000}.chat-fullpage .ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#f4f4f5)}.chat-fullpage.dark .ai-chat-follow-up-item.question-type{background:transparent;border:1px solid var(--border-subtle,#52525b);color:#fff}.chat-fullpage.dark .ai-chat-follow-up-item.question-type:hover{background:var(--bg-hover,#3f3f46)}.chat-fullpage .ai-chat-typing{padding:8px 16px}@media (max-width:480px){body.ai-chat-widget-open{height:100%!important;overflow:hidden!important;position:fixed!important;touch-action:none!important;width:100%!important}.ai-chat-widget-container.is-open{height:100vh!important;height:100dvh!important;width:100vw!important;z-index:var(--widget-z-index,2147483647)!important}.ai-chat-widget-container.is-open,.ai-chat-widget-container.is-open .ai-chat-window{bottom:0!important;left:0!important;position:fixed!important;right:0!important;top:0!important}.ai-chat-widget-container.is-open .ai-chat-window{animation:none!important;border:none!important;border-radius:0!important;box-shadow:none!important;height:100%!important;max-height:100%!important;max-width:100%!important;outline:none!important;transform:none!important;width:100%!important}.ai-chat-widget-container.is-open .ai-chat-button{display:none!important;pointer-events:none!important;visibility:hidden!important}.ai-chat-widget-container.is-open .ai-chat-header{border-radius:0!important;flex-shrink:0;padding-left:max(16px,env(safe-area-inset-left));padding-right:max(16px,env(safe-area-inset-right));padding-top:max(12px,env(safe-area-inset-top));position:relative;z-index:100}.ai-chat-widget-container.is-open .ai-chat-messages{-webkit-overflow-scrolling:touch;flex:1;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;padding-bottom:120px;padding-left:max(16px,env(safe-area-inset-left));padding-right:max(16px,env(safe-area-inset-right));touch-action:pan-y}.ai-chat-widget-container.is-open .ai-chat-input-container{background:var(--bg-primary,#fff);bottom:0!important;left:0!important;padding:8px max(12px,env(safe-area-inset-right)) max(16px,calc(env(safe-area-inset-bottom) + 8px)) max(12px,env(safe-area-inset-left));position:fixed!important;right:0!important;z-index:100}.ai-chat-widget.dark .ai-chat-widget-container.is-open .ai-chat-input-container{background:var(--bg-primary,#282625)}.ai-chat-widget-container.is-open .ai-chat-input-container:after{display:none}.ai-chat-widget-container.is-open .ai-chat-input-wrapper{margin:0;max-width:100%}.ai-chat-widget-container.is-open .ai-chat-scroll-button{bottom:calc(80px + env(safe-area-inset-bottom))}.ai-chat-widget-container.is-open .ai-chat-welcome{padding:16px 0}.ai-chat-widget-container.is-open .ai-chat-welcome-title{font-size:24px}.ai-chat-widget-container.is-open .ai-chat-suggested-questions{align-items:stretch;flex-direction:column}.ai-chat-widget-container.is-open .ai-chat-suggested-question{text-align:center;width:100%}}@media (min-width:481px) and (max-width:768px){.ai-chat-widget-container.is-open .ai-chat-window{border-radius:22px 22px 44px 44px;max-height:calc(100vh - 100px);max-width:calc(100vw - 32px)}}";
|
|
30847
|
+
styleInject(css_248z$1);
|
|
30848
|
+
|
|
30849
|
+
var css_248z = ".ai-chat-data-policy-view{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden}.ai-chat-data-policy-content{-webkit-overflow-scrolling:touch;flex:1;overflow-y:auto;padding:20px 16px 40px}.ai-chat-data-policy-intro{margin-bottom:24px}.ai-chat-data-policy-intro p{color:var(--text-primary,#18181b);font-size:13px;line-height:1.6;margin:0;text-align:left}.ai-chat-widget.dark .ai-chat-data-policy-intro p{color:var(--text-primary,#fafafa)}.ai-chat-data-policy-intro strong{color:var(--text-primary,#18181b)}.ai-chat-widget.dark .ai-chat-data-policy-intro strong{color:var(--text-primary,#fafafa)}.ai-chat-data-policy-section{margin-bottom:20px}.ai-chat-data-policy-section h3{color:var(--text-primary,#18181b);font-size:14px;font-weight:600;margin:0 0 12px}.ai-chat-widget.dark .ai-chat-data-policy-section h3{color:var(--text-primary,#fafafa)}.ai-chat-data-policy-section p{color:var(--text-secondary,#52525b);font-size:12px;line-height:1.6;margin:0 0 12px;text-align:justify;text-justify:inter-word}.ai-chat-widget.dark .ai-chat-data-policy-section p{color:var(--text-secondary,#a1a1aa)}.ai-chat-data-policy-section p strong{color:var(--text-primary,#18181b)}.ai-chat-widget.dark .ai-chat-data-policy-section p strong{color:var(--text-primary,#fafafa)}.ai-chat-data-policy-section p:last-child{margin-bottom:0}";
|
|
29688
30850
|
styleInject(css_248z);
|
|
29689
30851
|
|
|
29690
30852
|
// Icon components mapping
|
|
@@ -29692,11 +30854,23 @@ const iconComponents = {
|
|
|
29692
30854
|
FiMessageCircle: () => (jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("path", { d: "M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" }) })),
|
|
29693
30855
|
FiChevronDown: () => (jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "6 9 12 15 18 9" }) })),
|
|
29694
30856
|
};
|
|
29695
|
-
const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = false, previewConfig, position = 'bottom-right', primaryColor, size, headerTitle, welcomeTitle, welcomeMessage, placeholder, theme, suggestedQuestions, customStyles, currentRoute, defaultOpen = false, zIndex, onOpen, onClose, onMessage, onError, mode = 'bubble', }) => {
|
|
30857
|
+
const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = false, previewConfig, demoMode = false, demoInput, demoOutput, onDemoComplete, position = 'bottom-right', primaryColor, size, headerTitle, welcomeTitle, welcomeMessage, placeholder, welcomeBubbleText, triggerType, triggerText, theme, suggestedQuestions, customStyles, currentRoute, defaultOpen = false, zIndex, containerMode = false, onOpen, onClose, onMessage, onError, mode = 'bubble', }) => {
|
|
29696
30858
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
30859
|
+
const [inputBarValue, setInputBarValue] = useState('');
|
|
29697
30860
|
const [autoDetectedTheme, setAutoDetectedTheme] = useState('light');
|
|
30861
|
+
const [showWelcomeBubble, setShowWelcomeBubble] = useState(false);
|
|
29698
30862
|
const widgetRef = useRef(null);
|
|
29699
30863
|
const containerRef = useRef(null);
|
|
30864
|
+
// Demo mode state - track whether demo has been activated and completed
|
|
30865
|
+
const [demoMessages, setDemoMessages] = useState([]);
|
|
30866
|
+
const [isDemoComplete, setIsDemoComplete] = useState(false);
|
|
30867
|
+
const [isDemoTyping, setIsDemoTyping] = useState(false);
|
|
30868
|
+
const [demoInputBarText, setDemoInputBarText] = useState(''); // For animating text in input bar
|
|
30869
|
+
const [isDemoAnimatingInput, setIsDemoAnimatingInput] = useState(false); // True while typing into input bar
|
|
30870
|
+
const [isDemoActive, setIsDemoActive] = useState(false); // True when demo animation is running
|
|
30871
|
+
const demoStartedRef = useRef(false);
|
|
30872
|
+
// Track if user has sent a real message (transitions from demo to real chat)
|
|
30873
|
+
const [userSentRealMessage, setUserSentRealMessage] = useState(false);
|
|
29700
30874
|
// Determine mode
|
|
29701
30875
|
const isEmbedded = mode === 'embedded';
|
|
29702
30876
|
// Default config for preview mode
|
|
@@ -29716,6 +30890,7 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29716
30890
|
showChatHistory: true,
|
|
29717
30891
|
showTimestamps: true,
|
|
29718
30892
|
showTypingIndicator: true,
|
|
30893
|
+
showToolCalls: false,
|
|
29719
30894
|
enableFileUpload: false,
|
|
29720
30895
|
enableFeedback: true,
|
|
29721
30896
|
},
|
|
@@ -29741,29 +30916,54 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29741
30916
|
...previewConfig?.behavior,
|
|
29742
30917
|
},
|
|
29743
30918
|
};
|
|
29744
|
-
// Always call useChat hook (React rules), but skip initialization in preview mode
|
|
30919
|
+
// Always call useChat hook (React rules), but skip initialization in preview mode or during demo
|
|
30920
|
+
// Skip initialization during demo to prevent loading old conversations
|
|
30921
|
+
// Note: We still pass the real widgetId so createDemoConversation works correctly
|
|
30922
|
+
const shouldSkipInit = previewMode || (demoMode && !userSentRealMessage);
|
|
29745
30923
|
const chatHook = useChat({
|
|
29746
30924
|
widgetId: previewMode ? '__preview__' : (widgetId || '__preview__'),
|
|
29747
30925
|
apiUrl,
|
|
29748
30926
|
currentRoute,
|
|
29749
|
-
onMessage:
|
|
29750
|
-
onError:
|
|
29751
|
-
skipInitialization:
|
|
30927
|
+
onMessage: shouldSkipInit ? undefined : onMessage,
|
|
30928
|
+
onError: shouldSkipInit ? undefined : onError,
|
|
30929
|
+
skipInitialization: shouldSkipInit, // Don't make API calls in preview mode or during demo
|
|
29752
30930
|
});
|
|
29753
30931
|
// Extract values from hook or use preview defaults
|
|
29754
|
-
const
|
|
29755
|
-
const
|
|
29756
|
-
const
|
|
29757
|
-
const error = previewMode ? null : chatHook.error;
|
|
30932
|
+
const hookMessages = previewMode ? [] : chatHook.messages;
|
|
30933
|
+
const hookIsLoading = previewMode ? false : chatHook.isLoading;
|
|
30934
|
+
const hookIsTyping = previewMode ? false : chatHook.isTyping;
|
|
29758
30935
|
const config = previewMode ? mergedPreviewConfig : chatHook.config;
|
|
29759
|
-
const
|
|
30936
|
+
const hookSendMessage = previewMode ? (() => Promise.resolve()) : chatHook.sendMessage;
|
|
29760
30937
|
const submitFeedback = previewMode ? (() => Promise.resolve()) : chatHook.submitFeedback;
|
|
30938
|
+
const dismissAction = previewMode ? (() => Promise.resolve()) : chatHook.dismissAction;
|
|
29761
30939
|
const conversations = previewMode ? [] : chatHook.conversations;
|
|
29762
30940
|
const loadConversations = previewMode ? (() => { }) : chatHook.loadConversations;
|
|
29763
30941
|
const switchConversation = previewMode ? (() => Promise.resolve()) : chatHook.switchConversation;
|
|
29764
30942
|
const startNewConversation = previewMode ? (() => { }) : chatHook.startNewConversation;
|
|
29765
30943
|
const deleteConversation = previewMode ? (() => { }) : chatHook.deleteConversation;
|
|
30944
|
+
const createDemoConversation = previewMode ? (() => Promise.resolve(null)) : chatHook.createDemoConversation;
|
|
29766
30945
|
const conversationId = previewMode ? '' : chatHook.conversationId;
|
|
30946
|
+
// Message display logic:
|
|
30947
|
+
// - During demo (before user sends real message): show demoMessages
|
|
30948
|
+
// - After user sends real message: show hookMessages
|
|
30949
|
+
const showDemoMessages = demoMode && demoMessages.length > 0 && !userSentRealMessage;
|
|
30950
|
+
const messages = showDemoMessages ? demoMessages : hookMessages;
|
|
30951
|
+
const isLoading = showDemoMessages ? false : hookIsLoading;
|
|
30952
|
+
const isTyping = showDemoMessages ? isDemoTyping : hookIsTyping;
|
|
30953
|
+
// Send message handler - transitions from demo to real chat when user sends first message
|
|
30954
|
+
const sendMessage = useCallback((content) => {
|
|
30955
|
+
if (demoMode && isDemoActive && !isDemoComplete) {
|
|
30956
|
+
// Demo animation is still running - ignore user input
|
|
30957
|
+
return Promise.resolve();
|
|
30958
|
+
}
|
|
30959
|
+
// User is sending a real message - transition to real chat
|
|
30960
|
+
if (demoMode && !userSentRealMessage) {
|
|
30961
|
+
setUserSentRealMessage(true);
|
|
30962
|
+
// Clear demo messages so hook messages take over
|
|
30963
|
+
setDemoMessages([]);
|
|
30964
|
+
}
|
|
30965
|
+
return hookSendMessage(content);
|
|
30966
|
+
}, [demoMode, isDemoActive, isDemoComplete, userSentRealMessage, hookSendMessage]);
|
|
29767
30967
|
// Auto-detect theme from background
|
|
29768
30968
|
useEffect(() => {
|
|
29769
30969
|
if (!containerRef.current)
|
|
@@ -29789,8 +30989,16 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29789
30989
|
mediaQuery.removeEventListener('change', handleMediaChange);
|
|
29790
30990
|
};
|
|
29791
30991
|
}, [config]);
|
|
29792
|
-
//
|
|
30992
|
+
// Check if device is mobile
|
|
30993
|
+
const isMobile = typeof window !== 'undefined' && window.innerWidth <= 480;
|
|
30994
|
+
// Handle auto-open (only for bubble mode, disabled on mobile)
|
|
29793
30995
|
useEffect(() => {
|
|
30996
|
+
// Never auto-open on mobile devices
|
|
30997
|
+
if (isMobile)
|
|
30998
|
+
return undefined;
|
|
30999
|
+
// Don't auto-open if demo mode is active - let demo animation control opening
|
|
31000
|
+
if (demoMode && !isDemoComplete)
|
|
31001
|
+
return undefined;
|
|
29794
31002
|
if (!isEmbedded && config?.settings.autoOpen) {
|
|
29795
31003
|
const delay = config.settings.autoOpenDelay || 0;
|
|
29796
31004
|
const timer = setTimeout(() => {
|
|
@@ -29800,7 +31008,7 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29800
31008
|
return () => clearTimeout(timer);
|
|
29801
31009
|
}
|
|
29802
31010
|
return undefined;
|
|
29803
|
-
}, [config, onOpen, isEmbedded]);
|
|
31011
|
+
}, [config, onOpen, isEmbedded, isMobile, demoMode, isDemoComplete]);
|
|
29804
31012
|
// Handle close on Escape key (only for bubble mode)
|
|
29805
31013
|
useEffect(() => {
|
|
29806
31014
|
if (!isOpen || isEmbedded)
|
|
@@ -29814,6 +31022,203 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29814
31022
|
document.addEventListener('keydown', handleEscapeKey);
|
|
29815
31023
|
return () => document.removeEventListener('keydown', handleEscapeKey);
|
|
29816
31024
|
}, [isOpen, onClose, isEmbedded]);
|
|
31025
|
+
// Handle body scroll lock on mobile when widget is open
|
|
31026
|
+
useEffect(() => {
|
|
31027
|
+
if (!isOpen || isEmbedded)
|
|
31028
|
+
return;
|
|
31029
|
+
// Only apply scroll lock on mobile
|
|
31030
|
+
const checkMobile = window.innerWidth <= 480;
|
|
31031
|
+
if (!checkMobile)
|
|
31032
|
+
return;
|
|
31033
|
+
// Add class to body to lock scrolling
|
|
31034
|
+
document.body.classList.add('ai-chat-widget-open');
|
|
31035
|
+
return () => {
|
|
31036
|
+
document.body.classList.remove('ai-chat-widget-open');
|
|
31037
|
+
};
|
|
31038
|
+
}, [isOpen, isEmbedded]);
|
|
31039
|
+
// Handle welcome bubble visibility based on frequency setting
|
|
31040
|
+
// Frequency options: 'always' (every page visit), 'session', 'weekly', 'monthly'
|
|
31041
|
+
useEffect(() => {
|
|
31042
|
+
if (isEmbedded || previewMode)
|
|
31043
|
+
return;
|
|
31044
|
+
const bubbleText = welcomeBubbleText ?? config?.appearance?.welcomeBubbleText;
|
|
31045
|
+
if (!bubbleText) {
|
|
31046
|
+
setShowWelcomeBubble(false);
|
|
31047
|
+
return;
|
|
31048
|
+
}
|
|
31049
|
+
const frequency = config?.appearance?.welcomeBubbleFrequency ?? 'session';
|
|
31050
|
+
const storageKey = `ai-chat-bubble-dismissed-${widgetId || 'default'}`;
|
|
31051
|
+
// Check if bubble should be shown based on frequency
|
|
31052
|
+
const shouldShowBubble = () => {
|
|
31053
|
+
if (frequency === 'always') {
|
|
31054
|
+
// Always show on every page visit (no storage check)
|
|
31055
|
+
return true;
|
|
31056
|
+
}
|
|
31057
|
+
if (frequency === 'session') {
|
|
31058
|
+
// Show once per session
|
|
31059
|
+
return sessionStorage.getItem(storageKey) !== 'true';
|
|
31060
|
+
}
|
|
31061
|
+
// For weekly/monthly, use localStorage with timestamp
|
|
31062
|
+
try {
|
|
31063
|
+
const stored = localStorage.getItem(storageKey);
|
|
31064
|
+
if (!stored)
|
|
31065
|
+
return true;
|
|
31066
|
+
const dismissedAt = parseInt(stored, 10);
|
|
31067
|
+
if (isNaN(dismissedAt))
|
|
31068
|
+
return true;
|
|
31069
|
+
const now = Date.now();
|
|
31070
|
+
const weekMs = 7 * 24 * 60 * 60 * 1000;
|
|
31071
|
+
const monthMs = 30 * 24 * 60 * 60 * 1000;
|
|
31072
|
+
if (frequency === 'weekly') {
|
|
31073
|
+
return now - dismissedAt > weekMs;
|
|
31074
|
+
}
|
|
31075
|
+
if (frequency === 'monthly') {
|
|
31076
|
+
return now - dismissedAt > monthMs;
|
|
31077
|
+
}
|
|
31078
|
+
}
|
|
31079
|
+
catch {
|
|
31080
|
+
return true;
|
|
31081
|
+
}
|
|
31082
|
+
return true;
|
|
31083
|
+
};
|
|
31084
|
+
if (shouldShowBubble() && !isOpen) {
|
|
31085
|
+
setShowWelcomeBubble(true);
|
|
31086
|
+
}
|
|
31087
|
+
}, [widgetId, welcomeBubbleText, config, isOpen, isEmbedded, previewMode]);
|
|
31088
|
+
// Demo mode: animate typing into input bar, then open widget with conversation
|
|
31089
|
+
// This shows the widget in action without making expensive AI calls
|
|
31090
|
+
useEffect(() => {
|
|
31091
|
+
// Determine trigger type early (from prop or config)
|
|
31092
|
+
// IMPORTANT: For demo mode, we need config to be loaded to know the trigger type
|
|
31093
|
+
// If config is not loaded yet, wait for it (unless triggerType prop is provided)
|
|
31094
|
+
const effectiveTriggerTypeForDemo = triggerType ?? config?.appearance?.triggerType ?? 'button';
|
|
31095
|
+
const isInputBarTrigger = effectiveTriggerTypeForDemo === 'input-bar';
|
|
31096
|
+
// Debug logging
|
|
31097
|
+
console.log('[Widget] Demo effect check:', {
|
|
31098
|
+
demoMode,
|
|
31099
|
+
isDemoComplete,
|
|
31100
|
+
demoStartedRef: demoStartedRef.current,
|
|
31101
|
+
hasDemoInput: !!demoInput,
|
|
31102
|
+
hasDemoOutput: !!demoOutput,
|
|
31103
|
+
triggerType: effectiveTriggerTypeForDemo,
|
|
31104
|
+
isInputBar: isInputBarTrigger,
|
|
31105
|
+
isOpen,
|
|
31106
|
+
});
|
|
31107
|
+
// Start demo when demoMode is enabled and we have input/output
|
|
31108
|
+
const shouldStartDemo = demoMode && !isDemoComplete && !demoStartedRef.current && demoInput && demoOutput;
|
|
31109
|
+
if (!shouldStartDemo) {
|
|
31110
|
+
console.log('[Widget] Demo not starting:', { shouldStartDemo, reason: !demoMode ? 'demoMode false' : !demoInput ? 'no demoInput' : !demoOutput ? 'no demoOutput' : isDemoComplete ? 'already complete' : demoStartedRef.current ? 'already started' : 'unknown' });
|
|
31111
|
+
return;
|
|
31112
|
+
}
|
|
31113
|
+
// Wait for config to load before starting demo (unless triggerType prop is explicitly provided)
|
|
31114
|
+
// This prevents the demo from starting with wrong trigger type
|
|
31115
|
+
if (!triggerType && !config) {
|
|
31116
|
+
console.log('[Widget] Demo waiting for config or triggerType prop');
|
|
31117
|
+
return;
|
|
31118
|
+
}
|
|
31119
|
+
// For input-bar: start immediately (widget closed, type into bar)
|
|
31120
|
+
// For others: wait until widget is open
|
|
31121
|
+
if (!isInputBarTrigger && !isOpen) {
|
|
31122
|
+
console.log('[Widget] Demo waiting for widget to open (non-input-bar trigger)');
|
|
31123
|
+
return;
|
|
31124
|
+
}
|
|
31125
|
+
console.log('[Widget] Starting demo animation...', {
|
|
31126
|
+
triggerType: effectiveTriggerTypeForDemo,
|
|
31127
|
+
isInputBar: isInputBarTrigger,
|
|
31128
|
+
demoInput,
|
|
31129
|
+
demoOutput: demoOutput?.substring(0, 50) + '...',
|
|
31130
|
+
});
|
|
31131
|
+
demoStartedRef.current = true;
|
|
31132
|
+
setIsDemoActive(true);
|
|
31133
|
+
// Use stable IDs to prevent React from remounting components
|
|
31134
|
+
const userMessageId = 'demo-user-message';
|
|
31135
|
+
const assistantMessageId = 'demo-assistant-message';
|
|
31136
|
+
// Helper to create a message with stable ID
|
|
31137
|
+
const createMessage = (id, role, content, isStreaming = false) => ({
|
|
31138
|
+
id,
|
|
31139
|
+
message: { role, content },
|
|
31140
|
+
timestamp: new Date().toISOString(),
|
|
31141
|
+
sources: [],
|
|
31142
|
+
isStreaming,
|
|
31143
|
+
});
|
|
31144
|
+
// Animation sequence - runs to completion even if widget is closed
|
|
31145
|
+
// This ensures conversation is always created and saved consistently
|
|
31146
|
+
const runDemoAnimation = async () => {
|
|
31147
|
+
// STEP 1: Create conversation IMMEDIATELY at the start
|
|
31148
|
+
// This ensures we have a conversation ID before any animation
|
|
31149
|
+
console.log('[Widget] Creating demo conversation at start...');
|
|
31150
|
+
let conversationId = null;
|
|
31151
|
+
try {
|
|
31152
|
+
conversationId = await createDemoConversation(demoInput, demoOutput);
|
|
31153
|
+
if (conversationId) {
|
|
31154
|
+
console.log('[Widget] Demo conversation created:', conversationId);
|
|
31155
|
+
}
|
|
31156
|
+
}
|
|
31157
|
+
catch (err) {
|
|
31158
|
+
console.warn('[Widget] Failed to create demo conversation:', err);
|
|
31159
|
+
}
|
|
31160
|
+
// STEP 2: For input-bar trigger, animate typing into input bar first
|
|
31161
|
+
if (isInputBarTrigger && !isOpen) {
|
|
31162
|
+
setIsDemoAnimatingInput(true);
|
|
31163
|
+
// Wait before starting to type - ensure input bar is fully visible
|
|
31164
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
31165
|
+
// Type into input bar character by character
|
|
31166
|
+
const inputChars = demoInput.split('');
|
|
31167
|
+
for (let i = 0; i < inputChars.length; i++) {
|
|
31168
|
+
setDemoInputBarText(demoInput.slice(0, i + 1));
|
|
31169
|
+
// Slower typing speed for better visibility
|
|
31170
|
+
const delay = inputChars[i] === ' ' ? 100 : 60 + Math.random() * 40;
|
|
31171
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
31172
|
+
}
|
|
31173
|
+
// Longer pause after typing so user can read the question
|
|
31174
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
31175
|
+
setIsDemoAnimatingInput(false);
|
|
31176
|
+
setDemoInputBarText('');
|
|
31177
|
+
setIsOpen(true);
|
|
31178
|
+
onOpen?.();
|
|
31179
|
+
// Let widget open animation complete
|
|
31180
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
31181
|
+
}
|
|
31182
|
+
else {
|
|
31183
|
+
// For non-input-bar triggers, just wait for widget to settle
|
|
31184
|
+
await new Promise(resolve => setTimeout(resolve, 800));
|
|
31185
|
+
}
|
|
31186
|
+
// STEP 3: Add user message
|
|
31187
|
+
setDemoMessages([createMessage(userMessageId, 'user', demoInput)]);
|
|
31188
|
+
// Show typing indicator after a pause
|
|
31189
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
31190
|
+
setIsDemoTyping(true);
|
|
31191
|
+
// "Thinking" pause
|
|
31192
|
+
await new Promise(resolve => setTimeout(resolve, 1200));
|
|
31193
|
+
setIsDemoTyping(false);
|
|
31194
|
+
// STEP 4: Stream the response character by character
|
|
31195
|
+
const chars = demoOutput.split('');
|
|
31196
|
+
let currentText = '';
|
|
31197
|
+
// Initial message with empty content
|
|
31198
|
+
setDemoMessages([
|
|
31199
|
+
createMessage(userMessageId, 'user', demoInput),
|
|
31200
|
+
createMessage(assistantMessageId, 'assistant', '', true),
|
|
31201
|
+
]);
|
|
31202
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
31203
|
+
// Stream characters - continues even if widget is closed
|
|
31204
|
+
for (let i = 0; i < chars.length; i++) {
|
|
31205
|
+
currentText += chars[i];
|
|
31206
|
+
setDemoMessages([
|
|
31207
|
+
createMessage(userMessageId, 'user', demoInput),
|
|
31208
|
+
createMessage(assistantMessageId, 'assistant', currentText, i < chars.length - 1),
|
|
31209
|
+
]);
|
|
31210
|
+
const delay = chars[i] === ' ' ? 12 : 20;
|
|
31211
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
31212
|
+
}
|
|
31213
|
+
// STEP 5: Mark demo complete
|
|
31214
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
31215
|
+
setIsDemoComplete(true);
|
|
31216
|
+
setIsDemoActive(false);
|
|
31217
|
+
console.log('[Widget] Demo animation complete');
|
|
31218
|
+
onDemoComplete?.();
|
|
31219
|
+
};
|
|
31220
|
+
runDemoAnimation();
|
|
31221
|
+
}, [demoMode, isOpen, isDemoComplete, demoInput, demoOutput, onDemoComplete, triggerType, config?.appearance?.triggerType, onOpen, createDemoConversation]);
|
|
29817
31222
|
// Determine theme - use prop override if provided, otherwise auto-detect
|
|
29818
31223
|
const appearanceConfig = config?.appearance;
|
|
29819
31224
|
const effectiveTheme = theme ?? autoDetectedTheme;
|
|
@@ -29822,11 +31227,14 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29822
31227
|
// Get accent color from prop or config (empty string means no accent color / vanilla mode)
|
|
29823
31228
|
const accentColor = primaryColor ?? appearanceConfig?.primaryColor ?? '';
|
|
29824
31229
|
// Apply prop overrides for live preview (props take priority over config)
|
|
29825
|
-
size || appearanceConfig?.size || 'small';
|
|
31230
|
+
const effectiveSize = size || appearanceConfig?.size || 'small';
|
|
29826
31231
|
const effectiveHeaderTitle = headerTitle ?? appearanceConfig?.headerTitle ?? '';
|
|
29827
31232
|
const effectiveWelcomeTitle = welcomeTitle ?? appearanceConfig?.welcomeTitle ?? '';
|
|
29828
31233
|
const effectiveWelcomeMessage = welcomeMessage ?? appearanceConfig?.welcomeMessage ?? '';
|
|
29829
31234
|
const effectivePlaceholder = placeholder ?? appearanceConfig?.placeholder ?? '';
|
|
31235
|
+
const effectiveWelcomeBubbleText = welcomeBubbleText ?? appearanceConfig?.welcomeBubbleText ?? '';
|
|
31236
|
+
const effectiveTriggerType = triggerType ?? appearanceConfig?.triggerType ?? 'button';
|
|
31237
|
+
const effectiveTriggerText = triggerText ?? appearanceConfig?.triggerText ?? 'Chat';
|
|
29830
31238
|
// Generate styles using simplified theme system
|
|
29831
31239
|
const simpleAppearance = {
|
|
29832
31240
|
accentColor};
|
|
@@ -29843,18 +31251,65 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29843
31251
|
...customStyles,
|
|
29844
31252
|
...(zIndex !== undefined ? { '--widget-z-index': String(zIndex) } : {}),
|
|
29845
31253
|
};
|
|
31254
|
+
// Dismiss bubble and store based on frequency setting
|
|
31255
|
+
const dismissBubble = () => {
|
|
31256
|
+
setShowWelcomeBubble(false);
|
|
31257
|
+
const frequency = config?.appearance?.welcomeBubbleFrequency ?? 'session';
|
|
31258
|
+
const storageKey = `ai-chat-bubble-dismissed-${widgetId || 'default'}`;
|
|
31259
|
+
try {
|
|
31260
|
+
if (frequency === 'always') {
|
|
31261
|
+
// For 'always', use sessionStorage so it only hides until page refresh
|
|
31262
|
+
sessionStorage.setItem(storageKey, 'true');
|
|
31263
|
+
}
|
|
31264
|
+
else if (frequency === 'session') {
|
|
31265
|
+
sessionStorage.setItem(storageKey, 'true');
|
|
31266
|
+
}
|
|
31267
|
+
else {
|
|
31268
|
+
// For weekly/monthly, store timestamp in localStorage
|
|
31269
|
+
localStorage.setItem(storageKey, String(Date.now()));
|
|
31270
|
+
}
|
|
31271
|
+
}
|
|
31272
|
+
catch {
|
|
31273
|
+
// Ignore storage errors
|
|
31274
|
+
}
|
|
31275
|
+
};
|
|
29846
31276
|
const handleToggle = () => {
|
|
29847
31277
|
if (isEmbedded)
|
|
29848
31278
|
return;
|
|
29849
31279
|
const newState = !isOpen;
|
|
29850
31280
|
setIsOpen(newState);
|
|
31281
|
+
// Dismiss welcome bubble when chat is opened
|
|
31282
|
+
if (newState && showWelcomeBubble) {
|
|
31283
|
+
dismissBubble();
|
|
31284
|
+
}
|
|
29851
31285
|
if (newState) {
|
|
29852
31286
|
onOpen?.();
|
|
29853
31287
|
}
|
|
29854
31288
|
else {
|
|
31289
|
+
// Demo animation continues in background when widget is closed
|
|
31290
|
+
// The conversation was already created at the start, so closing is safe
|
|
31291
|
+
// When user reopens, they'll see the current state of the demo animation
|
|
29855
31292
|
onClose?.();
|
|
29856
31293
|
}
|
|
29857
31294
|
};
|
|
31295
|
+
const handleDismissBubble = (e) => {
|
|
31296
|
+
e.stopPropagation();
|
|
31297
|
+
dismissBubble();
|
|
31298
|
+
};
|
|
31299
|
+
// Handle input bar submit - opens widget and sends message
|
|
31300
|
+
const handleInputBarSubmit = useCallback((e) => {
|
|
31301
|
+
e.preventDefault();
|
|
31302
|
+
if (!inputBarValue.trim() || previewMode)
|
|
31303
|
+
return;
|
|
31304
|
+
// Open the widget
|
|
31305
|
+
setIsOpen(true);
|
|
31306
|
+
onOpen?.();
|
|
31307
|
+
// Send the message after a brief delay to allow widget to open
|
|
31308
|
+
setTimeout(() => {
|
|
31309
|
+
sendMessage(inputBarValue.trim());
|
|
31310
|
+
setInputBarValue('');
|
|
31311
|
+
}, 100);
|
|
31312
|
+
}, [inputBarValue, previewMode, onOpen, sendMessage]);
|
|
29858
31313
|
const handleFeedback = async (messageId, feedback) => {
|
|
29859
31314
|
await submitFeedback(messageId, feedback);
|
|
29860
31315
|
};
|
|
@@ -29870,22 +31325,33 @@ const ChatWidget = ({ widgetId, apiUrl = window.location.origin, previewMode = f
|
|
|
29870
31325
|
sendMessage(actionInstruction);
|
|
29871
31326
|
};
|
|
29872
31327
|
// Don't render until config is loaded to avoid flash of unstyled content
|
|
31328
|
+
// Exception: If we have essential props (triggerType), allow rendering the trigger immediately
|
|
31329
|
+
// This improves perceived loading speed - users see the trigger while config loads
|
|
29873
31330
|
// In preview mode, config is always available
|
|
29874
|
-
|
|
31331
|
+
const canRenderWithoutConfig = !!triggerType;
|
|
31332
|
+
if (!config && !previewMode && !canRenderWithoutConfig) {
|
|
29875
31333
|
return null;
|
|
29876
31334
|
}
|
|
29877
31335
|
// Get button icon based on state
|
|
29878
31336
|
const IconComponent = isOpen ? iconComponents.FiChevronDown : iconComponents.FiMessageCircle;
|
|
29879
31337
|
// Embedded mode renders directly without wrapper positioning
|
|
29880
31338
|
if (isEmbedded) {
|
|
29881
|
-
return (jsx("div", { ref: containerRef, className: `ai-chat-widget ai-chat-widget-embedded ${effectiveTheme}`, style: { ...mergedStyles, width: '100%', height: '100%' }, children: jsx(ChatWindow, { messages: messages, isLoading: isLoading, isTyping: isTyping,
|
|
29882
|
-
}
|
|
29883
|
-
|
|
31339
|
+
return (jsx("div", { ref: containerRef, className: `ai-chat-widget ai-chat-widget-embedded ${effectiveTheme}`, style: { ...mergedStyles, width: '100%', height: '100%' }, children: jsx(ChatWindow, { messages: messages, isLoading: isLoading, isTyping: isTyping, config: config, onSendMessage: sendMessage, onClose: () => { }, onFeedback: handleFeedback, onActionClick: handleActionClick, onActionDismiss: dismissAction, conversations: conversations, onLoadConversations: loadConversations, onSwitchConversation: switchConversation, onStartNewConversation: startNewConversation, onDeleteConversation: deleteConversation, currentConversationId: conversationId, headerTitleOverride: effectiveHeaderTitle, welcomeTitleOverride: effectiveWelcomeTitle, welcomeMessageOverride: effectiveWelcomeMessage, placeholderOverride: effectivePlaceholder, suggestedQuestionsOverride: suggestedQuestions }) }));
|
|
31340
|
+
}
|
|
31341
|
+
// Determine trigger class for container
|
|
31342
|
+
const triggerClass = effectiveTriggerType === 'pill-text'
|
|
31343
|
+
? 'trigger-pill-text'
|
|
31344
|
+
: effectiveTriggerType === 'input-bar'
|
|
31345
|
+
? 'trigger-input-bar'
|
|
31346
|
+
: '';
|
|
31347
|
+
// Size class for CSS targeting (used by input-bar trigger for width matching)
|
|
31348
|
+
const sizeClass = `size-${effectiveSize}`;
|
|
31349
|
+
return (jsx("div", { ref: containerRef, className: `ai-chat-widget ${effectiveTheme}`, style: mergedStyles, children: jsxs("div", { ref: widgetRef, className: `ai-chat-widget-container ${effectivePosition} ${isOpen ? 'is-open' : ''} ${containerMode ? 'container-mode' : ''} ${triggerClass} ${sizeClass}`, children: [isOpen && (jsx(ChatWindow, { messages: demoMode && !isDemoComplete ? demoMessages : messages, isLoading: demoMode && !isDemoComplete ? false : isLoading, isTyping: demoMode && !isDemoComplete ? isDemoTyping : isTyping, config: config, onSendMessage: sendMessage, onClose: handleToggle, onFeedback: handleFeedback, onActionClick: handleActionClick, onActionDismiss: dismissAction,
|
|
29884
31350
|
// Chat history props (only active when persistConversation is true)
|
|
29885
31351
|
conversations: conversations, onLoadConversations: loadConversations, onSwitchConversation: switchConversation, onStartNewConversation: startNewConversation, onDeleteConversation: deleteConversation, currentConversationId: conversationId,
|
|
29886
31352
|
// Override props for live preview
|
|
29887
|
-
headerTitleOverride: effectiveHeaderTitle, welcomeTitleOverride: effectiveWelcomeTitle, welcomeMessageOverride: effectiveWelcomeMessage, placeholderOverride: effectivePlaceholder, suggestedQuestionsOverride: suggestedQuestions })), jsx("button", { className: `ai-chat-button ${isOpen ? 'is-open' : ''}`, onClick: handleToggle, "aria-label": isOpen ? "Minimize chat" : "Open chat", children: jsx("div", { className: "ai-chat-button-svg", children: jsx(IconComponent, {}) }) })] }) }));
|
|
31353
|
+
headerTitleOverride: effectiveHeaderTitle, welcomeTitleOverride: effectiveWelcomeTitle, welcomeMessageOverride: effectiveWelcomeMessage, placeholderOverride: effectivePlaceholder, suggestedQuestionsOverride: suggestedQuestions })), effectiveTriggerType === 'button' && !isOpen && effectiveWelcomeBubbleText && (previewMode || showWelcomeBubble) && (jsxs("div", { className: "ai-chat-welcome-bubble", onClick: handleToggle, children: [jsx("span", { children: effectiveWelcomeBubbleText }), jsx("button", { className: "ai-chat-welcome-bubble-close", onClick: handleDismissBubble, "aria-label": "Dismiss", children: jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) }), jsx("div", { className: "ai-chat-welcome-bubble-arrow" })] })), effectiveTriggerType === 'button' && (jsx("button", { className: `ai-chat-button ${isOpen ? 'is-open' : ''}`, onClick: handleToggle, "aria-label": isOpen ? "Minimize chat" : "Open chat", children: jsx("div", { className: "ai-chat-button-svg", children: jsx(IconComponent, {}) }) })), effectiveTriggerType === 'pill-text' && (jsxs("button", { className: `ai-chat-trigger-pill ${isOpen ? 'is-open' : ''}`, onClick: handleToggle, "aria-label": isOpen ? "Close chat" : "Open chat", children: [jsx("div", { className: "ai-chat-trigger-pill-icon", children: jsx(IconComponent, {}) }), !isOpen && jsx("span", { children: effectiveTriggerText })] })), effectiveTriggerType === 'input-bar' && !isOpen && (jsxs("div", { className: "ai-chat-trigger-input-container", children: [jsx("button", { type: "button", className: "ai-chat-trigger-input-expand", onClick: handleToggle, "aria-label": "Open chat", children: jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "18 15 12 9 6 15" }) }) }), jsxs("form", { className: "ai-chat-trigger-input-wrapper", onSubmit: handleInputBarSubmit, children: [jsx("input", { type: "text", className: "ai-chat-trigger-input", placeholder: effectivePlaceholder || "Ask me anything...", value: isDemoAnimatingInput ? demoInputBarText : inputBarValue, onChange: (e) => !isDemoAnimatingInput && setInputBarValue(e.target.value), readOnly: isDemoAnimatingInput, "aria-label": "Chat input" }), jsx("button", { type: "submit", className: "ai-chat-trigger-input-btn", disabled: isDemoAnimatingInput ? !demoInputBarText.trim() : !inputBarValue.trim(), "aria-label": "Send message", children: jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }), jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })] }) })] })] }))] }) }));
|
|
29888
31354
|
};
|
|
29889
31355
|
|
|
29890
|
-
export { ApiError, ChatWidget, useChat };
|
|
31356
|
+
export { ApiError, ChatWidget, DataPolicyView, useChat };
|
|
29891
31357
|
//# sourceMappingURL=index.esm.js.map
|