@xcelsior/ui-chat 2.0.4 → 2.0.5

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 +1042 -427
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1009 -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 +253 -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,481 @@ 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
+ const bgColor = theme?.background || "#00001a";
1326
+ const isLightTheme = (() => {
1327
+ if (!bgColor.startsWith("#")) return false;
1328
+ const hex = bgColor.replace("#", "");
1329
+ const r = parseInt(hex.substring(0, 2), 16);
1330
+ const g = parseInt(hex.substring(2, 4), 16);
1331
+ const b = parseInt(hex.substring(4, 6), 16);
1332
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
1333
+ })();
1334
+ const primaryColor = theme?.primary || "#337eff";
1335
+ const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1336
+ const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
1337
+ const cardStyle = isLightTheme ? {
1338
+ backgroundColor: "rgba(0,0,0,0.03)",
1339
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
1340
+ borderRadius: "14px",
1341
+ padding: "14px"
1342
+ } : {
1343
+ backgroundColor: "rgba(255,255,255,0.04)",
1344
+ boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
1345
+ borderRadius: "14px",
1346
+ padding: "14px"
1347
+ };
1348
+ const activePillStyle = {
1349
+ backgroundColor: primaryColor,
1350
+ color: "#ffffff",
1351
+ borderRadius: "20px",
1352
+ padding: "5px 12px",
1353
+ fontSize: "12px",
1354
+ fontWeight: "600",
1355
+ letterSpacing: "0.01em",
1356
+ cursor: disabled ? "default" : "pointer",
1357
+ border: "none",
1358
+ flexShrink: 0
1359
+ };
1360
+ const inactivePillStyle = isLightTheme ? {
1361
+ backgroundColor: "rgba(0,0,0,0.04)",
1362
+ color: textColor,
1363
+ borderRadius: "20px",
1364
+ padding: "5px 12px",
1365
+ fontSize: "12px",
1366
+ fontWeight: "500",
1367
+ letterSpacing: "0.01em",
1368
+ cursor: disabled ? "default" : "pointer",
1369
+ border: "none",
1370
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.1)",
1371
+ flexShrink: 0
1372
+ } : {
1373
+ backgroundColor: "rgba(255,255,255,0.05)",
1374
+ color: textColor,
1375
+ borderRadius: "20px",
1376
+ padding: "5px 12px",
1377
+ fontSize: "12px",
1378
+ fontWeight: "500",
1379
+ letterSpacing: "0.01em",
1380
+ cursor: disabled ? "default" : "pointer",
1381
+ border: "none",
1382
+ boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.1)",
1383
+ flexShrink: 0
1384
+ };
1385
+ const activeSlot = selectedSlot?.date === data.availableDates[selectedDateIndex]?.date ? selectedSlot.time : null;
1386
+ const [confirming, setConfirming] = (0, import_react6.useState)(false);
1387
+ const handleSlotClick = (date, time) => {
1388
+ if (disabled || confirming) return;
1389
+ setSelectedSlot({ date, time });
1390
+ };
1391
+ const handleConfirm = (0, import_react6.useCallback)(() => {
1392
+ if (!selectedSlot || disabled || confirming) return;
1393
+ setConfirming(true);
1394
+ onSlotSelected(selectedSlot.date, selectedSlot.time);
1395
+ }, [selectedSlot, disabled, confirming, onSlotSelected]);
1396
+ const currentDate = data.availableDates[selectedDateIndex];
1397
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: cardStyle, children: [
1398
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1399
+ "p",
1400
+ {
1401
+ style: {
1402
+ fontSize: "11px",
1403
+ fontWeight: "600",
1404
+ letterSpacing: "0.06em",
1405
+ textTransform: "uppercase",
1406
+ color: textMuted,
1407
+ marginBottom: "10px"
1408
+ },
1409
+ children: "Select a date"
1410
+ }
1411
+ ),
1412
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1413
+ "div",
1414
+ {
1415
+ style: {
1416
+ display: "flex",
1417
+ gap: "6px",
1418
+ overflowX: "auto",
1419
+ paddingBottom: "2px",
1420
+ scrollbarWidth: "none",
1421
+ msOverflowStyle: "none",
1422
+ marginBottom: "14px"
1423
+ },
1424
+ children: data.availableDates.map((dateSlot, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1425
+ "button",
1426
+ {
1427
+ type: "button",
1428
+ onClick: () => !disabled && setSelectedDateIndex(index),
1429
+ style: index === selectedDateIndex ? activePillStyle : inactivePillStyle,
1430
+ title: dateSlot.dayName,
1431
+ "aria-pressed": index === selectedDateIndex,
1432
+ disabled,
1433
+ children: dateSlot.dayLabel
1434
+ },
1435
+ dateSlot.date
1436
+ ))
1437
+ }
1438
+ ),
1439
+ currentDate && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1440
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1441
+ "p",
1442
+ {
1443
+ style: {
1444
+ fontSize: "11px",
1445
+ fontWeight: "600",
1446
+ letterSpacing: "0.06em",
1447
+ textTransform: "uppercase",
1448
+ color: textMuted,
1449
+ marginBottom: "8px"
1450
+ },
1451
+ children: currentDate.dayName
1452
+ }
1453
+ ),
1454
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1455
+ "div",
1456
+ {
1457
+ style: {
1458
+ display: "grid",
1459
+ gridTemplateColumns: "repeat(3, 1fr)",
1460
+ gap: "6px",
1461
+ marginBottom: "14px"
1462
+ },
1463
+ children: currentDate.slots.map((time) => {
1464
+ const isSelected = activeSlot === time;
1465
+ const isConfirmed = selectedSlot?.date === currentDate.date && selectedSlot.time === time;
1466
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1467
+ "button",
1468
+ {
1469
+ type: "button",
1470
+ onClick: () => handleSlotClick(currentDate.date, time),
1471
+ disabled: disabled && !isConfirmed,
1472
+ "aria-pressed": isSelected,
1473
+ style: {
1474
+ ...isSelected ? activePillStyle : inactivePillStyle,
1475
+ padding: "7px 8px",
1476
+ borderRadius: "8px",
1477
+ fontSize: "13px",
1478
+ fontWeight: isSelected ? "600" : "400",
1479
+ textAlign: "center",
1480
+ opacity: disabled && !isConfirmed ? 0.4 : 1,
1481
+ transition: "opacity 0.15s ease"
1482
+ },
1483
+ children: time
1484
+ },
1485
+ time
1486
+ );
1487
+ })
1488
+ }
1489
+ )
1490
+ ] }),
1491
+ selectedSlot && !disabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1492
+ "button",
1493
+ {
1494
+ type: "button",
1495
+ onClick: handleConfirm,
1496
+ disabled: confirming,
1497
+ style: {
1498
+ width: "100%",
1499
+ padding: "10px 16px",
1500
+ borderRadius: "10px",
1501
+ border: "none",
1502
+ backgroundColor: confirming ? `${primaryColor}80` : primaryColor,
1503
+ color: "#ffffff",
1504
+ fontSize: "13px",
1505
+ fontWeight: "600",
1506
+ letterSpacing: "0.01em",
1507
+ cursor: confirming ? "default" : "pointer",
1508
+ marginBottom: "12px",
1509
+ display: "flex",
1510
+ alignItems: "center",
1511
+ justifyContent: "center",
1512
+ gap: "6px",
1513
+ transition: "background-color 0.15s ease"
1514
+ },
1515
+ children: [
1516
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Calendar, { size: 14, "aria-hidden": "true" }),
1517
+ confirming ? "Booking..." : `Book ${selectedSlot.time} on ${data.availableDates.find((d) => d.date === selectedSlot.date)?.dayLabel ?? selectedSlot.date}`
1518
+ ]
1519
+ }
1520
+ ),
1521
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1522
+ "div",
1523
+ {
1524
+ style: {
1525
+ display: "flex",
1526
+ alignItems: "center",
1527
+ justifyContent: "space-between",
1528
+ gap: "8px"
1529
+ },
1530
+ children: [
1531
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
1532
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Globe, { size: 11, color: textMuted, "aria-hidden": "true" }),
1533
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
1534
+ ] }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
1536
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Clock, { size: 11, color: textMuted, "aria-hidden": "true" }),
1537
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: [
1538
+ data.meetingDuration,
1539
+ " min"
1540
+ ] })
1541
+ ] })
1542
+ ]
1543
+ }
1544
+ )
1545
+ ] });
1546
+ }
1547
+
1548
+ // src/components/BookingConfirmationCard.tsx
1549
+ var import_lucide_react2 = require("lucide-react");
1550
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1551
+ function BookingConfirmationCard({ data, theme }) {
1552
+ const bgColor = theme?.background || "#00001a";
1553
+ const isLightTheme = (() => {
1554
+ if (!bgColor.startsWith("#")) return false;
1555
+ const hex = bgColor.replace("#", "");
1556
+ const r = parseInt(hex.substring(0, 2), 16);
1557
+ const g = parseInt(hex.substring(2, 4), 16);
1558
+ const b = parseInt(hex.substring(4, 6), 16);
1559
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
1560
+ })();
1561
+ const primaryColor = theme?.primary || "#337eff";
1562
+ const successColor = theme?.statusPositive || "#22c55e";
1563
+ const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1564
+ const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
1565
+ const cardStyle = isLightTheme ? {
1566
+ backgroundColor: "rgba(0,0,0,0.03)",
1567
+ boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
1568
+ borderRadius: "14px",
1569
+ padding: "16px",
1570
+ overflow: "hidden"
1571
+ } : {
1572
+ backgroundColor: "rgba(255,255,255,0.04)",
1573
+ boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
1574
+ borderRadius: "14px",
1575
+ padding: "16px",
1576
+ overflow: "hidden"
1577
+ };
1578
+ const detailRowStyle = {
1579
+ display: "flex",
1580
+ alignItems: "flex-start",
1581
+ gap: "8px",
1582
+ marginBottom: "8px"
1583
+ };
1584
+ const dividerStyle = {
1585
+ height: "1px",
1586
+ backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)",
1587
+ margin: "12px 0"
1588
+ };
1589
+ const actionButtonBase = {
1590
+ display: "flex",
1591
+ alignItems: "center",
1592
+ justifyContent: "center",
1593
+ gap: "6px",
1594
+ flex: 1,
1595
+ padding: "9px 12px",
1596
+ borderRadius: "9px",
1597
+ fontSize: "12px",
1598
+ fontWeight: "600",
1599
+ letterSpacing: "0.01em",
1600
+ cursor: "pointer",
1601
+ textDecoration: "none",
1602
+ border: "none",
1603
+ transition: "opacity 0.15s ease"
1604
+ };
1605
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: cardStyle, children: [
1606
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
1607
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.CircleCheck, { size: 22, color: successColor, "aria-hidden": "true" }),
1608
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1609
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1610
+ "p",
1611
+ {
1612
+ style: {
1613
+ fontSize: "14px",
1614
+ fontWeight: "700",
1615
+ color: textColor,
1616
+ letterSpacing: "0.006em",
1617
+ lineHeight: 1.3
1618
+ },
1619
+ children: "Meeting Confirmed"
1620
+ }
1621
+ ),
1622
+ /* @__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" })
1623
+ ] })
1624
+ ] }),
1625
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1626
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: detailRowStyle, children: [
1627
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Calendar, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1628
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
1629
+ data.meetingDate,
1630
+ " at ",
1631
+ data.meetingTime
1632
+ ] })
1633
+ ] }),
1634
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: detailRowStyle, children: [
1635
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Clock, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1636
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
1637
+ data.meetingDuration,
1638
+ " minutes"
1639
+ ] })
1640
+ ] }),
1641
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
1642
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.MessageSquare, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1643
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: data.meetingPurpose })
1644
+ ] })
1645
+ ] }),
1646
+ data.timezone && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px", marginTop: "8px" }, children: [
1647
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Globe, { size: 11, color: textMuted, "aria-hidden": "true" }),
1648
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
1649
+ ] }),
1650
+ (data.meetLink || data.calendarLink) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1651
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dividerStyle }),
1652
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1653
+ data.meetLink && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1654
+ "a",
1655
+ {
1656
+ href: data.meetLink,
1657
+ target: "_blank",
1658
+ rel: "noopener noreferrer",
1659
+ style: {
1660
+ ...actionButtonBase,
1661
+ background: `linear-gradient(135deg, ${primaryColor}, ${theme?.primaryStrong || "#005eff"})`,
1662
+ color: "#ffffff",
1663
+ boxShadow: `0 2px 8px -2px ${primaryColor}50`
1664
+ },
1665
+ children: [
1666
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Video, { size: 13, "aria-hidden": "true" }),
1667
+ "Join Meet"
1668
+ ]
1669
+ }
1670
+ ),
1671
+ data.calendarLink && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1672
+ "a",
1673
+ {
1674
+ href: data.calendarLink,
1675
+ target: "_blank",
1676
+ rel: "noopener noreferrer",
1677
+ style: {
1678
+ ...actionButtonBase,
1679
+ backgroundColor: isLightTheme ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.07)",
1680
+ color: textColor,
1681
+ 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)"
1682
+ },
1683
+ children: [
1684
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.CalendarPlus, { size: 13, "aria-hidden": "true" }),
1685
+ "Add to Calendar"
1686
+ ]
1687
+ }
1688
+ )
1689
+ ] })
1690
+ ] })
1691
+ ] });
1692
+ }
1693
+
1694
+ // src/components/BookingCancelledCard.tsx
1695
+ var import_lucide_react3 = require("lucide-react");
1696
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1697
+ function BookingCancelledCard({ data, theme }) {
1698
+ const bgColor = theme?.background || "#00001a";
1699
+ const isLightTheme = (() => {
1700
+ if (!bgColor.startsWith("#")) return false;
1701
+ const hex = bgColor.replace("#", "");
1702
+ const r = parseInt(hex.substring(0, 2), 16);
1703
+ const g = parseInt(hex.substring(2, 4), 16);
1704
+ const b = parseInt(hex.substring(4, 6), 16);
1705
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
1706
+ })();
1707
+ const negativeColor = theme?.statusNegative || "#ef4444";
1708
+ const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1709
+ const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
1710
+ const cardStyle = isLightTheme ? {
1711
+ backgroundColor: "rgba(239,68,68,0.04)",
1712
+ boxShadow: "inset 0 0 0 1px rgba(239,68,68,0.12)",
1713
+ borderRadius: "14px",
1714
+ padding: "16px"
1715
+ } : {
1716
+ backgroundColor: "rgba(239,68,68,0.06)",
1717
+ boxShadow: "inset 0 0 0 0.5px rgba(239,68,68,0.18)",
1718
+ borderRadius: "14px",
1719
+ padding: "16px"
1720
+ };
1721
+ const strikethroughTextStyle = {
1722
+ fontSize: "13px",
1723
+ color: textMuted,
1724
+ lineHeight: 1.4,
1725
+ textDecoration: "line-through",
1726
+ textDecorationColor: isLightTheme ? "rgba(0,0,0,0.25)" : "rgba(255,255,255,0.2)"
1727
+ };
1728
+ const detailRowStyle = {
1729
+ display: "flex",
1730
+ alignItems: "flex-start",
1731
+ gap: "8px",
1732
+ marginBottom: "8px"
1733
+ };
1734
+ const cancelledByLabel = data.cancelledBy === "visitor" ? "Cancelled by you" : "Cancelled by support";
1735
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: cardStyle, children: [
1736
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
1737
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.CircleX, { size: 22, color: negativeColor, "aria-hidden": "true" }),
1738
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1739
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1740
+ "p",
1741
+ {
1742
+ style: {
1743
+ fontSize: "14px",
1744
+ fontWeight: "700",
1745
+ color: textColor,
1746
+ letterSpacing: "0.006em",
1747
+ lineHeight: 1.3
1748
+ },
1749
+ children: "Meeting Cancelled"
1750
+ }
1751
+ ),
1752
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1753
+ "p",
1754
+ {
1755
+ style: {
1756
+ fontSize: "11px",
1757
+ color: negativeColor,
1758
+ letterSpacing: "0.01em",
1759
+ marginTop: "1px",
1760
+ opacity: 0.8
1761
+ },
1762
+ children: cancelledByLabel
1763
+ }
1764
+ )
1765
+ ] })
1766
+ ] }),
1767
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1768
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: detailRowStyle, children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Calendar, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: strikethroughTextStyle, children: [
1771
+ data.meetingDate,
1772
+ " at ",
1773
+ data.meetingTime
1774
+ ] })
1775
+ ] }),
1776
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
1777
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.MessageSquare, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
1778
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: strikethroughTextStyle, children: data.meetingPurpose })
1779
+ ] })
1780
+ ] })
1781
+ ] });
1782
+ }
1783
+
1784
+ // src/components/MessageItem.tsx
1785
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1280
1786
  function MessageItem({
1281
1787
  message,
1282
1788
  currentUser,
1283
1789
  showAvatar = true,
1284
1790
  showTimestamp = true,
1285
- theme
1791
+ theme,
1792
+ onBookingSlotSelected
1286
1793
  }) {
1287
1794
  const isOwnMessage = message.senderType === currentUser.type;
1288
1795
  const isSystemMessage = message.senderType === "system";
@@ -1301,8 +1808,63 @@ function MessageItem({
1301
1808
  const primaryStrong = theme?.primaryStrong || "#005eff";
1302
1809
  const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1303
1810
  const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.35)");
1811
+ if (message.messageType === "booking_slots" || message.messageType === "booking_confirmation" || message.messageType === "booking_cancelled") {
1812
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-2.5 mb-3 flex-row", children: [
1813
+ 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 }) }),
1814
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col max-w-[85%] items-start", children: [
1815
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1816
+ "span",
1817
+ {
1818
+ className: "mb-1 px-1 font-medium",
1819
+ style: {
1820
+ color: isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)",
1821
+ fontSize: "11px",
1822
+ letterSpacing: "0.019em"
1823
+ },
1824
+ children: "AI Assistant"
1825
+ }
1826
+ ),
1827
+ message.messageType === "booking_slots" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1828
+ BookingSlotPicker,
1829
+ {
1830
+ data: message.metadata?.bookingData,
1831
+ theme,
1832
+ onSlotSelected: (date, time) => {
1833
+ onBookingSlotSelected?.(date, time, message.id);
1834
+ },
1835
+ disabled: !!message.metadata?.slotSelected
1836
+ }
1837
+ ),
1838
+ message.messageType === "booking_confirmation" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1839
+ BookingConfirmationCard,
1840
+ {
1841
+ data: message.metadata?.bookingConfirmation,
1842
+ theme
1843
+ }
1844
+ ),
1845
+ message.messageType === "booking_cancelled" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1846
+ BookingCancelledCard,
1847
+ {
1848
+ data: message.metadata?.bookingCancelled,
1849
+ theme
1850
+ }
1851
+ ),
1852
+ 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)(
1853
+ "span",
1854
+ {
1855
+ style: {
1856
+ fontSize: "11px",
1857
+ letterSpacing: "0.019em",
1858
+ color: textMuted
1859
+ },
1860
+ children: (0, import_date_fns.formatDistanceToNow)(new Date(message.createdAt), { addSuffix: true })
1861
+ }
1862
+ ) })
1863
+ ] })
1864
+ ] });
1865
+ }
1304
1866
  if (isSystemMessage) {
1305
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1867
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1306
1868
  "div",
1307
1869
  {
1308
1870
  className: "px-4 py-1.5 rounded-full",
@@ -1310,7 +1872,7 @@ function MessageItem({
1310
1872
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.03)",
1311
1873
  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
1874
  },
1313
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1875
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1314
1876
  "p",
1315
1877
  {
1316
1878
  style: {
@@ -1349,12 +1911,12 @@ function MessageItem({
1349
1911
  borderRadius: "18px 18px 18px 4px",
1350
1912
  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
1913
  };
1352
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1914
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1353
1915
  "div",
1354
1916
  {
1355
1917
  className: `flex gap-2.5 mb-3 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
1356
1918
  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)(
1919
+ 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
1920
  "div",
1359
1921
  {
1360
1922
  className: "h-7 w-7 rounded-full flex items-center justify-center",
@@ -1362,34 +1924,24 @@ function MessageItem({
1362
1924
  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
1925
  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
1926
  },
1365
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1366
- "svg",
1927
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1928
+ import_lucide_react4.Headphones,
1367
1929
  {
1368
- width: "14",
1369
- height: "14",
1370
- viewBox: "0 0 24 24",
1371
- fill: "none",
1930
+ size: 14,
1372
1931
  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
- ]
1932
+ strokeWidth: 2,
1933
+ "aria-hidden": "true"
1382
1934
  }
1383
1935
  )
1384
1936
  }
1385
1937
  ) }),
1386
- showAvatar && isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-7 flex-shrink-0" }),
1387
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1938
+ showAvatar && isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-7 flex-shrink-0" }),
1939
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1388
1940
  "div",
1389
1941
  {
1390
1942
  className: `flex flex-col max-w-[75%] ${isOwnMessage ? "items-end" : "items-start"}`,
1391
1943
  children: [
1392
- senderLabel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1944
+ senderLabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1393
1945
  "span",
1394
1946
  {
1395
1947
  className: "mb-1 px-1 font-medium",
@@ -1401,7 +1953,7 @@ function MessageItem({
1401
1953
  children: senderLabel
1402
1954
  }
1403
1955
  ),
1404
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1956
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1405
1957
  "div",
1406
1958
  {
1407
1959
  className: "px-4 py-2.5",
@@ -1414,12 +1966,12 @@ function MessageItem({
1414
1966
  children: [
1415
1967
  message.messageType === "text" && (isOwnMessage ? (
1416
1968
  // 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 })
1969
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
1418
1970
  ) : (
1419
1971
  // Bot / agent messages: full markdown rendering
1420
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownMessage, { content: message.content, theme })
1972
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownMessage, { content: message.content, theme })
1421
1973
  )),
1422
- message.messageType === "image" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1974
+ message.messageType === "image" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1423
1975
  "img",
1424
1976
  {
1425
1977
  src: message.content,
@@ -1428,26 +1980,9 @@ function MessageItem({
1428
1980
  loading: "lazy"
1429
1981
  }
1430
1982
  ) }),
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)(
1983
+ message.messageType === "file" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [
1984
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Paperclip, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" }),
1985
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1451
1986
  "a",
1452
1987
  {
1453
1988
  href: message.content,
@@ -1465,12 +2000,12 @@ function MessageItem({
1465
2000
  ]
1466
2001
  }
1467
2002
  ),
1468
- showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2003
+ showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1469
2004
  "div",
1470
2005
  {
1471
2006
  className: `flex items-center gap-1.5 mt-1 px-1 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
1472
2007
  children: [
1473
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2008
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1474
2009
  "span",
1475
2010
  {
1476
2011
  style: {
@@ -1483,63 +2018,10 @@ function MessageItem({
1483
2018
  })
1484
2019
  }
1485
2020
  ),
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
- )
2021
+ isOwnMessage && message.status && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { color: textMuted }, children: [
2022
+ message.status === "sent" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Check, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
2023
+ message.status === "delivered" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.CheckCheck, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
2024
+ message.status === "read" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.CheckCheck, { size: 14, strokeWidth: 2.5, stroke: primaryColor, "aria-hidden": "true" })
1543
2025
  ] })
1544
2026
  ]
1545
2027
  }
@@ -1553,8 +2035,8 @@ function MessageItem({
1553
2035
  }
1554
2036
 
1555
2037
  // src/components/ThinkingIndicator.tsx
1556
- var import_react6 = require("react");
1557
- var import_jsx_runtime6 = require("react/jsx-runtime");
2038
+ var import_react7 = require("react");
2039
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1558
2040
  var PHRASE_POOLS = {
1559
2041
  // ── Greetings & small talk ──
1560
2042
  greeting: [
@@ -1737,17 +2219,17 @@ function ThinkingIndicator({
1737
2219
  const isLightTheme = computeIsLightTheme2(theme?.background);
1738
2220
  const primaryColor = theme?.primary || "#337eff";
1739
2221
  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)(() => {
2222
+ const [phraseIndex, setPhraseIndex] = (0, import_react7.useState)(0);
2223
+ const [displayText, setDisplayText] = (0, import_react7.useState)("");
2224
+ const [isDeleting, setIsDeleting] = (0, import_react7.useState)(false);
2225
+ const phrasesRef = (0, import_react7.useRef)(getShuffledPhrases(detectContext(lastUserMessage)));
2226
+ (0, import_react7.useEffect)(() => {
1745
2227
  phrasesRef.current = getShuffledPhrases(detectContext(lastUserMessage));
1746
2228
  setPhraseIndex(0);
1747
2229
  setDisplayText("");
1748
2230
  setIsDeleting(false);
1749
2231
  }, [lastUserMessage]);
1750
- (0, import_react6.useEffect)(() => {
2232
+ (0, import_react7.useEffect)(() => {
1751
2233
  const phrases = phrasesRef.current;
1752
2234
  const phrase = phrases[phraseIndex % phrases.length];
1753
2235
  let timeout;
@@ -1773,7 +2255,7 @@ function ThinkingIndicator({
1773
2255
  }
1774
2256
  return () => clearTimeout(timeout);
1775
2257
  }, [displayText, isDeleting, phraseIndex]);
1776
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2258
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1777
2259
  "div",
1778
2260
  {
1779
2261
  className: "flex gap-2.5 mb-3",
@@ -1781,8 +2263,8 @@ function ThinkingIndicator({
1781
2263
  "aria-live": "polite",
1782
2264
  "aria-label": "Xcelsior is thinking",
1783
2265
  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)(
2266
+ 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 }) }),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-col items-start", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1786
2268
  "div",
1787
2269
  {
1788
2270
  style: {
@@ -1792,8 +2274,8 @@ function ThinkingIndicator({
1792
2274
  padding: "12px 16px",
1793
2275
  minWidth: 160
1794
2276
  },
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)(
2277
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
2278
+ /* @__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
2279
  "span",
1798
2280
  {
1799
2281
  style: {
@@ -1807,7 +2289,7 @@ function ThinkingIndicator({
1807
2289
  },
1808
2290
  i
1809
2291
  )) }),
1810
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2292
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1811
2293
  "span",
1812
2294
  {
1813
2295
  style: {
@@ -1818,7 +2300,7 @@ function ThinkingIndicator({
1818
2300
  },
1819
2301
  children: [
1820
2302
  displayText,
1821
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2303
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1822
2304
  "span",
1823
2305
  {
1824
2306
  style: {
@@ -1844,7 +2326,7 @@ function ThinkingIndicator({
1844
2326
  }
1845
2327
 
1846
2328
  // src/components/MessageList.tsx
1847
- var import_jsx_runtime7 = require("react/jsx-runtime");
2329
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1848
2330
  function MessageList({
1849
2331
  messages,
1850
2332
  currentUser,
@@ -1857,15 +2339,16 @@ function MessageList({
1857
2339
  isLoadingMore = false,
1858
2340
  theme,
1859
2341
  onQuickAction,
1860
- isBotThinking = false
2342
+ isBotThinking = false,
2343
+ onBookingSlotSelected
1861
2344
  }) {
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);
2345
+ const messagesEndRef = (0, import_react8.useRef)(null);
2346
+ const containerRef = (0, import_react8.useRef)(null);
2347
+ const prevLengthRef = (0, import_react8.useRef)(messages.length);
2348
+ const loadMoreTriggerRef = (0, import_react8.useRef)(null);
2349
+ const prevScrollHeightRef = (0, import_react8.useRef)(0);
2350
+ const hasInitialScrolledRef = (0, import_react8.useRef)(false);
2351
+ const isUserScrollingRef = (0, import_react8.useRef)(false);
1869
2352
  const bgColor = theme?.background || "#00001a";
1870
2353
  const isLightTheme = (() => {
1871
2354
  if (!bgColor.startsWith("#")) return false;
@@ -1878,7 +2361,7 @@ function MessageList({
1878
2361
  const primaryColor = theme?.primary || "#337eff";
1879
2362
  const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1880
2363
  const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
1881
- (0, import_react7.useEffect)(() => {
2364
+ (0, import_react8.useEffect)(() => {
1882
2365
  if (autoScroll && messagesEndRef.current) {
1883
2366
  if (messages.length > prevLengthRef.current && !isLoadingMore) {
1884
2367
  messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
@@ -1886,7 +2369,7 @@ function MessageList({
1886
2369
  prevLengthRef.current = messages.length;
1887
2370
  }
1888
2371
  }, [messages.length, autoScroll, isLoadingMore]);
1889
- (0, import_react7.useEffect)(() => {
2372
+ (0, import_react8.useEffect)(() => {
1890
2373
  if (messages.length > 0 && messagesEndRef.current && !isLoading && !hasInitialScrolledRef.current) {
1891
2374
  setTimeout(() => {
1892
2375
  messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
@@ -1900,7 +2383,7 @@ function MessageList({
1900
2383
  hasInitialScrolledRef.current = true;
1901
2384
  }
1902
2385
  }, [isLoading, messages.length]);
1903
- (0, import_react7.useEffect)(() => {
2386
+ (0, import_react8.useEffect)(() => {
1904
2387
  if (isLoadingMore) {
1905
2388
  prevScrollHeightRef.current = containerRef.current?.scrollHeight || 0;
1906
2389
  } else if (prevScrollHeightRef.current > 0 && containerRef.current) {
@@ -1910,7 +2393,7 @@ function MessageList({
1910
2393
  prevScrollHeightRef.current = 0;
1911
2394
  }
1912
2395
  }, [isLoadingMore]);
1913
- const handleScroll = (0, import_react7.useCallback)(() => {
2396
+ const handleScroll = (0, import_react8.useCallback)(() => {
1914
2397
  if (!containerRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
1915
2398
  if (!isUserScrollingRef.current) return;
1916
2399
  const { scrollTop } = containerRef.current;
@@ -1918,18 +2401,18 @@ function MessageList({
1918
2401
  onLoadMore();
1919
2402
  }
1920
2403
  }, [onLoadMore, hasMore, isLoadingMore]);
1921
- (0, import_react7.useEffect)(() => {
2404
+ (0, import_react8.useEffect)(() => {
1922
2405
  const container = containerRef.current;
1923
2406
  if (!container) return;
1924
2407
  container.addEventListener("scroll", handleScroll);
1925
2408
  return () => container.removeEventListener("scroll", handleScroll);
1926
2409
  }, [handleScroll]);
1927
2410
  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" }) });
2411
+ 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
2412
  }
1930
2413
  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)(
2414
+ 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: [
2415
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1933
2416
  "h3",
1934
2417
  {
1935
2418
  className: "font-semibold mb-2",
@@ -1941,7 +2424,7 @@ function MessageList({
1941
2424
  children: "How can we help?"
1942
2425
  }
1943
2426
  ),
1944
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2427
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1945
2428
  "p",
1946
2429
  {
1947
2430
  className: "max-w-[240px]",
@@ -1955,11 +2438,11 @@ function MessageList({
1955
2438
  children: "Ask us anything. We are here to help you get the most out of Xcelsior."
1956
2439
  }
1957
2440
  ),
1958
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex flex-wrap justify-center gap-2", children: [
2441
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap justify-center gap-2", children: [
1959
2442
  { label: "Our services", message: "What services does Xcelsior offer?" },
1960
2443
  { label: "Get a quote", message: "I would like to get a quote for a project" },
1961
2444
  { label: "Support", message: "I need help with something" }
1962
- ].map((action) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2445
+ ].map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1963
2446
  "button",
1964
2447
  {
1965
2448
  type: "button",
@@ -1991,14 +2474,14 @@ function MessageList({
1991
2474
  )) })
1992
2475
  ] });
1993
2476
  }
1994
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2477
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1995
2478
  "div",
1996
2479
  {
1997
2480
  ref: containerRef,
1998
2481
  className: "flex-1 overflow-y-auto px-4 py-3",
1999
2482
  style: { scrollBehavior: "smooth" },
2000
2483
  children: [
2001
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
2484
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `
2002
2485
  @keyframes thinkingPulse {
2003
2486
  0%, 60%, 100% { opacity: 0.3; transform: scale(0.8); }
2004
2487
  30% { opacity: 1; transform: scale(1); }
@@ -2008,23 +2491,24 @@ function MessageList({
2008
2491
  50% { opacity: 0; }
2009
2492
  }
2010
2493
  ` }),
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)(
2494
+ isLoadingMore && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex justify-center py-3", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, { size: "sm" }) }),
2495
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: loadMoreTriggerRef }),
2496
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2014
2497
  MessageItem,
2015
2498
  {
2016
2499
  message,
2017
2500
  currentUser,
2018
2501
  showAvatar: true,
2019
2502
  showTimestamp: true,
2020
- theme
2503
+ theme,
2504
+ onBookingSlotSelected
2021
2505
  },
2022
2506
  message.id
2023
2507
  )),
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)(
2508
+ isTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-2.5 mb-3", children: [
2509
+ /* @__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 }) }),
2510
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-start", children: [
2511
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2028
2512
  "div",
2029
2513
  {
2030
2514
  className: "px-4 py-3",
@@ -2033,7 +2517,7 @@ function MessageList({
2033
2517
  borderRadius: "18px 18px 18px 4px",
2034
2518
  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
2519
  },
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)(
2520
+ 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
2521
  "span",
2038
2522
  {
2039
2523
  className: "rounded-full animate-bounce",
@@ -2049,7 +2533,7 @@ function MessageList({
2049
2533
  )) })
2050
2534
  }
2051
2535
  ),
2052
- typingUser && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2536
+ typingUser && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2053
2537
  "span",
2054
2538
  {
2055
2539
  className: "mt-1 px-1",
@@ -2066,24 +2550,24 @@ function MessageList({
2066
2550
  )
2067
2551
  ] })
2068
2552
  ] }),
2069
- isBotThinking && !isTyping && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2553
+ isBotThinking && !isTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2070
2554
  ThinkingIndicator,
2071
2555
  {
2072
2556
  theme,
2073
2557
  lastUserMessage: messages.filter((m) => m.senderType === "customer").pop()?.content
2074
2558
  }
2075
2559
  ),
2076
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: messagesEndRef })
2560
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
2077
2561
  ]
2078
2562
  }
2079
2563
  );
2080
2564
  }
2081
2565
 
2082
2566
  // src/components/ChatInput.tsx
2083
- var import_react8 = require("react");
2567
+ var import_react9 = require("react");
2084
2568
  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");
2569
+ var import_react10 = __toESM(require("@emoji-mart/react"));
2570
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2087
2571
  function isLightColor(color) {
2088
2572
  let r = 0, g = 0, b = 0;
2089
2573
  if (color.startsWith("#")) {
@@ -2101,18 +2585,18 @@ function ChatInput({
2101
2585
  fileUpload,
2102
2586
  disabled = false
2103
2587
  }) {
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);
2588
+ const [message, setMessage] = (0, import_react9.useState)("");
2589
+ const [showEmojiPicker, setShowEmojiPicker] = (0, import_react9.useState)(false);
2590
+ const [emojiData, setEmojiData] = (0, import_react9.useState)();
2591
+ const [emojiPickerPosition, setEmojiPickerPosition] = (0, import_react9.useState)(null);
2592
+ const [isFocused, setIsFocused] = (0, import_react9.useState)(false);
2593
+ const textAreaRef = (0, import_react9.useRef)(null);
2594
+ const emojiPickerRef = (0, import_react9.useRef)(null);
2595
+ const emojiButtonRef = (0, import_react9.useRef)(null);
2596
+ const fileInputRef = (0, import_react9.useRef)(null);
2597
+ const typingTimeoutRef = (0, import_react9.useRef)(null);
2598
+ const startTypingTimeoutRef = (0, import_react9.useRef)(null);
2599
+ const isTypingRef = (0, import_react9.useRef)(false);
2116
2600
  const enableEmoji = config.enableEmoji ?? true;
2117
2601
  const enableFileUpload = config.enableFileUpload ?? true;
2118
2602
  const bgColor = config.theme?.background || "#00001a";
@@ -2120,7 +2604,7 @@ function ChatInput({
2120
2604
  const textColor = config.theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
2121
2605
  const textMuted = config.theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
2122
2606
  const primaryColor = config.theme?.primary || "#337eff";
2123
- (0, import_react8.useEffect)(() => {
2607
+ (0, import_react9.useEffect)(() => {
2124
2608
  if (!enableEmoji) return;
2125
2609
  (async () => {
2126
2610
  try {
@@ -2131,7 +2615,7 @@ function ChatInput({
2131
2615
  }
2132
2616
  })();
2133
2617
  }, [enableEmoji]);
2134
- (0, import_react8.useEffect)(() => {
2618
+ (0, import_react9.useEffect)(() => {
2135
2619
  const handleClickOutside = (event) => {
2136
2620
  if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target)) {
2137
2621
  setShowEmojiPicker(false);
@@ -2144,7 +2628,7 @@ function ChatInput({
2144
2628
  document.removeEventListener("mousedown", handleClickOutside);
2145
2629
  };
2146
2630
  }, [showEmojiPicker]);
2147
- (0, import_react8.useEffect)(() => {
2631
+ (0, import_react9.useEffect)(() => {
2148
2632
  return () => {
2149
2633
  if (typingTimeoutRef.current) {
2150
2634
  clearTimeout(typingTimeoutRef.current);
@@ -2154,7 +2638,7 @@ function ChatInput({
2154
2638
  }
2155
2639
  };
2156
2640
  }, []);
2157
- (0, import_react8.useEffect)(() => {
2641
+ (0, import_react9.useEffect)(() => {
2158
2642
  if (!showEmojiPicker) return;
2159
2643
  const updatePosition = () => {
2160
2644
  if (emojiButtonRef.current) {
@@ -2261,7 +2745,7 @@ ${uploadedFile.markdown}
2261
2745
  };
2262
2746
  const canSend = message.trim().length > 0 && !disabled;
2263
2747
  const placeholderColor = isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.3)";
2264
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2748
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2265
2749
  "div",
2266
2750
  {
2267
2751
  className: "px-4 py-3",
@@ -2269,7 +2753,7 @@ ${uploadedFile.markdown}
2269
2753
  borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)"
2270
2754
  },
2271
2755
  children: [
2272
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2756
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2273
2757
  "div",
2274
2758
  {
2275
2759
  className: "relative flex items-center rounded-full transition-all duration-200",
@@ -2281,8 +2765,8 @@ ${uploadedFile.markdown}
2281
2765
  backdropFilter: "blur(32px)"
2282
2766
  },
2283
2767
  children: [
2284
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
2285
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2768
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
2769
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2286
2770
  "textarea",
2287
2771
  {
2288
2772
  ref: textAreaRef,
@@ -2311,8 +2795,8 @@ ${uploadedFile.markdown}
2311
2795
  disabled
2312
2796
  }
2313
2797
  ),
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)(
2798
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1 px-2 shrink-0", children: [
2799
+ enableEmoji && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2316
2800
  "button",
2317
2801
  {
2318
2802
  ref: emojiButtonRef,
@@ -2342,7 +2826,7 @@ ${uploadedFile.markdown}
2342
2826
  },
2343
2827
  disabled,
2344
2828
  "aria-label": "Add emoji",
2345
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2829
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2346
2830
  "svg",
2347
2831
  {
2348
2832
  width: "18",
@@ -2355,18 +2839,18 @@ ${uploadedFile.markdown}
2355
2839
  strokeLinejoin: "round",
2356
2840
  "aria-hidden": "true",
2357
2841
  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" })
2842
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Emoji" }),
2843
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2844
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
2845
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
2846
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
2363
2847
  ]
2364
2848
  }
2365
2849
  )
2366
2850
  }
2367
2851
  ),
2368
- enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2369
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2852
+ enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2853
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2370
2854
  "button",
2371
2855
  {
2372
2856
  type: "button",
@@ -2386,7 +2870,7 @@ ${uploadedFile.markdown}
2386
2870
  },
2387
2871
  disabled: disabled || fileUpload.isUploading,
2388
2872
  "aria-label": "Attach file",
2389
- children: fileUpload.isUploading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2873
+ children: fileUpload.isUploading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2390
2874
  "svg",
2391
2875
  {
2392
2876
  width: "18",
@@ -2398,11 +2882,11 @@ ${uploadedFile.markdown}
2398
2882
  className: "animate-spin",
2399
2883
  "aria-hidden": "true",
2400
2884
  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" })
2885
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Uploading" }),
2886
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
2403
2887
  ]
2404
2888
  }
2405
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2889
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2406
2890
  "svg",
2407
2891
  {
2408
2892
  width: "18",
@@ -2415,14 +2899,14 @@ ${uploadedFile.markdown}
2415
2899
  strokeLinejoin: "round",
2416
2900
  "aria-hidden": "true",
2417
2901
  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" })
2902
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Attach file" }),
2903
+ /* @__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
2904
  ]
2421
2905
  }
2422
2906
  )
2423
2907
  }
2424
2908
  ),
2425
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2909
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2426
2910
  "input",
2427
2911
  {
2428
2912
  ref: fileInputRef,
@@ -2433,7 +2917,7 @@ ${uploadedFile.markdown}
2433
2917
  }
2434
2918
  )
2435
2919
  ] }),
2436
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2920
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2437
2921
  "button",
2438
2922
  {
2439
2923
  type: "button",
@@ -2448,7 +2932,7 @@ ${uploadedFile.markdown}
2448
2932
  boxShadow: canSend ? `0 2px 8px -2px ${primaryColor}60` : "none"
2449
2933
  },
2450
2934
  "aria-label": "Send message",
2451
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2935
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2452
2936
  "svg",
2453
2937
  {
2454
2938
  width: "18",
@@ -2461,9 +2945,9 @@ ${uploadedFile.markdown}
2461
2945
  strokeLinejoin: "round",
2462
2946
  "aria-hidden": "true",
2463
2947
  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" })
2948
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Send" }),
2949
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
2950
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
2467
2951
  ]
2468
2952
  }
2469
2953
  )
@@ -2473,8 +2957,8 @@ ${uploadedFile.markdown}
2473
2957
  ]
2474
2958
  }
2475
2959
  ),
2476
- fileUpload.isUploading && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-2 px-1", children: [
2477
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2960
+ fileUpload.isUploading && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mt-2 px-1", children: [
2961
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2478
2962
  "div",
2479
2963
  {
2480
2964
  className: "w-full rounded-full overflow-hidden",
@@ -2482,7 +2966,7 @@ ${uploadedFile.markdown}
2482
2966
  height: 3,
2483
2967
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"
2484
2968
  },
2485
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2969
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2486
2970
  "div",
2487
2971
  {
2488
2972
  className: "h-full rounded-full transition-all duration-300",
@@ -2494,7 +2978,7 @@ ${uploadedFile.markdown}
2494
2978
  )
2495
2979
  }
2496
2980
  ),
2497
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2981
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2498
2982
  "p",
2499
2983
  {
2500
2984
  className: "mt-1",
@@ -2512,7 +2996,7 @@ ${uploadedFile.markdown}
2512
2996
  )
2513
2997
  ] }),
2514
2998
  showEmojiPicker && emojiData && emojiPickerPosition && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
2515
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2999
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2516
3000
  "div",
2517
3001
  {
2518
3002
  ref: emojiPickerRef,
@@ -2527,8 +3011,8 @@ ${uploadedFile.markdown}
2527
3011
  "0 16px 48px -8px rgba(0,0,0,0.5)"
2528
3012
  ].join(", ")
2529
3013
  },
2530
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2531
- import_react9.default,
3014
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3015
+ import_react10.default,
2532
3016
  {
2533
3017
  data: emojiData,
2534
3018
  onEmojiSelect: (emoji) => {
@@ -2553,7 +3037,7 @@ ${uploadedFile.markdown}
2553
3037
  }
2554
3038
 
2555
3039
  // src/components/ChatWidget.tsx
2556
- var import_jsx_runtime9 = require("react/jsx-runtime");
3040
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2557
3041
  function getAnonymousUser() {
2558
3042
  let anonId;
2559
3043
  try {
@@ -2599,14 +3083,20 @@ function ChatWidget({
2599
3083
  maxHeight: 900,
2600
3084
  enabled: !isFullPage
2601
3085
  });
2602
- const handleSendMessage = (0, import_react10.useCallback)(
3086
+ const lastSentRef = (0, import_react11.useRef)(null);
3087
+ const handleSendMessage = (0, import_react11.useCallback)(
2603
3088
  (content) => {
2604
3089
  if (!websocket.isConnected) {
2605
3090
  config.toast?.error("Not connected to chat server");
2606
3091
  return;
2607
3092
  }
3093
+ const now = Date.now();
3094
+ if (lastSentRef.current && lastSentRef.current.content === content && now - lastSentRef.current.time < 1e3) {
3095
+ return;
3096
+ }
3097
+ lastSentRef.current = { content, time: now };
2608
3098
  const optimisticMessage = {
2609
- id: `temp-${Date.now()}`,
3099
+ id: `temp-${now}`,
2610
3100
  conversationId: config.conversationId || "",
2611
3101
  senderId: effectiveUser.email,
2612
3102
  senderType: effectiveUser.type,
@@ -2625,7 +3115,18 @@ function ChatWidget({
2625
3115
  },
2626
3116
  [websocket, config, addMessage, effectiveUser]
2627
3117
  );
2628
- const handleTyping = (0, import_react10.useCallback)(
3118
+ const handleBookingSlotSelected = (0, import_react11.useCallback)(
3119
+ (date, time, _messageId) => {
3120
+ if (!websocket.isConnected) return;
3121
+ websocket.sendMessage("bookSlot", {
3122
+ conversationId: config.conversationId,
3123
+ date,
3124
+ time
3125
+ });
3126
+ },
3127
+ [websocket, config]
3128
+ );
3129
+ const handleTyping = (0, import_react11.useCallback)(
2629
3130
  (isTyping2) => {
2630
3131
  if (!websocket.isConnected || config.enableTypingIndicator === false) return;
2631
3132
  websocket.sendMessage("typing", {
@@ -2635,7 +3136,7 @@ function ChatWidget({
2635
3136
  },
2636
3137
  [websocket, config]
2637
3138
  );
2638
- (0, import_react10.useEffect)(() => {
3139
+ (0, import_react11.useEffect)(() => {
2639
3140
  if (websocket.error) {
2640
3141
  config.toast?.error(websocket.error.message || "An error occurred");
2641
3142
  }
@@ -2686,14 +3187,14 @@ function ChatWidget({
2686
3187
  outlineOffset: -1,
2687
3188
  transition: "outline 150ms ease"
2688
3189
  };
2689
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3190
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2690
3191
  "div",
2691
3192
  {
2692
3193
  className: isFullPage ? `flex flex-col h-full ${className}` : `fixed bottom-20 ${positionClass} z-50 flex flex-col overflow-hidden ${className}`,
2693
3194
  style: { ...containerStyle, ...dotGridBg, ...edgeHintStyle },
2694
3195
  ...!isFullPage ? containerResizeProps : {},
2695
3196
  children: [
2696
- !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3197
+ !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2697
3198
  ChatHeader,
2698
3199
  {
2699
3200
  agent: effectiveUser.type === "customer" ? {
@@ -2707,7 +3208,7 @@ function ChatWidget({
2707
3208
  theme: config.theme
2708
3209
  }
2709
3210
  ),
2710
- !websocket.isConnected && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3211
+ !websocket.isConnected && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2711
3212
  "div",
2712
3213
  {
2713
3214
  className: "px-4 py-2",
@@ -2715,15 +3216,15 @@ function ChatWidget({
2715
3216
  backgroundColor: isLightTheme ? "rgba(255,169,56,0.08)" : "rgba(255,169,56,0.06)",
2716
3217
  borderBottom: `1px solid rgba(255,169,56,${isLightTheme ? "0.15" : "0.12"})`
2717
3218
  },
2718
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
2719
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3219
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
3220
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2720
3221
  "div",
2721
3222
  {
2722
3223
  className: "w-1.5 h-1.5 rounded-full animate-pulse",
2723
3224
  style: { backgroundColor: config.theme?.statusCaution || "#ffa938" }
2724
3225
  }
2725
3226
  ),
2726
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3227
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2727
3228
  "span",
2728
3229
  {
2729
3230
  style: {
@@ -2734,7 +3235,7 @@ function ChatWidget({
2734
3235
  children: "Reconnecting..."
2735
3236
  }
2736
3237
  ),
2737
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3238
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2738
3239
  "button",
2739
3240
  {
2740
3241
  type: "button",
@@ -2758,8 +3259,8 @@ function ChatWidget({
2758
3259
  ] })
2759
3260
  }
2760
3261
  ),
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)(
3262
+ 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: [
3263
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2763
3264
  "div",
2764
3265
  {
2765
3266
  className: "w-7 h-7 border-2 border-t-transparent rounded-full animate-spin mx-auto mb-3",
@@ -2769,7 +3270,7 @@ function ChatWidget({
2769
3270
  }
2770
3271
  }
2771
3272
  ),
2772
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3273
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2773
3274
  "p",
2774
3275
  {
2775
3276
  style: {
@@ -2780,7 +3281,7 @@ function ChatWidget({
2780
3281
  children: "Loading messages..."
2781
3282
  }
2782
3283
  )
2783
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3284
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2784
3285
  MessageList,
2785
3286
  {
2786
3287
  messages,
@@ -2793,10 +3294,11 @@ function ChatWidget({
2793
3294
  isLoadingMore,
2794
3295
  theme: config.theme,
2795
3296
  onQuickAction: handleSendMessage,
2796
- isBotThinking
3297
+ isBotThinking,
3298
+ onBookingSlotSelected: handleBookingSlotSelected
2797
3299
  }
2798
3300
  ),
2799
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3301
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2800
3302
  ChatInput,
2801
3303
  {
2802
3304
  onSend: handleSendMessage,
@@ -2806,7 +3308,7 @@ function ChatWidget({
2806
3308
  disabled: !websocket.isConnected
2807
3309
  }
2808
3310
  ),
2809
- !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3311
+ !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2810
3312
  "div",
2811
3313
  {
2812
3314
  className: "px-4 py-1.5 text-center",
@@ -2814,7 +3316,7 @@ function ChatWidget({
2814
3316
  borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)",
2815
3317
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.03)" : "rgba(0,0,0,0.2)"
2816
3318
  },
2817
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3319
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2818
3320
  "p",
2819
3321
  {
2820
3322
  style: {
@@ -2825,7 +3327,7 @@ function ChatWidget({
2825
3327
  children: [
2826
3328
  "Powered by",
2827
3329
  " ",
2828
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: {
3330
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: {
2829
3331
  fontWeight: 600,
2830
3332
  color: isLightTheme ? "rgba(0,0,0,0.5)" : "rgba(247,247,248,0.45)"
2831
3333
  }, children: "Xcelsior" })
@@ -2840,11 +3342,11 @@ function ChatWidget({
2840
3342
  }
2841
3343
 
2842
3344
  // src/components/Chat.tsx
2843
- var import_react14 = require("react");
3345
+ var import_react15 = require("react");
2844
3346
 
2845
3347
  // src/components/PreChatForm.tsx
2846
- var import_react11 = require("react");
2847
- var import_jsx_runtime10 = require("react/jsx-runtime");
3348
+ var import_react12 = require("react");
3349
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2848
3350
  function PreChatForm({
2849
3351
  onSubmit,
2850
3352
  className = "",
@@ -2853,11 +3355,11 @@ function PreChatForm({
2853
3355
  onMinimize,
2854
3356
  onClose
2855
3357
  }) {
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);
3358
+ const [name, setName] = (0, import_react12.useState)(initialName);
3359
+ const [email, setEmail] = (0, import_react12.useState)(initialEmail);
3360
+ const [errors, setErrors] = (0, import_react12.useState)({});
3361
+ const [isSubmitting, setIsSubmitting] = (0, import_react12.useState)(false);
3362
+ const [focusedField, setFocusedField] = (0, import_react12.useState)(null);
2861
3363
  const validateForm = () => {
2862
3364
  const newErrors = {};
2863
3365
  if (!name.trim()) {
@@ -2905,7 +3407,7 @@ function PreChatForm({
2905
3407
  transition: "box-shadow 0.2s ease",
2906
3408
  caretColor: "#337eff"
2907
3409
  });
2908
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3410
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2909
3411
  "div",
2910
3412
  {
2911
3413
  className: `fixed bottom-4 right-4 z-50 flex flex-col overflow-hidden ${className}`,
@@ -2925,7 +3427,7 @@ function PreChatForm({
2925
3427
  backgroundSize: "24px 24px"
2926
3428
  },
2927
3429
  children: [
2928
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3430
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2929
3431
  "div",
2930
3432
  {
2931
3433
  className: "relative text-white overflow-hidden",
@@ -2933,7 +3435,7 @@ function PreChatForm({
2933
3435
  background: "linear-gradient(135deg, #337eff 0%, #005eff 50%, #001a66 100%)"
2934
3436
  },
2935
3437
  children: [
2936
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3438
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2937
3439
  "div",
2938
3440
  {
2939
3441
  className: "absolute inset-0 pointer-events-none",
@@ -2942,9 +3444,9 @@ function PreChatForm({
2942
3444
  }
2943
3445
  }
2944
3446
  ),
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)(
3447
+ /* @__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: [
3448
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex-1", children: [
3449
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2948
3450
  "h2",
2949
3451
  {
2950
3452
  className: "font-semibold",
@@ -2955,7 +3457,7 @@ function PreChatForm({
2955
3457
  children: "Start a Conversation"
2956
3458
  }
2957
3459
  ),
2958
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3460
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2959
3461
  "p",
2960
3462
  {
2961
3463
  className: "mt-1",
@@ -2968,8 +3470,8 @@ function PreChatForm({
2968
3470
  }
2969
3471
  )
2970
3472
  ] }),
2971
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1 ml-2", children: [
2972
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3473
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-1 ml-2", children: [
3474
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2973
3475
  "button",
2974
3476
  {
2975
3477
  type: "button",
@@ -2983,7 +3485,7 @@ function PreChatForm({
2983
3485
  e.currentTarget.style.backgroundColor = "transparent";
2984
3486
  },
2985
3487
  "aria-label": "Minimize chat",
2986
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3488
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2987
3489
  "svg",
2988
3490
  {
2989
3491
  width: "18",
@@ -2992,8 +3494,8 @@ function PreChatForm({
2992
3494
  stroke: "currentColor",
2993
3495
  viewBox: "0 0 24 24",
2994
3496
  children: [
2995
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Minimize" }),
2996
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3497
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Minimize" }),
3498
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2997
3499
  "path",
2998
3500
  {
2999
3501
  strokeLinecap: "round",
@@ -3007,7 +3509,7 @@ function PreChatForm({
3007
3509
  )
3008
3510
  }
3009
3511
  ),
3010
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3512
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3011
3513
  "button",
3012
3514
  {
3013
3515
  type: "button",
@@ -3021,7 +3523,7 @@ function PreChatForm({
3021
3523
  e.currentTarget.style.backgroundColor = "transparent";
3022
3524
  },
3023
3525
  "aria-label": "Close chat",
3024
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3526
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3025
3527
  "svg",
3026
3528
  {
3027
3529
  width: "18",
@@ -3030,8 +3532,8 @@ function PreChatForm({
3030
3532
  stroke: "currentColor",
3031
3533
  viewBox: "0 0 24 24",
3032
3534
  children: [
3033
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Close" }),
3034
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3535
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Close" }),
3536
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3035
3537
  "path",
3036
3538
  {
3037
3539
  strokeLinecap: "round",
@@ -3047,7 +3549,7 @@ function PreChatForm({
3047
3549
  )
3048
3550
  ] })
3049
3551
  ] }) }),
3050
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3552
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3051
3553
  "div",
3052
3554
  {
3053
3555
  className: "h-px",
@@ -3059,9 +3561,9 @@ function PreChatForm({
3059
3561
  ]
3060
3562
  }
3061
3563
  ),
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)(
3564
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("form", { onSubmit: handleSubmit, className: "p-5 flex flex-col gap-4", children: [
3565
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
3566
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3065
3567
  "label",
3066
3568
  {
3067
3569
  htmlFor: "chat-name",
@@ -3073,11 +3575,11 @@ function PreChatForm({
3073
3575
  },
3074
3576
  children: [
3075
3577
  "Name ",
3076
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3578
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3077
3579
  ]
3078
3580
  }
3079
3581
  ),
3080
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3582
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3081
3583
  "input",
3082
3584
  {
3083
3585
  type: "text",
@@ -3097,7 +3599,7 @@ function PreChatForm({
3097
3599
  autoComplete: "name"
3098
3600
  }
3099
3601
  ),
3100
- errors.name && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3602
+ errors.name && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3101
3603
  "p",
3102
3604
  {
3103
3605
  className: "mt-1.5",
@@ -3111,8 +3613,8 @@ function PreChatForm({
3111
3613
  }
3112
3614
  )
3113
3615
  ] }),
3114
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3115
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3616
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
3617
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3116
3618
  "label",
3117
3619
  {
3118
3620
  htmlFor: "chat-email",
@@ -3124,11 +3626,11 @@ function PreChatForm({
3124
3626
  },
3125
3627
  children: [
3126
3628
  "Email ",
3127
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3629
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3128
3630
  ]
3129
3631
  }
3130
3632
  ),
3131
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3633
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3132
3634
  "input",
3133
3635
  {
3134
3636
  type: "email",
@@ -3148,7 +3650,7 @@ function PreChatForm({
3148
3650
  autoComplete: "email"
3149
3651
  }
3150
3652
  ),
3151
- errors.email && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3653
+ errors.email && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3152
3654
  "p",
3153
3655
  {
3154
3656
  className: "mt-1.5",
@@ -3162,7 +3664,7 @@ function PreChatForm({
3162
3664
  }
3163
3665
  )
3164
3666
  ] }),
3165
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3667
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3166
3668
  "button",
3167
3669
  {
3168
3670
  type: "submit",
@@ -3184,8 +3686,8 @@ function PreChatForm({
3184
3686
  onMouseLeave: (e) => {
3185
3687
  e.currentTarget.style.boxShadow = "0 4px 16px -4px rgba(51,126,255,0.4)";
3186
3688
  },
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)(
3689
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [
3690
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3189
3691
  "svg",
3190
3692
  {
3191
3693
  className: "animate-spin",
@@ -3197,8 +3699,8 @@ function PreChatForm({
3197
3699
  strokeWidth: "2",
3198
3700
  "aria-label": "Loading",
3199
3701
  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" })
3702
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Loading" }),
3703
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
3202
3704
  ]
3203
3705
  }
3204
3706
  ),
@@ -3206,7 +3708,7 @@ function PreChatForm({
3206
3708
  ] }) : "Start Chat"
3207
3709
  }
3208
3710
  ),
3209
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3711
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3210
3712
  "p",
3211
3713
  {
3212
3714
  className: "text-center",
@@ -3219,7 +3721,7 @@ function PreChatForm({
3219
3721
  }
3220
3722
  )
3221
3723
  ] }),
3222
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3724
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3223
3725
  "div",
3224
3726
  {
3225
3727
  className: "px-4 py-1.5 text-center",
@@ -3227,7 +3729,7 @@ function PreChatForm({
3227
3729
  borderTop: "1px solid rgba(255,255,255,0.06)",
3228
3730
  backgroundColor: "rgba(0,0,0,0.2)"
3229
3731
  },
3230
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3732
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3231
3733
  "p",
3232
3734
  {
3233
3735
  style: {
@@ -3238,7 +3740,7 @@ function PreChatForm({
3238
3740
  children: [
3239
3741
  "Powered by",
3240
3742
  " ",
3241
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
3743
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
3242
3744
  ]
3243
3745
  }
3244
3746
  )
@@ -3250,8 +3752,15 @@ function PreChatForm({
3250
3752
  }
3251
3753
 
3252
3754
  // src/hooks/useDraggablePosition.ts
3253
- var import_react12 = require("react");
3755
+ var import_react13 = require("react");
3254
3756
  var STORAGE_KEY2 = "xcelsior-chat-position";
3757
+ var DRAG_THRESHOLD = 5;
3758
+ var FAB_SIZE = 64;
3759
+ var MARGIN = 16;
3760
+ var BOTTOM = 20;
3761
+ var VELOCITY_THRESHOLD = 0.4;
3762
+ var SETTLE_DURATION = 400;
3763
+ var SETTLE_EASING = "cubic-bezier(0.2, 0.9, 0.3, 1.1)";
3255
3764
  function getStoredPosition() {
3256
3765
  try {
3257
3766
  const stored = localStorage.getItem(STORAGE_KEY2);
@@ -3260,21 +3769,33 @@ function getStoredPosition() {
3260
3769
  }
3261
3770
  return "right";
3262
3771
  }
3263
- function storePosition(position) {
3772
+ function storePosition(pos) {
3264
3773
  try {
3265
- localStorage.setItem(STORAGE_KEY2, position);
3774
+ localStorage.setItem(STORAGE_KEY2, pos);
3266
3775
  } catch {
3267
3776
  }
3268
3777
  }
3778
+ function restingX(side) {
3779
+ return side === "left" ? MARGIN : window.innerWidth - MARGIN - FAB_SIZE;
3780
+ }
3781
+ function restingY() {
3782
+ return window.innerHeight - BOTTOM - FAB_SIZE;
3783
+ }
3269
3784
  function useDraggablePosition(configPosition = "auto") {
3270
3785
  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)(() => {
3786
+ const [position, setPosition] = (0, import_react13.useState)(resolvedDefault);
3787
+ const positionRef = (0, import_react13.useRef)(position);
3788
+ positionRef.current = position;
3789
+ const [isDragging, setIsDragging] = (0, import_react13.useState)(false);
3790
+ const containerRef = (0, import_react13.useRef)(null);
3791
+ const draggingRef = (0, import_react13.useRef)(false);
3792
+ const didDragRef = (0, import_react13.useRef)(false);
3793
+ const startPointer = (0, import_react13.useRef)({ x: 0, y: 0 });
3794
+ const startRect = (0, import_react13.useRef)({ x: 0, y: 0 });
3795
+ const samples = (0, import_react13.useRef)([]);
3796
+ const hasShownHint = (0, import_react13.useRef)(false);
3797
+ const [showHint, setShowHint] = (0, import_react13.useState)(false);
3798
+ (0, import_react13.useEffect)(() => {
3278
3799
  try {
3279
3800
  const hasVisited = localStorage.getItem("xcelsior-chat-hint-shown");
3280
3801
  if (!hasVisited && !hasShownHint.current) {
@@ -3289,53 +3810,115 @@ function useDraggablePosition(configPosition = "auto") {
3289
3810
  } catch {
3290
3811
  }
3291
3812
  }, []);
3292
- const handlePointerDown = (0, import_react12.useCallback)((e) => {
3293
- dragStartX.current = e.clientX;
3294
- setIsDragging(true);
3295
- e.target.setPointerCapture(e.pointerId);
3813
+ (0, import_react13.useEffect)(() => {
3814
+ const handleMove = (e) => {
3815
+ if (!draggingRef.current) return;
3816
+ const el = containerRef.current;
3817
+ if (!el) return;
3818
+ const dx = e.clientX - startPointer.current.x;
3819
+ const dy = e.clientY - startPointer.current.y;
3820
+ if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) {
3821
+ didDragRef.current = true;
3822
+ }
3823
+ el.style.transition = "none";
3824
+ el.style.transform = `translate(${dx}px, ${dy}px)`;
3825
+ const now = performance.now();
3826
+ samples.current.push({ x: e.clientX, t: now });
3827
+ if (samples.current.length > 5) samples.current.shift();
3828
+ };
3829
+ const handleUp = (e) => {
3830
+ if (!draggingRef.current) return;
3831
+ draggingRef.current = false;
3832
+ const el = containerRef.current;
3833
+ if (!el) return;
3834
+ let velocityX = 0;
3835
+ const s = samples.current;
3836
+ if (s.length >= 2) {
3837
+ const first = s[0];
3838
+ const last = s[s.length - 1];
3839
+ const dt = last.t - first.t;
3840
+ if (dt > 0) velocityX = (last.x - first.x) / dt;
3841
+ }
3842
+ samples.current = [];
3843
+ const currentVisualX = startRect.current.x + (e.clientX - startPointer.current.x);
3844
+ const screenMid = window.innerWidth / 2;
3845
+ let targetSide;
3846
+ if (Math.abs(velocityX) > VELOCITY_THRESHOLD) {
3847
+ targetSide = velocityX < 0 ? "left" : "right";
3848
+ } else {
3849
+ targetSide = currentVisualX + FAB_SIZE / 2 < screenMid ? "left" : "right";
3850
+ }
3851
+ const oldBaseX = restingX(positionRef.current);
3852
+ const oldBaseY = restingY();
3853
+ const currentX = startRect.current.x + (e.clientX - startPointer.current.x);
3854
+ const currentY = startRect.current.y + (e.clientY - startPointer.current.y);
3855
+ const targetX = restingX(targetSide);
3856
+ const targetY = restingY();
3857
+ const fromTx = currentX - oldBaseX;
3858
+ const fromTy = currentY - oldBaseY;
3859
+ const toTx = targetX - oldBaseX;
3860
+ const toTy = targetY - oldBaseY;
3861
+ el.style.transition = "none";
3862
+ el.style.transform = `translate(${fromTx}px, ${fromTy}px)`;
3863
+ void el.offsetHeight;
3864
+ el.style.transition = `transform ${SETTLE_DURATION}ms ${SETTLE_EASING}`;
3865
+ el.style.transform = `translate(${toTx}px, ${toTy}px)`;
3866
+ const finish = () => {
3867
+ el.style.transition = "";
3868
+ el.style.transform = "";
3869
+ el.removeEventListener("transitionend", finish);
3870
+ setIsDragging(false);
3871
+ if (targetSide !== positionRef.current) {
3872
+ setPosition(targetSide);
3873
+ storePosition(targetSide);
3874
+ }
3875
+ };
3876
+ el.addEventListener("transitionend", finish, { once: true });
3877
+ setTimeout(finish, SETTLE_DURATION + 50);
3878
+ };
3879
+ document.addEventListener("pointermove", handleMove);
3880
+ document.addEventListener("pointerup", handleUp);
3881
+ return () => {
3882
+ document.removeEventListener("pointermove", handleMove);
3883
+ document.removeEventListener("pointerup", handleUp);
3884
+ };
3296
3885
  }, []);
3297
- const handlePointerMove = (0, import_react12.useCallback)((_e) => {
3886
+ const handlePointerDown = (0, import_react13.useCallback)((e) => {
3887
+ e.preventDefault();
3888
+ const el = containerRef.current;
3889
+ if (!el) return;
3890
+ const rect = el.getBoundingClientRect();
3891
+ startPointer.current = { x: e.clientX, y: e.clientY };
3892
+ startRect.current = { x: rect.left, y: rect.top };
3893
+ draggingRef.current = true;
3894
+ didDragRef.current = false;
3895
+ samples.current = [];
3896
+ setIsDragging(true);
3298
3897
  }, []);
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
- );
3898
+ const shouldSuppressClick = (0, import_react13.useCallback)(() => didDragRef.current, []);
3315
3899
  return {
3316
3900
  position,
3317
3901
  isDragging,
3318
3902
  showHint,
3319
- fabRef,
3903
+ containerRef,
3904
+ shouldSuppressClick,
3320
3905
  handlers: {
3321
- onPointerDown: handlePointerDown,
3322
- onPointerMove: handlePointerMove,
3323
- onPointerUp: handlePointerUp
3906
+ onPointerDown: handlePointerDown
3324
3907
  }
3325
3908
  };
3326
3909
  }
3327
3910
 
3328
3911
  // src/hooks/useChatWidgetState.ts
3329
- var import_react13 = require("react");
3912
+ var import_react14 = require("react");
3330
3913
  function useChatWidgetState({
3331
3914
  state: controlledState,
3332
3915
  defaultState = "minimized",
3333
3916
  onStateChange
3334
3917
  }) {
3335
- const [uncontrolledState, setUncontrolledState] = (0, import_react13.useState)(defaultState);
3918
+ const [uncontrolledState, setUncontrolledState] = (0, import_react14.useState)(defaultState);
3336
3919
  const isControlled = controlledState !== void 0 && controlledState !== "undefined";
3337
3920
  const currentState = isControlled ? controlledState : uncontrolledState;
3338
- const setState = (0, import_react13.useCallback)(
3921
+ const setState = (0, import_react14.useCallback)(
3339
3922
  (newValue) => {
3340
3923
  if (!isControlled) {
3341
3924
  setUncontrolledState(newValue);
@@ -3352,7 +3935,7 @@ function useChatWidgetState({
3352
3935
  }
3353
3936
 
3354
3937
  // src/components/Chat.tsx
3355
- var import_jsx_runtime11 = require("react/jsx-runtime");
3938
+ var import_jsx_runtime14 = require("react/jsx-runtime");
3356
3939
  function generateSessionId() {
3357
3940
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
3358
3941
  return crypto.randomUUID();
@@ -3368,12 +3951,12 @@ function Chat({
3368
3951
  defaultState = "minimized",
3369
3952
  onStateChange
3370
3953
  }) {
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);
3954
+ const [userInfo, setUserInfo] = (0, import_react15.useState)(null);
3955
+ const [conversationId, setConversationId] = (0, import_react15.useState)("");
3956
+ const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
3374
3957
  const identityMode = config.identityCollection || "progressive";
3375
- const { position, isDragging, showHint, handlers } = useDraggablePosition(config.position);
3376
- const sessionInitializedRef = (0, import_react14.useRef)(false);
3958
+ const { position, isDragging, showHint, containerRef, shouldSuppressClick, handlers } = useDraggablePosition(config.position);
3959
+ const sessionInitializedRef = (0, import_react15.useRef)(false);
3377
3960
  const { currentState, setState } = useChatWidgetState({
3378
3961
  state,
3379
3962
  defaultState,
@@ -3384,7 +3967,7 @@ function Chat({
3384
3967
  const configUserName = config.currentUser?.name;
3385
3968
  const configUserAvatar = config.currentUser?.avatar;
3386
3969
  const configUserStatus = config.currentUser?.status;
3387
- (0, import_react14.useEffect)(() => {
3970
+ (0, import_react15.useEffect)(() => {
3388
3971
  if (sessionInitializedRef.current) return;
3389
3972
  const initializeSession = () => {
3390
3973
  try {
@@ -3424,7 +4007,33 @@ function Chat({
3424
4007
  const convId = configConversationId || generateSessionId();
3425
4008
  setConversationId(convId);
3426
4009
  if (identityMode === "progressive" || identityMode === "none") {
3427
- setUserInfo(null);
4010
+ let anonId;
4011
+ try {
4012
+ const stored = localStorage.getItem("xcelsior-chat-anon-id");
4013
+ if (stored) {
4014
+ anonId = stored;
4015
+ } else {
4016
+ anonId = `anon-${crypto.randomUUID?.() || Math.random().toString(36).slice(2)}`;
4017
+ localStorage.setItem("xcelsior-chat-anon-id", anonId);
4018
+ }
4019
+ } catch {
4020
+ anonId = `anon-${Math.random().toString(36).slice(2)}`;
4021
+ }
4022
+ const anonEmail = `${anonId}@anonymous.xcelsior.co`;
4023
+ const anonUser = { name: "Visitor", email: anonEmail, type: "customer", status: "online" };
4024
+ setUserInfo(anonUser);
4025
+ try {
4026
+ localStorage.setItem(
4027
+ `${storageKeyPrefix}_user`,
4028
+ JSON.stringify({
4029
+ name: anonUser.name,
4030
+ email: anonUser.email,
4031
+ conversationId: convId,
4032
+ timestamp: Date.now()
4033
+ })
4034
+ );
4035
+ } catch {
4036
+ }
3428
4037
  }
3429
4038
  sessionInitializedRef.current = true;
3430
4039
  } catch (error) {
@@ -3437,7 +4046,7 @@ function Chat({
3437
4046
  };
3438
4047
  initializeSession();
3439
4048
  }, [configConversationId, configUserEmail, configUserName, configUserAvatar, configUserStatus, storageKeyPrefix, identityMode]);
3440
- const handlePreChatSubmit = (0, import_react14.useCallback)(
4049
+ const handlePreChatSubmit = (0, import_react15.useCallback)(
3441
4050
  (name, email) => {
3442
4051
  const convId = conversationId || generateSessionId();
3443
4052
  const user = { name, email, type: "customer", status: "online" };
@@ -3463,49 +4072,52 @@ function Chat({
3463
4072
  const primaryColor = config.theme?.primary || "#337eff";
3464
4073
  const primaryStrong = config.theme?.primaryStrong || "#005eff";
3465
4074
  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",
4075
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4076
+ "div",
3468
4077
  {
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
- ]
4078
+ ref: containerRef,
4079
+ className: `fixed bottom-5 ${positionClass} z-50 ${className}`,
4080
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
4081
+ "button",
4082
+ {
4083
+ type: "button",
4084
+ onClick: () => {
4085
+ if (!shouldSuppressClick()) setState("open");
4086
+ },
4087
+ 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"}`,
4088
+ style: {
4089
+ width: 64,
4090
+ height: 64,
4091
+ background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
4092
+ boxShadow: [
4093
+ `0 4px 24px 0 ${primaryColor}80`,
4094
+ `0 8px 32px -4px rgba(0,0,0,0.3)`,
4095
+ `inset 0 1px 0 0 rgba(255,255,255,0.2)`
4096
+ ].join(", ")
4097
+ },
4098
+ "aria-label": "Open chat",
4099
+ ...handlers,
4100
+ children: [
4101
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChatBubbleIcon, { size: 36, className: "pointer-events-none" }),
4102
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4103
+ "span",
4104
+ {
4105
+ className: "absolute inset-0 rounded-full animate-ping pointer-events-none",
4106
+ style: {
4107
+ backgroundColor: primaryColor,
4108
+ opacity: 0.15,
4109
+ animationDuration: "2.5s"
4110
+ }
4111
+ }
4112
+ )
4113
+ ]
4114
+ }
4115
+ )
3504
4116
  }
3505
- ) });
4117
+ );
3506
4118
  }
3507
4119
  if (identityMode === "form" && (!userInfo || !userInfo.email || !userInfo.name)) {
3508
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4120
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3509
4121
  PreChatForm,
3510
4122
  {
3511
4123
  onSubmit: handlePreChatSubmit,
@@ -3522,7 +4134,7 @@ function Chat({
3522
4134
  conversationId,
3523
4135
  currentUser: userInfo || void 0
3524
4136
  };
3525
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4137
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3526
4138
  ChatWidget,
3527
4139
  {
3528
4140
  config: fullConfig,
@@ -3535,12 +4147,12 @@ function Chat({
3535
4147
  }
3536
4148
 
3537
4149
  // src/components/TypingIndicator.tsx
3538
- var import_jsx_runtime12 = require("react/jsx-runtime");
4150
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3539
4151
  function TypingIndicator({ isTyping, userName }) {
3540
4152
  if (!isTyping) {
3541
4153
  return null;
3542
4154
  }
3543
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4155
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3544
4156
  "div",
3545
4157
  {
3546
4158
  className: "px-4 py-2",
@@ -3548,8 +4160,8 @@ function TypingIndicator({ isTyping, userName }) {
3548
4160
  borderTop: "1px solid rgba(255,255,255,0.06)",
3549
4161
  backgroundColor: "rgba(0,0,0,0.15)"
3550
4162
  },
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)(
4163
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", children: [
4164
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3553
4165
  "span",
3554
4166
  {
3555
4167
  className: "rounded-full animate-bounce",
@@ -3563,7 +4175,7 @@ function TypingIndicator({ isTyping, userName }) {
3563
4175
  },
3564
4176
  i
3565
4177
  )) }),
3566
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4178
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3567
4179
  "span",
3568
4180
  {
3569
4181
  style: {
@@ -3580,6 +4192,9 @@ function TypingIndicator({ isTyping, userName }) {
3580
4192
  }
3581
4193
  // Annotate the CommonJS export names for ESM import in node:
3582
4194
  0 && (module.exports = {
4195
+ BookingCancelledCard,
4196
+ BookingConfirmationCard,
4197
+ BookingSlotPicker,
3583
4198
  Chat,
3584
4199
  ChatHeader,
3585
4200
  ChatInput,