vdb-ai-chat 1.0.6 → 1.0.8
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 +51 -12
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/components/BetaNotice.js +13 -12
- package/lib/commonjs/components/BetaNotice.js.map +1 -1
- package/lib/commonjs/components/ChatInput.js +59 -49
- package/lib/commonjs/components/ChatInput.js.map +1 -1
- package/lib/commonjs/components/ChatWidget.js +74 -61
- package/lib/commonjs/components/ChatWidget.js.map +1 -1
- package/lib/commonjs/components/MessageBubble.js +67 -52
- package/lib/commonjs/components/MessageBubble.js.map +1 -1
- package/lib/commonjs/components/MessageMetaRow.js +50 -31
- package/lib/commonjs/components/MessageMetaRow.js.map +1 -1
- package/lib/commonjs/components/ProductsListView.js +232 -153
- package/lib/commonjs/components/ProductsListView.js.map +1 -1
- package/lib/commonjs/components/SuggestionsRow.js +27 -24
- package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
- package/lib/commonjs/components/utils.js +15 -4
- package/lib/commonjs/components/utils.js.map +1 -1
- package/lib/commonjs/contexts/ThemeProvider.js +80 -0
- package/lib/commonjs/contexts/ThemeProvider.js.map +1 -0
- package/lib/commonjs/contexts/utils.js +142 -0
- package/lib/commonjs/contexts/utils.js.map +1 -0
- package/lib/module/api.js +51 -12
- package/lib/module/api.js.map +1 -1
- package/lib/module/components/BetaNotice.js +14 -13
- package/lib/module/components/BetaNotice.js.map +1 -1
- package/lib/module/components/ChatInput.js +61 -50
- package/lib/module/components/ChatInput.js.map +1 -1
- package/lib/module/components/ChatWidget.js +78 -63
- package/lib/module/components/ChatWidget.js.map +1 -1
- package/lib/module/components/MessageBubble.js +69 -52
- package/lib/module/components/MessageBubble.js.map +1 -1
- package/lib/module/components/MessageMetaRow.js +52 -32
- package/lib/module/components/MessageMetaRow.js.map +1 -1
- package/lib/module/components/ProductsListView.js +234 -154
- package/lib/module/components/ProductsListView.js.map +1 -1
- package/lib/module/components/SuggestionsRow.js +29 -25
- package/lib/module/components/SuggestionsRow.js.map +1 -1
- package/lib/module/components/utils.js +17 -3
- package/lib/module/components/utils.js.map +1 -1
- package/lib/module/contexts/ThemeProvider.js +75 -0
- package/lib/module/contexts/ThemeProvider.js.map +1 -0
- package/lib/module/contexts/utils.js +136 -0
- package/lib/module/contexts/utils.js.map +1 -0
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/components/BetaNotice.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 +1 -1
- package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
- package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -1
- package/lib/typescript/components/ProductsListView.d.ts.map +1 -1
- package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
- package/lib/typescript/components/utils.d.ts.map +1 -1
- package/lib/typescript/contexts/ThemeProvider.d.ts +7 -0
- package/lib/typescript/contexts/ThemeProvider.d.ts.map +1 -0
- package/lib/typescript/contexts/utils.d.ts +136 -0
- package/lib/typescript/contexts/utils.d.ts.map +1 -0
- package/package.json +6 -2
- package/src/api.ts +58 -21
- package/src/components/BetaNotice.tsx +15 -13
- package/src/components/ChatInput.tsx +63 -62
- package/src/components/ChatWidget.tsx +238 -206
- package/src/components/MessageBubble.tsx +113 -74
- package/src/components/MessageMetaRow.tsx +80 -50
- package/src/components/ProductsListView.tsx +243 -184
- package/src/components/SuggestionsRow.tsx +40 -25
- package/src/components/utils.ts +24 -8
- package/src/contexts/ThemeProvider.tsx +87 -0
- package/src/contexts/utils.ts +135 -0
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 [];
|
|
@@ -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,9 +202,9 @@ 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(
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { View,
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import styled from "styled-components/native";
|
|
4
|
+
import { DefaultTheme } from "styled-components/native";
|
|
3
5
|
|
|
4
6
|
export const BetaNotice = ({ active }: { active?: boolean }) => {
|
|
5
7
|
if (!active) return null;
|
|
6
8
|
return (
|
|
7
9
|
<View style={styles.container}>
|
|
8
|
-
<
|
|
9
|
-
This AI feature is in beta and may make mistakes.
|
|
10
|
-
</Text>
|
|
10
|
+
<TextView>This AI feature is in beta and may make mistakes.</TextView>
|
|
11
11
|
</View>
|
|
12
12
|
);
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
const TextView = styled.Text<{ theme: DefaultTheme }>`
|
|
16
|
+
color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
17
|
+
theme["core-06"] || "#4F4E57"};
|
|
18
|
+
font-family: "Roboto";
|
|
19
|
+
font-size: 13px;
|
|
20
|
+
font-style: normal;
|
|
21
|
+
font-weight: 400;
|
|
22
|
+
line-height: 18px;
|
|
23
|
+
text-align: center;
|
|
24
|
+
`;
|
|
25
|
+
|
|
15
26
|
const styles = StyleSheet.create({
|
|
16
27
|
container: {
|
|
17
28
|
alignSelf: "stretch",
|
|
@@ -21,15 +32,6 @@ const styles = StyleSheet.create({
|
|
|
21
32
|
alignItems: "center",
|
|
22
33
|
justifyContent: "center",
|
|
23
34
|
},
|
|
24
|
-
text: {
|
|
25
|
-
color: "#4F4E57",
|
|
26
|
-
fontFamily: "Roboto",
|
|
27
|
-
fontSize: 13,
|
|
28
|
-
fontStyle: "normal",
|
|
29
|
-
fontWeight: "400",
|
|
30
|
-
lineHeight: 18,
|
|
31
|
-
textAlign: "center",
|
|
32
|
-
},
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
export default BetaNotice;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import {
|
|
3
|
-
View,
|
|
4
3
|
TextInput,
|
|
5
|
-
TouchableOpacity,
|
|
6
|
-
Text,
|
|
7
4
|
StyleSheet,
|
|
8
5
|
Platform,
|
|
9
6
|
Image,
|
|
10
7
|
} from "react-native";
|
|
11
8
|
import type { ChatTheme } from "../types";
|
|
9
|
+
import { useTheme } from "styled-components/native";
|
|
10
|
+
import { DefaultTheme } from "styled-components/native";
|
|
11
|
+
import styled from "styled-components/native";
|
|
12
12
|
|
|
13
13
|
// Use expo-image on native if available, fallback to RN Image
|
|
14
14
|
let ImageComponent: typeof Image = Image;
|
|
@@ -17,7 +17,7 @@ if (Platform.OS !== "web") {
|
|
|
17
17
|
const ExpoImage = require("expo-image").Image;
|
|
18
18
|
if (ExpoImage) ImageComponent = ExpoImage;
|
|
19
19
|
} catch {
|
|
20
|
-
|
|
20
|
+
ImageComponent = Image;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -39,7 +39,7 @@ const SendIcon = ({
|
|
|
39
39
|
isEmpty: boolean;
|
|
40
40
|
}) => {
|
|
41
41
|
const isInactive = disabled || isEmpty;
|
|
42
|
-
|
|
42
|
+
const theme = useTheme();
|
|
43
43
|
return (
|
|
44
44
|
<ImageComponent
|
|
45
45
|
source={{
|
|
@@ -48,7 +48,9 @@ const SendIcon = ({
|
|
|
48
48
|
resizeMode="contain"
|
|
49
49
|
style={{
|
|
50
50
|
...styles.buttonIcon,
|
|
51
|
-
tintColor: isInactive
|
|
51
|
+
tintColor: isInactive
|
|
52
|
+
? theme["core-04"] || "#ACACB3"
|
|
53
|
+
: theme["core-05"] || "#020001",
|
|
52
54
|
cursor: isInactive ? "auto" : "pointer",
|
|
53
55
|
}}
|
|
54
56
|
/>
|
|
@@ -61,56 +63,80 @@ export const ChatInput: React.FC<Props> = ({
|
|
|
61
63
|
onSend,
|
|
62
64
|
disabled,
|
|
63
65
|
placeholder,
|
|
64
|
-
theme,
|
|
65
66
|
inputRef,
|
|
66
67
|
}) => {
|
|
67
|
-
const
|
|
68
|
-
|
|
68
|
+
const theme = useTheme();
|
|
69
69
|
return (
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Platform.OS === "web" ? styles.webShadow : styles.nativeShadow,
|
|
74
|
-
]}
|
|
70
|
+
<InputContainer
|
|
71
|
+
theme={theme}
|
|
72
|
+
style={[Platform.OS === "web" ? styles.webShadow : styles.nativeShadow]}
|
|
75
73
|
>
|
|
76
|
-
<
|
|
74
|
+
<Input
|
|
77
75
|
ref={inputRef}
|
|
78
76
|
// @ts-ignore - supported on web via react-native-web
|
|
79
77
|
id="chat-input"
|
|
80
|
-
style={[styles.
|
|
78
|
+
style={[styles.inputWeb]}
|
|
81
79
|
placeholder={placeholder || "What are you looking for?"}
|
|
82
|
-
placeholderTextColor="#999"
|
|
80
|
+
placeholderTextColor={theme["core-06"] || "#999"}
|
|
83
81
|
value={value}
|
|
84
82
|
onChangeText={onChangeText}
|
|
85
83
|
onSubmitEditing={onSend}
|
|
86
84
|
editable={!disabled}
|
|
87
85
|
autoFocus
|
|
88
86
|
/>
|
|
89
|
-
<
|
|
90
|
-
style={styles.button}
|
|
91
|
-
onPress={onSend}
|
|
92
|
-
disabled={disabled}
|
|
93
|
-
>
|
|
87
|
+
<SendButton onPress={onSend} disabled={disabled}>
|
|
94
88
|
<SendIcon disabled={disabled} isEmpty={value.trim() === ""} />
|
|
95
|
-
</
|
|
96
|
-
</
|
|
89
|
+
</SendButton>
|
|
90
|
+
</InputContainer>
|
|
97
91
|
);
|
|
98
92
|
};
|
|
99
93
|
|
|
94
|
+
const InputContainer = styled.View<{ theme: DefaultTheme }>`
|
|
95
|
+
flex-direction: row;
|
|
96
|
+
padding-horizontal: 12px;
|
|
97
|
+
padding-vertical: 16px;
|
|
98
|
+
justify-content: space-between;
|
|
99
|
+
align-items: center;
|
|
100
|
+
align-self: stretch;
|
|
101
|
+
border-radius: 8px;
|
|
102
|
+
background-color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
103
|
+
theme["core-01"] || "#FFFFFF"};
|
|
104
|
+
border-color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
105
|
+
theme["core-04"] || "#ACACB3"};
|
|
106
|
+
border-width: 1;
|
|
107
|
+
height: 56px;
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
const Input = styled.TextInput<{ theme: DefaultTheme }>`
|
|
111
|
+
flex: 1;
|
|
112
|
+
color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
113
|
+
theme["core-06"] || "#4F4E57"};
|
|
114
|
+
font-family: "Roboto";
|
|
115
|
+
font-size: 14px;
|
|
116
|
+
font-style: normal;
|
|
117
|
+
font-weight: 400;
|
|
118
|
+
line-height: 20px;
|
|
119
|
+
min-height: 20px;
|
|
120
|
+
max-height: 120px;
|
|
121
|
+
padding-horizontal: 0;
|
|
122
|
+
padding-vertical: 0;
|
|
123
|
+
background-color: transparent;
|
|
124
|
+
border-width: 0;
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const SendButton = styled.TouchableOpacity<{ theme: DefaultTheme }>`
|
|
128
|
+
margin-left: 0;
|
|
129
|
+
padding: 0;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
align-items: center;
|
|
132
|
+
height: 24;
|
|
133
|
+
aspect-ratio: 1;
|
|
134
|
+
border-radius: 0px;
|
|
135
|
+
background-color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
136
|
+
theme["core-01"] || "#FFFFFF"};
|
|
137
|
+
`;
|
|
138
|
+
|
|
100
139
|
const styles = StyleSheet.create({
|
|
101
|
-
inputContainer: {
|
|
102
|
-
flexDirection: "row",
|
|
103
|
-
paddingHorizontal: 12,
|
|
104
|
-
paddingVertical: 16,
|
|
105
|
-
justifyContent: "space-between",
|
|
106
|
-
alignItems: "center",
|
|
107
|
-
alignSelf: "stretch",
|
|
108
|
-
borderRadius: 8,
|
|
109
|
-
backgroundColor: "#FFF",
|
|
110
|
-
borderColor: "#ACACB3",
|
|
111
|
-
borderWidth: 1,
|
|
112
|
-
height: 56,
|
|
113
|
-
},
|
|
114
140
|
webShadow: {
|
|
115
141
|
...(Platform.OS === "web"
|
|
116
142
|
? ({ boxShadow: "2px 8px 24px -6px rgba(55, 54, 64, 0.26)" } as any)
|
|
@@ -123,36 +149,11 @@ const styles = StyleSheet.create({
|
|
|
123
149
|
shadowRadius: 12,
|
|
124
150
|
elevation: 3,
|
|
125
151
|
},
|
|
126
|
-
input: {
|
|
127
|
-
flex: 1,
|
|
128
|
-
color: "#4F4E57",
|
|
129
|
-
fontFamily: "Roboto",
|
|
130
|
-
fontSize: 14,
|
|
131
|
-
fontStyle: "normal",
|
|
132
|
-
fontWeight: "400",
|
|
133
|
-
lineHeight: 20,
|
|
134
|
-
minHeight: 20,
|
|
135
|
-
maxHeight: 120,
|
|
136
|
-
paddingHorizontal: 0,
|
|
137
|
-
paddingVertical: 0,
|
|
138
|
-
backgroundColor: "transparent",
|
|
139
|
-
borderWidth: 0,
|
|
140
|
-
},
|
|
141
152
|
inputWeb: {
|
|
142
153
|
...(Platform.OS === "web"
|
|
143
154
|
? ({ outlineStyle: "none", outlineWidth: 0, boxShadow: "none" } as any)
|
|
144
155
|
: {}),
|
|
145
156
|
},
|
|
146
|
-
button: {
|
|
147
|
-
marginLeft: 0,
|
|
148
|
-
padding: 0,
|
|
149
|
-
justifyContent: "center",
|
|
150
|
-
alignItems: "center",
|
|
151
|
-
height: 24,
|
|
152
|
-
aspectRatio: 1,
|
|
153
|
-
borderRadius: 0,
|
|
154
|
-
backgroundColor: "#FFFFFF",
|
|
155
|
-
},
|
|
156
157
|
buttonIcon: {
|
|
157
158
|
height: 24,
|
|
158
159
|
width: 24,
|