vdb-ai-chat 1.0.5 → 1.0.7
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/chat-widget.js +1 -1
- package/lib/commonjs/api.js +70 -14
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/components/BetaNotice.js +5 -2
- package/lib/commonjs/components/BetaNotice.js.map +1 -1
- package/lib/commonjs/components/ChatHeader.js +20 -5
- package/lib/commonjs/components/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/ChatWidget.js +35 -19
- package/lib/commonjs/components/ChatWidget.js.map +1 -1
- package/lib/commonjs/components/LazyProductsFetcher.js +1 -1
- package/lib/commonjs/components/LazyProductsFetcher.js.map +1 -1
- package/lib/commonjs/components/MessageMetaRow.js +18 -8
- package/lib/commonjs/components/MessageMetaRow.js.map +1 -1
- package/lib/commonjs/components/ProductsList.js +12 -5
- package/lib/commonjs/components/ProductsList.js.map +1 -1
- package/lib/commonjs/components/ProductsListView.js +143 -30
- package/lib/commonjs/components/ProductsListView.js.map +1 -1
- package/lib/commonjs/components/utils.js +22 -5
- package/lib/commonjs/components/utils.js.map +1 -1
- package/lib/module/api.js +70 -14
- package/lib/module/api.js.map +1 -1
- package/lib/module/components/BetaNotice.js +5 -2
- package/lib/module/components/BetaNotice.js.map +1 -1
- package/lib/module/components/ChatHeader.js +20 -5
- package/lib/module/components/ChatHeader.js.map +1 -1
- package/lib/module/components/ChatWidget.js +35 -19
- package/lib/module/components/ChatWidget.js.map +1 -1
- package/lib/module/components/LazyProductsFetcher.js +1 -1
- package/lib/module/components/LazyProductsFetcher.js.map +1 -1
- package/lib/module/components/MessageMetaRow.js +18 -8
- package/lib/module/components/MessageMetaRow.js.map +1 -1
- package/lib/module/components/ProductsList.js +12 -5
- package/lib/module/components/ProductsList.js.map +1 -1
- package/lib/module/components/ProductsListView.js +145 -32
- package/lib/module/components/ProductsListView.js.map +1 -1
- package/lib/module/components/utils.js +22 -3
- package/lib/module/components/utils.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.map +1 -1
- package/lib/typescript/components/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
- package/lib/typescript/components/LazyProductsFetcher.d.ts +1 -1
- package/lib/typescript/components/LazyProductsFetcher.d.ts.map +1 -1
- package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -1
- package/lib/typescript/components/ProductsList.d.ts +2 -1
- package/lib/typescript/components/ProductsList.d.ts.map +1 -1
- package/lib/typescript/components/ProductsListView.d.ts +2 -1
- package/lib/typescript/components/ProductsListView.d.ts.map +1 -1
- package/lib/typescript/components/utils.d.ts +2 -0
- package/lib/typescript/components/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +85 -24
- package/src/components/BetaNotice.tsx +3 -0
- package/src/components/ChatHeader.tsx +18 -3
- package/src/components/ChatWidget.tsx +32 -21
- package/src/components/LazyProductsFetcher.tsx +2 -2
- package/src/components/MessageMetaRow.tsx +14 -14
- package/src/components/ProductsList.tsx +14 -3
- package/src/components/ProductsListView.tsx +288 -134
- package/src/components/utils.ts +30 -4
package/src/api.ts
CHANGED
|
@@ -5,10 +5,14 @@ import type { ChatMessage } from "./types";
|
|
|
5
5
|
// Generic params type reused for downstream APIs (e.g. product search payload)
|
|
6
6
|
export type ChatApiParams = Record<string, any>;
|
|
7
7
|
|
|
8
|
-
async function
|
|
8
|
+
async function getUserToken(): Promise<string> {
|
|
9
9
|
const userInfo = await Storage.getJSON<UserDetails>("persist:userInfo", {});
|
|
10
10
|
const userData = JSON.parse(userInfo?.user || "{}");
|
|
11
|
-
|
|
11
|
+
return userData?.token || "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function buildHeaders() {
|
|
15
|
+
const token = await getUserToken();
|
|
12
16
|
const iid = (await Storage.getItem("persist:appState")) || "{}";
|
|
13
17
|
let installationId = "";
|
|
14
18
|
try {
|
|
@@ -29,6 +33,45 @@ async function buildHeaders() {
|
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
// Structure for storing conversations with session token
|
|
37
|
+
interface StoredConversations {
|
|
38
|
+
token: string;
|
|
39
|
+
conversations: Record<string, { conversation_id: string | number }>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Validates that stored conversations belong to current session
|
|
43
|
+
// If token changed (user re-logged), clears old data and returns empty
|
|
44
|
+
async function getValidConversations(): Promise<
|
|
45
|
+
Record<string, { conversation_id: string | number }>
|
|
46
|
+
> {
|
|
47
|
+
const currentToken = await getUserToken();
|
|
48
|
+
const stored = await Storage.getJSON<StoredConversations>(
|
|
49
|
+
"vdbchat_conversations",
|
|
50
|
+
null
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// If no stored data or token doesn't match, clear and return empty
|
|
54
|
+
if (!stored || stored.token !== currentToken) {
|
|
55
|
+
// Clear old conversation data since session changed
|
|
56
|
+
await Storage.removeItem("vdbchat_conversations");
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return stored.conversations || {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Saves conversations with current session token
|
|
64
|
+
async function saveConversations(
|
|
65
|
+
conversations: Record<string, { conversation_id: string | number }>
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
const currentToken = await getUserToken();
|
|
68
|
+
const data: StoredConversations = {
|
|
69
|
+
token: currentToken,
|
|
70
|
+
conversations,
|
|
71
|
+
};
|
|
72
|
+
await Storage.setJSON("vdbchat_conversations", data);
|
|
73
|
+
}
|
|
74
|
+
|
|
32
75
|
export function normaliseMessages(raw: any): ChatMessage[] {
|
|
33
76
|
const messages = Array.isArray(raw) ? raw : raw?.messages;
|
|
34
77
|
if (!Array.isArray(messages)) return [];
|
|
@@ -43,7 +86,7 @@ export function normaliseMessages(raw: any): ChatMessage[] {
|
|
|
43
86
|
: typeof m.createdAt === "number"
|
|
44
87
|
? m.createdAt
|
|
45
88
|
: Date.now(),
|
|
46
|
-
search_payload: m.search_payload,
|
|
89
|
+
search_payload: m.search_payload ?? {},
|
|
47
90
|
reaction: String(m.reaction ?? "0"),
|
|
48
91
|
suggestions: Array.isArray(m.suggestions) ? m.suggestions : undefined,
|
|
49
92
|
}));
|
|
@@ -54,10 +97,8 @@ export async function fetchInitialMessages(
|
|
|
54
97
|
_params?: ChatApiParams,
|
|
55
98
|
priceMode?: string | null
|
|
56
99
|
): Promise<any> {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{}
|
|
60
|
-
);
|
|
100
|
+
// Get conversations valid for current session (auto-clears if token changed)
|
|
101
|
+
const conversations = await getValidConversations();
|
|
61
102
|
const storedId = conversations?.[priceMode ?? ""]?.conversation_id ?? null;
|
|
62
103
|
if (priceMode === undefined) return;
|
|
63
104
|
|
|
@@ -82,11 +123,11 @@ export async function fetchInitialMessages(
|
|
|
82
123
|
|
|
83
124
|
// Persist conversation_id returned by the server for future calls
|
|
84
125
|
if (data && data.conversation_id != null && priceMode) {
|
|
85
|
-
const updatedConversations = conversations
|
|
126
|
+
const updatedConversations = { ...conversations };
|
|
86
127
|
updatedConversations[priceMode] = {
|
|
87
128
|
conversation_id: data.conversation_id,
|
|
88
129
|
};
|
|
89
|
-
await
|
|
130
|
+
await saveConversations(updatedConversations);
|
|
90
131
|
}
|
|
91
132
|
|
|
92
133
|
return data;
|
|
@@ -99,10 +140,8 @@ export async function sendUserMessage(
|
|
|
99
140
|
_history: ChatMessage[],
|
|
100
141
|
priceMode: any
|
|
101
142
|
): Promise<any> {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{}
|
|
105
|
-
);
|
|
143
|
+
// Get conversations valid for current session
|
|
144
|
+
const conversations = await getValidConversations();
|
|
106
145
|
const storedId = conversations?.[priceMode]?.conversation_id ?? null;
|
|
107
146
|
|
|
108
147
|
if (priceMode === undefined) {
|
|
@@ -131,11 +170,11 @@ export async function sendUserMessage(
|
|
|
131
170
|
const data = await res.json();
|
|
132
171
|
|
|
133
172
|
if (data && data.conversation_id != null && priceMode) {
|
|
134
|
-
const updatedConversations = conversations
|
|
173
|
+
const updatedConversations = { ...conversations };
|
|
135
174
|
updatedConversations[priceMode] = {
|
|
136
175
|
conversation_id: data.conversation_id,
|
|
137
176
|
};
|
|
138
|
-
await
|
|
177
|
+
await saveConversations(updatedConversations);
|
|
139
178
|
}
|
|
140
179
|
|
|
141
180
|
return data;
|
|
@@ -145,15 +184,13 @@ export async function clearChatHistory(
|
|
|
145
184
|
apiUrl: string,
|
|
146
185
|
priceMode: any
|
|
147
186
|
): Promise<void> {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{}
|
|
151
|
-
);
|
|
187
|
+
// Get conversations valid for current session
|
|
188
|
+
const conversations = await getValidConversations();
|
|
152
189
|
const storedId = conversations?.[priceMode]?.conversation_id ?? null;
|
|
153
190
|
if (!storedId || priceMode === undefined) return;
|
|
154
191
|
|
|
155
192
|
const url = new URL(apiUrl);
|
|
156
|
-
url.searchParams.set("conversation_id", storedId);
|
|
193
|
+
url.searchParams.set("conversation_id", String(storedId));
|
|
157
194
|
|
|
158
195
|
const res = await fetch(url.toString(), {
|
|
159
196
|
method: "DELETE",
|
|
@@ -165,14 +202,14 @@ export async function clearChatHistory(
|
|
|
165
202
|
}
|
|
166
203
|
|
|
167
204
|
// Clear from storage as well
|
|
168
|
-
const updatedConversations = conversations
|
|
205
|
+
const updatedConversations = { ...conversations };
|
|
169
206
|
delete updatedConversations[priceMode];
|
|
170
|
-
await
|
|
207
|
+
await saveConversations(updatedConversations);
|
|
171
208
|
}
|
|
172
209
|
|
|
173
210
|
export async function getProducts(
|
|
174
211
|
params: ChatApiParams,
|
|
175
|
-
priceMode: string
|
|
212
|
+
priceMode: string | null
|
|
176
213
|
): Promise<any> {
|
|
177
214
|
const userInfo = await Storage.getJSON<UserDetails>("persist:userInfo", {});
|
|
178
215
|
const { activeProduct } = JSON.parse(
|
|
@@ -191,6 +228,29 @@ export async function getProducts(
|
|
|
191
228
|
typeof rawState?.sectionName === "string"
|
|
192
229
|
? JSON.parse(rawState.sectionName)
|
|
193
230
|
: rawState?.sectionName;
|
|
231
|
+
|
|
232
|
+
const rootState = JSON.parse((await Storage.getItem("persist:root")) || "{}");
|
|
233
|
+
const selectedGroups =
|
|
234
|
+
typeof rootState?.selectedGroups === "string"
|
|
235
|
+
? JSON.parse(rootState.selectedGroups)
|
|
236
|
+
: rootState?.selectedGroups || {};
|
|
237
|
+
|
|
238
|
+
let mGroupIds = selectedGroups[activeProduct ?? ""];
|
|
239
|
+
let vdbSetting = false;
|
|
240
|
+
if (mGroupIds) {
|
|
241
|
+
if (mGroupIds.includes(0)) {
|
|
242
|
+
vdbSetting = true;
|
|
243
|
+
mGroupIds = mGroupIds.filter((groupId: number) => groupId !== 0);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let queryParamList: Record<string, any> = {};
|
|
248
|
+
if (mGroupIds && mGroupIds.length > 0) {
|
|
249
|
+
queryParamList = Object.assign(queryParamList, {
|
|
250
|
+
group_ids: mGroupIds,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
194
254
|
const res = await fetch(
|
|
195
255
|
"https://pdpdemo1.demo.customvirtual.app/v3/vdb/search_diamonds",
|
|
196
256
|
{
|
|
@@ -199,9 +259,10 @@ export async function getProducts(
|
|
|
199
259
|
body: JSON.stringify({
|
|
200
260
|
vdb: {
|
|
201
261
|
...params,
|
|
262
|
+
...queryParamList,
|
|
202
263
|
page_size: 5,
|
|
203
264
|
price_mode: priceMode,
|
|
204
|
-
vdb_setting: "true",
|
|
265
|
+
vdb_setting: vdbSetting ? "true" : "false",
|
|
205
266
|
results_view_type: searchResultViewType || "grid",
|
|
206
267
|
featured: "false",
|
|
207
268
|
pair: sectionName === "Single Stones" ? "other" : "pair",
|
|
@@ -18,6 +18,8 @@ const styles = StyleSheet.create({
|
|
|
18
18
|
paddingVertical: 6,
|
|
19
19
|
paddingHorizontal: 16,
|
|
20
20
|
backgroundColor: "transparent",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
justifyContent: "center",
|
|
21
23
|
},
|
|
22
24
|
text: {
|
|
23
25
|
color: "#4F4E57",
|
|
@@ -26,6 +28,7 @@ const styles = StyleSheet.create({
|
|
|
26
28
|
fontStyle: "normal",
|
|
27
29
|
fontWeight: "400",
|
|
28
30
|
lineHeight: 18,
|
|
31
|
+
textAlign: "center",
|
|
29
32
|
},
|
|
30
33
|
});
|
|
31
34
|
|
|
@@ -61,7 +61,10 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
61
61
|
<View style={styles.container}>
|
|
62
62
|
<View style={styles.logoContainer}>
|
|
63
63
|
<VDBLogo />
|
|
64
|
-
<Text style={styles.title}>
|
|
64
|
+
<Text style={styles.title}>
|
|
65
|
+
AI Search
|
|
66
|
+
{isBetaMode && <Text style={styles.betaSup}>Beta</Text>}
|
|
67
|
+
</Text>
|
|
65
68
|
</View>
|
|
66
69
|
<View style={styles.buttonContainer}>
|
|
67
70
|
<TouchableOpacity onPress={() => {
|
|
@@ -79,6 +82,7 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
79
82
|
onClose?.();
|
|
80
83
|
}}
|
|
81
84
|
>
|
|
85
|
+
<Text style={styles.clearChatText}>Minimize</Text>
|
|
82
86
|
<CloseIcon />
|
|
83
87
|
</TouchableOpacity>
|
|
84
88
|
</View>
|
|
@@ -105,13 +109,24 @@ const styles = StyleSheet.create({
|
|
|
105
109
|
fontWeight: "500",
|
|
106
110
|
color: "#020001",
|
|
107
111
|
},
|
|
112
|
+
betaSup: {
|
|
113
|
+
fontSize: 13,
|
|
114
|
+
fontWeight: "400",
|
|
115
|
+
color: "#4F4E57",
|
|
116
|
+
marginLeft: 4,
|
|
117
|
+
position: "relative",
|
|
118
|
+
top: -6,
|
|
119
|
+
},
|
|
108
120
|
closeButton: {
|
|
109
121
|
backgroundColor: "#FFF",
|
|
110
|
-
width: 24,
|
|
111
122
|
height: 24,
|
|
112
123
|
borderRadius: 12,
|
|
124
|
+
paddingHorizontal: 8,
|
|
113
125
|
justifyContent: "center",
|
|
114
126
|
alignItems: "center",
|
|
127
|
+
display: "flex",
|
|
128
|
+
flexDirection: "row",
|
|
129
|
+
gap: 4,
|
|
115
130
|
},
|
|
116
131
|
closeIconText: {
|
|
117
132
|
fontSize: 14,
|
|
@@ -126,7 +141,7 @@ const styles = StyleSheet.create({
|
|
|
126
141
|
clearChatText: {
|
|
127
142
|
color: "#4F4E57",
|
|
128
143
|
fontSize: 14,
|
|
129
|
-
fontWeight: "
|
|
144
|
+
fontWeight: "400",
|
|
130
145
|
textDecorationLine: "none",
|
|
131
146
|
},
|
|
132
147
|
buttonContainer: {
|
|
@@ -70,10 +70,8 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
70
70
|
);
|
|
71
71
|
const [typingMessageId, setTypingMessageId] = useState<string | null>(null);
|
|
72
72
|
const [typingFullText, setTypingFullText] = useState<string>("");
|
|
73
|
-
const [assistantResponse, setAssistantResponse] = useState<
|
|
74
|
-
ChatMessage | undefined
|
|
75
|
-
>(undefined);
|
|
76
73
|
const [scrollY, setScrollY] = useState(0);
|
|
74
|
+
const [autoScroll, setAutoScroll] = useState(true);
|
|
77
75
|
const [productsByMsg, setProductsByMsg] = useState<Record<string, any>>({});
|
|
78
76
|
const [reloadLoadingIds, setReloadLoadingIds] = useState<Set<string>>(
|
|
79
77
|
new Set()
|
|
@@ -111,11 +109,10 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
111
109
|
loadAuthData();
|
|
112
110
|
}, [userTokenProp]);
|
|
113
111
|
|
|
114
|
-
const onViewAll =
|
|
115
|
-
const searchPayload = JSON.stringify(
|
|
116
|
-
const payload =
|
|
112
|
+
const onViewAll = (item: any) => {
|
|
113
|
+
const searchPayload = JSON.stringify(item?.search_payload);
|
|
114
|
+
const payload = item?.search_payload;
|
|
117
115
|
if (!payload) return;
|
|
118
|
-
|
|
119
116
|
const domain = apiUrl.split("v3");
|
|
120
117
|
const deepLinkUrl = `${domain[0]}webapp/${
|
|
121
118
|
payload.lab_grown === "true" ? "lab-grown-diamonds" : "natural-diamonds"
|
|
@@ -128,7 +125,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
128
125
|
} else if (Platform.OS === "web") {
|
|
129
126
|
window.open(deepLinkUrl, "_parent");
|
|
130
127
|
}
|
|
131
|
-
}
|
|
128
|
+
};
|
|
132
129
|
|
|
133
130
|
const onItemPress = useCallback(
|
|
134
131
|
(item: any) => {
|
|
@@ -220,6 +217,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
220
217
|
"Search Natural Diamonds",
|
|
221
218
|
"Search Lab-Grown Diamonds",
|
|
222
219
|
],
|
|
220
|
+
search_payload: {},
|
|
223
221
|
reaction: "0",
|
|
224
222
|
};
|
|
225
223
|
setMessages([initialAssistant]);
|
|
@@ -267,6 +265,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
267
265
|
createdAt: Date.now(),
|
|
268
266
|
reaction: "0",
|
|
269
267
|
isLoading: true,
|
|
268
|
+
search_payload: {},
|
|
270
269
|
};
|
|
271
270
|
|
|
272
271
|
setMessages((prev) => [...prev, userMessage, loadingMessage]);
|
|
@@ -303,8 +302,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
303
302
|
(m) => m.role === "assistant"
|
|
304
303
|
);
|
|
305
304
|
|
|
306
|
-
setAssistantResponse(latestAssistant);
|
|
307
|
-
|
|
308
305
|
if (latestAssistant?.text) {
|
|
309
306
|
if (
|
|
310
307
|
latestAssistant?.search_payload &&
|
|
@@ -336,6 +333,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
336
333
|
id: latestAssistant?.id ?? msg.id,
|
|
337
334
|
text: noResultsText,
|
|
338
335
|
isLoading: false,
|
|
336
|
+
search_payload: latestAssistant.search_payload ?? {},
|
|
339
337
|
}
|
|
340
338
|
: msg
|
|
341
339
|
)
|
|
@@ -359,6 +357,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
359
357
|
text: "",
|
|
360
358
|
isLoading: false,
|
|
361
359
|
suggestions: latestAssistant.suggestions,
|
|
360
|
+
search_payload: latestAssistant.search_payload ?? {},
|
|
362
361
|
}
|
|
363
362
|
: msg
|
|
364
363
|
)
|
|
@@ -490,7 +489,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
490
489
|
await Storage.setJSON("vdbchat_conversations", updatedConversations);
|
|
491
490
|
}
|
|
492
491
|
setMessages([]);
|
|
493
|
-
setAssistantResponse(undefined);
|
|
494
492
|
setProductsByMsg({});
|
|
495
493
|
setReloadLoadingIds(new Set());
|
|
496
494
|
setLoading(false);
|
|
@@ -512,6 +510,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
512
510
|
"Search Lab-Grown Diamonds",
|
|
513
511
|
],
|
|
514
512
|
reaction: "0",
|
|
513
|
+
search_payload: {},
|
|
515
514
|
};
|
|
516
515
|
setMessages([initialAssistant]);
|
|
517
516
|
} else {
|
|
@@ -616,12 +615,21 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
616
615
|
<KeyboardAvoidingView
|
|
617
616
|
style={{ flex: 1 }}
|
|
618
617
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
619
|
-
keyboardVerticalOffset={
|
|
618
|
+
keyboardVerticalOffset={100}
|
|
620
619
|
>
|
|
621
620
|
<ScrollView
|
|
622
621
|
ref={scrollRef}
|
|
623
622
|
keyboardShouldPersistTaps="handled"
|
|
624
|
-
onScroll={(e) =>
|
|
623
|
+
onScroll={(e) => {
|
|
624
|
+
const { contentOffset, contentSize, layoutMeasurement } =
|
|
625
|
+
e.nativeEvent;
|
|
626
|
+
setScrollY(contentOffset.y);
|
|
627
|
+
const distanceFromBottom =
|
|
628
|
+
contentSize.height -
|
|
629
|
+
(layoutMeasurement.height + contentOffset.y);
|
|
630
|
+
// Enable auto-scroll when user is near the bottom; disable when scrolled up
|
|
631
|
+
setAutoScroll(distanceFromBottom < 48);
|
|
632
|
+
}}
|
|
625
633
|
scrollEventThrottle={16}
|
|
626
634
|
style={
|
|
627
635
|
modalHeight
|
|
@@ -638,16 +646,18 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
638
646
|
: {}),
|
|
639
647
|
}}
|
|
640
648
|
onContentSizeChange={() => {
|
|
641
|
-
|
|
649
|
+
if (autoScroll) {
|
|
650
|
+
scrollRef.current?.scrollToEnd({ animated: false });
|
|
651
|
+
}
|
|
642
652
|
}}
|
|
643
653
|
>
|
|
644
|
-
|
|
645
|
-
<
|
|
646
|
-
{
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
</
|
|
650
|
-
|
|
654
|
+
{messages?.[0]?.createdAt && (
|
|
655
|
+
<View style={styles.emptyContainer}>
|
|
656
|
+
<Text style={styles.emptyText}>
|
|
657
|
+
{`Chat Started at ${formatToTime(messages[0].createdAt)}`}
|
|
658
|
+
</Text>
|
|
659
|
+
</View>
|
|
660
|
+
)}
|
|
651
661
|
{(() => {
|
|
652
662
|
const ordered = [...messages];
|
|
653
663
|
return ordered.map((item, index) => {
|
|
@@ -731,6 +741,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
|
|
|
731
741
|
}
|
|
732
742
|
onViewAll={onViewAll}
|
|
733
743
|
onItemPress={onItemPress}
|
|
744
|
+
item={item}
|
|
734
745
|
/>
|
|
735
746
|
)}
|
|
736
747
|
{/* Suggestions are now rendered inside MessageBubble */}
|
|
@@ -7,7 +7,7 @@ interface LazyProductsFetcherProps {
|
|
|
7
7
|
messageId: string;
|
|
8
8
|
payload: Record<string, any> | undefined | null;
|
|
9
9
|
onFetched: (messageId: string, data: any) => void;
|
|
10
|
-
priceMode: string;
|
|
10
|
+
priceMode: string | null;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const LazyProductsFetcher: React.FC<LazyProductsFetcherProps> = ({
|
|
@@ -34,7 +34,7 @@ const LazyProductsFetcher: React.FC<LazyProductsFetcherProps> = ({
|
|
|
34
34
|
console.error("Lazy fetch products failed", e);
|
|
35
35
|
}
|
|
36
36
|
})();
|
|
37
|
-
}, [inView, messageId, onFetched, payload]);
|
|
37
|
+
}, [inView, messageId, onFetched, payload, priceMode]);
|
|
38
38
|
|
|
39
39
|
// Sentinel element; no visible UI needed
|
|
40
40
|
return <View ref={ref as any} style={{ height: 1 }} />;
|
|
@@ -79,6 +79,7 @@ export const MessageMetaRow: React.FC<Props> = ({
|
|
|
79
79
|
style={(state: any) => [
|
|
80
80
|
styles.borderButton,
|
|
81
81
|
state?.hovered && styles.borderButtonHover,
|
|
82
|
+
message.reaction === FeedbackAction.LIKE && styles.borderButtonLike,
|
|
82
83
|
]}
|
|
83
84
|
onPress={() =>
|
|
84
85
|
handleFeedbackAction(
|
|
@@ -90,20 +91,16 @@ export const MessageMetaRow: React.FC<Props> = ({
|
|
|
90
91
|
disabled={!canFeedback}
|
|
91
92
|
>
|
|
92
93
|
<ImageComponent
|
|
93
|
-
source={{
|
|
94
|
-
uri:
|
|
95
|
-
message.reaction === FeedbackAction.LIKE
|
|
96
|
-
? "https://cdn.vdbapp.com/ai/chat-widget/assets/img/like-filled.svg"
|
|
97
|
-
: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/like.svg",
|
|
98
|
-
}}
|
|
94
|
+
source={{ uri: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/like.svg" }}
|
|
99
95
|
resizeMode="contain"
|
|
100
|
-
style={{ width:
|
|
96
|
+
style={{ width: 20, height: 20, tintColor: message.reaction === FeedbackAction.LIKE ?"#00B140" : "#020001" }}
|
|
101
97
|
/>
|
|
102
98
|
</Pressable>
|
|
103
99
|
<Pressable
|
|
104
100
|
style={(state: any) => [
|
|
105
101
|
styles.borderButton,
|
|
106
102
|
state?.hovered && styles.borderButtonHover,
|
|
103
|
+
message.reaction === FeedbackAction.DISLIKE && styles.borderButtonDislike,
|
|
107
104
|
]}
|
|
108
105
|
onPress={() =>
|
|
109
106
|
handleFeedbackAction(
|
|
@@ -115,14 +112,9 @@ export const MessageMetaRow: React.FC<Props> = ({
|
|
|
115
112
|
disabled={!canFeedback}
|
|
116
113
|
>
|
|
117
114
|
<ImageComponent
|
|
118
|
-
source={{
|
|
119
|
-
uri:
|
|
120
|
-
message.reaction === FeedbackAction.DISLIKE
|
|
121
|
-
? "https://cdn.vdbapp.com/ai/chat-widget/assets/img/dislike-filled.svg"
|
|
122
|
-
: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/dislike.svg",
|
|
123
|
-
}}
|
|
115
|
+
source={{ uri: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/dislike.svg" }}
|
|
124
116
|
resizeMode="contain"
|
|
125
|
-
style={{ width:
|
|
117
|
+
style={{ width: 20, height: 20, tintColor: message.reaction === FeedbackAction.DISLIKE ? "#D0021B" : "#020001" }}
|
|
126
118
|
/>
|
|
127
119
|
</Pressable>
|
|
128
120
|
</View>
|
|
@@ -190,6 +182,14 @@ const styles = StyleSheet.create({
|
|
|
190
182
|
borderButtonHover: {
|
|
191
183
|
backgroundColor: "#EDEDF2",
|
|
192
184
|
},
|
|
185
|
+
borderButtonLike: {
|
|
186
|
+
backgroundColor: "#DBFFE4",
|
|
187
|
+
borderColor: "#00B140",
|
|
188
|
+
},
|
|
189
|
+
borderButtonDislike: {
|
|
190
|
+
backgroundColor: "#FFE2E0",
|
|
191
|
+
borderColor: "#D0021B",
|
|
192
|
+
},
|
|
193
193
|
timeText: {
|
|
194
194
|
fontSize: 12,
|
|
195
195
|
color: "#666",
|
|
@@ -4,10 +4,11 @@ import ProductsListView from "./ProductsListView";
|
|
|
4
4
|
|
|
5
5
|
interface ProductsListProps {
|
|
6
6
|
data: any[];
|
|
7
|
-
onViewAll?: () => void;
|
|
7
|
+
onViewAll?: (item: any) => void;
|
|
8
8
|
onItemPress?: (item: any) => void;
|
|
9
9
|
variant?: "grid" | "list";
|
|
10
|
-
totalResults: number
|
|
10
|
+
totalResults: number;
|
|
11
|
+
item: any;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const ProductsList: React.FC<ProductsListProps> = ({
|
|
@@ -16,20 +17,30 @@ const ProductsList: React.FC<ProductsListProps> = ({
|
|
|
16
17
|
onItemPress,
|
|
17
18
|
variant = "list",
|
|
18
19
|
totalResults,
|
|
20
|
+
item,
|
|
19
21
|
}) => {
|
|
20
22
|
if (!data || !data.length) return null;
|
|
21
23
|
if (variant === "list") {
|
|
22
24
|
return (
|
|
23
25
|
<ProductsListView
|
|
24
26
|
data={data}
|
|
27
|
+
// @ts-ignore
|
|
25
28
|
onViewAll={onViewAll}
|
|
26
29
|
onItemPress={onItemPress}
|
|
27
30
|
totalResults={totalResults}
|
|
31
|
+
item={item}
|
|
28
32
|
/>
|
|
29
33
|
);
|
|
30
34
|
}
|
|
31
35
|
return (
|
|
32
|
-
<ProductsGrid
|
|
36
|
+
<ProductsGrid
|
|
37
|
+
data={data}
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
onViewAll={onViewAll}
|
|
40
|
+
onItemPress={onItemPress}
|
|
41
|
+
totalResults={totalResults}
|
|
42
|
+
item={item}
|
|
43
|
+
/>
|
|
33
44
|
);
|
|
34
45
|
};
|
|
35
46
|
|