@xcelsior/ui-chat 2.0.3 → 2.0.4

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 (28) hide show
  1. package/.storybook/preview.tsx +2 -1
  2. package/dist/index.js +38 -53
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +66 -81
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +3 -2
  7. package/src/components/Chat.tsx +35 -74
  8. package/src/components/ChatWidget.tsx +0 -1
  9. package/src/hooks/useMessages.ts +22 -1
  10. package/storybook-static/assets/Chat.stories-BkbpOOSG.js +830 -0
  11. package/storybook-static/assets/{Color-YHDXOIA2-BMnd3YrF.js → Color-YHDXOIA2-CSuNIR0a.js} +1 -1
  12. package/storybook-static/assets/{DocsRenderer-CFRXHY34-i_W8iCu9.js → DocsRenderer-CFRXHY34-dpuOKTQp.js} +3 -3
  13. package/storybook-static/assets/{MessageItem-DAaKZ9s9.js → MessageItem-Dlb6dSKL.js} +9 -9
  14. package/storybook-static/assets/MessageItem.stories-CsxqSqu-.js +422 -0
  15. package/storybook-static/assets/{entry-preview-oDnntGcx.js → entry-preview-C_-WO6GJ.js} +1 -1
  16. package/storybook-static/assets/{iframe-CGBtu2Se.js → iframe-BXTccXxS.js} +2 -2
  17. package/storybook-static/assets/preview-B8y-wc-n.css +1 -0
  18. package/storybook-static/assets/preview-CC4t7T7W.js +1 -0
  19. package/storybook-static/assets/{preview-BRpahs9B.js → preview-Cyx3pE7Q.js} +2 -2
  20. package/storybook-static/iframe.html +1 -1
  21. package/storybook-static/index.json +1 -1
  22. package/storybook-static/project.json +1 -1
  23. package/tsconfig.json +4 -0
  24. package/storybook-static/assets/Chat.stories-J_Yp51wU.js +0 -803
  25. package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +0 -255
  26. package/storybook-static/assets/ToastContext-Bty1K7ya.js +0 -1
  27. package/storybook-static/assets/preview-DUOvJmsz.js +0 -1
  28. package/storybook-static/assets/preview-DcGwT3kv.css +0 -1
package/dist/index.mjs CHANGED
@@ -168,7 +168,7 @@ function useWebSocket(config, externalWebSocket) {
168
168
  }
169
169
 
170
170
  // src/hooks/useMessages.ts
171
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
171
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
172
172
 
173
173
  // src/utils/api.ts
174
174
  import axios from "axios";
@@ -200,6 +200,7 @@ async function fetchMessages(baseUrl, params, headers) {
200
200
  }
201
201
 
202
202
  // src/hooks/useMessages.ts
203
+ var BOT_THINKING_TIMEOUT = 45e3;
203
204
  function useMessages(websocket, config) {
204
205
  const [messages, setMessages] = useState2([]);
205
206
  const [isLoading, setIsLoading] = useState2(false);
@@ -208,6 +209,7 @@ function useMessages(websocket, config) {
208
209
  const [hasMore, setHasMore] = useState2(true);
209
210
  const [isLoadingMore, setIsLoadingMore] = useState2(false);
210
211
  const [isBotThinking, setIsBotThinking] = useState2(false);
212
+ const botThinkingTimerRef = useRef2(null);
211
213
  const { httpApiUrl, conversationId, headers, onError, toast } = config;
212
214
  const headersWithApiKey = useMemo(
213
215
  () => ({
@@ -258,6 +260,10 @@ function useMessages(websocket, config) {
258
260
  });
259
261
  if (newMessage.senderType === "bot" || newMessage.senderType === "system") {
260
262
  setIsBotThinking(false);
263
+ if (botThinkingTimerRef.current) {
264
+ clearTimeout(botThinkingTimerRef.current);
265
+ botThinkingTimerRef.current = null;
266
+ }
261
267
  }
262
268
  onMessageReceived?.(newMessage);
263
269
  }
@@ -271,6 +277,10 @@ function useMessages(websocket, config) {
271
277
  });
272
278
  if (message.senderType === "customer") {
273
279
  setIsBotThinking(true);
280
+ if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
281
+ botThinkingTimerRef.current = setTimeout(() => {
282
+ setIsBotThinking(false);
283
+ }, BOT_THINKING_TIMEOUT);
274
284
  }
275
285
  }, []);
