vdb-ai-chat 1.0.1 → 1.0.3

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 (128) 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 +20 -21
  17. package/lib/commonjs/components/ChatInput.js.map +1 -1
  18. package/lib/commonjs/components/ChatWidget.js +165 -92
  19. package/lib/commonjs/components/ChatWidget.js.map +1 -1
  20. package/lib/commonjs/components/LazyProductsFetcher.js +47 -0
  21. package/lib/commonjs/components/LazyProductsFetcher.js.map +1 -0
  22. package/lib/commonjs/components/MessageBubble.js +26 -90
  23. package/lib/commonjs/components/MessageBubble.js.map +1 -1
  24. package/lib/commonjs/components/MessageMetaRow.js +113 -0
  25. package/lib/commonjs/components/MessageMetaRow.js.map +1 -0
  26. package/lib/commonjs/components/ProductsGrid.js +139 -0
  27. package/lib/commonjs/components/ProductsGrid.js.map +1 -0
  28. package/lib/commonjs/components/ProductsList.js +22 -126
  29. package/lib/commonjs/components/ProductsList.js.map +1 -1
  30. package/lib/commonjs/components/ProductsListView.js +139 -0
  31. package/lib/commonjs/components/ProductsListView.js.map +1 -0
  32. package/lib/commonjs/components/SuggestionsRow.js +50 -27
  33. package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
  34. package/lib/commonjs/components/utils.js +4 -3
  35. package/lib/commonjs/components/utils.js.map +1 -1
  36. package/lib/commonjs/hooks/useInViewport.js +42 -0
  37. package/lib/commonjs/hooks/useInViewport.js.map +1 -0
  38. package/lib/commonjs/index.web.js +86 -29
  39. package/lib/commonjs/index.web.js.map +1 -1
  40. package/lib/commonjs/theme.js +4 -4
  41. package/lib/commonjs/theme.js.map +1 -1
  42. package/lib/module/api.js +4 -3
  43. package/lib/module/api.js.map +1 -1
  44. package/lib/module/components/BetaNotice.js +30 -0
  45. package/lib/module/components/BetaNotice.js.map +1 -0
  46. package/lib/module/components/ChatHeader.js +27 -20
  47. package/lib/module/components/ChatHeader.js.map +1 -1
  48. package/lib/module/components/ChatInput.js +20 -21
  49. package/lib/module/components/ChatInput.js.map +1 -1
  50. package/lib/module/components/ChatWidget.js +166 -93
  51. package/lib/module/components/ChatWidget.js.map +1 -1
  52. package/lib/module/components/LazyProductsFetcher.js +40 -0
  53. package/lib/module/components/LazyProductsFetcher.js.map +1 -0
  54. package/lib/module/components/MessageBubble.js +26 -92
  55. package/lib/module/components/MessageBubble.js.map +1 -1
  56. package/lib/module/components/MessageMetaRow.js +105 -0
  57. package/lib/module/components/MessageMetaRow.js.map +1 -0
  58. package/lib/module/components/ProductsGrid.js +133 -0
  59. package/lib/module/components/ProductsGrid.js.map +1 -0
  60. package/lib/module/components/ProductsList.js +21 -126
  61. package/lib/module/components/ProductsList.js.map +1 -1
  62. package/lib/module/components/ProductsListView.js +132 -0
  63. package/lib/module/components/ProductsListView.js.map +1 -0
  64. package/lib/module/components/SuggestionsRow.js +51 -28
  65. package/lib/module/components/SuggestionsRow.js.map +1 -1
  66. package/lib/module/components/utils.js +4 -3
  67. package/lib/module/components/utils.js.map +1 -1
  68. package/lib/module/hooks/useInViewport.js +36 -0
  69. package/lib/module/hooks/useInViewport.js.map +1 -0
  70. package/lib/module/index.web.js +86 -29
  71. package/lib/module/index.web.js.map +1 -1
  72. package/lib/module/theme.js +4 -4
  73. package/lib/module/theme.js.map +1 -1
  74. package/lib/typescript/api.d.ts.map +1 -1
  75. package/lib/typescript/components/BetaNotice.d.ts +5 -0
  76. package/lib/typescript/components/BetaNotice.d.ts.map +1 -0
  77. package/lib/typescript/components/ChatHeader.d.ts +5 -2
  78. package/lib/typescript/components/ChatHeader.d.ts.map +1 -1
  79. package/lib/typescript/components/ChatInput.d.ts.map +1 -1
  80. package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
  81. package/lib/typescript/components/LazyProductsFetcher.d.ts +9 -0
  82. package/lib/typescript/components/LazyProductsFetcher.d.ts.map +1 -0
  83. package/lib/typescript/components/MessageBubble.d.ts +7 -3
  84. package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
  85. package/lib/typescript/components/MessageMetaRow.d.ts +14 -0
  86. package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -0
  87. package/lib/typescript/components/ProductsGrid.d.ts +10 -0
  88. package/lib/typescript/components/ProductsGrid.d.ts.map +1 -0
  89. package/lib/typescript/components/ProductsList.d.ts +4 -2
  90. package/lib/typescript/components/ProductsList.d.ts.map +1 -1
  91. package/lib/typescript/components/ProductsListView.d.ts +10 -0
  92. package/lib/typescript/components/ProductsListView.d.ts.map +1 -0
  93. package/lib/typescript/components/SuggestionsRow.d.ts +2 -0
  94. package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
  95. package/lib/typescript/components/utils.d.ts +1 -0
  96. package/lib/typescript/components/utils.d.ts.map +1 -1
  97. package/lib/typescript/hooks/useInViewport.d.ts +5 -0
  98. package/lib/typescript/hooks/useInViewport.d.ts.map +1 -0
  99. package/lib/typescript/index.web.d.ts +1 -1
  100. package/lib/typescript/index.web.d.ts.map +1 -1
  101. package/lib/typescript/types.d.ts +3 -1
  102. package/lib/typescript/types.d.ts.map +1 -1
  103. package/package.json +1 -1
  104. package/src/api.ts +4 -3
  105. package/src/components/BetaNotice.tsx +32 -0
  106. package/src/components/ChatHeader.tsx +32 -18
  107. package/src/components/ChatInput.tsx +23 -21
  108. package/src/components/ChatWidget.tsx +249 -159
  109. package/src/components/LazyProductsFetcher.tsx +41 -0
  110. package/src/components/MessageBubble.tsx +46 -148
  111. package/src/components/MessageMetaRow.tsx +199 -0
  112. package/src/components/ProductsGrid.tsx +163 -0
  113. package/src/components/ProductsList.tsx +20 -146
  114. package/src/components/ProductsListView.tsx +149 -0
  115. package/src/components/SuggestionsRow.tsx +61 -32
  116. package/src/components/utils.ts +6 -4
  117. package/src/hooks/useInViewport.ts +38 -0
  118. package/src/index.web.tsx +87 -32
  119. package/src/theme.ts +4 -4
  120. package/src/types.ts +3 -2
  121. package/dist/751.chat-widget.js +0 -1
  122. package/lib/commonjs/contexts/SegmentClientContext.js +0 -19
  123. package/lib/commonjs/contexts/SegmentClientContext.js.map +0 -1
  124. package/lib/module/contexts/SegmentClientContext.js +0 -10
  125. package/lib/module/contexts/SegmentClientContext.js.map +0 -1
  126. package/lib/typescript/contexts/SegmentClientContext.d.ts +0 -9
  127. package/lib/typescript/contexts/SegmentClientContext.d.ts.map +0 -1
  128. package/src/contexts/SegmentClientContext.tsx +0 -20
