vdb-ai-chat 1.0.0 → 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/248.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 -0
- package/dist/572.chat-widget.js +1 -0
- package/dist/694.chat-widget.js +1 -0
- package/dist/chat-widget.js +1 -1
- package/dist/chat-widget.js.LICENSE.txt +2 -0
- package/lib/commonjs/api.js +6 -8
- 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 +41 -21
- 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 +162 -90
- package/lib/commonjs/components/ChatWidget.js.map +1 -1
- package/lib/commonjs/components/MessageBubble.js +28 -68
- 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 +50 -4
- package/lib/commonjs/components/utils.js.map +1 -1
- package/lib/commonjs/contexts/AnalyticsClientContext.js +19 -0
- package/lib/commonjs/contexts/AnalyticsClientContext.js.map +1 -0
- package/lib/commonjs/hooks/useAnalytics.js +158 -0
- package/lib/commonjs/hooks/useAnalytics.js.map +1 -0
- package/lib/commonjs/index.web.js +91 -8
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/storage.js +5 -5
- package/lib/commonjs/storage.js.map +1 -1
- package/lib/commonjs/theme.js +4 -4
- package/lib/commonjs/theme.js.map +1 -1
- package/lib/module/api.js +6 -8
- 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 +41 -21
- 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 +164 -92
- package/lib/module/components/ChatWidget.js.map +1 -1
- package/lib/module/components/MessageBubble.js +28 -70
- 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 +46 -3
- package/lib/module/components/utils.js.map +1 -1
- package/lib/module/contexts/AnalyticsClientContext.js +10 -0
- package/lib/module/contexts/AnalyticsClientContext.js.map +1 -0
- package/lib/module/hooks/useAnalytics.js +146 -0
- package/lib/module/hooks/useAnalytics.js.map +1 -0
- package/lib/module/index.native.js +5 -5
- package/lib/module/index.web.js +89 -6
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/storage.js +6 -6
- package/lib/module/storage.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 +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 +9 -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 +24 -0
- package/lib/typescript/components/utils.d.ts.map +1 -1
- package/lib/typescript/contexts/AnalyticsClientContext.d.ts +9 -0
- package/lib/typescript/contexts/AnalyticsClientContext.d.ts.map +1 -0
- package/lib/typescript/hooks/useAnalytics.d.ts +36 -0
- package/lib/typescript/hooks/useAnalytics.d.ts.map +1 -0
- package/lib/typescript/index.native.d.ts +5 -5
- package/lib/typescript/index.web.d.ts +1 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/storage.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +4 -2
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +13 -3
- package/src/api.ts +26 -14
- package/src/components/BetaNotice.tsx +32 -0
- package/src/components/ChatHeader.tsx +43 -18
- package/src/components/ChatInput.tsx +20 -21
- package/src/components/ChatWidget.tsx +282 -219
- package/src/components/MessageBubble.tsx +48 -111
- package/src/components/MessageMetaRow.tsx +199 -0
- package/src/components/ProductsGrid.tsx +163 -0
- package/src/components/ProductsList.tsx +20 -144
- package/src/components/ProductsListView.tsx +149 -0
- package/src/components/SuggestionsRow.tsx +45 -21
- package/src/components/utils.ts +66 -5
- package/src/contexts/AnalyticsClientContext.tsx +20 -0
- package/src/hooks/useAnalytics.tsx +176 -0
- package/src/index.native.tsx +5 -5
- package/src/index.web.tsx +92 -4
- package/src/storage.ts +16 -13
- package/src/theme.ts +4 -4
- package/src/types.ts +4 -3
|
@@ -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,16 +31,17 @@ 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 } from "./utils";
|
|
37
|
+
import { FeedbackAction, formatToTime, getUserDetails, UserDetails } from "./utils";
|
|
38
|
+
import { useUserAnalytics } from "../hooks/useAnalytics";
|
|
35
39
|
import { Storage } from "../storage";
|
|
36
40
|
|
|
37
41
|
export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
38
42
|
(
|
|
39
43
|
{
|
|
40
44
|
apiUrl,
|
|
41
|
-
userId: userIdProp,
|
|
42
45
|
userToken: userTokenProp,
|
|
43
46
|
priceMode: priceModeProp,
|
|
44
47
|
modalHeight,
|
|
@@ -49,6 +52,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
49
52
|
onClearChat,
|
|
50
53
|
onViewAllPress,
|
|
51
54
|
onItemPress: onItemPressProp,
|
|
55
|
+
isBetaMode: isBetaModeProp,
|
|
52
56
|
},
|
|
53
57
|
ref
|
|
54
58
|
) => {
|
|
@@ -63,33 +67,23 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
63
67
|
const [assistantResponse, setAssistantResponse] = useState<
|
|
64
68
|
ChatMessage | undefined
|
|
65
69
|
>(undefined);
|
|
66
|
-
const [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const [productsByMsg, setProductsByMsg] = useState<Record<string, any>>({});
|
|
71
|
+
const [reloadLoadingIds, setReloadLoadingIds] = useState<Set<string>>(
|
|
72
|
+
new Set()
|
|
73
|
+
);
|
|
70
74
|
const [priceMode, setPriceMode] = useState<string | null>(
|
|
71
75
|
priceModeProp || null
|
|
72
76
|
);
|
|
73
|
-
const [conversationId, setConversationId] = useState<string | null>(null);
|
|
74
|
-
const [userId, setUserId] = useState<string>(userIdProp || "");
|
|
75
77
|
const [userToken, setUserToken] = useState<string>(userTokenProp || "");
|
|
76
78
|
const scrollRef = useRef<ScrollView | null>(null);
|
|
77
79
|
const inputRef = useRef<TextInput | null>(null);
|
|
78
80
|
const theme = useMemo(() => mergeTheme(themeOverrides), [themeOverrides]);
|
|
79
|
-
|
|
81
|
+
const { _identify } = useUserAnalytics();
|
|
82
|
+
const betaActive = Boolean(isBetaModeProp);
|
|
80
83
|
// Load user auth data from storage on mount
|
|
81
84
|
useEffect(() => {
|
|
82
85
|
const loadAuthData = async () => {
|
|
83
86
|
try {
|
|
84
|
-
if (!userIdProp) {
|
|
85
|
-
const userData = await Storage.getJSON<{ id?: string }>(
|
|
86
|
-
"userData",
|
|
87
|
-
{}
|
|
88
|
-
);
|
|
89
|
-
if (userData?.id) {
|
|
90
|
-
setUserId(userData.id);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
87
|
if (!userTokenProp) {
|
|
94
88
|
const token = await Storage.getItem("token");
|
|
95
89
|
if (token) {
|
|
@@ -101,13 +95,11 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
101
95
|
}
|
|
102
96
|
};
|
|
103
97
|
loadAuthData();
|
|
104
|
-
}, [
|
|
98
|
+
}, [userTokenProp]);
|
|
105
99
|
|
|
106
100
|
const onViewAll = useCallback(() => {
|
|
107
|
-
const searchPayload = JSON.stringify(
|
|
108
|
-
|
|
109
|
-
);
|
|
110
|
-
const payload = assistantResponse?.agent_response?.payload;
|
|
101
|
+
const searchPayload = JSON.stringify(assistantResponse?.search_payload);
|
|
102
|
+
const payload = assistantResponse?.search_payload;
|
|
111
103
|
if (!payload) return;
|
|
112
104
|
|
|
113
105
|
const domain = apiUrl.split("v3");
|
|
@@ -149,15 +141,15 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
149
141
|
);
|
|
150
142
|
|
|
151
143
|
const hasAuth = useMemo(
|
|
152
|
-
() => Boolean(
|
|
153
|
-
[
|
|
144
|
+
() => Boolean(userToken),
|
|
145
|
+
[userToken]
|
|
154
146
|
);
|
|
155
147
|
|
|
156
148
|
const apiParams: ChatApiParams = useMemo(
|
|
157
149
|
() => ({
|
|
158
|
-
conversationId: userToken
|
|
150
|
+
conversationId: userToken,
|
|
159
151
|
}),
|
|
160
|
-
[
|
|
152
|
+
[userToken]
|
|
161
153
|
);
|
|
162
154
|
|
|
163
155
|
const handleFeedbackAction = useCallback(
|
|
@@ -211,7 +203,24 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
211
203
|
priceMode
|
|
212
204
|
);
|
|
213
205
|
if (!cancelled) {
|
|
214
|
-
|
|
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
|
+
}
|
|
215
224
|
}
|
|
216
225
|
} catch (error) {
|
|
217
226
|
console.error("Failed to fetch initial messages", error);
|
|
@@ -231,9 +240,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
231
240
|
async (rawText: string) => {
|
|
232
241
|
const trimmed = rawText.trim();
|
|
233
242
|
if (!trimmed || loading) return;
|
|
234
|
-
|
|
235
|
-
setProducts(null);
|
|
236
|
-
}
|
|
243
|
+
|
|
237
244
|
const userMessage: ChatMessage = {
|
|
238
245
|
id: `user-${Date.now()}`,
|
|
239
246
|
role: "user",
|
|
@@ -247,6 +254,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
247
254
|
role: "assistant",
|
|
248
255
|
text: "Thinking",
|
|
249
256
|
createdAt: Date.now(),
|
|
257
|
+
reaction: "0",
|
|
250
258
|
isLoading: true,
|
|
251
259
|
};
|
|
252
260
|
|
|
@@ -289,12 +297,12 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
289
297
|
if (latestAssistant?.text) {
|
|
290
298
|
// If there is an agent_response, try to fetch products first
|
|
291
299
|
if (
|
|
292
|
-
latestAssistant?.
|
|
293
|
-
typeof latestAssistant.
|
|
294
|
-
Object.keys(latestAssistant.
|
|
300
|
+
latestAssistant?.search_payload &&
|
|
301
|
+
typeof latestAssistant.search_payload === "object" &&
|
|
302
|
+
Object.keys(latestAssistant.search_payload).length > 0
|
|
295
303
|
) {
|
|
296
304
|
const productsResult = await getProducts(
|
|
297
|
-
latestAssistant.
|
|
305
|
+
latestAssistant.search_payload
|
|
298
306
|
);
|
|
299
307
|
|
|
300
308
|
const hasDiamonds =
|
|
@@ -311,7 +319,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
311
319
|
setLoadingMessageId(null);
|
|
312
320
|
setTypingMessageId(null);
|
|
313
321
|
setTypingFullText("");
|
|
314
|
-
setProducts(null);
|
|
315
322
|
|
|
316
323
|
setMessages((prev) =>
|
|
317
324
|
prev.map((msg) =>
|
|
@@ -329,10 +336,10 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
329
336
|
inputRef.current?.focus();
|
|
330
337
|
return;
|
|
331
338
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
});
|
|
339
|
+
setProductsByMsg((prev) => ({
|
|
340
|
+
...prev,
|
|
341
|
+
[latestAssistant.id]: productsResult,
|
|
342
|
+
}));
|
|
336
343
|
}
|
|
337
344
|
setLoadingMessageId(null);
|
|
338
345
|
|
|
@@ -386,9 +393,19 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
386
393
|
inputRef.current?.focus();
|
|
387
394
|
}
|
|
388
395
|
},
|
|
389
|
-
[apiUrl, loading, messages, apiParams, hasAuth, priceMode,
|
|
396
|
+
[apiUrl, loading, messages, apiParams, hasAuth, priceMode, productsByMsg]
|
|
390
397
|
);
|
|
391
398
|
|
|
399
|
+
const identifySegmentUser = async () => {
|
|
400
|
+
const user = await getUserDetails();
|
|
401
|
+
const userId = user?.id;
|
|
402
|
+
const email = JSON.stringify(user?.email) ?? undefined;
|
|
403
|
+
if (userId) {
|
|
404
|
+
_identify(`${userId}`, {
|
|
405
|
+
email: email,
|
|
406
|
+
}).then();
|
|
407
|
+
}
|
|
408
|
+
};
|
|
392
409
|
const handleSend = useCallback(async () => {
|
|
393
410
|
const trimmed = input.trim();
|
|
394
411
|
if (!trimmed) return;
|
|
@@ -397,98 +414,134 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
397
414
|
}, [input, sendMessage]);
|
|
398
415
|
|
|
399
416
|
const handleSuggestionSelect = useCallback(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
_priceMode = priceModeProp;
|
|
435
|
-
}
|
|
436
|
-
if (!_priceMode) {
|
|
437
|
-
const userData = await Storage.getJSON<{ price_mode?: string }>(
|
|
438
|
-
"userData",
|
|
439
|
-
{}
|
|
440
|
-
);
|
|
441
|
-
_priceMode = userData?.price_mode;
|
|
442
|
-
}
|
|
443
|
-
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");
|
|
444
451
|
};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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(
|
|
466
480
|
"vdbchat_conversations",
|
|
467
|
-
|
|
481
|
+
updatedConversations
|
|
468
482
|
);
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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);
|
|
475
512
|
}
|
|
476
|
-
setMessages([]);
|
|
477
|
-
setAssistantResponse(undefined);
|
|
478
|
-
setProducts(null);
|
|
479
|
-
setLoading(false);
|
|
480
|
-
setLoadingMessageId(null);
|
|
481
|
-
setTypingMessageId(null);
|
|
482
|
-
setTypingFullText("");
|
|
483
|
-
|
|
484
|
-
const fresh = await fetchInitialMessages(apiUrl, undefined, priceMode);
|
|
485
|
-
setMessages(normaliseMessages(fresh).reverse());
|
|
486
|
-
onClearChat?.();
|
|
487
|
-
} catch (err) {
|
|
488
|
-
console.error("Failed to clear chat", err);
|
|
489
513
|
}
|
|
490
|
-
|
|
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);
|
|
491
538
|
|
|
539
|
+
return () => {
|
|
540
|
+
clearInterval(interval);
|
|
541
|
+
};
|
|
542
|
+
}, [loadingMessageId, typingMessageId]);
|
|
543
|
+
|
|
544
|
+
|
|
492
545
|
// Expose clearChat method via ref for external use (React Native)
|
|
493
546
|
useImperativeHandle(
|
|
494
547
|
ref,
|
|
@@ -498,33 +551,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
498
551
|
[handleClearChat]
|
|
499
552
|
);
|
|
500
553
|
|
|
501
|
-
// "Thinking..." dot animation while waiting for the API
|
|
502
|
-
useEffect(() => {
|
|
503
|
-
if (!loadingMessageId || typingMessageId) {
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
let step = 0;
|
|
508
|
-
const base = "Thinking";
|
|
509
|
-
const msgId = loadingMessageId;
|
|
510
|
-
|
|
511
|
-
const interval = setInterval(() => {
|
|
512
|
-
step = (step + 1) % 3;
|
|
513
|
-
const dots = ".".repeat(step + 1);
|
|
514
|
-
setMessages((prev) =>
|
|
515
|
-
prev.map((msg) =>
|
|
516
|
-
msg.id === msgId && msg.isLoading
|
|
517
|
-
? { ...msg, text: `${base}${dots}` }
|
|
518
|
-
: msg
|
|
519
|
-
)
|
|
520
|
-
);
|
|
521
|
-
}, 500);
|
|
522
|
-
|
|
523
|
-
return () => {
|
|
524
|
-
clearInterval(interval);
|
|
525
|
-
};
|
|
526
|
-
}, [loadingMessageId, typingMessageId]);
|
|
527
|
-
|
|
528
554
|
// Typewriter-style animation for assistant reply once it arrives
|
|
529
555
|
const typingIndexRef = useRef(0);
|
|
530
556
|
|
|
@@ -577,73 +603,99 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
577
603
|
style={[styles.container, { backgroundColor: theme.backgroundColor }]}
|
|
578
604
|
>
|
|
579
605
|
{Platform.OS === "web" && (
|
|
580
|
-
<ChatHeader onClose={onClose} onClearChat={handleClearChat} />
|
|
606
|
+
<ChatHeader onClose={onClose} onClearChat={handleClearChat} isBetaMode={betaActive} />
|
|
581
607
|
)}
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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 && (
|
|
618
663
|
<ProductsList
|
|
619
|
-
data={
|
|
664
|
+
data={
|
|
665
|
+
productsByMsg[item.id]?.response?.body?.diamonds || []
|
|
666
|
+
}
|
|
667
|
+
totalResults={productsByMsg[item.id]?.response?.header?.total_diamonds_found || 0}
|
|
620
668
|
onViewAll={onViewAll}
|
|
621
669
|
onItemPress={onItemPress}
|
|
622
670
|
/>
|
|
623
671
|
)}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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>
|
|
647
699
|
</View>
|
|
648
700
|
);
|
|
649
701
|
}
|
|
@@ -669,11 +721,22 @@ const styles = StyleSheet.create({
|
|
|
669
721
|
padding: 16,
|
|
670
722
|
alignItems: "center",
|
|
671
723
|
justifyContent: "center",
|
|
672
|
-
backgroundColor: "#
|
|
724
|
+
backgroundColor: "#FFFFFF",
|
|
673
725
|
},
|
|
674
726
|
emptyText: {
|
|
675
|
-
fontSize:
|
|
676
|
-
fontWeight: "
|
|
677
|
-
color: "#
|
|
727
|
+
fontSize: 13,
|
|
728
|
+
fontWeight: "400",
|
|
729
|
+
color: "#4F4E57",
|
|
678
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
|
+
}
|
|
679
742
|
});
|