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.
Files changed (71) hide show
  1. package/dist/chat-widget.js +1 -1
  2. package/lib/commonjs/api.js +51 -12
  3. package/lib/commonjs/api.js.map +1 -1
  4. package/lib/commonjs/components/BetaNotice.js +13 -12
  5. package/lib/commonjs/components/BetaNotice.js.map +1 -1
  6. package/lib/commonjs/components/ChatInput.js +59 -49
  7. package/lib/commonjs/components/ChatInput.js.map +1 -1
  8. package/lib/commonjs/components/ChatWidget.js +74 -61
  9. package/lib/commonjs/components/ChatWidget.js.map +1 -1
  10. package/lib/commonjs/components/MessageBubble.js +67 -52
  11. package/lib/commonjs/components/MessageBubble.js.map +1 -1
  12. package/lib/commonjs/components/MessageMetaRow.js +50 -31
  13. package/lib/commonjs/components/MessageMetaRow.js.map +1 -1
  14. package/lib/commonjs/components/ProductsListView.js +232 -153
  15. package/lib/commonjs/components/ProductsListView.js.map +1 -1
  16. package/lib/commonjs/components/SuggestionsRow.js +27 -24
  17. package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
  18. package/lib/commonjs/components/utils.js +15 -4
  19. package/lib/commonjs/components/utils.js.map +1 -1
  20. package/lib/commonjs/contexts/ThemeProvider.js +80 -0
  21. package/lib/commonjs/contexts/ThemeProvider.js.map +1 -0
  22. package/lib/commonjs/contexts/utils.js +142 -0
  23. package/lib/commonjs/contexts/utils.js.map +1 -0
  24. package/lib/module/api.js +51 -12
  25. package/lib/module/api.js.map +1 -1
  26. package/lib/module/components/BetaNotice.js +14 -13
  27. package/lib/module/components/BetaNotice.js.map +1 -1
  28. package/lib/module/components/ChatInput.js +61 -50
  29. package/lib/module/components/ChatInput.js.map +1 -1
  30. package/lib/module/components/ChatWidget.js +78 -63
  31. package/lib/module/components/ChatWidget.js.map +1 -1
  32. package/lib/module/components/MessageBubble.js +69 -52
  33. package/lib/module/components/MessageBubble.js.map +1 -1
  34. package/lib/module/components/MessageMetaRow.js +52 -32
  35. package/lib/module/components/MessageMetaRow.js.map +1 -1
  36. package/lib/module/components/ProductsListView.js +234 -154
  37. package/lib/module/components/ProductsListView.js.map +1 -1
  38. package/lib/module/components/SuggestionsRow.js +29 -25
  39. package/lib/module/components/SuggestionsRow.js.map +1 -1
  40. package/lib/module/components/utils.js +17 -3
  41. package/lib/module/components/utils.js.map +1 -1
  42. package/lib/module/contexts/ThemeProvider.js +75 -0
  43. package/lib/module/contexts/ThemeProvider.js.map +1 -0
  44. package/lib/module/contexts/utils.js +136 -0
  45. package/lib/module/contexts/utils.js.map +1 -0
  46. package/lib/typescript/api.d.ts.map +1 -1
  47. package/lib/typescript/components/BetaNotice.d.ts.map +1 -1
  48. package/lib/typescript/components/ChatInput.d.ts.map +1 -1
  49. package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
  50. package/lib/typescript/components/MessageBubble.d.ts +1 -1
  51. package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
  52. package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -1
  53. package/lib/typescript/components/ProductsListView.d.ts.map +1 -1
  54. package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
  55. package/lib/typescript/components/utils.d.ts.map +1 -1
  56. package/lib/typescript/contexts/ThemeProvider.d.ts +7 -0
  57. package/lib/typescript/contexts/ThemeProvider.d.ts.map +1 -0
  58. package/lib/typescript/contexts/utils.d.ts +136 -0
  59. package/lib/typescript/contexts/utils.d.ts.map +1 -0
  60. package/package.json +6 -2
  61. package/src/api.ts +58 -21
  62. package/src/components/BetaNotice.tsx +15 -13
  63. package/src/components/ChatInput.tsx +63 -62
  64. package/src/components/ChatWidget.tsx +238 -206
  65. package/src/components/MessageBubble.tsx +113 -74
  66. package/src/components/MessageMetaRow.tsx +80 -50
  67. package/src/components/ProductsListView.tsx +243 -184
  68. package/src/components/SuggestionsRow.tsx +40 -25
  69. package/src/components/utils.ts +24 -8
  70. package/src/contexts/ThemeProvider.tsx +87 -0
  71. 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 buildHeaders() {
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
- const token = userData?.token || "";
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
- const conversations = await Storage.getJSON<Record<string, any>>(
58
- "vdbchat_conversations",
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 Storage.setJSON("vdbchat_conversations", updatedConversations);
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
- const conversations = await Storage.getJSON<Record<string, any>>(
103
- "vdbchat_conversations",
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 Storage.setJSON("vdbchat_conversations", updatedConversations);
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
- const conversations = await Storage.getJSON<Record<string, any>>(
149
- "vdbchat_conversations",
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 Storage.setJSON("vdbchat_conversations", updatedConversations);
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, Text, StyleSheet } from "react-native";
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
- <Text style={styles.text}>
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
- // expo-image not installed, use React Native Image
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 ? "#ACACB3" : "#020001",
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 isInactive = disabled || value.trim() === "";
68
-
68
+ const theme = useTheme();
69
69
  return (
70
- <View
71
- style={[
72
- styles.inputContainer,
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
- <TextInput
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.input, styles.inputWeb]}
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
- <TouchableOpacity
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
- </TouchableOpacity>
96
- </View>
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,