@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.
- package/.storybook/preview.tsx +2 -1
- package/dist/index.js +38 -53
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +66 -81
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/components/Chat.tsx +35 -74
- package/src/components/ChatWidget.tsx +0 -1
- package/src/hooks/useMessages.ts +22 -1
- package/storybook-static/assets/Chat.stories-BkbpOOSG.js +830 -0
- package/storybook-static/assets/{Color-YHDXOIA2-BMnd3YrF.js → Color-YHDXOIA2-CSuNIR0a.js} +1 -1
- package/storybook-static/assets/{DocsRenderer-CFRXHY34-i_W8iCu9.js → DocsRenderer-CFRXHY34-dpuOKTQp.js} +3 -3
- package/storybook-static/assets/{MessageItem-DAaKZ9s9.js → MessageItem-Dlb6dSKL.js} +9 -9
- package/storybook-static/assets/MessageItem.stories-CsxqSqu-.js +422 -0
- package/storybook-static/assets/{entry-preview-oDnntGcx.js → entry-preview-C_-WO6GJ.js} +1 -1
- package/storybook-static/assets/{iframe-CGBtu2Se.js → iframe-BXTccXxS.js} +2 -2
- package/storybook-static/assets/preview-B8y-wc-n.css +1 -0
- package/storybook-static/assets/preview-CC4t7T7W.js +1 -0
- package/storybook-static/assets/{preview-BRpahs9B.js → preview-Cyx3pE7Q.js} +2 -2
- package/storybook-static/iframe.html +1 -1
- package/storybook-static/index.json +1 -1
- package/storybook-static/project.json +1 -1
- package/tsconfig.json +4 -0
- package/storybook-static/assets/Chat.stories-J_Yp51wU.js +0 -803
- package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +0 -255
- package/storybook-static/assets/ToastContext-Bty1K7ya.js +0 -1
- package/storybook-static/assets/preview-DUOvJmsz.js +0 -1
- 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
|
|
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 =
|
|
548
|
+
const sizeRef = useRef3(size);
|
|
534
549
|
sizeRef.current = size;
|
|
535
|
-
const dragRef =
|
|
536
|
-
const containerRef =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
1797
|
-
const containerRef =
|
|
1798
|
-
const prevLengthRef =
|
|
1799
|
-
const loadMoreTriggerRef =
|
|
1800
|
-
const prevScrollHeightRef =
|
|
1801
|
-
const hasInitialScrolledRef =
|
|
1802
|
-
const isUserScrollingRef =
|
|
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
|
|
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 =
|
|
2044
|
-
const emojiPickerRef =
|
|
2045
|
-
const emojiButtonRef =
|
|
2046
|
-
const fileInputRef =
|
|
2047
|
-
const typingTimeoutRef =
|
|
2048
|
-
const startTypingTimeoutRef =
|
|
2049
|
-
const isTypingRef =
|
|
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
|
|
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 =
|
|
3209
|
-
const fabRef =
|
|
3210
|
-
const hasShownHint =
|
|
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
|
|
3325
|
+
const sessionInitializedRef = useRef8(false);
|
|
3326
|
+
const { currentState, setState } = useChatWidgetState({
|
|
3314
3327
|
state,
|
|
3315
3328
|
defaultState,
|
|
3316
3329
|
onStateChange
|
|
3317
3330
|
});
|
|
3318
|
-
const
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
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 (
|
|
3351
|
-
const convId2 =
|
|
3340
|
+
if (configUserEmail && configUserName) {
|
|
3341
|
+
const convId2 = configConversationId || generateSessionId();
|
|
3352
3342
|
const user = {
|
|
3353
|
-
name:
|
|
3354
|
-
email:
|
|
3355
|
-
avatar:
|
|
3343
|
+
name: configUserName,
|
|
3344
|
+
email: configUserEmail,
|
|
3345
|
+
avatar: configUserAvatar,
|
|
3356
3346
|
type: "customer",
|
|
3357
|
-
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 =
|
|
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(
|
|
3381
|
+
setConversationId(configConversationId || generateSessionId());
|
|
3382
|
+
sessionInitializedRef.current = true;
|
|
3389
3383
|
} finally {
|
|
3390
3384
|
setIsLoading(false);
|
|
3391
3385
|
}
|
|
3392
3386
|
};
|
|
3393
3387
|
initializeSession();
|
|
3394
|
-
}, [
|
|
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
|
-
|
|
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
|