@xcelsior/ui-chat 2.0.4 → 2.0.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.
Files changed (46) hide show
  1. package/dist/index.d.mts +58 -5
  2. package/dist/index.d.ts +58 -5
  3. package/dist/index.js +1043 -427
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1010 -397
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +2 -1
  8. package/src/components/BookingCancelledCard.tsx +103 -0
  9. package/src/components/BookingCards.stories.tsx +102 -0
  10. package/src/components/BookingConfirmationCard.tsx +170 -0
  11. package/src/components/BookingSlotPicker.stories.tsx +87 -0
  12. package/src/components/BookingSlotPicker.tsx +256 -0
  13. package/src/components/BrandIcons.stories.tsx +32 -1
  14. package/src/components/BrandIcons.tsx +21 -17
  15. package/src/components/Chat.tsx +43 -9
  16. package/src/components/ChatWidget.tsx +30 -2
  17. package/src/components/MessageItem.tsx +83 -72
  18. package/src/components/MessageList.tsx +4 -0
  19. package/src/hooks/useDraggablePosition.ts +147 -42
  20. package/src/hooks/useMessages.ts +106 -53
  21. package/src/hooks/useWebSocket.ts +17 -4
  22. package/src/index.tsx +11 -0
  23. package/src/types.ts +39 -2
  24. package/src/utils/api.ts +1 -0
  25. package/storybook-static/assets/BookingCancelledCard-XHuB-Ebp.js +31 -0
  26. package/storybook-static/assets/BookingCards.stories-DfJ482RS.js +66 -0
  27. package/storybook-static/assets/BookingSlotPicker-BkfssueW.js +1 -0
  28. package/storybook-static/assets/BookingSlotPicker.stories-fYlg1zLg.js +50 -0
  29. package/storybook-static/assets/BrandIcons-BsRAdWzL.js +4 -0
  30. package/storybook-static/assets/BrandIcons.stories-C6gBovfU.js +106 -0
  31. package/storybook-static/assets/Chat.stories-BrR7LHsz.js +830 -0
  32. package/storybook-static/assets/{Color-YHDXOIA2-CSuNIR0a.js → Color-YHDXOIA2-azE51u2m.js} +1 -1
  33. package/storybook-static/assets/{DocsRenderer-CFRXHY34-dpuOKTQp.js → DocsRenderer-CFRXHY34-jTmzKIDk.js} +3 -3
  34. package/storybook-static/assets/MessageItem-pEOwuLyh.js +34 -0
  35. package/storybook-static/assets/{MessageItem.stories-CsxqSqu-.js → MessageItem.stories-Cs5Vtkle.js} +2 -2
  36. package/storybook-static/assets/{entry-preview-C_-WO6GJ.js → entry-preview-vcpiajAT.js} +1 -1
  37. package/storybook-static/assets/globe-BtMvkLMD.js +31 -0
  38. package/storybook-static/assets/{iframe-BXTccXxS.js → iframe-Cx1n-SeE.js} +2 -2
  39. package/storybook-static/assets/{preview-Cyx3pE7Q.js → preview-Do3b3dZv.js} +2 -2
  40. package/storybook-static/iframe.html +1 -1
  41. package/storybook-static/index.json +1 -1
  42. package/storybook-static/project.json +1 -1
  43. package/storybook-static/assets/BrandIcons-Cjy5INAp.js +0 -4
  44. package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +0 -64
  45. package/storybook-static/assets/Chat.stories-BkbpOOSG.js +0 -830
  46. package/storybook-static/assets/MessageItem-Dlb6dSKL.js +0 -14
package/dist/index.js CHANGED
@@ -30,6 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.tsx
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BookingCancelledCard: () => BookingCancelledCard,
34
+ BookingConfirmationCard: () => BookingConfirmationCard,
35
+ BookingSlotPicker: () => BookingSlotPicker,
33
36
  Chat: () => Chat,
34
37
  ChatHeader: () => ChatHeader,
35
38
  ChatInput: () => ChatInput,