276
286
  const updateMessageStatus = useCallback2((messageId, status) => {
@@ -314,6 +324,11 @@ function useMessages(websocket, config) {
314
324
  headersWithApiKey,
315
325
  onError
316
326
  ]);
327
+ useEffect2(() => {
328
+ return () => {
329
+ if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
330
+ };
331
+ }, []);
317
332
  return {
318
333
  messages,
319
334
  addMessage,
@@ -462,7 +477,7 @@ function useTypingIndicator(websocket) {
462
477
  }
463
478
 
464
479
  // src/hooks/useResizableWidget.ts
465
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef2, useState as useState5 } from "react";
480
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
466
481
  var STORAGE_KEY = "xcelsior-chat-size";
467
482
  var EDGE_ZONE = 8;
468
483
  var CURSOR_MAP = {
@@ -530,10 +545,10 @@ function useResizableWidget({
530
545
  const [isResizing, setIsResizing] = useState5(false);
531
546
  const [isNearEdge, setIsNearEdge] = useState5(false);
532
547
  const [activeEdge, setActiveEdge] = useState5(null);
533
- const sizeRef = useRef2(size);
548
+ const sizeRef = useRef3(size);
534
549
  sizeRef.current = size;
535
- const dragRef = useRef2(null);
536
- const containerRef = useRef2(null);
550
+ const dragRef = useRef3(null);
551
+ const containerRef = useRef3(null);
537
552
  const clamp = useCallback3(
538
553
  (w, h) => {
539
554
  const mxW = Math.min(maxWidth, window.innerWidth - 24);
@@ -922,7 +937,7 @@ function ChatHeader({ agent, onClose, onMinimize, theme }) {
922
937
  }
923
938
 
924
939
  // src/components/MessageList.tsx
925
- import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef4 } from "react";
940
+ import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef5 } from "react";
926
941
 
927
942
  // src/components/Spinner.tsx
928
943
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -1487,7 +1502,7 @@ function MessageItem({
1487
1502
  }
1488
1503
 
1489
1504
  // src/components/ThinkingIndicator.tsx
1490
- import { useEffect as useEffect5, useRef as useRef3, useState as useState6 } from "react";
1505
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState6 } from "react";
1491
1506
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1492
1507
  var PHRASE_POOLS = {
1493
1508
  // ── Greetings & small talk ──
@@ -1674,7 +1689,7 @@ function ThinkingIndicator({
1674
1689
  const [phraseIndex, setPhraseIndex] = useState6(0);
1675
1690
  const [displayText, setDisplayText] = useState6("");
1676
1691
  const [isDeleting, setIsDeleting] = useState6(false);
1677
- const phrasesRef = useRef3(getShuffledPhrases(detectContext(lastUserMessage)));
1692
+ const phrasesRef = useRef4(getShuffledPhrases(detectContext(lastUserMessage)));
1678
1693
  useEffect5(() => {
1679
1694
  phrasesRef.current = getShuffledPhrases(detectContext(lastUserMessage));
1680
1695
  setPhraseIndex(0);
@@ -1793,13 +1808,13 @@ function MessageList({
1793
1808
  onQuickAction,
1794
1809
  isBotThinking = false
1795
1810
  }) {
1796
- const messagesEndRef = useRef4(null);
1797
- const containerRef = useRef4(null);
1798
- const prevLengthRef = useRef4(messages.length);
1799
- const loadMoreTriggerRef = useRef4(null);
1800
- const prevScrollHeightRef = useRef4(0);
1801
- const hasInitialScrolledRef = useRef4(false);
1802
- const isUserScrollingRef = useRef4(false);
1811
+ const messagesEndRef = useRef5(null);
1812
+ const containerRef = useRef5(null);
1813
+ const prevLengthRef = useRef5(messages.length);
1814
+ const loadMoreTriggerRef = useRef5(null);
1815
+ const prevScrollHeightRef = useRef5(0);
1816
+ const hasInitialScrolledRef = useRef5(false);
1817
+ const isUserScrollingRef = useRef5(false);
1803
1818
  const bgColor = theme?.background || "#00001a";
1804
1819
  const isLightTheme = (() => {
1805
1820
  if (!bgColor.startsWith("#")) return false;
@@ -2014,7 +2029,7 @@ function MessageList({
2014
2029
  }
2015
2030
 
2016
2031
  // src/components/ChatInput.tsx
2017
- import { useEffect as useEffect7, useRef as useRef5, useState as useState7 } from "react";
2032
+ import { useEffect as useEffect7, useRef as useRef6, useState as useState7 } from "react";
2018
2033
  import { createPortal } from "react-dom";
2019
2034
  import Picker from "@emoji-mart/react";
2020
2035
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -2040,13 +2055,13 @@ function ChatInput({
2040
2055
  const [emojiData, setEmojiData] = useState7();
2041
2056
  const [emojiPickerPosition, setEmojiPickerPosition] = useState7(null);
2042
2057
  const [isFocused, setIsFocused] = useState7(false);
2043
- const textAreaRef = useRef5(null);
2044
- const emojiPickerRef = useRef5(null);
2045
- const emojiButtonRef = useRef5(null);
2046
- const fileInputRef = useRef5(null);
2047
- const typingTimeoutRef = useRef5(null);
2048
- const startTypingTimeoutRef = useRef5(null);
2049
- const isTypingRef = useRef5(false);
2058
+ const textAreaRef = useRef6(null);
2059
+ const emojiPickerRef = useRef6(null);
2060
+ const emojiButtonRef = useRef6(null);
2061
+ const fileInputRef = useRef6(null);
2062
+ const typingTimeoutRef = useRef6(null);
2063
+ const startTypingTimeoutRef = useRef6(null);
2064
+ const isTypingRef = useRef6(false);
2050
2065
  const enableEmoji = config.enableEmoji ?? true;
2051
2066
  const enableFileUpload = config.enableFileUpload ?? true;
2052
2067
  const bgColor = config.theme?.background || "#00001a";
@@ -2589,7 +2604,6 @@ function ChatWidget({
2589
2604
  })();
2590
2605
  const positionClass = resolvedPosition === "left" ? "left-4" : "right-4";
2591
2606
  const containerStyle = isFullPage ? { backgroundColor: bgColor, color: textColor } : {
2592
- position: "relative",
2593
2607
  width,
2594
2608
  height,
2595
2609
  maxHeight: "calc(100vh - 100px)",
@@ -2775,7 +2789,7 @@ function ChatWidget({
2775
2789
  }
2776
2790
 
2777
2791
  // src/components/Chat.tsx
2778
- import { useCallback as useCallback8, useEffect as useEffect10, useState as useState11 } from "react";
2792
+ import { useCallback as useCallback8, useEffect as useEffect10, useRef as useRef8, useState as useState11 } from "react";
2779
2793
 
2780
2794
  // src/components/PreChatForm.tsx
2781
2795
  import { useState as useState8 } from "react";
@@ -3185,7 +3199,7 @@ function PreChatForm({
3185
3199
  }
3186
3200
 
3187
3201
  // src/hooks/useDraggablePosition.ts
3188
- import { useState as useState9, useCallback as useCallback6, useRef as useRef6, useEffect as useEffect9 } from "react";
3202
+ import { useState as useState9, useCallback as useCallback6, useRef as useRef7, useEffect as useEffect9 } from "react";
3189
3203
  var STORAGE_KEY2 = "xcelsior-chat-position";
3190
3204
  function getStoredPosition() {
3191
3205
  try {
@@ -3205,9 +3219,9 @@ function useDraggablePosition(configPosition = "auto") {
3205
3219
  const resolvedDefault = configPosition === "auto" ? getStoredPosition() : configPosition;
3206
3220
  const [position, setPosition] = useState9(resolvedDefault);
3207
3221
  const [isDragging, setIsDragging] = useState9(false);
3208
- const dragStartX = useRef6(0);
3209
- const fabRef = useRef6(null);
3210
- const hasShownHint = useRef6(false);
3222
+ const dragStartX = useRef7(0);
3223
+ const fabRef = useRef7(null);
3224
+ const hasShownHint = useRef7(false);
3211
3225
  const [showHint, setShowHint] = useState9(false);
3212
3226
  useEffect9(() => {
3213
3227
  try {
@@ -3306,59 +3320,36 @@ function Chat({
3306
3320
  const [userInfo, setUserInfo] = useState11(null);
3307
3321
  const [conversationId, setConversationId] = useState11("");
3308
3322
  const [isLoading, setIsLoading] = useState11(true);
3309
- const [isAnimating, setIsAnimating] = useState11(false);
3310
- const [showWidget, setShowWidget] = useState11(false);
3311
3323
  const identityMode = config.identityCollection || "progressive";
3312
3324
  const { position, isDragging, showHint, handlers } = useDraggablePosition(config.position);
3313
- const { currentState, setState: setStateRaw } = useChatWidgetState({
3325
+ const sessionInitializedRef = useRef8(false);
3326
+ const { currentState, setState } = useChatWidgetState({
3314
3327
  state,
3315
3328
  defaultState,
3316
3329
  onStateChange
3317
3330
  });
3318
- const setState = useCallback8(
3319
- (newState) => {
3320
- if (newState === "open" && currentState === "minimized") {
3321
- setShowWidget(true);
3322
- setIsAnimating(true);
3323
- setStateRaw(newState);
3324
- requestAnimationFrame(() => {
3325
- requestAnimationFrame(() => {
3326
- setIsAnimating(false);
3327
- });
3328
- });
3329
- } else if ((newState === "minimized" || newState === "closed") && currentState === "open") {
3330
- setIsAnimating(true);
3331
- setTimeout(() => {
3332
- setShowWidget(false);
3333
- setIsAnimating(false);
3334
- setStateRaw(newState);
3335
- }, 200);
3336
- } else {
3337
- setStateRaw(newState);
3338
- }
3339
- },
3340
- [currentState, setStateRaw]
3341
- );
3342
- useEffect10(() => {
3343
- if (currentState === "open") {
3344
- setShowWidget(true);
3345
- }
3346
- }, [currentState]);
3331
+ const configConversationId = config.conversationId;
3332
+ const configUserEmail = config.currentUser?.email;
3333
+ const configUserName = config.currentUser?.name;
3334
+ const configUserAvatar = config.currentUser?.avatar;
3335
+ const configUserStatus = config.currentUser?.status;
3347
3336
  useEffect10(() => {
3337
+ if (sessionInitializedRef.current) return;
3348
3338
  const initializeSession = () => {
3349
3339
  try {
3350
- if (config.currentUser?.email && config.currentUser?.name) {
3351
- const convId2 = config.conversationId || generateSessionId();
3340
+ if (configUserEmail && configUserName) {
3341
+ const convId2 = configConversationId || generateSessionId();
3352
3342
  const user = {
3353
- name: config.currentUser.name,
3354
- email: config.currentUser.email,
3355
- avatar: config.currentUser.avatar,
3343
+ name: configUserName,
3344
+ email: configUserEmail,
3345
+ avatar: configUserAvatar,
3356
3346
  type: "customer",
3357
- status: config.currentUser.status
3347
+ status: configUserStatus
3358
3348
  };
3359
3349
  setUserInfo(user);
3360
3350
  setConversationId(convId2);
3361
3351
  setIsLoading(false);
3352
+ sessionInitializedRef.current = true;
3362
3353
  return;
3363
3354
  }
3364
3355
  const storedDataJson = localStorage.getItem(`${storageKeyPrefix}_user`);
@@ -3375,23 +3366,26 @@ function Chat({
3375
3366
  setUserInfo(user);
3376
3367
  setConversationId(storedData.conversationId);
3377
3368
  setIsLoading(false);
3369
+ sessionInitializedRef.current = true;
3378
3370
  return;
3379
3371
  }
3380
3372
  }
3381
- const convId = config.conversationId || generateSessionId();
3373
+ const convId = configConversationId || generateSessionId();
3382
3374
  setConversationId(convId);
3383
3375
  if (identityMode === "progressive" || identityMode === "none") {
3384
3376
  setUserInfo(null);
3385
3377
  }
3378
+ sessionInitializedRef.current = true;
3386
3379
  } catch (error) {
3387
3380
  console.error("Error initializing chat session:", error);
3388
- setConversationId(config.conversationId || generateSessionId());
3381
+ setConversationId(configConversationId || generateSessionId());
3382
+ sessionInitializedRef.current = true;
3389
3383
  } finally {
3390
3384
  setIsLoading(false);
3391
3385
  }
3392
3386
  };
3393
3387
  initializeSession();
3394
- }, [config, storageKeyPrefix, identityMode]);
3388
+ }, [configConversationId, configUserEmail, configUserName, configUserAvatar, configUserStatus, storageKeyPrefix, identityMode]);
3395
3389
  const handlePreChatSubmit = useCallback8(
3396
3390
  (name, email) => {
3397
3391
  const convId = conversationId || generateSessionId();
@@ -3477,16 +3471,7 @@ function Chat({
3477
3471
  conversationId,
3478
3472
  currentUser: userInfo || void 0
3479
3473
  };
3480
- const widgetAnimationStyle = showWidget && !isAnimating ? {
3481
- opacity: 1,
3482
- transform: "translateY(0) scale(1)",
3483
- transition: "opacity 0.25s ease-out, transform 0.25s ease-out"
3484
- } : {
3485
- opacity: 0,
3486
- transform: "translateY(12px) scale(0.97)",
3487
- transition: "opacity 0.2s ease-in, transform 0.2s ease-in"
3488
- };
3489
- return /* @__PURE__ */ jsx11("div", { style: widgetAnimationStyle, children: /* @__PURE__ */ jsx11(
3474
+ return /* @__PURE__ */ jsx11(
3490
3475
  ChatWidget,
3491
3476
  {
3492
3477
  config: fullConfig,
@@ -3495,7 +3480,7 @@ function Chat({
3495
3480
  onMinimize: () => setState("minimized"),
3496
3481
  resolvedPosition: position
3497
3482
  }
3498
- ) });
3483
+ );
3499
3484
  }
3500
3485
 
3501
3486
  // src/components/TypingIndicator.tsx