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.
Files changed (114) hide show
  1. package/dist/10.chat-widget.js +2 -0
  2. package/dist/10.chat-widget.js.LICENSE.txt +1 -0
  3. package/dist/104.chat-widget.js +1 -0
  4. package/dist/50.chat-widget.js +1 -0
  5. package/dist/521.chat-widget.js +1 -0
  6. package/dist/538.chat-widget.js +1 -1
  7. package/dist/572.chat-widget.js +1 -0
  8. package/dist/694.chat-widget.js +1 -0
  9. package/dist/chat-widget.js +1 -1
  10. package/lib/commonjs/api.js +4 -3
  11. package/lib/commonjs/api.js.map +1 -1
  12. package/lib/commonjs/components/BetaNotice.js +38 -0
  13. package/lib/commonjs/components/BetaNotice.js.map +1 -0
  14. package/lib/commonjs/components/ChatHeader.js +27 -20
  15. package/lib/commonjs/components/ChatHeader.js.map +1 -1
  16. package/lib/commonjs/components/ChatInput.js +21 -21
  17. package/lib/commonjs/components/ChatInput.js.map +1 -1
  18. package/lib/commonjs/components/ChatWidget.js +119 -72
  19. package/lib/commonjs/components/ChatWidget.js.map +1 -1
  20. package/lib/commonjs/components/MessageBubble.js +26 -90
  21. package/lib/commonjs/components/MessageBubble.js.map +1 -1
  22. package/lib/commonjs/components/MessageMetaRow.js +135 -0
  23. package/lib/commonjs/components/MessageMetaRow.js.map +1 -0
  24. package/lib/commonjs/components/ProductsGrid.js +139 -0
  25. package/lib/commonjs/components/ProductsGrid.js.map +1 -0
  26. package/lib/commonjs/components/ProductsList.js +22 -126
  27. package/lib/commonjs/components/ProductsList.js.map +1 -1
  28. package/lib/commonjs/components/ProductsListView.js +139 -0
  29. package/lib/commonjs/components/ProductsListView.js.map +1 -0
  30. package/lib/commonjs/components/SuggestionsRow.js +41 -23
  31. package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
  32. package/lib/commonjs/components/utils.js +4 -3
  33. package/lib/commonjs/components/utils.js.map +1 -1
  34. package/lib/commonjs/index.web.js +86 -29
  35. package/lib/commonjs/index.web.js.map +1 -1
  36. package/lib/commonjs/theme.js +4 -4
  37. package/lib/commonjs/theme.js.map +1 -1
  38. package/lib/module/api.js +4 -3
  39. package/lib/module/api.js.map +1 -1
  40. package/lib/module/components/BetaNotice.js +30 -0
  41. package/lib/module/components/BetaNotice.js.map +1 -0
  42. package/lib/module/components/ChatHeader.js +27 -20
  43. package/lib/module/components/ChatHeader.js.map +1 -1
  44. package/lib/module/components/ChatInput.js +21 -21
  45. package/lib/module/components/ChatInput.js.map +1 -1
  46. package/lib/module/components/ChatWidget.js +120 -73
  47. package/lib/module/components/ChatWidget.js.map +1 -1
  48. package/lib/module/components/MessageBubble.js +26 -92
  49. package/lib/module/components/MessageBubble.js.map +1 -1
  50. package/lib/module/components/MessageMetaRow.js +127 -0
  51. package/lib/module/components/MessageMetaRow.js.map +1 -0
  52. package/lib/module/components/ProductsGrid.js +133 -0
  53. package/lib/module/components/ProductsGrid.js.map +1 -0
  54. package/lib/module/components/ProductsList.js +21 -126
  55. package/lib/module/components/ProductsList.js.map +1 -1
  56. package/lib/module/components/ProductsListView.js +132 -0
  57. package/lib/module/components/ProductsListView.js.map +1 -0
  58. package/lib/module/components/SuggestionsRow.js +41 -23
  59. package/lib/module/components/SuggestionsRow.js.map +1 -1
  60. package/lib/module/components/utils.js +4 -3
  61. package/lib/module/components/utils.js.map +1 -1
  62. package/lib/module/index.web.js +86 -29
  63. package/lib/module/index.web.js.map +1 -1
  64. package/lib/module/theme.js +4 -4
  65. package/lib/module/theme.js.map +1 -1
  66. package/lib/typescript/api.d.ts.map +1 -1
  67. package/lib/typescript/components/BetaNotice.d.ts +5 -0
  68. package/lib/typescript/components/BetaNotice.d.ts.map +1 -0
  69. package/lib/typescript/components/ChatHeader.d.ts +5 -2
  70. package/lib/typescript/components/ChatHeader.d.ts.map +1 -1
  71. package/lib/typescript/components/ChatInput.d.ts.map +1 -1
  72. package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
  73. package/lib/typescript/components/MessageBubble.d.ts +7 -3
  74. package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
  75. package/lib/typescript/components/MessageMetaRow.d.ts +14 -0
  76. package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -0
  77. package/lib/typescript/components/ProductsGrid.d.ts +10 -0
  78. package/lib/typescript/components/ProductsGrid.d.ts.map +1 -0
  79. package/lib/typescript/components/ProductsList.d.ts +4 -2
  80. package/lib/typescript/components/ProductsList.d.ts.map +1 -1
  81. package/lib/typescript/components/ProductsListView.d.ts +10 -0
  82. package/lib/typescript/components/ProductsListView.d.ts.map +1 -0
  83. package/lib/typescript/components/SuggestionsRow.d.ts +2 -0
  84. package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
  85. package/lib/typescript/components/utils.d.ts +1 -0
  86. package/lib/typescript/components/utils.d.ts.map +1 -1
  87. package/lib/typescript/index.web.d.ts +1 -1
  88. package/lib/typescript/index.web.d.ts.map +1 -1
  89. package/lib/typescript/types.d.ts +3 -1
  90. package/lib/typescript/types.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/api.ts +4 -3
  93. package/src/components/BetaNotice.tsx +32 -0
  94. package/src/components/ChatHeader.tsx +32 -18
  95. package/src/components/ChatInput.tsx +20 -21
  96. package/src/components/ChatWidget.tsx +258 -220
  97. package/src/components/MessageBubble.tsx +44 -149
  98. package/src/components/MessageMetaRow.tsx +199 -0
  99. package/src/components/ProductsGrid.tsx +163 -0
  100. package/src/components/ProductsList.tsx +20 -146
  101. package/src/components/ProductsListView.tsx +149 -0
  102. package/src/components/SuggestionsRow.tsx +45 -21
  103. package/src/components/utils.ts +6 -4
  104. package/src/index.web.tsx +87 -32
  105. package/src/theme.ts +4 -4
  106. package/src/types.ts +3 -2
  107. package/dist/751.chat-widget.js +0 -1
  108. package/lib/commonjs/contexts/SegmentClientContext.js +0 -19
  109. package/lib/commonjs/contexts/SegmentClientContext.js.map +0 -1
  110. package/lib/module/contexts/SegmentClientContext.js +0 -10
  111. package/lib/module/contexts/SegmentClientContext.js.map +0 -1
  112. package/lib/typescript/contexts/SegmentClientContext.d.ts +0 -9
  113. package/lib/typescript/contexts/SegmentClientContext.d.ts.map +0 -1
  114. 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
