vdb-ai-chat 1.0.13 → 1.0.15

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 (41) hide show
  1. package/dist/chat-widget.js +1 -1
  2. package/lib/commonjs/api.js +47 -1
  3. package/lib/commonjs/api.js.map +1 -1
  4. package/lib/commonjs/components/BetaNotice.js +1 -1
  5. package/lib/commonjs/components/BetaNotice.js.map +1 -1
  6. package/lib/commonjs/components/ChatInput.js +1 -1
  7. package/lib/commonjs/components/ChatInput.js.map +1 -1
  8. package/lib/commonjs/components/ChatWidget.js +142 -36
  9. package/lib/commonjs/components/ChatWidget.js.map +1 -1
  10. package/lib/commonjs/components/ProductsListView.js +26 -20
  11. package/lib/commonjs/components/ProductsListView.js.map +1 -1
  12. package/lib/commonjs/components/utils.js +37 -1
  13. package/lib/commonjs/components/utils.js.map +1 -1
  14. package/lib/module/api.js +46 -1
  15. package/lib/module/api.js.map +1 -1
  16. package/lib/module/components/BetaNotice.js +1 -1
  17. package/lib/module/components/BetaNotice.js.map +1 -1
  18. package/lib/module/components/ChatInput.js +1 -1
  19. package/lib/module/components/ChatInput.js.map +1 -1
  20. package/lib/module/components/ChatWidget.js +144 -38
  21. package/lib/module/components/ChatWidget.js.map +1 -1
  22. package/lib/module/components/ProductsListView.js +27 -21
  23. package/lib/module/components/ProductsListView.js.map +1 -1
  24. package/lib/module/components/utils.js +33 -0
  25. package/lib/module/components/utils.js.map +1 -1
  26. package/lib/typescript/api.d.ts +12 -0
  27. package/lib/typescript/api.d.ts.map +1 -1
  28. package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
  29. package/lib/typescript/components/ProductsListView.d.ts.map +1 -1
  30. package/lib/typescript/components/utils.d.ts +3 -0
  31. package/lib/typescript/components/utils.d.ts.map +1 -1
  32. package/lib/typescript/types.d.ts +2 -0
  33. package/lib/typescript/types.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/api.ts +71 -3
  36. package/src/components/BetaNotice.tsx +1 -1
  37. package/src/components/ChatInput.tsx +1 -1
  38. package/src/components/ChatWidget.tsx +196 -33
  39. package/src/components/ProductsListView.tsx +39 -32
  40. package/src/components/utils.ts +36 -0
  41. package/src/types.ts +2 -0
@@ -17,6 +17,7 @@ import {
17
17
  Keyboard,
18
18
  DeviceEventEmitter,
19
19
  Platform,
20
+ ActivityIndicator,
20
21
  } from "react-native";
21
22
  import { ChatInput } from "./ChatInput";
22
23
  import { MessageBubble } from "./MessageBubble";
@@ -26,6 +27,7 @@ import { mergeTheme } from "../theme";
26
27
  import {
27
28
  ChatApiParams,
28
29
  fetchInitialMessages,
30
+ fetchOlderMessages,
29
31
  getProducts,
30
32
  handleFeedbackActionApi,
31
33
  normaliseMessages,
@@ -63,6 +65,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
63
65
  onViewAllPress,
64
66
  onItemPress: onItemPressProp,
65
67
  isBetaMode: isBetaModeProp,
68
+ activeProductType,
66
69
  },
67
70
  ref
68
71
  ) => {
@@ -74,8 +77,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
74
77
  );
75
78
  const [typingMessageId, setTypingMessageId] = useState<string | null>(null);
76
79
  const [typingFullText, setTypingFullText] = useState<string>("");
77
- const [scrollY, setScrollY] = useState(0);
78
- const [autoScroll, setAutoScroll] = useState(true);
79
80
  const [productsByMsg, setProductsByMsg] = useState<Record<string, any>>({});
80
81
  const [reloadLoadingIds, setReloadLoadingIds] = useState<Set<string>>(
81
82
  new Set()
@@ -84,8 +85,21 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
84
85
  priceModeProp || null
85
86
  );
