vdb-ai-chat 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/10.chat-widget.js +2 -0
- package/dist/10.chat-widget.js.LICENSE.txt +1 -0
- package/dist/104.chat-widget.js +1 -0
- package/dist/50.chat-widget.js +1 -0
- package/dist/521.chat-widget.js +1 -0
- package/dist/538.chat-widget.js +1 -1
- package/dist/572.chat-widget.js +1 -0
- package/dist/694.chat-widget.js +1 -0
- package/dist/chat-widget.js +1 -1
- package/lib/commonjs/api.js +4 -3
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/components/BetaNotice.js +38 -0
- package/lib/commonjs/components/BetaNotice.js.map +1 -0
- package/lib/commonjs/components/ChatHeader.js +27 -20
- package/lib/commonjs/components/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/ChatInput.js +21 -21
- package/lib/commonjs/components/ChatInput.js.map +1 -1
- package/lib/commonjs/components/ChatWidget.js +119 -72
- package/lib/commonjs/components/ChatWidget.js.map +1 -1
- package/lib/commonjs/components/MessageBubble.js +26 -90
- package/lib/commonjs/components/MessageBubble.js.map +1 -1
- package/lib/commonjs/components/MessageMetaRow.js +135 -0
- package/lib/commonjs/components/MessageMetaRow.js.map +1 -0
- package/lib/commonjs/components/ProductsGrid.js +139 -0
- package/lib/commonjs/components/ProductsGrid.js.map +1 -0
- package/lib/commonjs/components/ProductsList.js +22 -126
- package/lib/commonjs/components/ProductsList.js.map +1 -1
- package/lib/commonjs/components/ProductsListView.js +139 -0
- package/lib/commonjs/components/ProductsListView.js.map +1 -0
- package/lib/commonjs/components/SuggestionsRow.js +41 -23
- package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
- package/lib/commonjs/components/utils.js +4 -3
- package/lib/commonjs/components/utils.js.map +1 -1
- package/lib/commonjs/index.web.js +86 -29
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/theme.js +4 -4
- package/lib/commonjs/theme.js.map +1 -1
- package/lib/module/api.js +4 -3
- package/lib/module/api.js.map +1 -1
- package/lib/module/components/BetaNotice.js +30 -0
- package/lib/module/components/BetaNotice.js.map +1 -0
- package/lib/module/components/ChatHeader.js +27 -20
- package/lib/module/components/ChatHeader.js.map +1 -1
- package/lib/module/components/ChatInput.js +21 -21
- package/lib/module/components/ChatInput.js.map +1 -1
- package/lib/module/components/ChatWidget.js +120 -73
- package/lib/module/components/ChatWidget.js.map +1 -1
- package/lib/module/components/MessageBubble.js +26 -92
- package/lib/module/components/MessageBubble.js.map +1 -1
- package/lib/module/components/MessageMetaRow.js +127 -0
- package/lib/module/components/MessageMetaRow.js.map +1 -0
- package/lib/module/components/ProductsGrid.js +133 -0
- package/lib/module/components/ProductsGrid.js.map +1 -0
- package/lib/module/components/ProductsList.js +21 -126
- package/lib/module/components/ProductsList.js.map +1 -1
- package/lib/module/components/ProductsListView.js +132 -0
- package/lib/module/components/ProductsListView.js.map +1 -0
- package/lib/module/components/SuggestionsRow.js +41 -23
- package/lib/module/components/SuggestionsRow.js.map +1 -1
- package/lib/module/components/utils.js +4 -3
- package/lib/module/components/utils.js.map +1 -1
- package/lib/module/index.web.js +86 -29
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/theme.js +4 -4
- package/lib/module/theme.js.map +1 -1
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/components/BetaNotice.d.ts +5 -0
- package/lib/typescript/components/BetaNotice.d.ts.map +1 -0
- package/lib/typescript/components/ChatHeader.d.ts +5 -2
- package/lib/typescript/components/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/ChatInput.d.ts.map +1 -1
- package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble.d.ts +7 -3
- package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
- package/lib/typescript/components/MessageMetaRow.d.ts +14 -0
- package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -0
- package/lib/typescript/components/ProductsGrid.d.ts +10 -0
- package/lib/typescript/components/ProductsGrid.d.ts.map +1 -0
- package/lib/typescript/components/ProductsList.d.ts +4 -2
- package/lib/typescript/components/ProductsList.d.ts.map +1 -1
- package/lib/typescript/components/ProductsListView.d.ts +10 -0
- package/lib/typescript/components/ProductsListView.d.ts.map +1 -0
- package/lib/typescript/components/SuggestionsRow.d.ts +2 -0
- package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
- package/lib/typescript/components/utils.d.ts +1 -0
- package/lib/typescript/components/utils.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +1 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +3 -1
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +4 -3
- package/src/components/BetaNotice.tsx +32 -0
- package/src/components/ChatHeader.tsx +32 -18
- package/src/components/ChatInput.tsx +20 -21
- package/src/components/ChatWidget.tsx +258 -220
- package/src/components/MessageBubble.tsx +44 -149
- package/src/components/MessageMetaRow.tsx +199 -0
- package/src/components/ProductsGrid.tsx +163 -0
- package/src/components/ProductsList.tsx +20 -146
- package/src/components/ProductsListView.tsx +149 -0
- package/src/components/SuggestionsRow.tsx +45 -21
- package/src/components/utils.ts +6 -4
- package/src/index.web.tsx +87 -32
- package/src/theme.ts +4 -4
- package/src/types.ts +3 -2
- package/dist/751.chat-widget.js +0 -1
- package/lib/commonjs/contexts/SegmentClientContext.js +0 -19
- package/lib/commonjs/contexts/SegmentClientContext.js.map +0 -1
- package/lib/module/contexts/SegmentClientContext.js +0 -10
- package/lib/module/contexts/SegmentClientContext.js.map +0 -1
- package/lib/typescript/contexts/SegmentClientContext.d.ts +0 -9
- package/lib/typescript/contexts/SegmentClientContext.d.ts.map +0 -1
- package/src/contexts/SegmentClientContext.tsx +0 -20
|
@@ -13,11 +13,13 @@ import {
|
|
|
13
13
|
ScrollView,
|
|
14
14
|
Text,
|
|
15
15
|
TextInput,
|
|
16
|
+
KeyboardAvoidingView,
|
|
16
17
|
DeviceEventEmitter,
|
|
17
18
|
Platform,
|
|
18
19
|
} from "react-native";
|
|
19
20
|
import { ChatInput } from "./ChatInput";
|
|
20
21
|
import { MessageBubble } from "./MessageBubble";
|
|
22
|
+
import MessageMetaRow from "./MessageMetaRow";
|
|
21
23
|
import type { ChatMessage, ChatWidgetProps, ChatWidgetRef } from "../types";
|
|
22
24
|
import { mergeTheme } from "../theme";
|
|
23
25
|
import {
|
|
@@ -29,9 +31,10 @@ import {
|
|
|
29
31
|
sendUserMessage,
|
|
30
32
|
} from "../api";
|
|
31
33
|
import ChatHeader from "./ChatHeader";
|
|
34
|
+
import BetaNotice from "./BetaNotice";
|
|
32
35
|
import SuggestionsRow from "./SuggestionsRow";
|
|
33
36
|
import ProductsList from "./ProductsList";
|
|
34
|
-
import { FeedbackAction, formatToTime, getUserDetails } from "./utils";
|
|
37
|
+
import { FeedbackAction, formatToTime, getUserDetails, UserDetails } from "./utils";
|
|
35
38
|
import { useUserAnalytics } from "../hooks/useAnalytics";
|
|
36
39
|
import { Storage } from "../storage";
|
|
37
40
|
|
|
@@ -39,7 +42,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
39
42
|
(
|
|
40
43
|
{
|
|
41
44
|
apiUrl,
|
|
42
|
-
userId: userIdProp,
|
|
43
45
|
userToken: userTokenProp,
|
|
44
46
|
priceMode: priceModeProp,
|
|
45
47
|
modalHeight,
|
|
@@ -50,6 +52,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
50
52
|
onClearChat,
|
|
51
53
|
onViewAllPress,
|
|
52
54
|
onItemPress: onItemPressProp,
|
|
55
|
+
isBetaMode: isBetaModeProp,
|
|
53
56
|
},
|
|
54
57
|
ref
|
|
55
58
|
) => {
|
|
@@ -71,26 +74,16 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
71
74
|
const [priceMode, setPriceMode] = useState<string | null>(
|
|
72
75
|
priceModeProp || null
|
|
73
76
|
);
|
|
74
|
-
const [conversationId, setConversationId] = useState<string | null>(null);
|
|
75
|
-
const [userId, setUserId] = useState<string>(userIdProp || "");
|
|
76
77
|
const [userToken, setUserToken] = useState<string>(userTokenProp || "");
|
|
77
78
|
const scrollRef = useRef<ScrollView | null>(null);
|
|
78
79
|
const inputRef = useRef<TextInput | null>(null);
|
|
79
80
|
const theme = useMemo(() => mergeTheme(themeOverrides), [themeOverrides]);
|
|
80
81
|
const { _identify } = useUserAnalytics();
|
|
82
|
+
const betaActive = Boolean(isBetaModeProp);
|
|
81
83
|
// Load user auth data from storage on mount
|
|
82
84
|
useEffect(() => {
|
|
83
85
|
const loadAuthData = async () => {
|
|
84
86
|
try {
|
|
85
|
-
if (!userIdProp) {
|
|
86
|
-
const userData = await Storage.getJSON<{ id?: string }>(
|
|
87
|
-
"userData",
|
|
88
|
-
{}
|
|
89
|
-
);
|
|
90
|
-
if (userData?.id) {
|
|
91
|
-
setUserId(userData.id);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
87
|
if (!userTokenProp) {
|
|
95
88
|
const token = await Storage.getItem("token");
|
|
96
89
|
if (token) {
|
|
@@ -102,7 +95,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
102
95
|
}
|
|
103
96
|
};
|
|
104
97
|
loadAuthData();
|
|
105
|
-
}, [
|
|
98
|
+
}, [userTokenProp]);
|
|
106
99
|
|
|
107
100
|
const onViewAll = useCallback(() => {
|
|
108
101
|
const searchPayload = JSON.stringify(assistantResponse?.search_payload);
|
|
@@ -148,15 +141,15 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
148
141
|
);
|
|
149
142
|
|
|
150
143
|
const hasAuth = useMemo(
|
|
151
|
-
() => Boolean(
|
|
152
|
-
[
|
|
144
|
+
() => Boolean(userToken),
|
|
145
|
+
[userToken]
|
|
153
146
|
);
|
|
154
147
|
|
|
155
148
|
const apiParams: ChatApiParams = useMemo(
|
|
156
149
|
() => ({
|
|
157
|
-
conversationId: userToken
|
|
150
|
+
conversationId: userToken,
|
|
158
151
|
}),
|
|
159
|
-
[
|
|
152
|
+
[userToken]
|
|
160
153
|
);
|
|
161
154
|
|
|
162
155
|
const handleFeedbackAction = useCallback(
|
|
@@ -210,7 +203,24 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
210
203
|
priceMode
|
|
211
204
|
);
|
|
212
205
|
if (!cancelled) {
|
|
213
|
-
|
|
206
|
+
const normalised = normaliseMessages(initial).reverse();
|
|
207
|
+
if (normalised.length === 0) {
|
|
208
|
+
const initialAssistant: ChatMessage = {
|
|
209
|
+
id: "",
|
|
210
|
+
role: "assistant",
|
|
211
|
+
text: "Hello! How can I help you today?",
|
|
212
|
+
createdAt: Date.now(),
|
|
213
|
+
isLoading: false,
|
|
214
|
+
suggestions: [
|
|
215
|
+
"Search Natural Diamonds",
|
|
216
|
+
"Search Lab-Grown Diamonds",
|
|
217
|
+
],
|
|
218
|
+
reaction: "0",
|
|
219
|
+
};
|
|
220
|
+
setMessages([initialAssistant]);
|
|
221
|
+
} else {
|
|
222
|
+
setMessages(normalised);
|
|
223
|
+
}
|
|
214
224
|
}
|
|
215
225
|
} catch (error) {
|
|
216
226
|
console.error("Failed to fetch initial messages", error);
|
|
@@ -244,6 +254,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
244
254
|
role: "assistant",
|
|
245
255
|
text: "Thinking",
|
|
246
256
|
createdAt: Date.now(),
|
|
257
|
+
reaction: "0",
|
|
247
258
|
isLoading: true,
|
|
248
259
|
};
|
|
249
260
|
|
|
@@ -325,10 +336,10 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
325
336
|
inputRef.current?.focus();
|
|
326
337
|
return;
|
|
327
338
|
}
|
|
328
|
-
setProductsByMsg({
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
});
|
|
339
|
+
setProductsByMsg((prev) => ({
|
|
340
|
+
...prev,
|
|
341
|
+
[latestAssistant.id]: productsResult,
|
|
342
|
+
}));
|
|
332
343
|
}
|
|
333
344
|
setLoadingMessageId(null);
|
|
334
345
|
|
|
@@ -403,108 +414,134 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
403
414
|
}, [input, sendMessage]);
|
|
404
415
|
|
|
405
416
|
const handleSuggestionSelect = useCallback(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
return () => {
|
|
441
|
-
DeviceEventEmitter.removeAllListeners("clearChat");
|
|
442
|
-
DeviceEventEmitter.removeAllListeners("changePriceMode");
|
|
443
|
-
};
|
|
444
|
-
}, []);
|
|
445
|
-
|
|
446
|
-
const changePriceMode = async (_priceMode?: any) => {
|
|
447
|
-
// Priority: passed argument > prop > storage
|
|
448
|
-
if (!_priceMode && priceModeProp) {
|
|
449
|
-
_priceMode = priceModeProp;
|
|
450
|
-
}
|
|
451
|
-
if (!_priceMode) {
|
|
452
|
-
const userData = await Storage.getJSON<{ price_mode?: string }>(
|
|
453
|
-
"userData",
|
|
454
|
-
{}
|
|
455
|
-
);
|
|
456
|
-
_priceMode = userData?.price_mode;
|
|
457
|
-
}
|
|
458
|
-
setPriceMode(_priceMode || null);
|
|
417
|
+
(value: string) => {
|
|
418
|
+
sendMessage(value);
|
|
419
|
+
},
|
|
420
|
+
[sendMessage]
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const handleReloadResults = useCallback(async (msg: ChatMessage) => {
|
|
424
|
+
const id = msg.id;
|
|
425
|
+
try {
|
|
426
|
+
setReloadLoadingIds((prev) => new Set(prev).add(id));
|
|
427
|
+
const payload = msg?.search_payload;
|
|
428
|
+
if (!payload || Object.keys(payload).length === 0) return;
|
|
429
|
+
const productsResult = await getProducts(payload);
|
|
430
|
+
setProductsByMsg((prev) => ({ ...prev, [id]: productsResult }));
|
|
431
|
+
} catch (e) {
|
|
432
|
+
console.error("Reload results failed", e);
|
|
433
|
+
} finally {
|
|
434
|
+
setReloadLoadingIds((prev) => {
|
|
435
|
+
const next = new Set(prev);
|
|
436
|
+
next.delete(id);
|
|
437
|
+
return next;
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}, []);
|
|
441
|
+
|
|
442
|
+
useEffect(() => {
|
|
443
|
+
DeviceEventEmitter.addListener("clearChat", handleClearChat);
|
|
444
|
+
DeviceEventEmitter.addListener("changePriceMode", (data: { priceMode: string; }) => changePriceMode(data.priceMode));
|
|
445
|
+
changePriceMode();
|
|
446
|
+
identifySegmentUser();
|
|
447
|
+
|
|
448
|
+
return () => {
|
|
449
|
+
DeviceEventEmitter.removeAllListeners("clearChat");
|
|
450
|
+
DeviceEventEmitter.removeAllListeners("changePriceMode");
|
|
459
451
|
};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
452
|
+
}, []);
|
|
453
|
+
|
|
454
|
+
const changePriceMode = async (_priceMode?: any) => {
|
|
455
|
+
if (!_priceMode && priceModeProp) {
|
|
456
|
+
_priceMode = priceModeProp;
|
|
457
|
+
}
|
|
458
|
+
if (!_priceMode) {
|
|
459
|
+
let userData = await Storage.getJSON(
|
|
460
|
+
"persist:userInfo",
|
|
461
|
+
{ user: undefined } as { user?: string }
|
|
462
|
+
);
|
|
463
|
+
userData = userData && userData.user ? JSON.parse(userData.user) : {};
|
|
464
|
+
_priceMode = (userData as UserDetails)?.price_mode;
|
|
465
|
+
}
|
|
466
|
+
setPriceMode(_priceMode === undefined ? null : String(_priceMode));
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const handleClearChat = useCallback(async () => {
|
|
470
|
+
try {
|
|
471
|
+
const conversations = await Storage.getJSON<Record<string, any>>(
|
|
472
|
+
"vdbchat_conversations",
|
|
473
|
+
{}
|
|
474
|
+
);
|
|
475
|
+
const storedId = conversations ? conversations[priceMode as any]?.conversation_id : null;
|
|
476
|
+
if (storedId && priceMode) {
|
|
477
|
+
const updatedConversations = conversations ?? {};
|
|
478
|
+
delete updatedConversations[priceMode];
|
|
479
|
+
await Storage.setJSON(
|
|
481
480
|
"vdbchat_conversations",
|
|
482
|
-
|
|
481
|
+
updatedConversations
|
|
483
482
|
);
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
483
|
+
}
|
|
484
|
+
setMessages([]);
|
|
485
|
+
setAssistantResponse(undefined);
|
|
486
|
+
setProductsByMsg({});
|
|
487
|
+
setReloadLoadingIds(new Set());
|
|
488
|
+
setLoading(false);
|
|
489
|
+
setLoadingMessageId(null);
|
|
490
|
+
setTypingMessageId(null);
|
|
491
|
+
setTypingFullText("");
|
|
492
|
+
|
|
493
|
+
const fresh = await fetchInitialMessages(apiUrl, undefined, priceMode);
|
|
494
|
+
{
|
|
495
|
+
const normalised = normaliseMessages(fresh).reverse();
|
|
496
|
+
if (normalised.length === 0) {
|
|
497
|
+
const initialAssistant: ChatMessage = {
|
|
498
|
+
id: "",
|
|
499
|
+
role: "assistant",
|
|
500
|
+
text: "Hello! How can I help you today?",
|
|
501
|
+
createdAt: Date.now(),
|
|
502
|
+
isLoading: false,
|
|
503
|
+
suggestions: [
|
|
504
|
+
"Search Natural Diamonds",
|
|
505
|
+
"Search Lab-Grown Diamonds",
|
|
506
|
+
],
|
|
507
|
+
reaction: "0",
|
|
508
|
+
};
|
|
509
|
+
setMessages([initialAssistant]);
|
|
510
|
+
} else {
|
|
511
|
+
setMessages(normalised);
|
|
490
512
|
}
|
|
491
|
-
setMessages([]);
|
|
492
|
-
setAssistantResponse(undefined);
|
|
493
|
-
setProductsByMsg({});
|
|
494
|
-
setReloadLoadingIds(new Set());
|
|
495
|
-
setLoading(false);
|
|
496
|
-
setLoadingMessageId(null);
|
|
497
|
-
setTypingMessageId(null);
|
|
498
|
-
setTypingFullText("");
|
|
499
|
-
|
|
500
|
-
const fresh = await fetchInitialMessages(apiUrl, undefined, priceMode);
|
|
501
|
-
setMessages(normaliseMessages(fresh).reverse());
|
|
502
|
-
onClearChat?.();
|
|
503
|
-
} catch (err) {
|
|
504
|
-
console.error("Failed to clear chat", err);
|
|
505
513
|
}
|
|
506
|
-
|
|
514
|
+
onClearChat?.();
|
|
515
|
+
} catch (err) {
|
|
516
|
+
// eslint-disable-next-line no-console
|
|
517
|
+
console.error("Failed to clear chat", err);
|
|
518
|
+
}
|
|
519
|
+
}, [apiUrl, priceMode]);
|
|
520
|
+
|
|
521
|
+
// "Thinking..." dot animation while waiting for the API
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
if (!loadingMessageId || typingMessageId) return;
|
|
524
|
+
|
|
525
|
+
let step = 0;
|
|
526
|
+
const base = "Thinking";
|
|
527
|
+
const interval = setInterval(() => {
|
|
528
|
+
step = (step + 1) % 3;
|
|
529
|
+
const dots = ".".repeat(step + 1);
|
|
530
|
+
setMessages((prev) =>
|
|
531
|
+
prev.map((msg) =>
|
|
532
|
+
msg.id === loadingMessageId && msg.isLoading
|
|
533
|
+
? { ...msg, text: `${base}${dots}` }
|
|
534
|
+
: msg
|
|
535
|
+
)
|
|
536
|
+
);
|
|
537
|
+
}, 500);
|
|
507
538
|
|
|
539
|
+
return () => {
|
|
540
|
+
clearInterval(interval);
|
|
541
|
+
};
|
|
542
|
+
}, [loadingMessageId, typingMessageId]);
|
|
543
|
+
|
|
544
|
+
|
|
508
545
|
// Expose clearChat method via ref for external use (React Native)
|
|
509
546
|
useImperativeHandle(
|
|
510
547
|
ref,
|
|
@@ -514,33 +551,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
514
551
|
[handleClearChat]
|
|
515
552
|
);
|
|
516
553
|
|
|
517
|
-
// "Thinking..." dot animation while waiting for the API
|
|
518
|
-
useEffect(() => {
|
|
519
|
-
if (!loadingMessageId || typingMessageId) {
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
let step = 0;
|
|
524
|
-
const base = "Thinking";
|
|
525
|
-
const msgId = loadingMessageId;
|
|
526
|
-
|
|
527
|
-
const interval = setInterval(() => {
|
|
528
|
-
step = (step + 1) % 3;
|
|
529
|
-
const dots = ".".repeat(step + 1);
|
|
530
|
-
setMessages((prev) =>
|
|
531
|
-
prev.map((msg) =>
|
|
532
|
-
msg.id === msgId && msg.isLoading
|
|
533
|
-
? { ...msg, text: `${base}${dots}` }
|
|
534
|
-
: msg
|
|
535
|
-
)
|
|
536
|
-
);
|
|
537
|
-
}, 500);
|
|
538
|
-
|
|
539
|
-
return () => {
|
|
540
|
-
clearInterval(interval);
|
|
541
|
-
};
|
|
542
|
-
}, [loadingMessageId, typingMessageId]);
|
|
543
|
-
|
|
544
554
|
// Typewriter-style animation for assistant reply once it arrives
|
|
545
555
|
const typingIndexRef = useRef(0);
|
|
546
556
|
|
|
@@ -593,82 +603,99 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
593
603
|
style={[styles.container, { backgroundColor: theme.backgroundColor }]}
|
|
594
604
|
>
|
|
595
605
|
{Platform.OS === "web" && (
|
|
596
|
-
<ChatHeader onClose={onClose} onClearChat={handleClearChat} />
|
|
606
|
+
<ChatHeader onClose={onClose} onClearChat={handleClearChat} isBetaMode={betaActive} />
|
|
597
607
|
)}
|
|
598
|
-
<
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
608
|
+
<KeyboardAvoidingView
|
|
609
|
+
style={{ flex: 1 }}
|
|
610
|
+
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
611
|
+
keyboardVerticalOffset={-16}
|
|
612
|
+
>
|
|
613
|
+
<ScrollView
|
|
614
|
+
ref={scrollRef}
|
|
615
|
+
keyboardShouldPersistTaps="handled"
|
|
616
|
+
style={
|
|
617
|
+
modalHeight
|
|
618
|
+
? { height: modalHeight, backgroundColor: "#FFFFFF" }
|
|
619
|
+
: { backgroundColor: "#FFFFFF" }
|
|
620
|
+
}
|
|
621
|
+
contentContainerStyle={{
|
|
622
|
+
backgroundColor: theme?.listContentBackgroundColor || "#FFFFFF",
|
|
623
|
+
...styles.listContent,
|
|
624
|
+
justifyContent: messages.length === 0 ? "center" : "flex-end",
|
|
625
|
+
minHeight: modalHeight ? modalHeight : undefined,
|
|
626
|
+
}}
|
|
627
|
+
onContentSizeChange={() => {
|
|
628
|
+
scrollRef.current?.scrollToEnd({ animated: false });
|
|
629
|
+
}}
|
|
630
|
+
>
|
|
631
|
+
<View style={styles.emptyContainer}>
|
|
632
|
+
<Text style={styles.emptyText}>
|
|
633
|
+
{messages.length === 0
|
|
634
|
+
? "Start a conversation to Find the Perfect Diamond"
|
|
635
|
+
: `Chat Started at ${formatToTime(messages[0].createdAt)}`}
|
|
636
|
+
</Text>
|
|
637
|
+
</View>
|
|
638
|
+
{(() => {
|
|
639
|
+
const ordered = [...messages];
|
|
640
|
+
return ordered.map((item, index) => {
|
|
641
|
+
const isLatest = index === ordered.length - 1;
|
|
642
|
+
const hasDiamonds = Boolean(
|
|
643
|
+
productsByMsg[item.id]?.response?.body?.diamonds &&
|
|
644
|
+
productsByMsg[item.id]?.response?.body?.diamonds.length > 0
|
|
645
|
+
);
|
|
646
|
+
return (
|
|
647
|
+
<View key={item.id + index} style={{ gap: 12 }}>
|
|
648
|
+
<MessageBubble
|
|
649
|
+
message={item}
|
|
650
|
+
theme={theme}
|
|
651
|
+
priceMode={priceMode as string}
|
|
652
|
+
handleFeedbackAction={handleFeedbackAction}
|
|
653
|
+
onReloadResults={handleReloadResults}
|
|
654
|
+
reloading={reloadLoadingIds.has(item.id)}
|
|
655
|
+
hasResults={hasDiamonds}
|
|
656
|
+
totalResults={productsByMsg[item.id]?.response?.header?.total_diamonds_found || 0}
|
|
657
|
+
shownResults={productsByMsg[item.id]?.response?.body?.diamonds.length || 0}
|
|
658
|
+
onSuggestionSelect={handleSuggestionSelect}
|
|
659
|
+
isLatest={isLatest}
|
|
660
|
+
isTyping={typingMessageId === item.id}
|
|
661
|
+
/>
|
|
662
|
+
{item.role === "assistant" && hasDiamonds && (
|
|
643
663
|
<ProductsList
|
|
644
|
-
data={
|
|
664
|
+
data={
|
|
665
|
+
productsByMsg[item.id]?.response?.body?.diamonds || []
|
|
666
|
+
}
|
|
667
|
+
totalResults={productsByMsg[item.id]?.response?.header?.total_diamonds_found || 0}
|
|
645
668
|
onViewAll={onViewAll}
|
|
646
669
|
onItemPress={onItemPress}
|
|
647
670
|
/>
|
|
648
671
|
)}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
+
{/* Suggestions are now rendered inside MessageBubble */}
|
|
673
|
+
<MessageMetaRow
|
|
674
|
+
message={item}
|
|
675
|
+
priceMode={priceMode || ""}
|
|
676
|
+
handleFeedbackAction={handleFeedbackAction}
|
|
677
|
+
onReloadResults={handleReloadResults}
|
|
678
|
+
reloading={reloadLoadingIds.has(item.id)}
|
|
679
|
+
hasResults={hasDiamonds}
|
|
680
|
+
/>
|
|
681
|
+
</View>
|
|
682
|
+
);
|
|
683
|
+
});
|
|
684
|
+
})()}
|
|
685
|
+
</ScrollView>
|
|
686
|
+
<View style={ styles.bottomContainer }>
|
|
687
|
+
<ChatInput
|
|
688
|
+
value={input}
|
|
689
|
+
onChangeText={setInput}
|
|
690
|
+
onSend={handleSend}
|
|
691
|
+
disabled={loading}
|
|
692
|
+
placeholder={placeholder}
|
|
693
|
+
theme={theme}
|
|
694
|
+
inputRef={inputRef}
|
|
695
|
+
/>
|
|
696
|
+
<BetaNotice active={betaActive} />
|
|
697
|
+
</View>
|
|
698
|
+
</KeyboardAvoidingView>
|
|
672
699
|
</View>
|
|
673
700
|
);
|
|
674
701
|
}
|
|
@@ -694,11 +721,22 @@ const styles = StyleSheet.create({
|
|
|
694
721
|
padding: 16,
|
|
695
722
|
alignItems: "center",
|
|
696
723
|
justifyContent: "center",
|
|
697
|
-
backgroundColor: "#
|
|
724
|
+
backgroundColor: "#FFFFFF",
|
|
698
725
|
},
|
|
699
726
|
emptyText: {
|
|
700
|
-
fontSize:
|
|
701
|
-
fontWeight: "
|
|
702
|
-
color: "#
|
|
727
|
+
fontSize: 13,
|
|
728
|
+
fontWeight: "400",
|
|
729
|
+
color: "#4F4E57",
|
|
703
730
|
},
|
|
731
|
+
bottomContainer: {
|
|
732
|
+
paddingVertical: 12,
|
|
733
|
+
paddingHorizontal: 16,
|
|
734
|
+
borderTopColor: "#E0E0E0",
|
|
735
|
+
borderTopWidth: 1,
|
|
736
|
+
flexDirection: "column",
|
|
737
|
+
justifyContent: "space-between",
|
|
738
|
+
alignItems: "center",
|
|
739
|
+
alignSelf: "stretch",
|
|
740
|
+
gap: 12,
|
|
741
|
+
}
|
|
704
742
|
});
|