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
|
@@ -1,160 +1,36 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
-
<ImageComponent
|
|
52
|
-
style={styles.image}
|
|
53
|
-
source={{ uri: item.image_thumb_url }}
|
|
54
|
-
/>
|
|
55
|
-
|
|
56
|
-
<View style={styles.content}>
|
|
57
|
-
<Text numberOfLines={2} style={styles.title}>
|
|
58
|
-
{item.short_title}
|
|
59
|
-
</Text>
|
|
60
|
-
<Text style={styles.price}>${item.total_sales_price}</Text>
|
|
61
|
-
</View>
|
|
62
|
-
</View>
|
|
63
|
-
</TouchableOpacity>
|
|
64
|
-
))}
|
|
65
|
-
</ScrollView>
|
|
66
|
-
|
|
67
|
-
{/* View All Button */}
|
|
68
|
-
<TouchableOpacity
|
|
69
|
-
style={styles.button}
|
|
70
|
-
activeOpacity={0.8}
|
|
71
|
-
onPress={onViewAll}
|
|
72
|
-
>
|
|
73
|
-
<Text style={styles.buttonText}>View All {">>"}</Text>
|
|
74
|
-
</TouchableOpacity>
|
|
75
|
-
</View>
|
|
32
|
+
<ProductsGrid data={data} onViewAll={onViewAll} onItemPress={onItemPress} totalResults={totalResults} />
|
|
76
33
|
);
|
|
77
34
|
};
|
|
78
35
|
|
|
79
|
-
const styles = StyleSheet.create({
|
|
80
|
-
wrapper: {
|
|
81
|
-
paddingHorizontal: 12,
|
|
82
|
-
marginHorizontal: 12,
|
|
83
|
-
marginBottom: 12,
|
|
84
|
-
paddingTop: 6,
|
|
85
|
-
paddingBottom: 14,
|
|
86
|
-
backgroundColor: "#fff",
|
|
87
|
-
borderRadius: 8,
|
|
88
|
-
borderWidth: 1,
|
|
89
|
-
borderColor: "#e8e8e8",
|
|
90
|
-
elevation: 3,
|
|
91
|
-
shadowColor: "#000",
|
|
92
|
-
shadowOpacity: 0.08,
|
|
93
|
-
shadowOffset: { width: 0, height: 2 },
|
|
94
|
-
shadowRadius: 6,
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
listContent: {
|
|
98
|
-
gap: 12,
|
|
99
|
-
paddingVertical: 6,
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
card: {
|
|
103
|
-
width: 150,
|
|
104
|
-
backgroundColor: "#fff",
|
|
105
|
-
borderRadius: 14,
|
|
106
|
-
overflow: "hidden",
|
|
107
|
-
borderColor: "#e8e8e8",
|
|
108
|
-
borderWidth: 1,
|
|
109
|
-
elevation: 3,
|
|
110
|
-
shadowColor: "#000",
|
|
111
|
-
shadowOpacity: 0.08,
|
|
112
|
-
shadowOffset: { width: 0, height: 2 },
|
|
113
|
-
shadowRadius: 6,
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
image: {
|
|
117
|
-
width: "100%",
|
|
118
|
-
height: 120,
|
|
119
|
-
borderBottomWidth: 1,
|
|
120
|
-
borderBottomColor: "#e8e8e8",
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
content: {
|
|
124
|
-
padding: 10,
|
|
125
|
-
gap: 4,
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
title: {
|
|
129
|
-
fontSize: 13,
|
|
130
|
-
color: "#222",
|
|
131
|
-
fontWeight: "500",
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
price: {
|
|
135
|
-
marginTop: 4,
|
|
136
|
-
fontSize: 14,
|
|
137
|
-
fontWeight: "700",
|
|
138
|
-
color: "#000",
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
button: {
|
|
142
|
-
marginTop: 12,
|
|
143
|
-
alignSelf: "center",
|
|
144
|
-
paddingHorizontal: 20,
|
|
145
|
-
paddingVertical: 8,
|
|
146
|
-
backgroundColor: "#804195",
|
|
147
|
-
borderRadius: 20,
|
|
148
|
-
width: 300,
|
|
149
|
-
alignItems: "center",
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
buttonText: {
|
|
153
|
-
color: "#fff",
|
|
154
|
-
fontSize: 14,
|
|
155
|
-
fontWeight: "600",
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const ProductsList = memo(ProductsListComponent);
|
|
160
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,24 +10,30 @@ 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
|
|
|
21
24
|
return (
|
|
22
25
|
<ScrollView
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
33
|
{suggestions.map((s) => (
|
|
28
34
|
<TouchableOpacity
|
|
29
35
|
key={s}
|
|
30
|
-
style={styles.chip}
|
|
36
|
+
style={[styles.chip, variant === "inline" && styles.chipInline]}
|
|
31
37
|
onPress={() => onSelect(s)}
|
|
32
38
|
activeOpacity={0.75}
|
|
33
39
|
>
|
|
@@ -39,33 +45,51 @@ const SuggestionsRowComponent: React.FC<SuggestionsRowProps> = ({
|
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
const styles = StyleSheet.create({
|
|
48
|
+
scroll: {
|
|
49
|
+
maxWidth: "80%",
|
|
50
|
+
alignSelf: "flex-start",
|
|
51
|
+
paddingHorizontal: 8,
|
|
52
|
+
},
|
|
53
|
+
scrollInline: {
|
|
54
|
+
maxWidth: "100%",
|
|
55
|
+
alignSelf: "stretch",
|
|
56
|
+
paddingHorizontal: 0,
|
|
57
|
+
},
|
|
42
58
|
container: {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
paddingVertical: 0,
|
|
60
|
+
flexDirection: "column",
|
|
61
|
+
justifyContent: "center",
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
gap: 4,
|
|
64
|
+
alignSelf: "stretch",
|
|
65
|
+
// backgroundColor: "#EDEDF2",
|
|
66
|
+
},
|
|
67
|
+
containerInline: {
|
|
68
|
+
// inside bubble, rely on bubble background
|
|
69
|
+
gap: 6,
|
|
47
70
|
},
|
|
48
71
|
|
|
49
72
|
chip: {
|
|
50
|
-
backgroundColor: "#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
backgroundColor: "#EDEDF2",
|
|
74
|
+
borderRadius: 4,
|
|
75
|
+
paddingHorizontal: 8,
|
|
76
|
+
paddingVertical: 6,
|
|
77
|
+
width: "100%",
|
|
78
|
+
alignSelf: "stretch",
|
|
79
|
+
justifyContent: "center",
|
|
80
|
+
alignItems: "flex-start",
|
|
81
|
+
borderWidth: 1,
|
|
82
|
+
borderColor: "#E0E0E0",
|
|
83
|
+
},
|
|
84
|
+
chipInline: {
|
|
85
|
+
backgroundColor: "#FFFFFF",
|
|
62
86
|
},
|
|
63
87
|
|
|
64
88
|
chipText: {
|
|
65
89
|
fontSize: 14,
|
|
66
90
|
color: "#1A1A1A",
|
|
67
91
|
fontWeight: "500",
|
|
68
|
-
|
|
92
|
+
textAlign: "left",
|
|
69
93
|
},
|
|
70
94
|
});
|
|
71
95
|
|
package/src/components/utils.ts
CHANGED
|
@@ -31,13 +31,74 @@ export const fetchConversationId = async (
|
|
|
31
31
|
if (!conversations) return null;
|
|
32
32
|
|
|
33
33
|
let priceMode = _priceMode;
|
|
34
|
-
if (
|
|
35
|
-
const
|
|
36
|
-
"
|
|
34
|
+
if (priceMode === undefined) {
|
|
35
|
+
const userInfo = await Storage.getJSON<UserDetails>(
|
|
36
|
+
"persist:userInfo",
|
|
37
37
|
{}
|
|
38
38
|
);
|
|
39
|
-
|
|
39
|
+
const userData = userInfo?.user as UserDetails;
|
|
40
|
+
priceMode = String(userData?.price_mode) || "";
|
|
40
41
|
}
|
|
41
|
-
|
|
42
42
|
return conversations[priceMode]?.conversation_id || null;
|
|
43
43
|
};
|
|
44
|
+
|
|
45
|
+
export interface UserDetails {
|
|
46
|
+
id?: string | number;
|
|
47
|
+
email?: string;
|
|
48
|
+
first_name?: string;
|
|
49
|
+
last_name?: string;
|
|
50
|
+
company?: string;
|
|
51
|
+
company_id?: string | number;
|
|
52
|
+
country?: string;
|
|
53
|
+
price_mode?: string | number;
|
|
54
|
+
plan_details?: any;
|
|
55
|
+
user?: any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const getUserDetails = async (): Promise<UserDetails | null> => {
|
|
59
|
+
try {
|
|
60
|
+
const stored = await Storage.getJSON<{ user?: string | UserDetails }>(
|
|
61
|
+
"persist:userInfo",
|
|
62
|
+
{}
|
|
63
|
+
);
|
|
64
|
+
if (!stored?.user) return null;
|
|
65
|
+
|
|
66
|
+
// If user is stored as a JSON string, parse it
|
|
67
|
+
if (typeof stored.user === "string") {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(stored.user) as UserDetails;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return stored.user as UserDetails;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export enum DeviceType {
|
|
82
|
+
WEB = "web",
|
|
83
|
+
IOS = "ios",
|
|
84
|
+
ANDROID = "android",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const getDeviceType = (platform?: string) => {
|
|
88
|
+
switch (platform) {
|
|
89
|
+
case DeviceType.WEB:
|
|
90
|
+
return 0;
|
|
91
|
+
case DeviceType.IOS:
|
|
92
|
+
return 1;
|
|
93
|
+
case DeviceType.ANDROID:
|
|
94
|
+
return 2;
|
|
95
|
+
default:
|
|
96
|
+
return 2;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export enum AnalyticsEventNames {
|
|
101
|
+
WIDGET_OPENED = "ai_chat_widget_opened",
|
|
102
|
+
WIDGET_CLOSED = "ai_chat_widget_closed",
|
|
103
|
+
CHAT_CLEARED = "ai_chat_cleared",
|
|
104
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { createContext, useContext } from "react";
|
|
2
|
+
import type { Analytics } from "@segment/analytics-next";
|
|
3
|
+
|
|
4
|
+
export const AnalyticsClientContext = createContext<Analytics | undefined>(
|
|
5
|
+
undefined
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
export const useAnalyticsClient = () => useContext(AnalyticsClientContext);
|
|
9
|
+
|
|
10
|
+
export const AnalyticsClientProvider = ({
|
|
11
|
+
client,
|
|
12
|
+
children,
|
|
13
|
+
}: {
|
|
14
|
+
client: Analytics;
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}) => (
|
|
17
|
+
<AnalyticsClientContext.Provider value={client}>
|
|
18
|
+
{children}
|
|
19
|
+
</AnalyticsClientContext.Provider>
|
|
20
|
+
);
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
type JsonMap = Record<string, unknown>;
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import { DeviceType, getDeviceType, getUserDetails } from "../components/utils";
|
|
4
|
+
import { useAnalyticsClient } from "../contexts/AnalyticsClientContext";
|
|
5
|
+
|
|
6
|
+
const getSystemTypeEnum = (deviceType: any) => {
|
|
7
|
+
let type: string = "web";
|
|
8
|
+
if (deviceType) {
|
|
9
|
+
switch (deviceType) {
|
|
10
|
+
case "Linux":
|
|
11
|
+
type = "android";
|
|
12
|
+
break;
|
|
13
|
+
case "iPhone":
|
|
14
|
+
type = "ios";
|
|
15
|
+
break;
|
|
16
|
+
default:
|
|
17
|
+
type = "web";
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
type = String(Platform.OS);
|
|
22
|
+
}
|
|
23
|
+
return getDeviceType(type).toString();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const formatOSName = (rawOsName: string) => {
|
|
27
|
+
if (!rawOsName) return "";
|
|
28
|
+
|
|
29
|
+
const osName = rawOsName.toLowerCase();
|
|
30
|
+
|
|
31
|
+
const osMap = {
|
|
32
|
+
android: "Android",
|
|
33
|
+
ios: "iOS",
|
|
34
|
+
ipad: "iPadOS",
|
|
35
|
+
ipados: "iPadOS",
|
|
36
|
+
windows: "Windows",
|
|
37
|
+
macos: "macOS",
|
|
38
|
+
mac: "macOS",
|
|
39
|
+
linux: "Linux",
|
|
40
|
+
web: "Web",
|
|
41
|
+
tizen: "Tizen",
|
|
42
|
+
harmonyos: "HarmonyOS",
|
|
43
|
+
fireos: "Fire OS",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
for (const key of Object.keys(osMap) as Array<keyof typeof osMap>) {
|
|
47
|
+
if (osName.includes(key)) {
|
|
48
|
+
return osMap[key];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return rawOsName.charAt(0).toUpperCase() + rawOsName.slice(1);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const getUserAnalyticsPayload = async () => {
|
|
56
|
+
const user = await getUserDetails();
|
|
57
|
+
const deviceDetails =
|
|
58
|
+
Platform.OS === "web"
|
|
59
|
+
? {
|
|
60
|
+
system_type_enum: getSystemTypeEnum(null),
|
|
61
|
+
platform_action_enum: "0",
|
|
62
|
+
}
|
|
63
|
+
: {};
|
|
64
|
+
return {
|
|
65
|
+
user_company_id: user?.company_id?.toString(),
|
|
66
|
+
user_company_name: user?.company,
|
|
67
|
+
user_email: user?.email,
|
|
68
|
+
user_id: user?.id?.toString(),
|
|
69
|
+
user_name: user?.first_name
|
|
70
|
+
? `${user?.first_name} ${user?.last_name}`
|
|
71
|
+
: undefined,
|
|
72
|
+
user_price_mode: user?.price_mode?.toString(),
|
|
73
|
+
price_mode: user?.price_mode?.toString(),
|
|
74
|
+
has_plan_details: user?.plan_details ? true : false,
|
|
75
|
+
userAgent: Platform.OS === "web" ? navigator.userAgent : "",
|
|
76
|
+
...deviceDetails,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const isSegmentUserAgent = (userAgent: string) => {
|
|
81
|
+
if (!userAgent) return false;
|
|
82
|
+
|
|
83
|
+
// Regex to match Segment's specific Safari-like pattern
|
|
84
|
+
const segmentPattern =
|
|
85
|
+
/^Mozilla\/5\.0 \([^;]+; CPU [^)]+ like Mac OS X\) AppleWebKit\/600\.1\.4 \(KHTML, like Gecko\) Version\/\d\.0 Mobile\/10B329 Safari\/8536\.25$/;
|
|
86
|
+
|
|
87
|
+
return segmentPattern.test(userAgent);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export function isBot(userAgent = "") {
|
|
91
|
+
if (!userAgent && Platform.OS === "web") return true;
|
|
92
|
+
|
|
93
|
+
const botPatterns = [
|
|
94
|
+
/bot/i, // "Googlebot", "Bingbot"
|
|
95
|
+
/crawl/i, // "crawler"
|
|
96
|
+
/spider/i, // "spider"
|
|
97
|
+
/HeadlessChrome/i, // Puppeteer/Headless
|
|
98
|
+
/PhantomJS/i, // Old automation
|
|
99
|
+
/slurp/i, // Yahoo Slurp bot
|
|
100
|
+
/curl/i, // CLI traffic
|
|
101
|
+
/wget/i, // CLI downloads
|
|
102
|
+
/httpclient/i, // Generic clients
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
return botPatterns.some((pattern) => pattern.test(userAgent));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const useUserAnalytics = () => {
|
|
109
|
+
// This will return undefined if not wrapped in provider, which is fine
|
|
110
|
+
const analyticsClient = useAnalyticsClient();
|
|
111
|
+
|
|
112
|
+
const _track = async (eventName: string, options: any) => {
|
|
113
|
+
const userAnalyticsPayload = await getUserAnalyticsPayload();
|
|
114
|
+
if (isSegmentUserAgent(userAnalyticsPayload?.userAgent)) {
|
|
115
|
+
console.warn(
|
|
116
|
+
"Skipping tracking for Segment user agent:",
|
|
117
|
+
userAnalyticsPayload.userAgent
|
|
118
|
+
);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (isBot(userAnalyticsPayload?.userAgent)) {
|
|
122
|
+
console.warn(
|
|
123
|
+
"Skipping tracking for bot user agent:",
|
|
124
|
+
userAnalyticsPayload.userAgent
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const client = analyticsClient || (window as any).analytics;
|
|
129
|
+
if (!client) return;
|
|
130
|
+
|
|
131
|
+
const user = await getUserDetails();
|
|
132
|
+
if (!user?.id) {
|
|
133
|
+
console.error(
|
|
134
|
+
"Track Event tracked without user:",
|
|
135
|
+
eventName,
|
|
136
|
+
JSON.stringify(options)
|
|
137
|
+
);
|
|
138
|
+
console.error(
|
|
139
|
+
"Can't identify as user is not logged in",
|
|
140
|
+
JSON.stringify(user)
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
await client.identify(`${user.id}`, {
|
|
144
|
+
name: `${user.first_name} ${user.last_name}`,
|
|
145
|
+
email: user.email,
|
|
146
|
+
country: user.country,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
await client.track(eventName, {
|
|
150
|
+
...userAnalyticsPayload,
|
|
151
|
+
...options,
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const _identify = async (userId?: string, userTraits?: JsonMap) => {
|
|
156
|
+
const client = analyticsClient || (window as any).analytics;
|
|
157
|
+
if (!client) return;
|
|
158
|
+
await client.identify(userId, userTraits);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const trackEvent = async (eventName: string, options: any) => {
|
|
162
|
+
const userAnalyticsPayload = await getUserAnalyticsPayload();
|
|
163
|
+
const client = analyticsClient || (window as any).analytics;
|
|
164
|
+
if (!client) return;
|
|
165
|
+
await client.track(eventName, {
|
|
166
|
+
...userAnalyticsPayload,
|
|
167
|
+
...options,
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
trackEvent,
|
|
173
|
+
_identify,
|
|
174
|
+
_track,
|
|
175
|
+
};
|
|
176
|
+
};
|