86
87
  const [userToken, setUserToken] = useState<string>(userTokenProp || "");
88
+ // Pagination state
89
+ const [loadingOlder, setLoadingOlder] = useState(false);
90
+ const [hasMoreMessages, setHasMoreMessages] = useState(true);
91
+ const [nextCursor, setNextCursor] = useState<string | number | undefined>(
92
+ undefined
93
+ );
94
+ const [currentPage, setCurrentPage] = useState(1);
87
95
  const scrollRef = useRef<ScrollView | null>(null);
88
96
  const inputRef = useRef<TextInput | null>(null);
97
+ // Scroll management refs - using refs instead of state to avoid re-renders
98
+ const scrollYRef = useRef(0);
99
+ const contentHeightRef = useRef(0);
100
+ const isUserScrollingRef = useRef(false);
101
+ const isPaginatingRef = useRef(false);
102
+ const initialScrollDoneRef = useRef(false);
89
103
  const themeProps = useMemo(
90
104
  () => mergeTheme(themeOverrides),
91
105
  [themeOverrides]
@@ -117,13 +131,29 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
117
131
  loadAuthData();
118
132
  }, [userTokenProp]);
119
133
 
134
+ useEffect(() => {
135
+ const setActiveProduct = async () => {
136
+ if (activeProductType) {
137
+ await Storage.setJSON("external_context", activeProductType);
138
+ }
139
+ };
140
+ setActiveProduct();
141
+ }, [activeProductType]);
142
+
120
143
  const onViewAll = (item: any) => {
121
144
  let searchPayload = item?.search_payload;
122
- if(searchPayload?.cut_from || searchPayload?.cut_to || searchPayload?.polish_from || searchPayload?.polish_to || searchPayload?.symmetry_from || searchPayload?.symmetry_to) {
145
+ if (
146
+ searchPayload?.cut_from ||
147
+ searchPayload?.cut_to ||
148
+ searchPayload?.polish_from ||
149
+ searchPayload?.polish_to ||
150
+ searchPayload?.symmetry_from ||
151
+ searchPayload?.symmetry_to
152
+ ) {
123
153
  searchPayload.cutGrades = [];
124
154
  searchPayload.polishes = [];
125
155
  searchPayload.symmetries = [];
126
- if(!searchPayload?.shapes) {
156
+ if (!searchPayload?.shapes) {
127
157
  searchPayload.shapes = [];
128
158
  }
129
159
  }
@@ -145,14 +175,19 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
145
175
  if (searchPayload?.symmetry_to) {
146
176
  searchPayload.symmetries.push(searchPayload?.symmetry_to);
147
177
  }
148
- if(searchPayload?.three_x) {
178
+ if (searchPayload?.three_x) {
149
179
  delete searchPayload.three_x;
150
180
  }
151
181
 
152
182
  const stringifiedSearchPayload = JSON.stringify(searchPayload);
153
183
  const payload = item?.search_payload;
154
184
  if (!payload) return;
155
- const isLabgrown = (typeof payload.lab_grown === "string" ? payload.lab_grown === "true" : typeof payload.lab_grown === "boolean" ? payload.lab_grown : false);
185
+ const isLabgrown =
186
+ typeof payload.lab_grown === "string"
187
+ ? payload.lab_grown === "true"
188
+ : typeof payload.lab_grown === "boolean"
189
+ ? payload.lab_grown
190
+ : false;
156
191
  const domain = apiUrl.split("v3");
157
192
  const deepLinkUrl = `${domain[0]}webapp/${
158
193
  isLabgrown ? "lab-grown-diamonds" : "natural-diamonds"
@@ -248,11 +283,23 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
248
283
  );
249
284
  if (!cancelled) {
250
285
  const normalised = normaliseMessages(initial).reverse();
286
+ // Capture pagination info from initial response
287
+ if (initial?.has_more !== undefined) {
288
+ setHasMoreMessages(initial.has_more);
289
+ }
290
+ if (initial?.next_cursor !== undefined) {
291
+ setNextCursor(initial.next_cursor);
292
+ }
293
+
294
+ // Reset scroll tracking for fresh load
295
+ initialScrollDoneRef.current = false;
296
+ isUserScrollingRef.current = false;
297
+
251
298
  if (normalised.length === 0) {
252
299
  const initialAssistant: ChatMessage = {
253
300
  id: "",
254
301
  role: "assistant",
255
- text: "Search our diamond inventory instantly. Try asking for a 'Natural Diamonds 1.5ct Round under $2,000 VS' or 'Lab Grown Pear 2ct VS2+ $10000'",
302
+ text: 'Describe the diamond you\'re looking for for example, "2ct F VS2 under $10,000" or "1–2ct D–F VS pear" — and I\'ll take it from there.',
256
303
  createdAt: Date.now(),
257
304
  isLoading: false,
258
305
  suggestions: [],
@@ -260,15 +307,11 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
260
307
  reaction: "0",
261
308
  };
262
309
  setMessages([initialAssistant]);
263
- setTimeout(() => {
264
- scrollRef.current?.scrollToEnd({ animated: false });
265
- }, 0);
310
+ setHasMoreMessages(false);
266
311
  } else {
267
312
  setMessages(normalised);
268
- setTimeout(() => {
269
- scrollRef.current?.scrollToEnd({ animated: false });
270
- }, 0);
271
313
  }
314
+ // onContentSizeChange will handle scrollToEnd
272
315
  }
273
316
  } catch (error) {
274
317
  console.error("Failed to fetch initial messages", error);
@@ -288,12 +331,16 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
288
331
  async (rawText: string) => {
289
332
  const trimmed = rawText.trim();
290
333
  if (!trimmed || loading) return;
334
+ const external_context = await Storage.getJSON<string>(
335
+ "external_context"
336
+ );
291
337
 
292
338
  const userMessage: ChatMessage = {
293
339
  id: `user-${Date.now()}`,
294
340
  role: "user",
295
341
  text: trimmed,
296
342
  createdAt: Date.now(),
343
+ external_context: external_context || null,
297
344
  };
298
345
 
299
346
  const loadingId = `bot-loading-${Date.now()}`;
@@ -484,6 +531,71 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
484
531
  }
485
532
  }, []);
486
533
 
534
+ // Load older messages when user scrolls to top
535
+ const loadOlderMessages = useCallback(async () => {
536
+ if (loadingOlder || !hasMoreMessages || !hasAuth) return;
537
+
538
+ // Mark that we're paginating - this prevents auto-scroll to bottom
539
+ isPaginatingRef.current = true;
540
+ const prevHeight = contentHeightRef.current;
541
+ setLoadingOlder(true);
542
+
543
+ try {
544
+ const result = await fetchOlderMessages(
545
+ apiUrl,
546
+ priceMode,
547
+ nextCursor,
548
+ currentPage + 1
549
+ );
550
+
551
+ const olderMessages = normaliseMessages(result);
552
+ if (olderMessages.length > 0) {
553
+ // Prepend older messages to the beginning
554
+ setMessages((prev) => {
555
+ // Filter out duplicates by id
556
+ const existingIds = new Set(prev.map((m) => m.id));
557
+ const newMessages = olderMessages.filter(
558
+ (m) => !existingIds.has(m.id)
559
+ );
560
+ return [...newMessages, ...prev];
561
+ });
562
+ setCurrentPage((prev) => prev + 1);
563
+
564
+ // After state update, adjust scroll to maintain position
565
+ // Use requestAnimationFrame to wait for layout
566
+ requestAnimationFrame(() => {
567
+ const newHeight = contentHeightRef.current;
568
+ const heightDiff = newHeight - prevHeight;
569
+ if (heightDiff > 0 && scrollRef.current) {
570
+ scrollRef.current.scrollTo({
571
+ y: scrollYRef.current + heightDiff,
572
+ animated: false,
573
+ });
574
+ }
575
+ isPaginatingRef.current = false;
576
+ });
577
+ } else {
578
+ isPaginatingRef.current = false;
579
+ }
580
+
581
+ setHasMoreMessages(result.has_more ?? olderMessages.length > 0);
582
+ setNextCursor(result.next_cursor);
583
+ } catch (e) {
584
+ console.error("Failed to load older messages", e);
585
+ isPaginatingRef.current = false;
586
+ } finally {
587
+ setLoadingOlder(false);
588
+ }
589
+ }, [
590
+ loadingOlder,
591
+ hasMoreMessages,
592
+ hasAuth,
593
+ apiUrl,
594
+ priceMode,
595
+ nextCursor,
596
+ currentPage,
597
+ ]);
598
+
487
599
  useEffect(() => {
488
600
  const scrollToBottom = () => {
489
601
  setTimeout(() => {
@@ -493,8 +605,10 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
493
605
  }, 100);
494
606
  };
495
607
 
496
- const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
497
- const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
608
+ const showEvent =
609
+ Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
610
+ const hideEvent =
611
+ Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
498
612
 
499
613
  const subShow = Keyboard.addListener(showEvent, scrollToBottom);
500
614
  const subHide = Keyboard.addListener(hideEvent, scrollToBottom);
@@ -562,6 +676,14 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
562
676
  setLoadingMessageId(null);
563
677
  setTypingMessageId(null);
564
678
  setTypingFullText("");
679
+ // Reset pagination state
680
+ setHasMoreMessages(true);
681
+ setNextCursor(undefined);
682
+ setCurrentPage(1);
683
+ // Reset scroll tracking
684
+ initialScrollDoneRef.current = false;
685
+ isUserScrollingRef.current = false;
686
+ isPaginatingRef.current = false;
565
687
 
566
688
  const fresh = await fetchInitialMessages(apiUrl, undefined, priceMode);
567
689
  const normalised = normaliseMessages(fresh).reverse();
@@ -569,7 +691,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
569
691
  const initialAssistant: ChatMessage = {
570
692
  id: "",
571
693
  role: "assistant",
572
- text: "Search our diamond inventory instantly. Try asking for a 'Natural Diamonds 1.5ct Round under $2,000 VS' or 'Lab Grown Pear 2ct VS2+ $10000'",
694
+ text: 'Describe the diamond you\'re looking for for example, "2ct F VS2 under $10,000" or "1–2ct D–F VS pear" — and I\'ll take it from there.',
573
695
  createdAt: Date.now(),
574
696
  isLoading: false,
575
697
  suggestions: [],
@@ -580,9 +702,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
580
702
  } else {
581
703
  setMessages(normalised);
582
704
  }
583
- setTimeout(() => {
584
- scrollRef.current?.scrollToEnd({ animated: false });
585
- }, 500);
705
+ // onContentSizeChange will handle scrollToEnd
586
706
  onClearChat?.();
587
707
  } catch (err) {
588
708
  console.error("Failed to clear chat", err);
@@ -686,12 +806,22 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
686
806
  onScroll={(e) => {
687
807
  const { contentOffset, contentSize, layoutMeasurement } =
688
808
  e.nativeEvent;
689
- setScrollY(contentOffset.y);
809
+ scrollYRef.current = contentOffset.y;
690
810
  const distanceFromBottom =
691
811
  contentSize.height -
692
812
  (layoutMeasurement.height + contentOffset.y);
693
- // Enable auto-scroll when user is near the bottom; disable when scrolled up
694
- setAutoScroll(distanceFromBottom < 48);
813
+ // Track if user has scrolled away from bottom
814
+ isUserScrollingRef.current = distanceFromBottom > 100;
815
+
816
+ // Load older messages when scrolled near the top (within 50px)
817
+ if (
818
+ contentOffset.y < 50 &&
819
+ hasMoreMessages &&
820
+ !loadingOlder &&
821
+ !isPaginatingRef.current
822
+ ) {
823
+ loadOlderMessages();
824
+ }
695
825
  }}
696
826
  scrollEventThrottle={16}
697
827
  style={
@@ -714,12 +844,38 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
714
844
  ? { maxWidth: 608, alignSelf: "center", width: "100%" }
715
845
  : {}),
716
846
  }}
717
- onContentSizeChange={() => {
718
- if (autoScroll) {
847
+ onContentSizeChange={(_width, height) => {
848
+ contentHeightRef.current = height;
849
+
850
+ // Skip auto-scroll during pagination - scroll is handled in loadOlderMessages
851
+ if (isPaginatingRef.current || loadingOlder) {
852
+ return;
853
+ }
854
+
855
+ // Only auto-scroll to bottom if:
856
+ // 1. Initial scroll hasn't been done yet, OR
857
+ // 2. User is near the bottom (not scrolled up to view history)
858
+ if (!initialScrollDoneRef.current) {
859
+ scrollRef.current?.scrollToEnd({ animated: false });
860
+ initialScrollDoneRef.current = true;
861
+ } else if (!isUserScrollingRef.current) {
862
+ // User is near bottom, auto-scroll for new messages
719
863
  scrollRef.current?.scrollToEnd({ animated: false });
720
864
  }
721
865
  }}
722
866
  >
867
+ {/* Loading indicator for pagination */}
868
+ {loadingOlder && (
869
+ <LoadingOlderContainer>
870
+ <ActivityIndicator
871
+ size="small"
872
+ color={theme["core-06"] || "#4F4E57"}
873
+ />
874
+ <LoadingOlderText theme={theme}>
875
+ Loading older messages...
876
+ </LoadingOlderText>
877
+ </LoadingOlderContainer>
878
+ )}
723
879
  {messages?.[0]?.createdAt && (
724
880
  <EmptyContainer theme={theme}>
725
881
  <EmptyText theme={theme}>
@@ -775,15 +931,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
775
931
  const diamonds = result?.response?.body?.diamonds;
776
932
  const hasAny =
777
933
  Array.isArray(diamonds) && diamonds.length > 0;
778
- if (hasAny) {
779
- try {
780
- scrollRef.current?.scrollTo({
781
- x: 0,
782
- y: scrollY + 120,
783
- animated: true,
784
- });
785
- } catch (_) {}
786
- } else {
934
+ if (!hasAny) {
787
935
  setMessages((prev) =>
788
936
  prev.map((msg) =>
789
937
  msg.id === id
@@ -797,6 +945,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
797
945
  )
798
946
  );
799
947
  }
948
+ // Let onContentSizeChange handle scrolling naturally
800
949
  }}
801
950
  />
802
951
  )}
@@ -896,6 +1045,20 @@ const LineView = styled.View<{ theme: DefaultTheme }>`
896
1045
  border-top-width: 1;
897
1046
  `;
898
1047
 
1048
+ const LoadingOlderContainer = styled.View`
1049
+ padding: 12px;
1050
+ align-items: center;
1051
+ justify-content: center;
1052
+ flex-direction: row;
1053
+ gap: 8px;
1054
+ `;
1055
+
1056
+ const LoadingOlderText = styled.Text<{ theme: DefaultTheme }>`
1057
+ font-size: 12px;
1058
+ color: ${({ theme }: { theme: DefaultTheme }) =>
1059
+ theme["core-06"] || "#4F4E57"};
1060
+ `;
1061
+
899
1062
  const styles = StyleSheet.create({
900
1063
  listContent: {
901
1064
  paddingVertical: 8,
@@ -9,7 +9,7 @@ import {
9
9
  Image,
10
10
  Pressable,
11
11
  } from "react-native";
12
- import { getUserCurrencySymbol } from "./utils";
12
+ import { getUserCurrencySymbol, formatPriceValue, roundUpPrices } from "./utils";
13
13
  import { useTheme } from "styled-components/native";
14
14
  import { DefaultTheme } from "styled-components/native";
15
15
  import styled from "styled-components/native";
@@ -128,7 +128,8 @@ const renderToken = (
128
128
  token: TokenConfig,
129
129
  item: any,
130
130
  userCurrency: string | null,
131
- index: number
131
+ index: number,
132
+ shouldRound: boolean = false
132
133
  ): React.ReactNode => {
133
134
  const TextView = token.style === "secondary" ? TextStyleTwo : TextStyleOne;
134
135
  const PrefixTextView = token.prefixStyle === "secondary" ? TextStyleTwo : TextStyleOne;
@@ -176,11 +177,14 @@ const renderToken = (
176
177
 
177
178
  case "priceField": {
178
179
  const value = item[token.field!];
180
+ const formattedPrice = formatPriceValue(token.field!, value, shouldRound);
181
+ console.log("formattedPrice", formattedPrice);
182
+
179
183
  return (
180
184
  <View key={index} style={styles.rowCenter}>
181
185
  {token.prefix && <TextView theme={theme}>{token.prefix}</TextView>}
182
186
  {value && <TextView theme={theme}>{currency}</TextView>}
183
- <TextView theme={theme}>{value ?? "-"}</TextView>
187
+ <TextView theme={theme}>{formattedPrice ?? "-"}</TextView>
184
188
  {token.suffix && <SuffixTextView theme={theme}>{token.suffix}</SuffixTextView>}
185
189
  </View>
186
190
  );
@@ -190,7 +194,7 @@ const renderToken = (
190
194
  return (
191
195
  <View key={index} style={styles.rowCenter}>
192
196
  {token.items?.map((subToken, subIndex) =>
193
- renderToken(subToken, item, userCurrency, subIndex)
197
+ renderToken(subToken, item, userCurrency, subIndex, shouldRound)
194
198
  )}
195
199
  </View>
196
200
  );
@@ -210,13 +214,20 @@ const ProductsListViewComponent: React.FC<ProductsListViewProps> = ({
210
214
  if (!data || !data.length) return null;
211
215
  const theme = useTheme();
212
216
  const [userCurrency, setUserCurrency] = useState<string | null>(null);
217
+ const [shouldRound, setShouldRound] = useState<boolean>(false);
213
218
 
214
219
  useEffect(() => {
215
220
  const fetchCurrency = async () => {
216
221
  const currency = await getUserCurrencySymbol();
217
222
  setUserCurrency(currency);
218
223
  };
224
+
225
+ const getRoundOffPrices = async () => {
226
+ const roundOff = await roundUpPrices();
227
+ setShouldRound(roundOff);
228
+ };
219
229
  fetchCurrency();
230
+ getRoundOffPrices();
220
231
  }, []);
221
232
 
222
233
  return (
@@ -229,28 +240,28 @@ const ProductsListViewComponent: React.FC<ProductsListViewProps> = ({
229
240
  <Pressable
230
241
  key={dataItem.id}
231
242
  onPress={() => onItemPress?.(dataItem)}
232
- // @ts-ignore - hovered is available on web
233
- style={({ hovered }) => [
234
- styles.itemContainer,
235
- hovered && styles.itemHover,
236
- ]}
237
243
  >
238
- <View style={styles.left}>
239
- <View style={styles.serialContainer}>
240
- <Serial>{index + 1}.</Serial>
241
- </View>
242
- <View style={styles.tokens}>
243
- {DIAMOND_TOKENS.map((token, tokenIndex) =>
244
- renderToken(token, dataItem, userCurrency, tokenIndex)
245
- )}
246
- </View>
247
- </View>
244
+ {(state) => (
245
+ // @ts-ignore
246
+ <ListItemParent theme={theme} hovered={state.hovered}>
247
+ <View style={styles.left}>
248
+ <View style={styles.serialContainer}>
249
+ <Serial>{index + 1}.</Serial>
250
+ </View>
251
+ <View style={styles.tokens}>
252
+ {DIAMOND_TOKENS.map((token, tokenIndex) =>
253
+ renderToken(token, dataItem, userCurrency, tokenIndex, shouldRound)
254
+ )}
255
+ </View>
256
+ </View>
257
+ </ListItemParent>
258
+ )}
248
259
  </Pressable>
249
260
  ))}
250
261
  </ScrollView>
251
262
 
252
263
  <ButtonView activeOpacity={0.8} onPress={() => onViewAll(item)}>
253
- <ButtonText>{`View All ${totalResults} Results`}</ButtonText>
264
+ <ButtonText>{`View All ${totalResults} diamonds`}</ButtonText>
254
265
  <ImageComponent
255
266
  source={{
256
267
  uri: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/right.svg",
@@ -320,6 +331,14 @@ const ButtonText = styled.Text<{ theme: DefaultTheme }>`
320
331
  font-weight: 600;
321
332
  `;
322
333
 
334
+ const ListItemParent = styled.Pressable<{ theme: DefaultTheme, hovered?: boolean }>`
335
+ padding-vertical: 2px;
336
+ background-color: ${({ theme, hovered }: { theme: DefaultTheme, hovered?: boolean }) =>
337
+ hovered
338
+ ? theme["core-02"] || "#EDEDF2"
339
+ : "transparent"};
340
+ `;
341
+
323
342
  const styles = StyleSheet.create({
324
343
  listContent: {
325
344
  gap: 8,
@@ -351,18 +370,6 @@ const styles = StyleSheet.create({
351
370
  flexDirection: "row",
352
371
  alignItems: "center",
353
372
  },
354
- priceContainer: {
355
- alignItems: "flex-start",
356
- justifyContent: "center",
357
- },
358
-
359
- itemContainer: {
360
- paddingVertical: 2,
361
- },
362
-
363
- itemHover: {
364
- backgroundColor: "#EDEDF2",
365
- },
366
373
  });
367
374
 
368
375
  const ProductsListView = memo(ProductsListViewComponent);
@@ -68,6 +68,42 @@ export const getUserCurrencySymbol = async (): Promise<string | null> => {
68
68
  return userData.currency_symbol || "$";
69
69
  };
70
70
 
71
+
72
+ export const roundUpPrices = async (): Promise<boolean> => {
73
+ const userInfo = await Storage.getJSON<UserDetails>("persist:userInfo", {});
74
+ const userData = userInfo?.user as UserDetails;
75
+ // @ts-ignore
76
+ return true; // org 1
77
+ // return !!userData?.feature_settings?.round_up_prices;
78
+ };
79
+
80
+ // Helper to add commas to a number
81
+ export function formatWithCommas(value: number | string): string {
82
+ const num = typeof value === 'string' ? parseFloat(value) : value;
83
+ if (isNaN(num)) return String(value);
84
+ return num.toLocaleString();
85
+ }
86
+
87
+ // Format price values according to requirements
88
+ export function formatPriceValue(key: string, value: any, shouldRound: boolean) {
89
+ console.log("formatPriceValue", key, value);
90
+
91
+ if (key === 'price_per_carat') {
92
+ // Remove decimals, round, add commas
93
+ const num = Math.round(Number(value));
94
+ return formatWithCommas(num);
95
+ }
96
+ if (key === 'total_sales_price') {
97
+ let num = Number(value);
98
+ if (shouldRound) {
99
+ num = Math.round(num);
100
+ }
101
+ return formatWithCommas(num);
102
+ }
103
+ // fallback: just add commas if number
104
+ return formatWithCommas(value);
105
+ }
106
+
71
107
  export interface UserDetails {
72
108
  id?: string | number;
73
109
  email?: string;
package/src/types.ts CHANGED
@@ -9,6 +9,7 @@ export interface ChatMessage {
9
9
  suggestions?: string[];
10
10
  reaction?: string;
11
11
  search_payload?: Record<string, any>;
12
+ external_context?: string | null;
12
13
  }
13
14
 
14
15
  export interface ChatTheme {
@@ -45,6 +46,7 @@ export interface ChatWidgetProps {
45
46
  onItemPress?: (deepLinkUrl: string, item: any) => void;
46
47
  /** When true, shows beta labels and disclaimer. */
47
48
  isBetaMode?: boolean;
49
+ activeProductType?: string;
48
50
  }
49
51
 
50
52
  export interface ChatWidgetRef {