@@ -50,7 +53,7 @@ __export(index_exports, {
50
53
  module.exports = __toCommonJS(index_exports);
51
54
 
52
55
  // src/components/ChatWidget.tsx
53
- var import_react10 = require("react");
56
+ var import_react11 = require("react");
54
57
 
55
58
  // src/hooks/useWebSocket.ts
56
59
  var import_react = require("react");
@@ -63,6 +66,7 @@ function useWebSocket(config, externalWebSocket) {
63
66
  const reconnectAttemptsRef = (0, import_react.useRef)(0);
64
67
  const messageHandlerRef = (0, import_react.useRef)(null);
65
68
  const abortedRef = (0, import_react.useRef)(false);
69
+ const connectionGenerationRef = (0, import_react.useRef)(0);
66
70
  const maxReconnectAttempts = 5;
67
71
  const reconnectDelay = 3e3;
68
72
  const isUsingExternalWs = !!externalWebSocket;
@@ -74,9 +78,7 @@ function useWebSocket(config, externalWebSocket) {
74
78
  try {
75
79
  const message = JSON.parse(event.data);
76
80
  setLastMessage(message);
77
- if (message.type === "message" && message.data) {
78
- config.onMessageReceived?.(message.data);
79
- } else if (message.type === "error") {
81
+ if (message.type === "error") {
80
82
  const err = new Error(message.data?.message || "WebSocket error");
81
83
  setError(err);
82
84
  config.onError?.(err);
@@ -96,6 +98,7 @@ function useWebSocket(config, externalWebSocket) {
96
98
  }, []);
97
99
  const connect = (0, import_react.useCallback)(() => {
98
100
  if (abortedRef.current) return;
101
+ const generation = ++connectionGenerationRef.current;
99
102
  console.log("connecting to WebSocket...", config.currentUser, config.conversationId);
100
103
  try {
101
104
  if (wsRef.current) {
@@ -115,6 +118,7 @@ function useWebSocket(config, externalWebSocket) {
115
118
  }
116
119
  const ws = new WebSocket(url.toString());
117
120
  ws.onopen = () => {
121
+ if (generation !== connectionGenerationRef.current) return;
118
122
  if (abortedRef.current) {
119
123
  ws.close(1e3, "Effect cleaned up");
120
124
  return;
@@ -126,6 +130,7 @@ function useWebSocket(config, externalWebSocket) {
126
130
  config.onConnectionChange?.(true);
127
131
  };
128
132
  ws.onerror = (event) => {
133
+ if (generation !== connectionGenerationRef.current) return;
129
134
  if (abortedRef.current) return;
130
135
  console.error("WebSocket error:", event);
131
136
  const err = new Error("WebSocket connection error");
@@ -133,6 +138,7 @@ function useWebSocket(config, externalWebSocket) {
133
138
  config.onError?.(err);
134
139
  };
135
140
  ws.onclose = (event) => {
141
+ if (generation !== connectionGenerationRef.current) return;
136
142
  if (abortedRef.current) return;
137
143
  console.log("WebSocket closed:", event.code, event.reason);
138
144
  setIsConnected(false);
@@ -234,7 +240,8 @@ async function fetchMessages(baseUrl, params, headers) {
234
240
  headers: {
235
241
  "Content-Type": "application/json",
236
242
  ...headers
237
- }
243
+ },
244
+ timeout: 8e3
238
245
  });
239
246
  return {
240
247
  data: response.data.data ?? [],
@@ -251,7 +258,8 @@ async function fetchMessages(baseUrl, params, headers) {
251
258
  }
252
259
 
253
260
  // src/hooks/useMessages.ts
254
- var BOT_THINKING_TIMEOUT = 45e3;
261
+ var BOT_THINKING_TIMEOUT = 15e3;
262
+ var AGENT_STATUSES = /* @__PURE__ */ new Set(["agent_active", "pending_agent"]);
255
263
  function useMessages(websocket, config) {
256
264
  const [messages, setMessages] = (0, import_react2.useState)([]);
257
265
  const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
@@ -261,6 +269,7 @@ function useMessages(websocket, config) {
261
269
  const [isLoadingMore, setIsLoadingMore] = (0, import_react2.useState)(false);
262
270
  const [isBotThinking, setIsBotThinking] = (0, import_react2.useState)(false);
263
271
  const botThinkingTimerRef = (0, import_react2.useRef)(null);
272
+ const agentActiveRef = (0, import_react2.useRef)(false);
264
273
  const { httpApiUrl, conversationId, headers, onError, toast } = config;
265
274
  const headersWithApiKey = (0, import_react2.useMemo)(
266
275
  () => ({
@@ -269,11 +278,33 @@ function useMessages(websocket, config) {
269
278
  }),
270
279
  [headers, config.apiKey]
271
280
  );
281
+ const clearBotThinking = (0, import_react2.useCallback)(() => {
282
+ setIsBotThinking(false);
283
+ if (botThinkingTimerRef.current) {
284
+ clearTimeout(botThinkingTimerRef.current);
285
+ botThinkingTimerRef.current = null;
286
+ }
287
+ }, []);
272
288
  (0, import_react2.useEffect)(() => {
273
- const loadMessages = async () => {
274
- if (!httpApiUrl || !conversationId) {
275
- return;
289
+ if (!httpApiUrl || !conversationId) return;
290
+ const fetchStatus = async () => {
291
+ try {
292
+ const url = `${httpApiUrl}/conversation?conversationId=${encodeURIComponent(conversationId)}`;
293
+ const res = await fetch(url, { headers: headersWithApiKey });
294
+ if (!res.ok) return;
295
+ const json = await res.json();
296
+ const conv = json.data?.conversation ?? json.data;
297
+ if (conv?.status && AGENT_STATUSES.has(conv.status)) {
298
+ agentActiveRef.current = true;
299
+ }
300
+ } catch {
276
301
  }
302
+ };
303
+ fetchStatus();
304
+ }, [httpApiUrl, conversationId, headersWithApiKey]);
305
+ (0, import_react2.useEffect)(() => {
306
+ if (!httpApiUrl || !conversationId) return;
307
+ const loadMessages = async () => {
277
308
  setIsLoading(true);
278
309
  setError(null);
279
310
  try {
@@ -285,6 +316,9 @@ function useMessages(websocket, config) {
285
316
  setMessages(result.data);
286
317
  setNextPageToken(result.nextPageToken);
287
318
  setHasMore(!!result.nextPageToken);
319
+ if (result.data.some((m) => m.senderType === "agent")) {
320
+ agentActiveRef.current = true;
321
+ }
288
322
  } catch (err) {
289
323
  const error2 = err instanceof Error ? err : new Error("Failed to load messages");
290
324
  setError(error2);
@@ -300,33 +334,55 @@ function useMessages(websocket, config) {
300
334
  (0, import_react2.useEffect)(() => {
301
335
  if (websocket.lastMessage?.type === "message" && websocket.lastMessage.data) {
302
336
  const newMessage = websocket.lastMessage.data;
303
- if (conversationId && newMessage.conversationId !== conversationId) {
304
- return;
305
- }
337
+ if (conversationId && newMessage.conversationId !== conversationId) return;
306
338
  setMessages((prev) => {
307
- if (prev.some((msg) => msg.id === newMessage.id)) {
308
- return prev;
339
+ if (prev.some((msg) => msg.id === newMessage.id)) return prev;
340
+ const newMsgTime = new Date(newMessage.createdAt).getTime();
341
+ const tempIdx = prev.findIndex(
342
+ (msg) => msg.id.startsWith("temp-") && msg.content === newMessage.content && msg.senderId === newMessage.senderId && Math.abs(new Date(msg.createdAt).getTime() - newMsgTime) < 1e4
343
+ );
344
+ if (tempIdx >= 0) {
345
+ const next = [...prev];
346
+ next[tempIdx] = newMessage;
347
+ return next;
309
348
  }
310
349
  return [...prev, newMessage];
311
350
  });
312
- if (newMessage.senderType === "bot" || newMessage.senderType === "system") {
313
- setIsBotThinking(false);
314
- if (botThinkingTimerRef.current) {
315
- clearTimeout(botThinkingTimerRef.current);
316
- botThinkingTimerRef.current = null;
317
- }
351
+ if (newMessage.senderType === "agent") {
352
+ agentActiveRef.current = true;
353
+ }
354
+ if (newMessage.senderType !== "customer") {
355
+ clearBotThinking();
318
356
  }
319
357
  onMessageReceived?.(newMessage);
320
358
  }
321
- }, [websocket.lastMessage, onMessageReceived, conversationId]);
359
+ }, [websocket.lastMessage, onMessageReceived, conversationId, clearBotThinking]);
360
+ (0, import_react2.useEffect)(() => {
361
+ if (!websocket.lastMessage) return;
362
+ const { type, data } = websocket.lastMessage;
363
+ if (type === "bot_not_responding") {
364
+ if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
365
+ clearBotThinking();
366
+ agentActiveRef.current = true;
367
+ }
368
+ if (type === "conversation_updated") {
369
+ if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
370
+ if (data?.status && AGENT_STATUSES.has(data.status)) {
371
+ agentActiveRef.current = true;
372
+ clearBotThinking();
373
+ }
374
+ }
375
+ }, [websocket.lastMessage, conversationId, clearBotThinking]);
322
376
  const addMessage = (0, import_react2.useCallback)((message) => {
377
+ const isAgentActive = agentActiveRef.current;
323
378
  setMessages((prev) => {
324
- if (prev.some((msg) => msg.id === message.id)) {
325
- return prev;
326
- }
379
+ if (prev.some((msg) => msg.id === message.id)) return prev;
327
380
  return [...prev, message];
328
381
  });
329
- if (message.senderType === "customer") {
382
+ if (message.senderType === "agent") {
383
+ agentActiveRef.current = true;
384
+ }
385
+ if (message.senderType === "customer" && !isAgentActive) {
330
386
  setIsBotThinking(true);
331
387
  if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
332
388
  botThinkingTimerRef.current = setTimeout(() => {
@@ -339,21 +395,16 @@ function useMessages(websocket, config) {
339
395
  }, []);
340
396
  const clearMessages = (0, import_react2.useCallback)(() => {
341
397
  setMessages([]);
398
+ agentActiveRef.current = false;
342
399
  }, []);
343
400
  const loadMore = (0, import_react2.useCallback)(async () => {
344
- if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) {
345
- return;
346
- }
401
+ if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) return;
347
402
  setIsLoadingMore(true);
348
403
  setError(null);
349
404
  try {
350
405
  const result = await fetchMessages(
351
406
  httpApiUrl,
352
- {
353
- conversationId,
354
- limit: 20,
355
- pageToken: nextPageToken
356
- },
407
+ { conversationId, limit: 20, pageToken: nextPageToken },
357
408
  headersWithApiKey
358
409
  );
359
410
  setMessages((prev) => [...result.data, ...prev]);
@@ -366,15 +417,7 @@ function useMessages(websocket, config) {
366
417
  } finally {
367
418
  setIsLoadingMore(false);
368
419
  }
369
- }, [
370
- hasMore,
371
- isLoadingMore,
372
- httpApiUrl,
373
- conversationId,
374
- nextPageToken,
375
- headersWithApiKey,
376
- onError
377
- ]);
420
+ }, [hasMore, isLoadingMore, httpApiUrl, conversationId, nextPageToken, headersWithApiKey, onError]);
378
421
  (0, import_react2.useEffect)(() => {
379
422
  return () => {
380
423
  if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
@@ -807,24 +850,20 @@ function XcelsiorSymbol({ size = 24, color = "white", className = "", style }) {
807
850
  }
808
851
  );
809
852
  }
810
- function ChatBubbleIcon({ size = 28, color = "white", className = "", style }) {
811
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
812
- "svg",
813
- {
814
- width: size,
815
- height: size,
816
- viewBox: "0 0 24 24",
817
- fill: "none",
818
- stroke: color,
819
- strokeWidth: 2,
820
- strokeLinecap: "round",
821
- strokeLinejoin: "round",
822
- className,
823
- style,
824
- "aria-hidden": "true",
825
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
826
- }
827
- );
853
+ function ChatBubbleIcon({ size = 36, color = "white", className = "", style }) {
854
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", xmlns: "http://www.w3.org/2000/svg", className, style, "aria-hidden": "true", children: [
855
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "3.5", r: "3", fill: "#67e8f9", opacity: 0.4 }),
856
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "3.5", r: "1.5", fill: "#67e8f9" }),
857
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "15", y: "4.5", width: "2", height: "4", rx: "1", fill: color }),
858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "1", y: "15", width: "4", height: "6", rx: "2", fill: color, opacity: 0.5 }),
859
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "27", y: "15", width: "4", height: "6", rx: "2", fill: color, opacity: 0.5 }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "5", y: "8.5", width: "22", height: "19", rx: "5", fill: color }),
861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "16.5", r: "3", fill: "#1a4fd0" }),
862
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "20", cy: "16.5", r: "3", fill: "#1a4fd0" }),
863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "13", cy: "15.5", r: "1.2", fill: color }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "21", cy: "15.5", r: "1.2", fill: color }),
865
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M13 22.5q3 2.5 6 0", stroke: "#1a4fd0", strokeWidth: "1.5", strokeLinecap: "round" })
866
+ ] });
828
867
  }
829
868
  function XcelsiorAvatar({ size = 40, className = "" }) {
830
869
  const iconSize = Math.round(size * 0.55);
@@ -988,7 +1027,7 @@ function ChatHeader({ agent, onClose, onMinimize, theme }) {
988
1027
  }
989
1028
 
990
1029
  // src/components/MessageList.tsx
991
- var import_react7 = require("react");
1030
+ var import_react8 = require("react");
992
1031
 
993
1032
  // src/components/Spinner.tsx
994
1033
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -1036,6 +1075,7 @@ function Spinner({ size = "md", color = "currentColor" }) {
1036
1075
 
1037
1076
  // src/components/MessageItem.tsx
1038
1077
  var import_date_fns = require("date-fns");
1078
+ var import_lucide_react4 = require("lucide-react");
1039
1079
 
1040
1080
  // src/components/MarkdownMessage.tsx
1041
1081
  var import_react_markdown = __toESM(require("react-markdown"));
@@ -1275,14 +1315,482 @@ function MarkdownMessage({ content, theme }) {
1275
1315
  ) });
1276
1316
  }
1277
1317
 
1278
- // src/components/MessageItem.tsx
1318
+ // src/components/BookingSlotPicker.tsx
1319
+ var import_react6 = require("react");
1320
+ var import_lucide_react = require("lucide-react");
1279
1321
  var import_jsx_runtime5 = require("react/jsx-runtime");
1322
+ function BookingSlotPicker({ data, theme, onSlotSelected, disabled = false }) {
1323
+ const [selectedDateIndex, setSelectedDateIndex] = (0, import_react6.useState)(0);
1324
+ const [selectedSlot, setSelectedSlot] = (0, import_react6.useState)(null);
1325
+ if (!data?.availableDates?.length) return null;
1326
+ const bgColor = theme?.background || "#00001a";
1327
+ const isLightTheme = (() => {
1328
+ if (!bgColor.startsWith("#")) return false;
1329
+ const hex = bgColor.replace("#", "");
1330
+ const r = parseInt(hex.substring(0, 2), 16);
1331
+ const g = parseInt(hex.substring(2, 4), 16);
1332
+ const b = parseInt(hex.substring(4, 6), 16);
1333
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
1334
+ })();
1335
+ const primaryColor = theme?.primary || "#337eff";
1336
+ const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1337
+ const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
1338
+ const cardStyle = isLightTheme ? {
1339
+ backgroundColor: "rgba(0,0,0,0.03)",
1340
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
1341
+ borderRadius: "14px",
1342
+ padding: "14px"
1343
+ } : {
1344
+ backgroundColor: "rgba(255,255,255,0.04)",
1345
+ boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
1346
+ borderRadius: "14px",
1347
+ padding: "14px"
1348
+ };
1349
+ const activePillStyle = {
1350
+ backgroundColor: primaryColor,
1351
+ color: "#ffffff",
1352
+ borderRadius: "20px",
1353
+ padding: "5px 12px",
1354
+ fontSize: "12px",
1355
+ fontWeight: "600",
1356
+ letterSpacing: "0.01em",
1357
+ cursor: disabled ? "default" : "pointer",
1358
+ border: "none",
1359
+ flexShrink: 0
1360
+ };
1361
+ const inactivePillStyle = isLightTheme ? {
1362
+ backgroundColor: "rgba(0,0,0,0.04)",
1363
+ color: textColor,
1364
+ borderRadius: "20px",
1365
+ padding: "5px 12px",
1366
+ fontSize: "12px",
1367
+ fontWeight: "500",
1368
+ letterSpacing: "0.01em",
1369
+ cursor: disabled ? "default" : "pointer",
1370
+ border: "none",
1371
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.1)",
1372
+ flexShrink: 0
1373
+ } : {
1374
+ backgroundColor: "rgba(255,255,255,0.05)",
1375
+ color: textColor,
1376
+ borderRadius: "20px",
1377
+ padding: "5px 12px",
1378
+ fontSize: "12px",
1379
+ fontWeight: "500",
1380
+ letterSpacing: "0.01em",
1381
+ cursor: disabled ? "default" : "pointer",
1382
+ border: "none",
1383
+ boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.1)",
1384
+ flexShrink: 0
1385
+ };
1386
+ const activeSlot = selectedSlot?.date === data.availableDates[selectedDateIndex]?.date ? selectedSlot.time : null;
1387
+ const [confirming, setConfirming] = (0, import_react6.useState)(false);
1388
+ const handleSlotClick = (date, time) => {
1389
+ if (disabled || confirming) return;
1390
+ setSelectedSlot({ date, time });
1391
+ };
1392
+ const handleConfirm = (0, import_react6.useCallback)(() => {
1393
+ if (!selectedSlot || disabled || confirming) return;
1394
+ setConfirming(true);
1395
+ onSlotSelected(selectedSlot.date, selectedSlot.time);
1396
+ }, [selectedSlot, disabled, confirming, onSlotSelected]);
1397
+ const currentDate = data.availableDates[selectedDateIndex];
1398
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: cardStyle, children: [
1399
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1400
+ "p",
1401
+ {
1402
+ style: {
1403
+ fontSize: "11px",
1404
+ fontWeight: "600",
1405
+ letterSpacing: "0.06em",
1406
+ textTransform: "uppercase",
1407
+ color: textMuted,
1408
+ marginBottom: "10px"
1409
+ },
1410
+ children: "Select a date"
1411
+ }
1412
+ ),
1413
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1414
+ "div",
1415
+ {
1416
+ style: {
1417
+ display: "flex",
1418
+ gap: "6px",
1419
+ overflowX: "auto",
1420
+ paddingBottom: "2px",
1421
+ scrollbarWidth: "none",
1422
+ msOverflowStyle: "none",
1423
+ marginBottom: "14px"
1424
+ },
1425
+ children: data.availableDates.map((dateSlot, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1426
+ "button",
1427
+ {
1428
+ type: "button",
1429
+ onClick: () => !disabled && setSelectedDateIndex(index),
1430
+ style: index === selectedDateIndex ? activePillStyle : inactivePillStyle,
1431
+ title: dateSlot.dayName,
1432
+ "aria-pressed": index === selectedDateIndex,
1433
+ disabled,
1434
+ children: dateSlot.dayLabel
1435
+ },
1436
+ dateSlot.date
1437
+ ))
1438
+ }
1439
+ ),
1440
+ currentDate && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1441
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1442
+ "p",
1443
+ {
1444
+ style: {
1445
+ fontSize: "11px",
1446
+ fontWeight: "600",
1447
+ letterSpacing: "0.06em",
1448
+ textTransform: "uppercase",
1449
+ color: textMuted,
1450
+ marginBottom: "8px"
1451
+ },
1452
+ children: currentDate.dayName
1453
+ }
1454
+ ),
1455
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1456
+ "div",
1457
+ {
1458
+ style: {
1459
+ display: "grid",
1460
+ gridTemplateColumns: "repeat(3, 1fr)",
1461
+ gap: "6px",
1462
+ marginBottom: "14px"
1463
+ },
1464
+ children: currentDate.slots.map((time) => {
1465
+ const isSelected = activeSlot === time;
1466
+ const isConfirmed = selectedSlot?.date === currentDate.date && selectedSlot.time === time;
1467
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1468
+ "button",
1469
+ {
1470
+ type: "button",
1471
+ onClick: () => handleSlotClick(currentDate.date, time),
1472
+ disabled: disabled && !isConfirmed,
1473
+ "aria-pressed": isSelected,
1474
+ style: {
1475
+ ...isSelected ? activePillStyle : inactivePillStyle,
1476
+ padding: "7px 8px",
1477
+ borderRadius: "8px",
1478
+ fontSize: "13px",
1479
+ fontWeight: isSelected ? "600" : "400",
1480
+ textAlign: "center",
1481
+ opacity: disabled && !isConfirmed ? 0.4 : 1,
1482
+ transition: "opacity 0.15s ease"
1483
+ },
1484
+ children: time
1485
+ },
1486
+ time
1487
+ );
1488
+ })
1489
+ }
1490
+ )
1491
+ ] }),
1492
+ selectedSlot && !disabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1493
+ "button",
1494
+ {
1495
+ type: "button",
1496
+ onClick: handleConfirm,
1497
+ disabled: confirming,
1498
+ style: {
1499
+ width: "100%",
1500
+ padding: "10px 16px",
1501
+ borderRadius: "10px",
1502
+ border: "none",
1503
+ backgroundColor: confirming ? `${primaryColor}80` : primaryColor,
1504
+ color: "#ffffff",
1505
+ fontSize: "13px",
1506
+ fontWeight: "600",
1507
+ letterSpacing: "0.01em",
1508
+ cursor: confirming ? "default" : "pointer",
1509
+ marginBottom: "12px",
1510
+ display: "flex",
1511
+ alignItems: "center",
1512
+ justifyContent: "center",
1513
+ gap: "6px",
1514
+ transition: "background-color 0.15s ease"
1515
+ },
1516
+ children: [
1517
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Calendar, { size: 14, "aria-hidden": "true" }),
1518
+ confirming ? "Booking..." : `Book ${selectedSlot.time} on ${data.availableDates.find((d) => d.date === selectedSlot.date)?.dayLabel ?? selectedSlot.date}`
1519
+ ]
1520
+ }
1521
+ ),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1523
+ "div",
1524
+ {
1525
+ style: {
1526
+ display: "flex",
1527
+ alignItems: "center",
1528
+ justifyContent: "space-between",
1529
+ gap: "8px"
1530
+ },
1531
+ children: [
1532
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
1533
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Globe, { size: 11, color: textMuted, "aria-hidden": "true" }),
1534
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
1535
+ ] }),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
1537
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Clock, { size: 11, color: textMuted, "aria-hidden": "true" }),
1538
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: [
1539
+ data.meetingDuration,
1540
+ " min"
1541
+ ] })
1542
+ ] })
1543
+ ]
1544
+ }
1545
+ )
1546
+ ] });
1547
+ }
1548
+
1549
+ // src/components/BookingConfirmationCard.tsx
1550
+ var import_lucide_react2 = require("lucide-react");
1551
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1552
+ function BookingConfirmationCard({ data, theme }) {
1553
+ const bgColor = theme?.background || "#00001a";
1554
+ const isLightTheme = (() => {
1555
+ if (!bgColor.startsWith("#")) return false;
1556
+ const hex = bgColor.replace("#", "");
1557
+ const r = parseInt(hex.substring(0, 2), 16);
1558
+ const g = parseInt(hex.substring(2, 4), 16);
1559
+ const b = parseInt(hex.substring(4, 6), 16);
1560
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
1561
+ })();
1562
+ const primaryColor = theme?.primary || "#337eff";
1563
+ const successColor = theme?.statusPositive || "#22c55e";
1564
+ const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1565
+ const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
1566
+ const cardStyle = isLightTheme ? {
1567
+ backgroundColor: "rgba(0,0,0,0.03)",
1568
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
1569
+ borderRadius: "14px",
1570
+ padding: "16px",
1571
+ overflow: "hidden"
1572
+ } : {
1573
+ backgroundColor: "rgba(255,255,255,0.04)",
1574
+ boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
1575
+ borderRadius: "14px",
1576
+ padding: "16px",
1577
+ overflow: "hidden"
1578
+ };
1579
+ const detailRowStyle = {
1580
+ display: "flex",
1581
+ alignItems: "flex-start",
1582
+ gap: "8px",
1583
+ marginBottom: "8px"
1584
+ };
1585
+ const dividerStyle = {
1586
+ height: "1px",
1587
+ backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)",
1588
+ margin: "12px 0"
1589
+ };
1590
+ const actionButtonBase = {
1591
+ display: "flex",
1592
+ alignItems: "center",
1593
+ justifyContent: "center",
1594
+ gap: "6px",
1595
+ flex: 1,
1596
+ padding: "9px 12px",
1597
+ borderRadius: "9px",
1598
+ fontSize: "12px",
1599
+ fontWeight: "600",
1600
+ letterSpacing: "0.01em",
1601
+ cursor: "pointer",
1602
+ textDecoration: "none",
1603
+ border: "none",
1604
+ transition: "opacity 0.15s ease"
1605
+ };
1606
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: cardStyle, children: [
1607
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
1608
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.CircleCheck, { size: 22, color: successColor, "aria-hidden": "true" }),
1609
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1610
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1611
+ "p",
1612
+ {
1613
+ style: {
1614
+ fontSize: "14px",
1615
+ fontWeight: "700",
1616
+ color: textColor,
1617
+ letterSpacing: "0.006em",
1618
+ lineHeight: 1.3
1619
+ },
1620
+ children: "Meeting Confirmed"
1621
+ }
1622
+ ),
1623
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { fontSize: "11px", color: successColor, letterSpacing: "0.01em", marginTop: "1px" }, children: "You'll receive a calendar invite shortly" })
1624
+ ] })
1625
+ ] }),
1626
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1627
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: detailRowStyle, children: [
1628
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Calendar, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1629
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
1630
+ data.meetingDate,
1631
+ " at ",
1632
+ data.meetingTime
1633
+ ] })
1634
+ ] }),
1635
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: detailRowStyle, children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Clock, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1637
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
1638
+ data.meetingDuration,
1639
+ " minutes"
1640
+ ] })
1641
+ ] }),
1642
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
1643
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.MessageSquare, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1644
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: data.meetingPurpose })
1645
+ ] })
1646
+ ] }),
1647
+ data.timezone && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px", marginTop: "8px" }, children: [
1648
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Globe, { size: 11, color: textMuted, "aria-hidden": "true" }),
1649
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
1650
+ ] }),
1651
+ (data.meetLink || data.calendarLink) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1652
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dividerStyle }),
1653
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1654
+ data.meetLink && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1655
+ "a",
1656
+ {
1657
+ href: data.meetLink,
1658
+ target: "_blank",
1659
+ rel: "noopener noreferrer",
1660
+ style: {
1661
+ ...actionButtonBase,
1662
+ background: `linear-gradient(135deg, ${primaryColor}, ${theme?.primaryStrong || "#005eff"})`,
1663
+ color: "#ffffff",
1664
+ boxShadow: `0 2px 8px -2px ${primaryColor}50`
1665
+ },
1666
+ children: [
1667
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Video, { size: 13, "aria-hidden": "true" }),
1668
+ "Join Meet"
1669
+ ]
1670
+ }
1671
+ ),
1672
+ data.calendarLink && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1673
+ "a",
1674
+ {
1675
+ href: data.calendarLink,
1676
+ target: "_blank",
1677
+ rel: "noopener noreferrer",
1678
+ style: {
1679
+ ...actionButtonBase,
1680
+ backgroundColor: isLightTheme ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.07)",
1681
+ color: textColor,
1682
+ boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.1)" : "inset 0 0 0 0.5px rgba(255,255,255,0.12)"
1683
+ },
1684
+ children: [
1685
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.CalendarPlus, { size: 13, "aria-hidden": "true" }),
1686
+ "Add to Calendar"
1687
+ ]
1688
+ }
1689
+ )
1690
+ ] })
1691
+ ] })
1692
+ ] });
1693
+ }
1694
+
1695
+ // src/components/BookingCancelledCard.tsx
1696
+ var import_lucide_react3 = require("lucide-react");
1697
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1698
+ function BookingCancelledCard({ data, theme }) {
1699
+ const bgColor = theme?.background || "#00001a";
1700
+ const isLightTheme = (() => {
1701
+ if (!bgColor.startsWith("#")) return false;
1702
+ const hex = bgColor.replace("#", "");
1703
+ const r = parseInt(hex.substring(0, 2), 16);
1704
+ const g = parseInt(hex.substring(2, 4), 16);
1705
+ const b = parseInt(hex.substring(4, 6), 16);
1706
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
1707
+ })();
1708
+ const negativeColor = theme?.statusNegative || "#ef4444";
1709
+ const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1710
+ const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
1711
+ const cardStyle = isLightTheme ? {
1712
+ backgroundColor: "rgba(239,68,68,0.04)",
1713
+ boxShadow: "inset 0 0 0 1px rgba(239,68,68,0.12)",
1714
+ borderRadius: "14px",
1715
+ padding: "16px"
1716
+ } : {
1717
+ backgroundColor: "rgba(239,68,68,0.06)",
1718
+ boxShadow: "inset 0 0 0 0.5px rgba(239,68,68,0.18)",
1719
+ borderRadius: "14px",
1720
+ padding: "16px"
1721
+ };
1722
+ const strikethroughTextStyle = {
1723
+ fontSize: "13px",
1724
+ color: textMuted,
1725
+ lineHeight: 1.4,
1726
+ textDecoration: "line-through",
1727
+ textDecorationColor: isLightTheme ? "rgba(0,0,0,0.25)" : "rgba(255,255,255,0.2)"
1728
+ };
1729
+ const detailRowStyle = {
1730
+ display: "flex",
1731
+ alignItems: "flex-start",
1732
+ gap: "8px",
1733
+ marginBottom: "8px"
1734
+ };
1735
+ const cancelledByLabel = data.cancelledBy === "visitor" ? "Cancelled by you" : "Cancelled by support";
1736
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: cardStyle, children: [
1737
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
1738
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.CircleX, { size: 22, color: negativeColor, "aria-hidden": "true" }),
1739
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1740
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1741
+ "p",
1742
+ {
1743
+ style: {
1744
+ fontSize: "14px",
1745
+ fontWeight: "700",
1746
+ color: textColor,
1747
+ letterSpacing: "0.006em",
1748
+ lineHeight: 1.3
1749
+ },
1750
+ children: "Meeting Cancelled"
1751
+ }
1752
+ ),
1753
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1754
+ "p",
1755
+ {
1756
+ style: {
1757
+ fontSize: "11px",
1758
+ color: negativeColor,
1759
+ letterSpacing: "0.01em",
1760
+ marginTop: "1px",
1761
+ opacity: 0.8
1762
+ },
1763
+ children: cancelledByLabel
1764
+ }
1765
+ )
1766
+ ] })
1767
+ ] }),
1768
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: detailRowStyle, children: [
1770
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Calendar, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1771
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: strikethroughTextStyle, children: [
1772
+ data.meetingDate,
1773
+ " at ",
1774
+ data.meetingTime
1775
+ ] })
1776
+ ] }),
1777
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
1778
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.MessageSquare, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1779
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: strikethroughTextStyle, children: data.meetingPurpose })
1780
+ ] })
1781
+ ] })
1782
+ ] });
1783
+ }
1784
+
1785
+ // src/components/MessageItem.tsx
1786
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1280
1787
  function MessageItem({
1281
1788
  message,
1282
1789
  currentUser,
1283
1790
  showAvatar = true,
1284
1791
  showTimestamp = true,
1285
- theme
1792
+ theme,
1793
+ onBookingSlotSelected
1286
1794
  }) {
1287
1795
  const isOwnMessage = message.senderType === currentUser.type;
1288
1796
  const isSystemMessage = message.senderType === "system";
@@ -1301,8 +1809,63 @@ function MessageItem({
1301
1809
  const primaryStrong = theme?.primaryStrong || "#005eff";
1302
1810
  const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1303
1811
  const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.35)");
1812
+ if (message.messageType === "booking_slots" || message.messageType === "booking_confirmation" || message.messageType === "booking_cancelled") {
1813
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-2.5 mb-3 flex-row", children: [
1814
+ showAvatar && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(XcelsiorAvatar, { size: 28 }) }),
1815
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col max-w-[85%] items-start", children: [
1816
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1817
+ "span",
1818
+ {
1819
+ className: "mb-1 px-1 font-medium",
1820
+ style: {
1821
+ color: isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)",
1822
+ fontSize: "11px",
1823
+ letterSpacing: "0.019em"
1824
+ },
1825
+ children: "AI Assistant"
1826
+ }
1827
+ ),
1828
+ message.messageType === "booking_slots" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1829
+ BookingSlotPicker,
1830
+ {
1831
+ data: message.metadata?.bookingData,
1832
+ theme,
1833
+ onSlotSelected: (date, time) => {
1834
+ onBookingSlotSelected?.(date, time, message.id);
1835
+ },
1836
+ disabled: !!message.metadata?.slotSelected
1837
+ }
1838
+ ),
1839
+ message.messageType === "booking_confirmation" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1840
+ BookingConfirmationCard,
1841
+ {
1842
+ data: message.metadata?.bookingConfirmation,
1843
+ theme
1844
+ }
1845
+ ),
1846
+ message.messageType === "booking_cancelled" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1847
+ BookingCancelledCard,
1848
+ {
1849
+ data: message.metadata?.bookingCancelled,
1850
+ theme
1851
+ }
1852
+ ),
1853
+ showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex items-center gap-1.5 mt-1 px-1", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1854
+ "span",
1855
+ {
1856
+ style: {
1857
+ fontSize: "11px",
1858
+ letterSpacing: "0.019em",
1859
+ color: textMuted
1860
+ },
1861
+ children: (0, import_date_fns.formatDistanceToNow)(new Date(message.createdAt), { addSuffix: true })
1862
+ }
1863
+ ) })
1864
+ ] })
1865
+ ] });
1866
+ }
1304
1867
  if (isSystemMessage) {
1305
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1868
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1306
1869
  "div",
1307
1870
  {
1308
1871
  className: "px-4 py-1.5 rounded-full",
@@ -1310,7 +1873,7 @@ function MessageItem({
1310
1873
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.03)",
1311
1874
  boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.06)" : "inset 0 0 0 0.5px rgba(255,255,255,0.06)"
1312
1875
  },
1313
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1876
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1314
1877
  "p",
1315
1878
  {
1316
1879
  style: {
@@ -1349,12 +1912,12 @@ function MessageItem({
1349
1912
  borderRadius: "18px 18px 18px 4px",
1350
1913
  boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.06), inset 0 1px 0 0 rgba(255,255,255,0.08)"
1351
1914
  };
1352
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1915
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1353
1916
  "div",
1354
1917
  {
1355
1918
  className: `flex gap-2.5 mb-3 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
1356
1919
  children: [
1357
- showAvatar && !isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: isBotMessage || isAIMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XcelsiorAvatar, { size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1920
+ showAvatar && !isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: isBotMessage || isAIMessage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(XcelsiorAvatar, { size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1358
1921
  "div",
1359
1922
  {
1360
1923
  className: "h-7 w-7 rounded-full flex items-center justify-center",
@@ -1362,34 +1925,24 @@ function MessageItem({
1362
1925
  background: isLightTheme ? `linear-gradient(135deg, ${primaryColor}30, rgba(0,0,0,0.04))` : `linear-gradient(135deg, ${primaryColor}60, rgba(255,255,255,0.06))`,
1363
1926
  boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.08)" : "inset 0 0 0 0.5px rgba(255,255,255,0.1)"
1364
1927
  },
1365
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1366
- "svg",
1928
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1929
+ import_lucide_react4.Headphones,
1367
1930
  {
1368
- width: "14",
1369
- height: "14",
1370
- viewBox: "0 0 24 24",
1371
- fill: "none",
1931
+ size: 14,
1372
1932
  stroke: isLightTheme ? primaryColor : "white",
1373
- strokeWidth: "2",
1374
- strokeLinecap: "round",
1375
- strokeLinejoin: "round",
1376
- "aria-hidden": "true",
1377
- children: [
1378
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Agent" }),
1379
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3 18v-6a9 9 0 0 1 18 0v6" }),
1380
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" })
1381
- ]
1933
+ strokeWidth: 2,
1934
+ "aria-hidden": "true"
1382
1935
  }
1383
1936
  )
1384
1937
  }
1385
1938
  ) }),
1386
- showAvatar && isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-7 flex-shrink-0" }),
1387
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1939
+ showAvatar && isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-7 flex-shrink-0" }),
1940
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1388
1941
  "div",
1389
1942
  {
1390
1943
  className: `flex flex-col max-w-[75%] ${isOwnMessage ? "items-end" : "items-start"}`,
1391
1944
  children: [
1392
- senderLabel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1945
+ senderLabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1393
1946
  "span",
1394
1947
  {
1395
1948
  className: "mb-1 px-1 font-medium",
@@ -1401,7 +1954,7 @@ function MessageItem({
1401
1954
  children: senderLabel
1402
1955
  }
1403
1956
  ),
1404
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1957
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1405
1958
  "div",
1406
1959
  {
1407
1960
  className: "px-4 py-2.5",
@@ -1414,12 +1967,12 @@ function MessageItem({
1414
1967
  children: [
1415
1968
  message.messageType === "text" && (isOwnMessage ? (
1416
1969
  // User messages: plain text — no markdown parsing
1417
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
1970
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
1418
1971
  ) : (
1419
1972
  // Bot / agent messages: full markdown rendering
1420
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownMessage, { content: message.content, theme })
1973
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownMessage, { content: message.content, theme })
1421
1974
  )),
1422
- message.messageType === "image" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1975
+ message.messageType === "image" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1423
1976
  "img",
1424
1977
  {
1425
1978
  src: message.content,
@@ -1428,26 +1981,9 @@ function MessageItem({
1428
1981
  loading: "lazy"
1429
1982
  }
1430
1983
  ) }),
1431
- message.messageType === "file" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
1432
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1433
- "svg",
1434
- {
1435
- width: "18",
1436
- height: "18",
1437
- viewBox: "0 0 24 24",
1438
- fill: "none",
1439
- stroke: "currentColor",
1440
- strokeWidth: "1.75",
1441
- strokeLinecap: "round",
1442
- strokeLinejoin: "round",
1443
- "aria-hidden": "true",
1444
- children: [
1445
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "File" }),
1446
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" })
1447
- ]
1448
- }
1449
- ),
1450
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1984
+ message.messageType === "file" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [
1985
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Paperclip, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" }),
1986
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1451
1987
  "a",
1452
1988
  {
1453
1989
  href: message.content,
@@ -1465,12 +2001,12 @@ function MessageItem({
1465
2001
  ]
1466
2002
  }
1467
2003
  ),
1468
- showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2004
+ showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1469
2005
  "div",
1470
2006
  {
1471
2007
  className: `flex items-center gap-1.5 mt-1 px-1 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
1472
2008
  children: [
1473
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2009
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1474
2010
  "span",
1475
2011
  {
1476
2012
  style: {
@@ -1483,63 +2019,10 @@ function MessageItem({
1483
2019
  })
1484
2020
  }
1485
2021
  ),
1486
- isOwnMessage && message.status && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: textMuted }, children: [
1487
- message.status === "sent" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1488
- "svg",
1489
- {
1490
- width: "14",
1491
- height: "14",
1492
- viewBox: "0 0 24 24",
1493
- fill: "none",
1494
- stroke: "currentColor",
1495
- strokeWidth: "2.5",
1496
- strokeLinecap: "round",
1497
- strokeLinejoin: "round",
1498
- "aria-hidden": "true",
1499
- children: [
1500
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Sent" }),
1501
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "20 6 9 17 4 12" })
1502
- ]
1503
- }
1504
- ),
1505
- message.status === "delivered" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1506
- "svg",
1507
- {
1508
- width: "14",
1509
- height: "14",
1510
- viewBox: "0 0 24 24",
1511
- fill: "none",
1512
- stroke: "currentColor",
1513
- strokeWidth: "2.5",
1514
- strokeLinecap: "round",
1515
- strokeLinejoin: "round",
1516
- "aria-hidden": "true",
1517
- children: [
1518
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Delivered" }),
1519
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "18 6 7 17 2 12" }),
1520
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "22 6 11 17" })
1521
- ]
1522
- }
1523
- ),
1524
- message.status === "read" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1525
- "svg",
1526
- {
1527
- width: "14",
1528
- height: "14",
1529
- viewBox: "0 0 24 24",
1530
- fill: "none",
1531
- stroke: primaryColor,
1532
- strokeWidth: "2.5",
1533
- strokeLinecap: "round",
1534
- strokeLinejoin: "round",
1535
- "aria-hidden": "true",
1536
- children: [
1537
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Read" }),
1538
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "18 6 7 17 2 12" }),
1539
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "22 6 11 17" })
1540
- ]
1541
- }
1542
- )
2022
+ isOwnMessage && message.status && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { color: textMuted }, children: [
2023
+ message.status === "sent" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Check, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
2024
+ message.status === "delivered" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.CheckCheck, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
2025
+ message.status === "read" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.CheckCheck, { size: 14, strokeWidth: 2.5, stroke: primaryColor, "aria-hidden": "true" })
1543
2026
  ] })