- }, [userIdProp, userTokenProp]);
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(userId || userToken),
152
- [userId, userToken]
144
+ () => Boolean(userToken),
145
+ [userToken]
153
146
  );
154
147
 
155
148
  const apiParams: ChatApiParams = useMemo(
156
149
  () => ({
157
- conversationId: userToken || userId,
150
+ conversationId: userToken,
158
151
  }),
159
- [userId, userToken]
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
- setMessages(normaliseMessages(initial).reverse());
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
- messageId: latestAssistant.id,
330
- data: productsResult,
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
- (value: string) => {
407
- sendMessage(value);
408
- },
409
- [sendMessage]
410
- );
411
-
412
- const handleReloadResults = useCallback(async (msg: ChatMessage) => {
413
- const id = msg.id;
414
- try {
415
- setReloadLoadingIds((prev) => new Set(prev).add(id));
416
- const payload = msg?.search_payload;
417
- if (!payload || Object.keys(payload).length === 0) return;
418
- const productsResult = await getProducts(payload);
419
- setProductsByMsg((prev) => ({ ...prev, [id]: productsResult }));
420
- } catch (e) {
421
- console.error("Reload results failed", e);
422
- } finally {
423
- setReloadLoadingIds((prev) => {
424
- const next = new Set(prev);
425
- next.delete(id);
426
- return next;
427
- });
428
- }
429
- }, []);
430
-
431
- useEffect(() => {
432
- DeviceEventEmitter.addListener("clearChat", handleClearChat);
433
- DeviceEventEmitter.addListener(
434
- "changePriceMode",
435
- (data: { priceMode: string }) => changePriceMode(data.priceMode)
436
- );
437
- changePriceMode(undefined);
438
- identifySegmentUser();
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
- // Update conversationId when priceMode changes
462
- useEffect(() => {
463
- const loadConversationId = async () => {
464
- if (!priceMode) {
465
- setConversationId(null);
466
- return;
467
- }
468
- const conversations = await Storage.getJSON<Record<string, any>>(
469
- "vdbchat_conversations",
470
- {}
471
- );
472
- const storedId = conversations?.[priceMode]?.conversation_id ?? null;
473
- setConversationId(storedId ? String(storedId) : null);
474
- };
475
- loadConversationId();
476
- }, [priceMode, messages]); // Re-fetch when messages change (new conversation might be created)
477
-
478
- const handleClearChat = useCallback(async () => {
479
- try {
480
- const conversations = await Storage.getJSON<Record<string, any>>(
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
- const storedId =
485
- conversations?.[priceMode as string]?.conversation_id ?? null;
486
- if (storedId && priceMode) {
487
- const updatedConversations = conversations || {};
488
- delete updatedConversations[priceMode];
489
- await Storage.setJSON("vdbchat_conversations", updatedConversations);
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
- }, [apiUrl, onClearChat, priceMode]);
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
- <ScrollView
599
- ref={scrollRef}
600
- style={
601
- modalHeight
602
- ? { height: modalHeight, backgroundColor: "#f5f5f5" }
603
- : { backgroundColor: "#f5f5f5" }
604
- }
605
- contentContainerStyle={{
606
- backgroundColor: theme?.listContentBackgroundColor || "#f5f5f5",
607
- ...styles.listContent,
608
- justifyContent: messages.length === 0 ? "center" : "flex-end",
609
- minHeight: modalHeight ? modalHeight : undefined,
610
- }}
611
- onContentSizeChange={() => {
612
- scrollRef.current?.scrollToEnd({ animated: false });
613
- }}
614
- >
615
- <View style={styles.emptyContainer}>
616
- <Text style={styles.emptyText}>
617
- {messages.length === 0
618
- ? "Start a conversation to Find the Perfect Diamond"
619
- : `Chat Started at ${formatToTime(messages[0].createdAt)}`}
620
- </Text>
621
- </View>
622
- {(() => {
623
- const ordered = [...messages];
624
- return ordered.map((item, index) => {
625
- const isLatest = index === ordered.length - 1;
626
- return (
627
- <View key={item.id + index}>
628
- <MessageBubble
629
- message={item}
630
- theme={theme}
631
- conversationId={conversationId}
632
- handleFeedbackAction={handleFeedbackAction}
633
- onReloadResults={handleReloadResults}
634
- reloading={reloadLoadingIds.has(item.id)}
635
- hasResults={Boolean(
636
- productsByMsg[item.id]?.response?.body?.diamonds &&
637
- productsByMsg[item.id].response.body.diamonds.length > 0
638
- )}
639
- />
640
- {item.role === "assistant" &&
641
- productsByMsg &&
642
- productsByMsg.messageId === item.id && (
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={productsByMsg?.data?.response?.body?.diamonds}
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
- {item.role === "assistant" &&
650
- item.suggestions &&
651
- item.suggestions.length > 0 &&
652
- isLatest && (
653
- <SuggestionsRow
654
- suggestions={item.suggestions || []}
655
- onSelect={handleSuggestionSelect}
656
- />
657
- )}
658
- </View>
659
- );
660
- });
661
- })()}
662
- </ScrollView>
663
- <ChatInput
664
- value={input}
665
- onChangeText={setInput}
666
- onSend={handleSend}
667
- disabled={loading}
668
- placeholder={placeholder}
669
- theme={theme}
670
- inputRef={inputRef}
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: "#f5f5f5",
724
+ backgroundColor: "#FFFFFF",
698
725
  },
699
726
  emptyText: {
700
- fontSize: 14,
701
- fontWeight: "500",
702
- color: "#666666",
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
  });