@@ -1,162 +1,36 @@
1
- import {
2
- View,
3
- Image,
4
- StyleSheet,
5
- Text,
6
- ScrollView,
7
- TouchableOpacity,
8
- Platform,
9
- } from "react-native";
10
- import React, { memo } from "react";
11
-
12
- // Use expo-image on native if available, fallback to RN Image
13
- let ImageComponent: typeof Image = Image;
14
- if (Platform.OS !== "web") {
15
- try {
16
- const ExpoImage = require("expo-image").Image;
17
- if (ExpoImage) ImageComponent = ExpoImage;
18
- } catch {
19
- // expo-image not installed, use React Native Image
20
- }
21
- }
1
+ import React from "react";
2
+ import ProductsGrid from "./ProductsGrid";
3
+ import ProductsListView from "./ProductsListView";
22
4
 
23
5
  interface ProductsListProps {
24
- data: any;
6
+ data: any[];
25
7
  onViewAll?: () => void;
26
8
  onItemPress?: (item: any) => void;
9
+ variant?: "grid" | "list";
10
+ totalResults: number
27
11
  }
28
12
 
29
- const ProductsListComponent: React.FC<ProductsListProps> = ({
13
+ const ProductsList: React.FC<ProductsListProps> = ({
30
14
  data,
31
15
  onViewAll,
32
16
  onItemPress,
17
+ variant = "list",
18
+ totalResults,
33
19
  }) => {
34
20
  if (!data || !data.length) return null;
35
-
21
+ if (variant === "list") {
22
+ return (
23
+ <ProductsListView
24
+ data={data}
25
+ onViewAll={onViewAll}
26
+ onItemPress={onItemPress}
27
+ totalResults={totalResults}
28
+ />
29
+ );
30
+ }
36
31
  return (
37
- <View style={styles.wrapper}>
38
- <ScrollView
39
- horizontal
40
- showsHorizontalScrollIndicator={false}
41
- contentContainerStyle={styles.listContent}
42
- >
43
- {data.map((item: any) => (
44
- <TouchableOpacity
45
- key={item.id}
46
- onPress={() => {
47
- onItemPress?.(item);
48
- }}
49
- >
50
- <View key={item.id} style={styles.card}>
51
- {item.image_thumb_url ? (
52
- <ImageComponent
53
- style={styles.image}
54
- source={{ uri: item.image_thumb_url }}
55
- />
56
- ) : null}
57
-
58
- <View style={styles.content}>
59
- <Text numberOfLines={2} style={styles.title}>
60
- {item.short_title}
61
- </Text>
62
- <Text style={styles.price}>${item.total_sales_price}</Text>
63
- </View>
64
- </View>
65
- </TouchableOpacity>
66
- ))}
67
- </ScrollView>
68
-
69
- {/* View All Button */}
70
- <TouchableOpacity
71
- style={styles.button}
72
- activeOpacity={0.8}
73
- onPress={onViewAll}
74
- >
75
- <Text style={styles.buttonText}>View All {">>"}</Text>
76
- </TouchableOpacity>
77
- </View>
32
+ <ProductsGrid data={data} onViewAll={onViewAll} onItemPress={onItemPress} totalResults={totalResults} />
78
33
  );
79
34
  };
80
35
 
81
- const styles = StyleSheet.create({
82
- wrapper: {
83
- paddingHorizontal: 12,
84
- marginHorizontal: 12,
85
- marginBottom: 12,
86
- paddingTop: 6,
87
- paddingBottom: 14,
88
- backgroundColor: "#fff",
89
- borderRadius: 8,
90
- borderWidth: 1,
91
- borderColor: "#e8e8e8",
92
- elevation: 3,
93
- shadowColor: "#000",
94
- shadowOpacity: 0.08,
95
- shadowOffset: { width: 0, height: 2 },
96
- shadowRadius: 6,
97
- },
98
-
99
- listContent: {
100
- gap: 12,
101
- paddingVertical: 6,
102
- },
103
-
104
- card: {
105
- width: 150,
106
- backgroundColor: "#fff",
107
- borderRadius: 14,
108
- overflow: "hidden",
109
- borderColor: "#e8e8e8",
110
- borderWidth: 1,
111
- elevation: 3,
112
- shadowColor: "#000",
113
- shadowOpacity: 0.08,
114
- shadowOffset: { width: 0, height: 2 },
115
- shadowRadius: 6,
116
- },
117
-
118
- image: {
119
- width: "100%",
120
- height: 120,
121
- borderBottomWidth: 1,
122
- borderBottomColor: "#e8e8e8",
123
- },
124
-
125
- content: {
126
- padding: 10,
127
- gap: 4,
128
- },
129
-
130
- title: {
131
- fontSize: 13,
132
- color: "#222",
133
- fontWeight: "500",
134
- },
135
-
136
- price: {
137
- marginTop: 4,
138
- fontSize: 14,
139
- fontWeight: "700",
140
- color: "#000",
141
- },
142
-
143
- button: {
144
- marginTop: 12,
145
- alignSelf: "center",
146
- paddingHorizontal: 20,
147
- paddingVertical: 8,
148
- backgroundColor: "#804195",
149
- borderRadius: 20,
150
- width: 300,
151
- alignItems: "center",
152
- },
153
-
154
- buttonText: {
155
- color: "#fff",
156
- fontSize: 14,
157
- fontWeight: "600",
158
- },
159
- });
160
-
161
- const ProductsList = memo(ProductsListComponent);
162
36
  export default ProductsList;
@@ -0,0 +1,149 @@
1
+ import React, { memo, useState } from "react";
2
+ import { View, StyleSheet, Text, TouchableOpacity, ScrollView, Platform, Image } from "react-native";
3
+
4
+ let ImageComponent: typeof Image = Image;
5
+ if (Platform.OS !== "web") {
6
+ try {
7
+ const ExpoImage = require("expo-image").Image;
8
+ if (ExpoImage) ImageComponent = ExpoImage;
9
+ } catch {
10
+ // expo-image not installed, use React Native Image
11
+ }
12
+ }
13
+
14
+ interface ProductsListViewProps {
15
+ data: any[];
16
+ totalResults?: number;
17
+ onViewAll?: () => void;
18
+ onItemPress?: (item: any) => void;
19
+ }
20
+
21
+ const ProductsListViewComponent: React.FC<ProductsListViewProps> = ({ data, totalResults, onViewAll, onItemPress }) => {
22
+ if (!data || !data.length) return null;
23
+
24
+ const [priceWidth, setPriceWidth] = useState<number>(0);
25
+
26
+ return (
27
+ <View style={styles.wrapper}>
28
+ <ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.listContent}>
29
+ {data.map((item: any, index: number) => (
30
+ <TouchableOpacity key={item.id} onPress={() => onItemPress?.(item)} activeOpacity={0.8}>
31
+ <View style={styles.row}>
32
+ <View style={styles.left}>
33
+ <Text style={styles.serial}>{index + 1}.</Text>
34
+ <Text
35
+ numberOfLines={1}
36
+ ellipsizeMode="tail"
37
+ style={styles.title}
38
+ >
39
+ {item.short_title}
40
+ </Text>
41
+ </View>
42
+ {item.total_sales_price ? (
43
+ <View style={[styles.priceContainer, priceWidth ? { width: priceWidth } : null]}>
44
+ <Text
45
+ style={styles.price}
46
+ onLayout={(e) => {
47
+ const w = e.nativeEvent.layout.width;
48
+ if (w > priceWidth) setPriceWidth(w);
49
+ }}
50
+ >
51
+ ${item.total_sales_price}
52
+ </Text>
53
+ </View>
54
+ ) : (
55
+ <></>
56
+ )}
57
+ </View>
58
+ </TouchableOpacity>
59
+ ))}
60
+ </ScrollView>
61
+
62
+ <TouchableOpacity style={styles.button} activeOpacity={0.8} onPress={onViewAll}>
63
+ <Text style={styles.buttonText}>{`View All ${totalResults} Results`}</Text>
64
+ <Image
65
+ source={{
66
+ uri: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/right.svg",
67
+ }}
68
+ resizeMode="contain"
69
+ style={{ width: 20, height: 20, tintColor: "#fff" }}
70
+ />
71
+ </TouchableOpacity>
72
+ </View>
73
+ );
74
+ };
75
+
76
+ const styles = StyleSheet.create({
77
+ wrapper: {
78
+ paddingHorizontal: 8,
79
+ },
80
+
81
+ listContent: {
82
+ // No extra padding/margins
83
+ },
84
+
85
+ row: {
86
+ flexDirection: "row",
87
+ justifyContent: "space-between",
88
+ alignItems: "center",
89
+ paddingVertical: 6,
90
+ backgroundColor: "#fff",
91
+ },
92
+
93
+ left: {
94
+ flexDirection: "row",
95
+ alignItems: "center",
96
+ gap: 4,
97
+ flex: 1,
98
+ minWidth: 0,
99
+ },
100
+
101
+ serial: {
102
+ fontSize: 13,
103
+ color: "#020001",
104
+ fontWeight: "500",
105
+ },
106
+
107
+ title: {
108
+ flexShrink: 1,
109
+ fontSize: 13,
110
+ color: "#3378F6",
111
+ fontWeight: "500",
112
+ },
113
+
114
+ price: {
115
+ fontSize: 13,
116
+ fontWeight: "500",
117
+ color: "#020001",
118
+ textAlign: "left",
119
+ },
120
+ priceContainer: {
121
+ alignItems: "flex-start",
122
+ justifyContent: "center",
123
+ },
124
+
125
+ button: {
126
+ marginTop: 12,
127
+ marginHorizontal: 12,
128
+ alignSelf: "center",
129
+ paddingHorizontal: 16,
130
+ paddingVertical: 6,
131
+ backgroundColor: "#292735",
132
+ borderRadius: 8,
133
+ alignItems: "center",
134
+ gap: 16,
135
+ width: "100%",
136
+ flexDirection: "row",
137
+ justifyContent: "center",
138
+ alignContent: "center",
139
+ },
140
+
141
+ buttonText: {
142
+ color: "#fff",
143
+ fontSize: 14,
144
+ fontWeight: "600",
145
+ },
146
+ });
147
+
148
+ const ProductsListView = memo(ProductsListViewComponent);
149
+ export default ProductsListView;
@@ -10,62 +10,91 @@ import {
10
10
  interface SuggestionsRowProps {
11
11
  suggestions: string[];
12
12
  onSelect: (value: string) => void;
13
+ /** Inline variant renders inside a message bubble, without outer padding/background */
14
+ variant?: "inline" | "default";
13
15
  }
14
16
 
15
17
  const SuggestionsRowComponent: React.FC<SuggestionsRowProps> = ({
16
18
  suggestions,
17
19
  onSelect,
20
+ variant = "default",
18
21
  }) => {
19
22
  if (!suggestions.length) return null;
20
-
23
+ const ScrollViewComponent = variant === "inline" ? View : ScrollView;
21
24
  return (
22
- <ScrollView
23
- horizontal
24
- showsHorizontalScrollIndicator={false}
25
- contentContainerStyle={styles.container}
25
+ <ScrollViewComponent
26
+ showsVerticalScrollIndicator={false}
27
+ contentContainerStyle={[
28
+ styles.container,
29
+ variant === "inline" && styles.containerInline,
30
+ ]}
31
+ style={[styles.scroll, variant === "inline" && styles.scrollInline]}
26
32
  >
27
- {suggestions.map((s) => (
28
- <TouchableOpacity
29
- key={s}
30
- style={styles.chip}
31
- onPress={() => onSelect(s)}
32
- activeOpacity={0.75}
33
- >
34
- <Text style={styles.chipText}>{s}</Text>
35
- </TouchableOpacity>
33
+ {suggestions.map((s, index) => (
34
+ <>
35
+ <TouchableOpacity
36
+ key={`${s}-${index}`}
37
+ style={[styles.chip, variant === "inline" && styles.chipInline]}
38
+ onPress={() => onSelect(s)}
39
+ activeOpacity={0.75}
40
+ >
41
+ <Text style={styles.chipText}>{s}</Text>
42
+ </TouchableOpacity>
43
+ {index < suggestions.length - 1 && <View style={styles.spacer} />}
44
+ </>
36
45
  ))}
37
- </ScrollView>
46
+ </ScrollViewComponent>
38
47
  );
39
48
  };
40
49
 
41
50
  const styles = StyleSheet.create({
51
+ scroll: {
52
+ maxWidth: "80%",
53
+ alignSelf: "flex-start",
54
+ paddingHorizontal: 8,
55
+ },
56
+ scrollInline: {
57
+ maxWidth: "100%",
58
+ alignSelf: "stretch",
59
+ paddingHorizontal: 0,
60
+ },
42
61
  container: {
43
- paddingHorizontal: 12,
44
- paddingBottom: 8,
45
- flexDirection: "row",
46
- gap: 10,
62
+ paddingVertical: 0,
63
+ flexDirection: "column",
64
+ justifyContent: "center",
65
+ alignItems: "center",
66
+ gap: 4,
67
+ alignSelf: "stretch",
68
+ // backgroundColor: "#EDEDF2",
69
+ },
70
+ containerInline: {
71
+ // inside bubble, rely on bubble background
72
+ gap: 6,
47
73
  },
48
74
 
49
75
  chip: {
50
- backgroundColor: "#ffffff",
51
- paddingHorizontal: 16,
52
- paddingVertical: 8,
53
- borderRadius: 18,
54
-
55
- // Modern soft shadow (Material 3 / iOS style)
56
- shadowColor: "#000",
57
- shadowOpacity: 0.07,
58
- shadowRadius: 4,
59
- shadowOffset: { width: 0, height: 2 },
60
-
61
- elevation: 2,
76
+ backgroundColor: "#EDEDF2",
77
+ borderRadius: 6,
78
+ paddingHorizontal: 8,
79
+ paddingVertical: 6,
80
+ width: "100%",
81
+ alignSelf: "stretch",
82
+ justifyContent: "center",
83
+ alignItems: "flex-start",
84
+ },
85
+ chipInline: {
86
+ backgroundColor: "#FFFFFF",
62
87
  },
63
88
 
64
89
  chipText: {
65
90
  fontSize: 14,
66
91
  color: "#1A1A1A",
67
92
  fontWeight: "500",
68
- letterSpacing: 0.3,
93
+ textAlign: "left",
94
+ },
95
+ spacer: {
96
+ width: 4,
97
+ height: 4,
69
98
  },
70
99
  });
71
100
 
@@ -31,12 +31,13 @@ export const fetchConversationId = async (
31
31
  if (!conversations) return null;
32
32
 
33
33
  let priceMode = _priceMode;
34
- if (!priceMode) {
35
- const userData = await Storage.getJSON<{ price_mode?: string }>(
36
- "userData",
34
+ if (priceMode === undefined) {
35
+ const userInfo = await Storage.getJSON<UserDetails>(
36
+ "persist:userInfo",
37
37
  {}
38
38
  );
39
- priceMode = userData?.price_mode || "";
39
+ const userData = userInfo?.user as UserDetails;
40
+ priceMode = String(userData?.price_mode) || "";
40
41
  }
41
42
  return conversations[priceMode]?.conversation_id || null;
42
43
  };
@@ -51,6 +52,7 @@ export interface UserDetails {
51
52
  country?: string;
52
53
  price_mode?: string | number;
53
54
  plan_details?: any;
55
+ user?: any;
54
56
  }
55
57
 
56
58
  export const getUserDetails = async (): Promise<UserDetails | null> => {
@@ -0,0 +1,38 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { Platform } from "react-native";
3
+
4
+ export function useInViewport<T extends HTMLElement>() {
5
+ const ref = useRef<T | null>(null);
6
+ const [inView, setInView] = useState(false);
7
+
8
+ useEffect(() => {
9
+ // On native, treat as in view to avoid DOM-specific code
10
+ if (Platform.OS !== "web") {
11
+ setInView(true);
12
+ return;
13
+ }
14
+
15
+ const node = ref.current as unknown as Element | null;
16
+ if (!node || typeof IntersectionObserver === "undefined") {
17
+ // Fallback: consider it in view so functionality still works
18
+ setInView(true);
19
+ return;
20
+ }
21
+
22
+ const observer = new IntersectionObserver(
23
+ (entries) => {
24
+ const entry = entries[0];
25
+ setInView(entry.isIntersecting);
26
+ },
27
+ { root: null, rootMargin: "0px", threshold: 0.1 }
28
+ );
29
+
30
+ observer.observe(node);
31
+
32
+ return () => {
33
+ observer.disconnect();
34
+ };
35
+ }, []);
36
+
37
+ return { ref, inView } as const;
38
+ }
package/src/index.web.tsx CHANGED
@@ -1,10 +1,11 @@
1
- import * as React from "react";
1
+ import React from "react";
2
+ import { Platform, StyleSheet, View } from "react-native";
2
3
  import { createRoot } from "react-dom/client";
3
- import { View, StyleSheet, Platform } from "react-native";
4
+ import { AnalyticsBrowser } from "@segment/analytics-next";
5
+ import { AnalyticsClientProvider } from "./contexts/AnalyticsClientContext";
4
6
  import { ChatWidget } from "./components/ChatWidget";
5
7
  import type { ChatTheme } from "./types";
6
- import { Analytics } from "@segment/analytics-next";
7
- import { AnalyticsClientProvider } from "./contexts/AnalyticsClientContext";
8
+ import { getUserDetails } from "./components/utils";
8
9
 
9
10
  export function renderChatApp(
10
11
  domElement: HTMLElement,
@@ -13,31 +14,45 @@ export function renderChatApp(
13
14
  onClose?: () => void,
14
15
  onClearChat?: () => void,
15
16
  segmentWriteKey?: string,
17
+ onViewAllPress?: (url: string, payload: any) => void,
18
+ onItemPress?: (url: string, item: any) => void,
19
+ isBetaMode?: boolean,
16
20
  ) {
17
21
  const root = createRoot(domElement);
18
- let analyticsClient: Analytics | undefined;
19
- if (Platform.OS === "web" && segmentWriteKey) {
20
- try {
21
- const client = new Analytics({ writeKey: segmentWriteKey });
22
-
23
- analyticsClient = client;
24
- (window as any).analytics = client;
25
- client
26
- .track("AI Chat: Widget Loaded", {
27
- userAgent:
28
- typeof navigator !== "undefined" ? navigator.userAgent : "",
29
- })
30
- .then?.(() => {
31
- // eslint-disable-next-line no-console
32
- console.log("Segment: Widget Loaded event sent");
33
- })
34
- .catch((e: unknown) => {
35
- console.warn("Segment track failed", e);
36
- });
37
- } catch (e) {
38
- console.warn("Segment analytics load failed", e);
22
+
23
+ // Bridge to parent RN WebView / browser
24
+ const sendToParent = (type: string, payload: any) => {
25
+ const msg = { source: "vdb-ai-chat", type, payload };
26
+ const json = JSON.stringify(msg);
27
+
28
+ // RN WebView bridge
29
+ const rnwv = (window as any).ReactNativeWebView;
30
+ if (rnwv?.postMessage) {
31
+ rnwv.postMessage(json);
32
+ } else {
33
+ // Browser/iframe
34
+ try {
35
+ window.parent?.postMessage(msg, "*");
36
+ } catch {}
37
+ try {
38
+ window.dispatchEvent(new CustomEvent("vdb-ai-event", { detail: msg }));
39
+ } catch {}
39
40
  }
40
- }
41
+ };
42
+
43
+ const handleViewAll = (url: string, payload: any) => {
44
+ onViewAllPress?.(url, payload);
45
+ sendToParent("ai_view_all", { url, payload });
46
+ onClose?.();
47
+ };
48
+
49
+ const handleItemPress = (url: string, item: any) => {
50
+ onItemPress?.(url, item);
51
+ sendToParent("ai_item_click", { url, item });
52
+ onClose?.();
53
+ };
54
+
55
+ // Build base content and render immediately
41
56
  const content = (
42
57
  <View style={styles.root}>
43
58
  <ChatWidget
@@ -45,16 +60,56 @@ export function renderChatApp(
45
60
  theme={theme}
46
61
  onClose={onClose}
47
62
  onClearChat={onClearChat}
63
+ onViewAllPress={handleViewAll}
64
+ onItemPress={handleItemPress}
65
+ isBetaMode={isBetaMode}
48
66
  />
49
67
  </View>
50
68
  );
51
- const contentWithAnalytics = analyticsClient ? (
52
- <AnalyticsClientProvider client={analyticsClient}>{content}</AnalyticsClientProvider>
53
- ) : (
54
- content
55
- );
69
+ root.render(content);
70
+
71
+ if (Platform.OS === "web" && segmentWriteKey) {
72
+ try {
73
+ const isEdge =
74
+ typeof navigator !== "undefined" &&
75
+ (navigator.userAgent.includes("Edg") || navigator.userAgent.includes("Edge"));
76
+
77
+ const clientPromise = AnalyticsBrowser.load({
78
+ writeKey: segmentWriteKey,
79
+ trackAppLifecycleEvents: false as any,
80
+ flushAt: (isEdge ? 1 : 10) as any,
81
+ flushInterval: (10000 as any),
82
+ maxBatchSize: (isEdge ? 1 : 10) as any,
83
+ } as any);
84
+
85
+ clientPromise
86
+ .then( async ([client]: any) => {
87
+ (client as any)?.debug?.(true);
88
+ (window as any).analytics = client;
56
89
 
57
- root.render(contentWithAnalytics);
90
+ client.page?.();
91
+ try {
92
+ const user = await getUserDetails();
93
+ const userId = user?.id;
94
+ if (userId) client.identify(userId);
95
+ } catch {}
96
+
97
+ const contentWithAnalytics = (
98
+ <AnalyticsClientProvider client={client}>
99
+ {content}
100
+ </AnalyticsClientProvider>
101
+ );
102
+ root.render(contentWithAnalytics);
103
+ })
104
+ .catch((e: any) => {
105
+ // eslint-disable-next-line no-console
106
+ console.error("[Segment] init failed", e);
107
+ });
108
+ } catch (e) {
109
+ // eslint-disable-next-line no-console
110
+ console.error("[Segment] init failed", e);
111
+ }
112
+ }
58
113
  }
59
114
 
60
115
  const styles = StyleSheet.create({
package/src/theme.ts CHANGED
@@ -4,14 +4,14 @@ export const defaultTheme: ChatTheme = {
4
4
  primaryColor: "#0b93f6",
5
5
  backgroundColor: "#ffffff",
6
6
  inputColor: "#FFF",
7
- inputBackgroundColor: "#f5f5f5",
7
+ inputBackgroundColor: "#FFFFFF",
8
8
  inputBorderRadius: 8,
9
9
  inputTextColor: "#000000",
10
- userBubbleColor: "#804195",
10
+ userBubbleColor: "#4F4E57",
11
11
  userTextColor: "#ffffff",
12
- botBubbleColor: "#e5e5ea",
12
+ botBubbleColor: "#EDEDF2",
13
13
  botTextColor: "#000000",
14
- borderRadius: 18,
14
+ borderRadius: 8,
15
15
  fontFamily: undefined,
16
16
  fontSize: 16,
17
17
  };