1544
2027
  ]
1545
2028
  }
@@ -1553,8 +2036,8 @@ function MessageItem({
1553
2036
  }
1554
2037
 
1555
2038
  // src/components/ThinkingIndicator.tsx
1556
- var import_react6 = require("react");
1557
- var import_jsx_runtime6 = require("react/jsx-runtime");
2039
+ var import_react7 = require("react");
2040
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1558
2041
  var PHRASE_POOLS = {
1559
2042
  // ── Greetings & small talk ──
1560
2043
  greeting: [
@@ -1737,17 +2220,17 @@ function ThinkingIndicator({
1737
2220
  const isLightTheme = computeIsLightTheme2(theme?.background);
1738
2221
  const primaryColor = theme?.primary || "#337eff";
1739
2222
  const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
1740
- const [phraseIndex, setPhraseIndex] = (0, import_react6.useState)(0);
1741
- const [displayText, setDisplayText] = (0, import_react6.useState)("");
1742
- const [isDeleting, setIsDeleting] = (0, import_react6.useState)(false);
1743
- const phrasesRef = (0, import_react6.useRef)(getShuffledPhrases(detectContext(lastUserMessage)));
1744
- (0, import_react6.useEffect)(() => {
2223
+ const [phraseIndex, setPhraseIndex] = (0, import_react7.useState)(0);
2224
+ const [displayText, setDisplayText] = (0, import_react7.useState)("");
2225
+ const [isDeleting, setIsDeleting] = (0, import_react7.useState)(false);
2226
+ const phrasesRef = (0, import_react7.useRef)(getShuffledPhrases(detectContext(lastUserMessage)));
2227
+ (0, import_react7.useEffect)(() => {
1745
2228
  phrasesRef.current = getShuffledPhrases(detectContext(lastUserMessage));
1746
2229
  setPhraseIndex(0);
1747
2230
  setDisplayText("");
1748
2231
  setIsDeleting(false);
1749
2232
  }, [lastUserMessage]);
1750
- (0, import_react6.useEffect)(() => {
2233
+ (0, import_react7.useEffect)(() => {
1751
2234
  const phrases = phrasesRef.current;
1752
2235
  const phrase = phrases[phraseIndex % phrases.length];
1753
2236
  let timeout;
@@ -1773,7 +2256,7 @@ function ThinkingIndicator({
1773
2256
  }
1774
2257
  return () => clearTimeout(timeout);
1775
2258
  }, [displayText, isDeleting, phraseIndex]);
1776
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2259
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1777
2260
  "div",
1778
2261
  {
1779
2262
  className: "flex gap-2.5 mb-3",
@@ -1781,8 +2264,8 @@ function ThinkingIndicator({
1781
2264
  "aria-live": "polite",
1782
2265
  "aria-label": "Xcelsior is thinking",
1783
2266
  children: [
1784
- showAvatar && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(XcelsiorAvatar, { size: 28 }) }),
1785
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-col items-start", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2267
+ showAvatar && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(XcelsiorAvatar, { size: 28 }) }),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-col items-start", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1786
2269
  "div",
1787
2270
  {
1788
2271
  style: {
@@ -1792,8 +2275,8 @@ function ThinkingIndicator({
1792
2275
  padding: "12px 16px",
1793
2276
  minWidth: 160
1794
2277
  },
1795
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1796
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2278
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
2279
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1797
2280
  "span",
1798
2281
  {
1799
2282
  style: {
@@ -1807,7 +2290,7 @@ function ThinkingIndicator({
1807
2290
  },
1808
2291
  i
1809
2292
  )) }),
1810
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2293
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1811
2294
  "span",
1812
2295
  {
1813
2296
  style: {
@@ -1818,7 +2301,7 @@ function ThinkingIndicator({
1818
2301
  },
1819
2302
  children: [
1820
2303
  displayText,
1821
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2304
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1822
2305
  "span",
1823
2306
  {
1824
2307
  style: {
@@ -1844,7 +2327,7 @@ function ThinkingIndicator({
1844
2327
  }
1845
2328
 
1846
2329
  // src/components/MessageList.tsx
1847
- var import_jsx_runtime7 = require("react/jsx-runtime");
2330
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1848
2331
  function MessageList({
1849
2332
  messages,
1850
2333
  currentUser,
@@ -1857,15 +2340,16 @@ function MessageList({
1857
2340
  isLoadingMore = false,
1858
2341
  theme,
1859
2342
  onQuickAction,
1860
- isBotThinking = false
2343
+ isBotThinking = false,
2344
+ onBookingSlotSelected
1861
2345
  }) {
1862
- const messagesEndRef = (0, import_react7.useRef)(null);
1863
- const containerRef = (0, import_react7.useRef)(null);
1864
- const prevLengthRef = (0, import_react7.useRef)(messages.length);
1865
- const loadMoreTriggerRef = (0, import_react7.useRef)(null);
1866
- const prevScrollHeightRef = (0, import_react7.useRef)(0);
1867
- const hasInitialScrolledRef = (0, import_react7.useRef)(false);
1868
- const isUserScrollingRef = (0, import_react7.useRef)(false);
2346
+ const messagesEndRef = (0, import_react8.useRef)(null);
2347
+ const containerRef = (0, import_react8.useRef)(null);
2348
+ const prevLengthRef = (0, import_react8.useRef)(messages.length);
2349
+ const loadMoreTriggerRef = (0, import_react8.useRef)(null);
2350
+ const prevScrollHeightRef = (0, import_react8.useRef)(0);
2351
+ const hasInitialScrolledRef = (0, import_react8.useRef)(false);
2352
+ const isUserScrollingRef = (0, import_react8.useRef)(false);
1869
2353
  const bgColor = theme?.background || "#00001a";
1870
2354
  const isLightTheme = (() => {
1871
2355
  if (!bgColor.startsWith("#")) return false;
@@ -1878,7 +2362,7 @@ function MessageList({
1878
2362
  const primaryColor = theme?.primary || "#337eff";
1879
2363
  const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1880
2364
  const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
1881
- (0, import_react7.useEffect)(() => {
2365
+ (0, import_react8.useEffect)(() => {
1882
2366
  if (autoScroll && messagesEndRef.current) {
1883
2367
  if (messages.length > prevLengthRef.current && !isLoadingMore) {
1884
2368
  messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
@@ -1886,7 +2370,7 @@ function MessageList({
1886
2370
  prevLengthRef.current = messages.length;
1887
2371
  }
1888
2372
  }, [messages.length, autoScroll, isLoadingMore]);
1889
- (0, import_react7.useEffect)(() => {
2373
+ (0, import_react8.useEffect)(() => {
1890
2374
  if (messages.length > 0 && messagesEndRef.current && !isLoading && !hasInitialScrolledRef.current) {
1891
2375
  setTimeout(() => {
1892
2376
  messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
@@ -1900,7 +2384,7 @@ function MessageList({
1900
2384
  hasInitialScrolledRef.current = true;
1901
2385
  }
1902
2386
  }, [isLoading, messages.length]);
1903
- (0, import_react7.useEffect)(() => {
2387
+ (0, import_react8.useEffect)(() => {
1904
2388
  if (isLoadingMore) {
1905
2389
  prevScrollHeightRef.current = containerRef.current?.scrollHeight || 0;
1906
2390
  } else if (prevScrollHeightRef.current > 0 && containerRef.current) {
@@ -1910,7 +2394,7 @@ function MessageList({
1910
2394
  prevScrollHeightRef.current = 0;
1911
2395
  }
1912
2396
  }, [isLoadingMore]);
1913
- const handleScroll = (0, import_react7.useCallback)(() => {
2397
+ const handleScroll = (0, import_react8.useCallback)(() => {
1914
2398
  if (!containerRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
1915
2399
  if (!isUserScrollingRef.current) return;
1916
2400
  const { scrollTop } = containerRef.current;
@@ -1918,18 +2402,18 @@ function MessageList({
1918
2402
  onLoadMore();
1919
2403
  }
1920
2404
  }, [onLoadMore, hasMore, isLoadingMore]);
1921
- (0, import_react7.useEffect)(() => {
2405
+ (0, import_react8.useEffect)(() => {
1922
2406
  const container = containerRef.current;
1923
2407
  if (!container) return;
1924
2408
  container.addEventListener("scroll", handleScroll);
1925
2409
  return () => container.removeEventListener("scroll", handleScroll);
1926
2410
  }, [handleScroll]);
1927
2411
  if (isLoading) {
1928
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Spinner, { size: "lg" }) });
2412
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, { size: "lg" }) });
1929
2413
  }
1930
2414
  if (messages.length === 0) {
1931
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col items-center justify-center h-full text-center", style: { padding: "40px 32px" }, children: [
1932
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2415
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-center justify-center h-full text-center", style: { padding: "40px 32px" }, children: [
2416
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1933
2417
  "h3",
1934
2418
  {
1935
2419
  className: "font-semibold mb-2",
@@ -1941,7 +2425,7 @@ function MessageList({
1941
2425
  children: "How can we help?"
1942
2426
  }
1943
2427
  ),
1944
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2428
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1945
2429
  "p",
1946
2430
  {
1947
2431
  className: "max-w-[240px]",
@@ -1955,11 +2439,11 @@ function MessageList({
1955
2439
  children: "Ask us anything. We are here to help you get the most out of Xcelsior."
1956
2440
  }
1957
2441
  ),
1958
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex flex-wrap justify-center gap-2", children: [
2442
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap justify-center gap-2", children: [
1959
2443
  { label: "Our services", message: "What services does Xcelsior offer?" },
1960
2444
  { label: "Get a quote", message: "I would like to get a quote for a project" },
1961
2445
  { label: "Support", message: "I need help with something" }
1962
- ].map((action) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2446
+ ].map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1963
2447
  "button",
1964
2448
  {
1965
2449
  type: "button",
@@ -1991,14 +2475,14 @@ function MessageList({
1991
2475
  )) })
1992
2476
  ] });
1993
2477
  }
1994
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2478
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1995
2479
  "div",
1996
2480
  {
1997
2481
  ref: containerRef,
1998
2482
  className: "flex-1 overflow-y-auto px-4 py-3",
1999
2483
  style: { scrollBehavior: "smooth" },
2000
2484
  children: [
2001
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
2485
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `
2002
2486
  @keyframes thinkingPulse {
2003
2487
  0%, 60%, 100% { opacity: 0.3; transform: scale(0.8); }
2004
2488
  30% { opacity: 1; transform: scale(1); }
@@ -2008,23 +2492,24 @@ function MessageList({
2008
2492
  50% { opacity: 0; }
2009
2493
  }
2010
2494
  ` }),
2011
- isLoadingMore && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex justify-center py-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Spinner, { size: "sm" }) }),
2012
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: loadMoreTriggerRef }),
2013
- messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2495
+ isLoadingMore && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex justify-center py-3", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, { size: "sm" }) }),
2496
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: loadMoreTriggerRef }),
2497
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2014
2498
  MessageItem,
2015
2499
  {
2016
2500
  message,
2017
2501
  currentUser,
2018
2502
  showAvatar: true,
2019
2503
  showTimestamp: true,
2020
- theme
2504
+ theme,
2505
+ onBookingSlotSelected
2021
2506
  },
2022
2507
  message.id
2023
2508
  )),
2024
- isTyping && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex gap-2.5 mb-3", children: [
2025
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(XcelsiorAvatar, { size: 28 }) }),
2026
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col items-start", children: [
2027
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2509
+ isTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-2.5 mb-3", children: [
2510
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(XcelsiorAvatar, { size: 28 }) }),
2511
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-start", children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2028
2513
  "div",
2029
2514
  {
2030
2515
  className: "px-4 py-3",
@@ -2033,7 +2518,7 @@ function MessageList({
2033
2518
  borderRadius: "18px 18px 18px 4px",
2034
2519
  boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.06)" : "inset 0 0 0 0.5px rgba(255,255,255,0.06), inset 0 1px 0 0 rgba(255,255,255,0.08)"
2035
2520
  },
2036
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex gap-1.5 items-center", style: { height: 16 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2521
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex gap-1.5 items-center", style: { height: 16 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2037
2522
  "span",
2038
2523
  {
2039
2524
  className: "rounded-full animate-bounce",
@@ -2049,7 +2534,7 @@ function MessageList({
2049
2534
  )) })
2050
2535
  }
2051
2536
  ),
2052
- typingUser && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2537
+ typingUser && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2053
2538
  "span",
2054
2539
  {
2055
2540
  className: "mt-1 px-1",
@@ -2066,24 +2551,24 @@ function MessageList({
2066
2551
  )
2067
2552
  ] })
2068
2553
  ] }),
2069
- isBotThinking && !isTyping && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2554
+ isBotThinking && !isTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2070
2555
  ThinkingIndicator,
2071
2556
  {
2072
2557
  theme,
2073
2558
  lastUserMessage: messages.filter((m) => m.senderType === "customer").pop()?.content
2074
2559
  }
2075
2560
  ),
2076
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: messagesEndRef })
2561
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
2077
2562
  ]
2078
2563
  }
2079
2564
  );
2080
2565
  }
2081
2566
 
2082
2567
  // src/components/ChatInput.tsx
2083
- var import_react8 = require("react");
2568
+ var import_react9 = require("react");
2084
2569
  var import_react_dom = require("react-dom");
2085
- var import_react9 = __toESM(require("@emoji-mart/react"));
2086
- var import_jsx_runtime8 = require("react/jsx-runtime");
2570
+ var import_react10 = __toESM(require("@emoji-mart/react"));
2571
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2087
2572
  function isLightColor(color) {
2088
2573
  let r = 0, g = 0, b = 0;
2089
2574
  if (color.startsWith("#")) {
@@ -2101,18 +2586,18 @@ function ChatInput({
2101
2586
  fileUpload,
2102
2587
  disabled = false
2103
2588
  }) {
2104
- const [message, setMessage] = (0, import_react8.useState)("");
2105
- const [showEmojiPicker, setShowEmojiPicker] = (0, import_react8.useState)(false);
2106
- const [emojiData, setEmojiData] = (0, import_react8.useState)();
2107
- const [emojiPickerPosition, setEmojiPickerPosition] = (0, import_react8.useState)(null);
2108
- const [isFocused, setIsFocused] = (0, import_react8.useState)(false);
2109
- const textAreaRef = (0, import_react8.useRef)(null);
2110
- const emojiPickerRef = (0, import_react8.useRef)(null);
2111
- const emojiButtonRef = (0, import_react8.useRef)(null);
2112
- const fileInputRef = (0, import_react8.useRef)(null);
2113
- const typingTimeoutRef = (0, import_react8.useRef)(null);
2114
- const startTypingTimeoutRef = (0, import_react8.useRef)(null);
2115
- const isTypingRef = (0, import_react8.useRef)(false);
2589
+ const [message, setMessage] = (0, import_react9.useState)("");
2590
+ const [showEmojiPicker, setShowEmojiPicker] = (0, import_react9.useState)(false);
2591
+ const [emojiData, setEmojiData] = (0, import_react9.useState)();
2592
+ const [emojiPickerPosition, setEmojiPickerPosition] = (0, import_react9.useState)(null);
2593
+ const [isFocused, setIsFocused] = (0, import_react9.useState)(false);
2594
+ const textAreaRef = (0, import_react9.useRef)(null);
2595
+ const emojiPickerRef = (0, import_react9.useRef)(null);
2596
+ const emojiButtonRef = (0, import_react9.useRef)(null);
2597
+ const fileInputRef = (0, import_react9.useRef)(null);
2598
+ const typingTimeoutRef = (0, import_react9.useRef)(null);
2599
+ const startTypingTimeoutRef = (0, import_react9.useRef)(null);
2600
+ const isTypingRef = (0, import_react9.useRef)(false);
2116
2601
  const enableEmoji = config.enableEmoji ?? true;
2117
2602
  const enableFileUpload = config.enableFileUpload ?? true;
2118
2603
  const bgColor = config.theme?.background || "#00001a";
@@ -2120,7 +2605,7 @@ function ChatInput({
2120
2605
  const textColor = config.theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
2121
2606
  const textMuted = config.theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
2122
2607
  const primaryColor = config.theme?.primary || "#337eff";
2123
- (0, import_react8.useEffect)(() => {
2608
+ (0, import_react9.useEffect)(() => {
2124
2609
  if (!enableEmoji) return;
2125
2610
  (async () => {
2126
2611
  try {
@@ -2131,7 +2616,7 @@ function ChatInput({
2131
2616
  }
2132
2617
  })();
2133
2618
  }, [enableEmoji]);
2134
- (0, import_react8.useEffect)(() => {
2619
+ (0, import_react9.useEffect)(() => {
2135
2620
  const handleClickOutside = (event) => {
2136
2621
  if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target)) {
2137
2622
  setShowEmojiPicker(false);
@@ -2144,7 +2629,7 @@ function ChatInput({
2144
2629
  document.removeEventListener("mousedown", handleClickOutside);
2145
2630
  };
2146
2631
  }, [showEmojiPicker]);
2147
- (0, import_react8.useEffect)(() => {
2632
+ (0, import_react9.useEffect)(() => {
2148
2633
  return () => {
2149
2634
  if (typingTimeoutRef.current) {
2150
2635
  clearTimeout(typingTimeoutRef.current);
@@ -2154,7 +2639,7 @@ function ChatInput({
2154
2639
  }
2155
2640
  };
2156
2641
  }, []);
2157
- (0, import_react8.useEffect)(() => {
2642
+ (0, import_react9.useEffect)(() => {
2158
2643
  if (!showEmojiPicker) return;
2159
2644
  const updatePosition = () => {
2160
2645
  if (emojiButtonRef.current) {
@@ -2261,7 +2746,7 @@ ${uploadedFile.markdown}
2261
2746
  };
2262
2747
  const canSend = message.trim().length > 0 && !disabled;
2263
2748
  const placeholderColor = isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.3)";
2264
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2749
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2265
2750
  "div",
2266
2751
  {
2267
2752
  className: "px-4 py-3",
@@ -2269,7 +2754,7 @@ ${uploadedFile.markdown}
2269
2754
  borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)"
2270
2755
  },
2271
2756
  children: [
2272
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2757
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2273
2758
  "div",
2274
2759
  {
2275
2760
  className: "relative flex items-center rounded-full transition-all duration-200",
@@ -2281,8 +2766,8 @@ ${uploadedFile.markdown}
2281
2766
  backdropFilter: "blur(32px)"
2282
2767
  },
2283
2768
  children: [
2284
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
2285
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2769
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
2770
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2286
2771
  "textarea",
2287
2772
  {
2288
2773
  ref: textAreaRef,
@@ -2311,8 +2796,8 @@ ${uploadedFile.markdown}
2311
2796
  disabled
2312
2797
  }
2313
2798
  ),
2314
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1 px-2 shrink-0", children: [
2315
- enableEmoji && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2799
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1 px-2 shrink-0", children: [
2800
+ enableEmoji && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2316
2801
  "button",
2317
2802
  {
2318
2803
  ref: emojiButtonRef,
@@ -2342,7 +2827,7 @@ ${uploadedFile.markdown}
2342
2827
  },
2343
2828
  disabled,
2344
2829
  "aria-label": "Add emoji",
2345
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2830
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2346
2831
  "svg",
2347
2832
  {
2348
2833
  width: "18",
@@ -2355,18 +2840,18 @@ ${uploadedFile.markdown}
2355
2840
  strokeLinejoin: "round",
2356
2841
  "aria-hidden": "true",
2357
2842
  children: [
2358
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Emoji" }),
2359
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2360
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
2361
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
2362
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
2843
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Emoji" }),
2844
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2845
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
2846
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
2847
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
2363
2848
  ]
2364
2849
  }
2365
2850
  )
2366
2851
  }
2367
2852
  ),
2368
- enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2369
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2853
+ enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2854
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2370
2855
  "button",
2371
2856
  {
2372
2857
  type: "button",
@@ -2386,7 +2871,7 @@ ${uploadedFile.markdown}
2386
2871
  },
2387
2872
  disabled: disabled || fileUpload.isUploading,
2388
2873
  "aria-label": "Attach file",
2389
- children: fileUpload.isUploading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2874
+ children: fileUpload.isUploading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2390
2875
  "svg",
2391
2876
  {
2392
2877
  width: "18",
@@ -2398,11 +2883,11 @@ ${uploadedFile.markdown}
2398
2883
  className: "animate-spin",
2399
2884
  "aria-hidden": "true",
2400
2885
  children: [
2401
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Uploading" }),
2402
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
2886
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Uploading" }),
2887
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
2403
2888
  ]
2404
2889
  }
2405
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2890
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2406
2891
  "svg",
2407
2892
  {
2408
2893
  width: "18",
@@ -2415,14 +2900,14 @@ ${uploadedFile.markdown}
2415
2900
  strokeLinejoin: "round",
2416
2901
  "aria-hidden": "true",
2417
2902
  children: [
2418
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Attach file" }),
2419
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" })
2903
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Attach file" }),
2904
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" })
2420
2905
  ]
2421
2906
  }
2422
2907
  )
2423
2908
  }
2424
2909
  ),
2425
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2910
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2426
2911
  "input",
2427
2912
  {
2428
2913
  ref: fileInputRef,
@@ -2433,7 +2918,7 @@ ${uploadedFile.markdown}
2433
2918
  }
2434
2919
  )
2435
2920
  ] }),
2436
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2921
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2437
2922
  "button",
2438
2923
  {
2439
2924
  type: "button",
@@ -2448,7 +2933,7 @@ ${uploadedFile.markdown}
2448
2933
  boxShadow: canSend ? `0 2px 8px -2px ${primaryColor}60` : "none"
2449
2934
  },
2450
2935
  "aria-label": "Send message",
2451
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2936
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2452
2937
  "svg",
2453
2938
  {
2454
2939
  width: "18",
@@ -2461,9 +2946,9 @@ ${uploadedFile.markdown}
2461
2946
  strokeLinejoin: "round",
2462
2947
  "aria-hidden": "true",
2463
2948
  children: [
2464
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Send" }),
2465
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
2466
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
2949
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Send" }),
2950
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
2951
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
2467
2952
  ]
2468
2953
  }
2469
2954
  )
@@ -2473,8 +2958,8 @@ ${uploadedFile.markdown}
2473
2958
  ]
2474
2959
  }
2475
2960
  ),
2476
- fileUpload.isUploading && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-2 px-1", children: [
2477
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2961
+ fileUpload.isUploading && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mt-2 px-1", children: [
2962
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2478
2963
  "div",
2479
2964
  {
2480
2965
  className: "w-full rounded-full overflow-hidden",
@@ -2482,7 +2967,7 @@ ${uploadedFile.markdown}
2482
2967
  height: 3,
2483
2968
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"
2484
2969
  },
2485
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2970
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2486
2971
  "div",
2487
2972
  {
2488
2973
  className: "h-full rounded-full transition-all duration-300",
@@ -2494,7 +2979,7 @@ ${uploadedFile.markdown}
2494
2979
  )
2495
2980
  }
2496
2981
  ),
2497
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2982
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2498
2983
  "p",
2499
2984
  {
2500
2985
  className: "mt-1",
@@ -2512,7 +2997,7 @@ ${uploadedFile.markdown}
2512
2997
  )
2513
2998
  ] }),
2514
2999
  showEmojiPicker && emojiData && emojiPickerPosition && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
2515
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3000
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2516
3001
  "div",
2517
3002
  {
2518
3003
  ref: emojiPickerRef,
@@ -2527,8 +3012,8 @@ ${uploadedFile.markdown}
2527
3012
  "0 16px 48px -8px rgba(0,0,0,0.5)"
2528
3013
  ].join(", ")
2529
3014
  },
2530
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2531
- import_react9.default,
3015
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3016
+ import_react10.default,
2532
3017
  {
2533
3018
  data: emojiData,
2534
3019
  onEmojiSelect: (emoji) => {
@@ -2553,7 +3038,7 @@ ${uploadedFile.markdown}
2553
3038
  }
2554
3039
 
2555
3040
  // src/components/ChatWidget.tsx
2556
- var import_jsx_runtime9 = require("react/jsx-runtime");
3041
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2557
3042
  function getAnonymousUser() {
2558
3043
  let anonId;
2559
3044
  try {
@@ -2599,14 +3084,20 @@ function ChatWidget({
2599
3084
  maxHeight: 900,
2600
3085
  enabled: !isFullPage
2601
3086
  });
2602
- const handleSendMessage = (0, import_react10.useCallback)(
3087
+ const lastSentRef = (0, import_react11.useRef)(null);
3088
+ const handleSendMessage = (0, import_react11.useCallback)(
2603
3089
  (content) => {
2604
3090
  if (!websocket.isConnected) {
2605
3091
  config.toast?.error("Not connected to chat server");
2606
3092
  return;
2607
3093
  }
3094
+ const now = Date.now();
3095
+ if (lastSentRef.current && lastSentRef.current.content === content && now - lastSentRef.current.time < 1e3) {
3096
+ return;
3097
+ }
3098
+ lastSentRef.current = { content, time: now };
2608
3099
  const optimisticMessage = {
2609
- id: `temp-${Date.now()}`,
3100
+ id: `temp-${now}`,
2610
3101
  conversationId: config.conversationId || "",
2611
3102
  senderId: effectiveUser.email,
2612
3103
  senderType: effectiveUser.type,
@@ -2625,7 +3116,18 @@ function ChatWidget({
2625
3116
  },
2626
3117
  [websocket, config, addMessage, effectiveUser]
2627
3118
  );
2628
- const handleTyping = (0, import_react10.useCallback)(
3119
+ const handleBookingSlotSelected = (0, import_react11.useCallback)(
3120
+ (date, time, _messageId) => {
3121
+ if (!websocket.isConnected) return;
3122
+ websocket.sendMessage("bookSlot", {
3123
+ conversationId: config.conversationId,
3124
+ date,
3125
+ time
3126
+ });
3127
+ },
3128
+ [websocket, config]
3129
+ );
3130
+ const handleTyping = (0, import_react11.useCallback)(
2629
3131
  (isTyping2) => {
2630
3132
  if (!websocket.isConnected || config.enableTypingIndicator === false) return;
2631
3133
  websocket.sendMessage("typing", {
@@ -2635,7 +3137,7 @@ function ChatWidget({
2635
3137
  },
2636
3138
  [websocket, config]
2637
3139
  );
2638
- (0, import_react10.useEffect)(() => {
3140
+ (0, import_react11.useEffect)(() => {
2639
3141
  if (websocket.error) {
2640
3142
  config.toast?.error(websocket.error.message || "An error occurred");
2641
3143
  }
@@ -2686,14 +3188,14 @@ function ChatWidget({
2686
3188
  outlineOffset: -1,
2687
3189
  transition: "outline 150ms ease"
2688
3190
  };
2689
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3191
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2690
3192
  "div",
2691
3193
  {
2692
3194
  className: isFullPage ? `flex flex-col h-full ${className}` : `fixed bottom-20 ${positionClass} z-50 flex flex-col overflow-hidden ${className}`,
2693
3195
  style: { ...containerStyle, ...dotGridBg, ...edgeHintStyle },
2694
3196
  ...!isFullPage ? containerResizeProps : {},
2695
3197
  children: [
2696
- !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3198
+ !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2697
3199
  ChatHeader,
2698
3200
  {
2699
3201
  agent: effectiveUser.type === "customer" ? {
@@ -2707,7 +3209,7 @@ function ChatWidget({
2707
3209
  theme: config.theme
2708
3210
  }
2709
3211
  ),
2710
- !websocket.isConnected && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3212
+ !websocket.isConnected && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2711
3213
  "div",
2712
3214
  {
2713
3215
  className: "px-4 py-2",
@@ -2715,15 +3217,15 @@ function ChatWidget({
2715
3217
  backgroundColor: isLightTheme ? "rgba(255,169,56,0.08)" : "rgba(255,169,56,0.06)",
2716
3218
  borderBottom: `1px solid rgba(255,169,56,${isLightTheme ? "0.15" : "0.12"})`
2717
3219
  },
2718
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
2719
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3220
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
3221
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2720
3222
  "div",
2721
3223
  {
2722
3224
  className: "w-1.5 h-1.5 rounded-full animate-pulse",
2723
3225
  style: { backgroundColor: config.theme?.statusCaution || "#ffa938" }
2724
3226
  }
2725
3227
  ),
2726
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3228
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2727
3229
  "span",
2728
3230
  {
2729
3231
  style: {
@@ -2734,7 +3236,7 @@ function ChatWidget({
2734
3236
  children: "Reconnecting..."
2735
3237
  }
2736
3238
  ),
2737
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3239
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2738
3240
  "button",
2739
3241
  {
2740
3242
  type: "button",
@@ -2758,8 +3260,8 @@ function ChatWidget({
2758
3260
  ] })
2759
3261
  }
2760
3262
  ),
2761
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "text-center", children: [
2762
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3263
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-center", children: [
3264
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2763
3265
  "div",
2764
3266
  {
2765
3267
  className: "w-7 h-7 border-2 border-t-transparent rounded-full animate-spin mx-auto mb-3",
@@ -2769,7 +3271,7 @@ function ChatWidget({
2769
3271
  }
2770
3272
  }
2771
3273
  ),
2772
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3274
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2773
3275
  "p",
2774
3276
  {
2775
3277
  style: {
@@ -2780,7 +3282,7 @@ function ChatWidget({
2780
3282
  children: "Loading messages..."
2781
3283
  }
2782
3284
  )
2783
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3285
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2784
3286
  MessageList,
2785
3287
  {
2786
3288
  messages,
@@ -2793,10 +3295,11 @@ function ChatWidget({
2793
3295
  isLoadingMore,
2794
3296
  theme: config.theme,
2795
3297
  onQuickAction: handleSendMessage,
2796
- isBotThinking
3298
+ isBotThinking,
3299
+ onBookingSlotSelected: handleBookingSlotSelected
2797
3300
  }
2798
3301
  ),
2799
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3302
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2800
3303
  ChatInput,
2801
3304
  {
2802
3305
  onSend: handleSendMessage,
@@ -2806,7 +3309,7 @@ function ChatWidget({
2806
3309
  disabled: !websocket.isConnected
2807
3310
  }
2808
3311
  ),
2809
- !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3312
+ !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2810
3313
  "div",
2811
3314
  {
2812
3315
  className: "px-4 py-1.5 text-center",
@@ -2814,7 +3317,7 @@ function ChatWidget({
2814
3317
  borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)",
2815
3318
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.03)" : "rgba(0,0,0,0.2)"
2816
3319
  },
2817
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3320
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2818
3321
  "p",
2819
3322
  {
2820
3323
  style: {
@@ -2825,7 +3328,7 @@ function ChatWidget({
2825
3328
  children: [
2826
3329
  "Powered by",
2827
3330
  " ",
2828
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: {
3331
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: {
2829
3332
  fontWeight: 600,
2830
3333
  color: isLightTheme ? "rgba(0,0,0,0.5)" : "rgba(247,247,248,0.45)"
2831
3334
  }, children: "Xcelsior" })
@@ -2840,11 +3343,11 @@ function ChatWidget({
2840
3343
  }
2841
3344
 
2842
3345
  // src/components/Chat.tsx
2843
- var import_react14 = require("react");
3346
+ var import_react15 = require("react");
2844
3347
 
2845
3348
  // src/components/PreChatForm.tsx
2846
- var import_react11 = require("react");
2847
- var import_jsx_runtime10 = require("react/jsx-runtime");
3349
+ var import_react12 = require("react");
3350
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2848
3351
  function PreChatForm({
2849
3352
  onSubmit,
2850
3353
  className = "",
@@ -2853,11 +3356,11 @@ function PreChatForm({
2853
3356
  onMinimize,
2854
3357
  onClose
2855
3358
  }) {
2856
- const [name, setName] = (0, import_react11.useState)(initialName);
2857
- const [email, setEmail] = (0, import_react11.useState)(initialEmail);
2858
- const [errors, setErrors] = (0, import_react11.useState)({});
2859
- const [isSubmitting, setIsSubmitting] = (0, import_react11.useState)(false);
2860
- const [focusedField, setFocusedField] = (0, import_react11.useState)(null);
3359
+ const [name, setName] = (0, import_react12.useState)(initialName);
3360
+ const [email, setEmail] = (0, import_react12.useState)(initialEmail);
3361
+ const [errors, setErrors] = (0, import_react12.useState)({});
3362
+ const [isSubmitting, setIsSubmitting] = (0, import_react12.useState)(false);
3363
+ const [focusedField, setFocusedField] = (0, import_react12.useState)(null);
2861
3364
  const validateForm = () => {
2862
3365
  const newErrors = {};
2863
3366
  if (!name.trim()) {
@@ -2905,7 +3408,7 @@ function PreChatForm({
2905
3408
  transition: "box-shadow 0.2s ease",
2906
3409
  caretColor: "#337eff"
2907
3410
  });
2908
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3411
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2909
3412
  "div",
2910
3413
  {
2911
3414
  className: `fixed bottom-4 right-4 z-50 flex flex-col overflow-hidden ${className}`,
@@ -2925,7 +3428,7 @@ function PreChatForm({
2925
3428
  backgroundSize: "24px 24px"
2926
3429
  },
2927
3430
  children: [
2928
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3431
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2929
3432
  "div",
2930
3433
  {
2931
3434
  className: "relative text-white overflow-hidden",
@@ -2933,7 +3436,7 @@ function PreChatForm({
2933
3436
  background: "linear-gradient(135deg, #337eff 0%, #005eff 50%, #001a66 100%)"
2934
3437
  },
2935
3438
  children: [
2936
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3439
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2937
3440
  "div",
2938
3441
  {
2939
3442
  className: "absolute inset-0 pointer-events-none",
@@ -2942,9 +3445,9 @@ function PreChatForm({
2942
3445
  }
2943
3446
  }
2944
3447
  ),
2945
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "relative px-5 py-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-start justify-between", children: [
2946
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1", children: [
2947
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3448
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "relative px-5 py-4", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-start justify-between", children: [
3449
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex-1", children: [
3450
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2948
3451
  "h2",
2949
3452
  {
2950
3453
  className: "font-semibold",
@@ -2955,7 +3458,7 @@ function PreChatForm({
2955
3458
  children: "Start a Conversation"
2956
3459
  }
2957
3460
  ),
2958
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3461
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2959
3462
  "p",
2960
3463
  {
2961
3464
  className: "mt-1",
@@ -2968,8 +3471,8 @@ function PreChatForm({
2968
3471
  }
2969
3472
  )
2970
3473
  ] }),
2971
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1 ml-2", children: [
2972
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3474
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-1 ml-2", children: [
3475
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2973
3476
  "button",
2974
3477
  {
2975
3478
  type: "button",
@@ -2983,7 +3486,7 @@ function PreChatForm({
2983
3486
  e.currentTarget.style.backgroundColor = "transparent";
2984
3487
  },
2985
3488
  "aria-label": "Minimize chat",
2986
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3489
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2987
3490
  "svg",
2988
3491
  {
2989
3492
  width: "18",
@@ -2992,8 +3495,8 @@ function PreChatForm({
2992
3495
  stroke: "currentColor",
2993
3496
  viewBox: "0 0 24 24",
2994
3497
  children: [
2995
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Minimize" }),
2996
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3498
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Minimize" }),
3499
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2997
3500
  "path",
2998
3501
  {
2999
3502
  strokeLinecap: "round",
@@ -3007,7 +3510,7 @@ function PreChatForm({
3007
3510
  )
3008
3511
  }
3009
3512
  ),
3010
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3513
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3011
3514
  "button",
3012
3515
  {
3013
3516
  type: "button",
@@ -3021,7 +3524,7 @@ function PreChatForm({
3021
3524
  e.currentTarget.style.backgroundColor = "transparent";
3022
3525
  },
3023
3526
  "aria-label": "Close chat",
3024
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3527
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3025
3528
  "svg",
3026
3529
  {
3027
3530
  width: "18",
@@ -3030,8 +3533,8 @@ function PreChatForm({
3030
3533
  stroke: "currentColor",
3031
3534
  viewBox: "0 0 24 24",
3032
3535
  children: [
3033
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Close" }),
3034
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3536
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Close" }),
3537
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3035
3538
  "path",
3036
3539
  {
3037
3540
  strokeLinecap: "round",
@@ -3047,7 +3550,7 @@ function PreChatForm({
3047
3550
  )
3048
3551
  ] })
3049
3552
  ] }) }),
3050
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3553
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3051
3554
  "div",
3052
3555
  {
3053
3556
  className: "h-px",
@@ -3059,9 +3562,9 @@ function PreChatForm({
3059
3562
  ]
3060
3563
  }
3061
3564
  ),
3062
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "p-5 flex flex-col gap-4", children: [
3063
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3064
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3565
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("form", { onSubmit: handleSubmit, className: "p-5 flex flex-col gap-4", children: [
3566
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
3567
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3065
3568
  "label",
3066
3569
  {
3067
3570
  htmlFor: "chat-name",
@@ -3073,11 +3576,11 @@ function PreChatForm({
3073
3576
  },
3074
3577
  children: [
3075
3578
  "Name ",
3076
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3579
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3077
3580
  ]
3078
3581
  }
3079
3582
  ),
3080
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3583
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3081
3584
  "input",
3082
3585
  {
3083
3586
  type: "text",
@@ -3097,7 +3600,7 @@ function PreChatForm({
3097
3600
  autoComplete: "name"
3098
3601
  }
3099
3602
  ),
3100
- errors.name && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3603
+ errors.name && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3101
3604
  "p",
3102
3605
  {
3103
3606
  className: "mt-1.5",
@@ -3111,8 +3614,8 @@ function PreChatForm({
3111
3614
  }
3112
3615
  )
3113
3616
  ] }),
3114
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3115
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3617
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
3618
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3116
3619
  "label",
3117
3620
  {
3118
3621
  htmlFor: "chat-email",
@@ -3124,11 +3627,11 @@ function PreChatForm({
3124
3627
  },
3125
3628
  children: [
3126
3629
  "Email ",
3127
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3630
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3128
3631
  ]
3129
3632
  }
3130
3633
  ),
3131
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3634
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3132
3635
  "input",
3133
3636
  {
3134
3637
  type: "email",
@@ -3148,7 +3651,7 @@ function PreChatForm({
3148
3651
  autoComplete: "email"
3149
3652
  }
3150
3653
  ),
3151
- errors.email && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3654
+ errors.email && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3152
3655
  "p",
3153
3656
  {
3154
3657
  className: "mt-1.5",
@@ -3162,7 +3665,7 @@ function PreChatForm({
3162
3665
  }
3163
3666
  )
3164
3667
  ] }),
3165
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3668
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3166
3669
  "button",
3167
3670
  {
3168
3671
  type: "submit",
@@ -3184,8 +3687,8 @@ function PreChatForm({
3184
3687
  onMouseLeave: (e) => {
3185
3688
  e.currentTarget.style.boxShadow = "0 4px 16px -4px rgba(51,126,255,0.4)";
3186
3689
  },
3187
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [
3188
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3690
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [
3691
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3189
3692
  "svg",
3190
3693
  {
3191
3694
  className: "animate-spin",
@@ -3197,8 +3700,8 @@ function PreChatForm({
3197
3700
  strokeWidth: "2",
3198
3701
  "aria-label": "Loading",
3199
3702
  children: [
3200
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Loading" }),
3201
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
3703
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Loading" }),
3704
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
3202
3705
  ]
3203
3706
  }
3204
3707
  ),
@@ -3206,7 +3709,7 @@ function PreChatForm({
3206
3709
  ] }) : "Start Chat"
3207
3710
  }
3208
3711
  ),
3209
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3712
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3210
3713
  "p",
3211
3714
  {
3212
3715
  className: "text-center",
@@ -3219,7 +3722,7 @@ function PreChatForm({
3219
3722
  }
3220
3723
  )
3221
3724
  ] }),
3222
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3725
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3223
3726
  "div",
3224
3727
  {
3225
3728
  className: "px-4 py-1.5 text-center",
@@ -3227,7 +3730,7 @@ function PreChatForm({
3227
3730
  borderTop: "1px solid rgba(255,255,255,0.06)",
3228
3731
  backgroundColor: "rgba(0,0,0,0.2)"
3229
3732
  },
3230
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3733
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3231
3734
  "p",
3232
3735
  {
3233
3736
  style: {
@@ -3238,7 +3741,7 @@ function PreChatForm({
3238
3741
  children: [
3239
3742
  "Powered by",
3240
3743
  " ",
3241
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
3744
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
3242
3745
  ]
3243
3746
  }
3244
3747
  )
@@ -3250,8 +3753,15 @@ function PreChatForm({
3250
3753
  }
3251
3754
 
3252
3755
  // src/hooks/useDraggablePosition.ts
3253
- var import_react12 = require("react");
3756
+ var import_react13 = require("react");
3254
3757
  var STORAGE_KEY2 = "xcelsior-chat-position";
3758
+ var DRAG_THRESHOLD = 5;
3759
+ var FAB_SIZE = 64;
3760
+ var MARGIN = 16;
3761
+ var BOTTOM = 20;
3762
+ var VELOCITY_THRESHOLD = 0.4;
3763
+ var SETTLE_DURATION = 400;
3764
+ var SETTLE_EASING = "cubic-bezier(0.2, 0.9, 0.3, 1.1)";
3255
3765
  function getStoredPosition() {
3256
3766
  try {
3257
3767
  const stored = localStorage.getItem(STORAGE_KEY2);
@@ -3260,21 +3770,33 @@ function getStoredPosition() {
3260
3770
  }
3261
3771
  return "right";
3262
3772
  }
3263
- function storePosition(position) {
3773
+ function storePosition(pos) {
3264
3774
  try {
3265
- localStorage.setItem(STORAGE_KEY2, position);
3775
+ localStorage.setItem(STORAGE_KEY2, pos);
3266
3776
  } catch {
3267
3777
  }
3268
3778
  }
3779
+ function restingX(side) {
3780
+ return side === "left" ? MARGIN : window.innerWidth - MARGIN - FAB_SIZE;
3781
+ }
3782
+ function restingY() {
3783
+ return window.innerHeight - BOTTOM - FAB_SIZE;
3784
+ }
3269
3785
  function useDraggablePosition(configPosition = "auto") {
3270
3786
  const resolvedDefault = configPosition === "auto" ? getStoredPosition() : configPosition;
3271
- const [position, setPosition] = (0, import_react12.useState)(resolvedDefault);
3272
- const [isDragging, setIsDragging] = (0, import_react12.useState)(false);
3273
- const dragStartX = (0, import_react12.useRef)(0);
3274
- const fabRef = (0, import_react12.useRef)(null);
3275
- const hasShownHint = (0, import_react12.useRef)(false);
3276
- const [showHint, setShowHint] = (0, import_react12.useState)(false);
3277
- (0, import_react12.useEffect)(() => {
3787
+ const [position, setPosition] = (0, import_react13.useState)(resolvedDefault);
3788
+ const positionRef = (0, import_react13.useRef)(position);
3789
+ positionRef.current = position;
3790
+ const [isDragging, setIsDragging] = (0, import_react13.useState)(false);
3791
+ const containerRef = (0, import_react13.useRef)(null);
3792
+ const draggingRef = (0, import_react13.useRef)(false);
3793
+ const didDragRef = (0, import_react13.useRef)(false);
3794
+ const startPointer = (0, import_react13.useRef)({ x: 0, y: 0 });
3795
+ const startRect = (0, import_react13.useRef)({ x: 0, y: 0 });
3796
+ const samples = (0, import_react13.useRef)([]);
3797
+ const hasShownHint = (0, import_react13.useRef)(false);
3798
+ const [showHint, setShowHint] = (0, import_react13.useState)(false);
3799
+ (0, import_react13.useEffect)(() => {
3278
3800
  try {
3279
3801
  const hasVisited = localStorage.getItem("xcelsior-chat-hint-shown");
3280
3802
  if (!hasVisited && !hasShownHint.current) {
@@ -3289,53 +3811,115 @@ function useDraggablePosition(configPosition = "auto") {
3289
3811
  } catch {
3290
3812
  }
3291
3813
  }, []);
3292
- const handlePointerDown = (0, import_react12.useCallback)((e) => {
3293
- dragStartX.current = e.clientX;
3294
- setIsDragging(true);
3295
- e.target.setPointerCapture(e.pointerId);
3814
+ (0, import_react13.useEffect)(() => {
3815
+ const handleMove = (e) => {
3816
+ if (!draggingRef.current) return;
3817
+ const el = containerRef.current;
3818
+ if (!el) return;
3819
+ const dx = e.clientX - startPointer.current.x;
3820
+ const dy = e.clientY - startPointer.current.y;
3821
+ if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) {
3822
+ didDragRef.current = true;
3823
+ }
3824
+ el.style.transition = "none";
3825
+ el.style.transform = `translate(${dx}px, ${dy}px)`;
3826
+ const now = performance.now();
3827
+ samples.current.push({ x: e.clientX, t: now });
3828
+ if (samples.current.length > 5) samples.current.shift();
3829
+ };
3830
+ const handleUp = (e) => {
3831
+ if (!draggingRef.current) return;
3832
+ draggingRef.current = false;
3833
+ const el = containerRef.current;
3834
+ if (!el) return;
3835
+ let velocityX = 0;
3836
+ const s = samples.current;
3837
+ if (s.length >= 2) {
3838
+ const first = s[0];
3839
+ const last = s[s.length - 1];
3840
+ const dt = last.t - first.t;
3841
+ if (dt > 0) velocityX = (last.x - first.x) / dt;
3842
+ }
3843
+ samples.current = [];
3844
+ const currentVisualX = startRect.current.x + (e.clientX - startPointer.current.x);
3845
+ const screenMid = window.innerWidth / 2;
3846
+ let targetSide;
3847
+ if (Math.abs(velocityX) > VELOCITY_THRESHOLD) {
3848
+ targetSide = velocityX < 0 ? "left" : "right";
3849
+ } else {
3850
+ targetSide = currentVisualX + FAB_SIZE / 2 < screenMid ? "left" : "right";
3851
+ }
3852
+ const oldBaseX = restingX(positionRef.current);
3853
+ const oldBaseY = restingY();
3854
+ const currentX = startRect.current.x + (e.clientX - startPointer.current.x);
3855
+ const currentY = startRect.current.y + (e.clientY - startPointer.current.y);
3856
+ const targetX = restingX(targetSide);
3857
+ const targetY = restingY();
3858
+ const fromTx = currentX - oldBaseX;
3859
+ const fromTy = currentY - oldBaseY;
3860
+ const toTx = targetX - oldBaseX;
3861
+ const toTy = targetY - oldBaseY;
3862
+ el.style.transition = "none";
3863
+ el.style.transform = `translate(${fromTx}px, ${fromTy}px)`;
3864
+ void el.offsetHeight;
3865
+ el.style.transition = `transform ${SETTLE_DURATION}ms ${SETTLE_EASING}`;
3866
+ el.style.transform = `translate(${toTx}px, ${toTy}px)`;
3867
+ const finish = () => {
3868
+ el.style.transition = "";
3869
+ el.style.transform = "";
3870
+ el.removeEventListener("transitionend", finish);
3871
+ setIsDragging(false);
3872
+ if (targetSide !== positionRef.current) {
3873
+ setPosition(targetSide);
3874
+ storePosition(targetSide);
3875
+ }
3876
+ };
3877
+ el.addEventListener("transitionend", finish, { once: true });
3878
+ setTimeout(finish, SETTLE_DURATION + 50);
3879
+ };
3880
+ document.addEventListener("pointermove", handleMove);
3881
+ document.addEventListener("pointerup", handleUp);
3882
+ return () => {
3883
+ document.removeEventListener("pointermove", handleMove);
3884
+ document.removeEventListener("pointerup", handleUp);
3885
+ };
3296
3886
  }, []);
3297
- const handlePointerMove = (0, import_react12.useCallback)((_e) => {
3887
+ const handlePointerDown = (0, import_react13.useCallback)((e) => {
3888
+ e.preventDefault();
3889
+ const el = containerRef.current;
3890
+ if (!el) return;
3891
+ const rect = el.getBoundingClientRect();
3892
+ startPointer.current = { x: e.clientX, y: e.clientY };
3893
+ startRect.current = { x: rect.left, y: rect.top };
3894
+ draggingRef.current = true;
3895
+ didDragRef.current = false;
3896
+ samples.current = [];
3897
+ setIsDragging(true);
3298
3898
  }, []);
3299
- const handlePointerUp = (0, import_react12.useCallback)(
3300
- (e) => {
3301
- setIsDragging(false);
3302
- e.target.releasePointerCapture(e.pointerId);
3303
- const deltaX = e.clientX - dragStartX.current;
3304
- if (Math.abs(deltaX) > 50) {
3305
- const screenMid = window.innerWidth / 2;
3306
- const newPosition = e.clientX < screenMid ? "left" : "right";
3307
- if (newPosition !== position) {
3308
- setPosition(newPosition);
3309
- storePosition(newPosition);
3310
- }
3311
- }
3312
- },
3313
- [position]
3314
- );
3899
+ const shouldSuppressClick = (0, import_react13.useCallback)(() => didDragRef.current, []);
3315
3900
  return {
3316
3901
  position,
3317
3902
  isDragging,
3318
3903
  showHint,
3319
- fabRef,
3904
+ containerRef,
3905
+ shouldSuppressClick,
3320
3906
  handlers: {
3321
- onPointerDown: handlePointerDown,
3322
- onPointerMove: handlePointerMove,
3323
- onPointerUp: handlePointerUp
3907
+ onPointerDown: handlePointerDown
3324
3908
  }
3325
3909
  };
3326
3910
  }
3327
3911
 
3328
3912
  // src/hooks/useChatWidgetState.ts
3329
- var import_react13 = require("react");
3913
+ var import_react14 = require("react");
3330
3914
  function useChatWidgetState({
3331
3915
  state: controlledState,
3332
3916
  defaultState = "minimized",
3333
3917
  onStateChange
3334
3918
  }) {
3335
- const [uncontrolledState, setUncontrolledState] = (0, import_react13.useState)(defaultState);
3919
+ const [uncontrolledState, setUncontrolledState] = (0, import_react14.useState)(defaultState);
3336
3920
  const isControlled = controlledState !== void 0 && controlledState !== "undefined";
3337
3921
  const currentState = isControlled ? controlledState : uncontrolledState;
3338
- const setState = (0, import_react13.useCallback)(
3922
+ const setState = (0, import_react14.useCallback)(
3339
3923
  (newValue) => {
3340
3924
  if (!isControlled) {
3341
3925
  setUncontrolledState(newValue);
@@ -3352,7 +3936,7 @@ function useChatWidgetState({
3352
3936
  }
3353
3937
 
3354
3938
  // src/components/Chat.tsx
3355
- var import_jsx_runtime11 = require("react/jsx-runtime");
3939
+ var import_jsx_runtime14 = require("react/jsx-runtime");
3356
3940
  function generateSessionId() {
3357
3941
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
3358
3942
  return crypto.randomUUID();
@@ -3368,12 +3952,12 @@ function Chat({
3368
3952
  defaultState = "minimized",
3369
3953
  onStateChange
3370
3954
  }) {
3371
- const [userInfo, setUserInfo] = (0, import_react14.useState)(null);
3372
- const [conversationId, setConversationId] = (0, import_react14.useState)("");
3373
- const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
3955
+ const [userInfo, setUserInfo] = (0, import_react15.useState)(null);
3956
+ const [conversationId, setConversationId] = (0, import_react15.useState)("");
3957
+ const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
3374
3958
  const identityMode = config.identityCollection || "progressive";
3375
- const { position, isDragging, showHint, handlers } = useDraggablePosition(config.position);
3376
- const sessionInitializedRef = (0, import_react14.useRef)(false);
3959
+ const { position, isDragging, showHint, containerRef, shouldSuppressClick, handlers } = useDraggablePosition(config.position);
3960
+ const sessionInitializedRef = (0, import_react15.useRef)(false);
3377
3961
  const { currentState, setState } = useChatWidgetState({
3378
3962
  state,
3379
3963
  defaultState,
@@ -3384,7 +3968,7 @@ function Chat({
3384
3968
  const configUserName = config.currentUser?.name;
3385
3969
  const configUserAvatar = config.currentUser?.avatar;
3386
3970
  const configUserStatus = config.currentUser?.status;
3387
- (0, import_react14.useEffect)(() => {
3971
+ (0, import_react15.useEffect)(() => {
3388
3972
  if (sessionInitializedRef.current) return;
3389
3973
  const initializeSession = () => {
3390
3974
  try {
@@ -3424,7 +4008,33 @@ function Chat({
3424
4008
  const convId = configConversationId || generateSessionId();
3425
4009
  setConversationId(convId);
3426
4010
  if (identityMode === "progressive" || identityMode === "none") {
3427
- setUserInfo(null);
4011
+ let anonId;
4012
+ try {
4013
+ const stored = localStorage.getItem("xcelsior-chat-anon-id");
4014
+ if (stored) {
4015
+ anonId = stored;
4016
+ } else {
4017
+ anonId = `anon-${crypto.randomUUID?.() || Math.random().toString(36).slice(2)}`;
4018
+ localStorage.setItem("xcelsior-chat-anon-id", anonId);
4019
+ }
4020
+ } catch {
4021
+ anonId = `anon-${Math.random().toString(36).slice(2)}`;
4022
+ }
4023
+ const anonEmail = `${anonId}@anonymous.xcelsior.co`;
4024
+ const anonUser = { name: "Visitor", email: anonEmail, type: "customer", status: "online" };
4025
+ setUserInfo(anonUser);
4026
+ try {
4027
+ localStorage.setItem(
4028
+ `${storageKeyPrefix}_user`,
4029
+ JSON.stringify({
4030
+ name: anonUser.name,
4031
+ email: anonUser.email,
4032
+ conversationId: convId,
4033
+ timestamp: Date.now()
4034
+ })
4035
+ );
4036
+ } catch {
4037
+ }
3428
4038
  }
3429
4039
  sessionInitializedRef.current = true;
3430
4040
  } catch (error) {
@@ -3437,7 +4047,7 @@ function Chat({
3437
4047
  };
3438
4048
  initializeSession();
3439
4049
  }, [configConversationId, configUserEmail, configUserName, configUserAvatar, configUserStatus, storageKeyPrefix, identityMode]);
3440
- const handlePreChatSubmit = (0, import_react14.useCallback)(
4050
+ const handlePreChatSubmit = (0, import_react15.useCallback)(
3441
4051
  (name, email) => {
3442
4052
  const convId = conversationId || generateSessionId();
3443
4053
  const user = { name, email, type: "customer", status: "online" };
@@ -3463,49 +4073,52 @@ function Chat({
3463
4073
  const primaryColor = config.theme?.primary || "#337eff";
3464
4074
  const primaryStrong = config.theme?.primaryStrong || "#005eff";
3465
4075
  if (currentState === "minimized") {
3466
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: `fixed bottom-5 ${positionClass} z-50 ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3467
- "button",
4076
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4077
+ "div",
3468
4078
  {
3469
- type: "button",
3470
- onClick: () => setState("open"),
3471
- className: `group relative rounded-full text-white transition-all duration-300 flex items-center justify-center touch-none select-none ${showHint ? "animate-bounce" : ""} ${isDragging ? "cursor-grabbing" : "cursor-grab"}`,
3472
- onMouseEnter: (e) => {
3473
- e.currentTarget.style.transform = "scale(1.08)";
3474
- },
3475
- onMouseLeave: (e) => {
3476
- e.currentTarget.style.transform = isDragging ? "scale(1.1)" : "scale(1)";
3477
- },
3478
- style: {
3479
- width: 64,
3480
- height: 64,
3481
- background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
3482
- boxShadow: [
3483
- `0 4px 24px 0 ${primaryColor}80`,
3484
- `0 8px 32px -4px rgba(0,0,0,0.3)`,
3485
- `inset 0 1px 0 0 rgba(255,255,255,0.2)`
3486
- ].join(", ")
3487
- },
3488
- "aria-label": "Open chat",
3489
- ...handlers,
3490
- children: [
3491
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatBubbleIcon, { size: 28, color: "white", className: "pointer-events-none" }),
3492
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3493
- "span",
3494
- {
3495
- className: "absolute inset-0 rounded-full animate-ping pointer-events-none",
3496
- style: {
3497
- backgroundColor: primaryColor,
3498
- opacity: 0.15,
3499
- animationDuration: "2.5s"
3500
- }
3501
- }
3502
- )
3503
- ]
4079
+ ref: containerRef,
4080
+ className: `fixed bottom-5 ${positionClass} z-50 ${className}`,
4081
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
4082
+ "button",
4083
+ {
4084
+ type: "button",
4085
+ onClick: () => {
4086
+ if (!shouldSuppressClick()) setState("open");
4087
+ },
4088
+ className: `group relative rounded-full text-white flex items-center justify-center touch-none select-none ${showHint ? "animate-bounce" : ""} ${isDragging ? "cursor-grabbing scale-110" : "cursor-grab hover:scale-[1.08] transition-transform duration-300"}`,
4089
+ style: {
4090
+ width: 64,
4091
+ height: 64,
4092
+ background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
4093
+ boxShadow: [
4094
+ `0 4px 24px 0 ${primaryColor}80`,
4095
+ `0 8px 32px -4px rgba(0,0,0,0.3)`,
4096
+ `inset 0 1px 0 0 rgba(255,255,255,0.2)`
4097
+ ].join(", ")
4098
+ },
4099
+ "aria-label": "Open chat",
4100
+ ...handlers,
4101
+ children: [
4102
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChatBubbleIcon, { size: 36, className: "pointer-events-none" }),
4103
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4104
+ "span",
4105
+ {
4106
+ className: "absolute inset-0 rounded-full animate-ping pointer-events-none",
4107
+ style: {
4108
+ backgroundColor: primaryColor,
4109
+ opacity: 0.15,
4110
+ animationDuration: "2.5s"
4111
+ }
4112
+ }
4113
+ )
4114
+ ]
4115
+ }
4116
+ )
3504
4117
  }
3505
- ) });
4118
+ );
3506
4119
  }
3507
4120
  if (identityMode === "form" && (!userInfo || !userInfo.email || !userInfo.name)) {
3508
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4121
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3509
4122
  PreChatForm,
3510
4123
  {
3511
4124
  onSubmit: handlePreChatSubmit,
@@ -3522,7 +4135,7 @@ function Chat({
3522
4135
  conversationId,
3523
4136
  currentUser: userInfo || void 0
3524
4137
  };
3525
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4138
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3526
4139
  ChatWidget,
3527
4140
  {
3528
4141
  config: fullConfig,
@@ -3535,12 +4148,12 @@ function Chat({
3535
4148
  }
3536
4149
 
3537
4150
  // src/components/TypingIndicator.tsx
3538
- var import_jsx_runtime12 = require("react/jsx-runtime");
4151
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3539
4152
  function TypingIndicator({ isTyping, userName }) {
3540
4153
  if (!isTyping) {
3541
4154
  return null;
3542
4155
  }
3543
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4156
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3544
4157
  "div",
3545
4158
  {
3546
4159
  className: "px-4 py-2",
@@ -3548,8 +4161,8 @@ function TypingIndicator({ isTyping, userName }) {
3548
4161
  borderTop: "1px solid rgba(255,255,255,0.06)",
3549
4162
  backgroundColor: "rgba(0,0,0,0.15)"
3550
4163
  },
3551
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
3552
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4164
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", children: [
4165
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3553
4166
  "span",
3554
4167
  {
3555
4168
  className: "rounded-full animate-bounce",
@@ -3563,7 +4176,7 @@ function TypingIndicator({ isTyping, userName }) {
3563
4176
  },
3564
4177
  i
3565
4178
  )) }),
3566
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4179
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3567
4180
  "span",
3568
4181
  {
3569
4182
  style: {
@@ -3580,6 +4193,9 @@ function TypingIndicator({ isTyping, userName }) {
3580
4193
  }
3581
4194
  // Annotate the CommonJS export names for ESM import in node:
3582
4195
  0 && (module.exports = {
4196
+ BookingCancelledCard,
4197
+ BookingConfirmationCard,
4198
+ BookingSlotPicker,
3583
4199
  Chat,
3584
4200
  ChatHeader,
3585
4201
  ChatInput,