vdb-ai-chat 1.0.0

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 (102) hide show
  1. package/README.md +153 -0
  2. package/dist/chat-widget.js +2 -0
  3. package/dist/chat-widget.js.LICENSE.txt +29 -0
  4. package/lib/commonjs/api.js +157 -0
  5. package/lib/commonjs/api.js.map +1 -0
  6. package/lib/commonjs/components/ChatHeader.js +111 -0
  7. package/lib/commonjs/components/ChatHeader.js.map +1 -0
  8. package/lib/commonjs/components/ChatInput.js +144 -0
  9. package/lib/commonjs/components/ChatInput.js.map +1 -0
  10. package/lib/commonjs/components/ChatWidget.js +469 -0
  11. package/lib/commonjs/components/ChatWidget.js.map +1 -0
  12. package/lib/commonjs/components/MessageBubble.js +122 -0
  13. package/lib/commonjs/components/MessageBubble.js.map +1 -0
  14. package/lib/commonjs/components/ProductsList.js +139 -0
  15. package/lib/commonjs/components/ProductsList.js.map +1 -0
  16. package/lib/commonjs/components/SuggestionsRow.js +59 -0
  17. package/lib/commonjs/components/SuggestionsRow.js.map +1 -0
  18. package/lib/commonjs/components/utils.js +37 -0
  19. package/lib/commonjs/components/utils.js.map +1 -0
  20. package/lib/commonjs/index.js +70 -0
  21. package/lib/commonjs/index.js.map +1 -0
  22. package/lib/commonjs/index.native.js +70 -0
  23. package/lib/commonjs/index.native.js.map +1 -0
  24. package/lib/commonjs/index.web.js +30 -0
  25. package/lib/commonjs/index.web.js.map +1 -0
  26. package/lib/commonjs/storage.js +136 -0
  27. package/lib/commonjs/storage.js.map +1 -0
  28. package/lib/commonjs/theme.js +29 -0
  29. package/lib/commonjs/theme.js.map +1 -0
  30. package/lib/commonjs/types.js +2 -0
  31. package/lib/commonjs/types.js.map +1 -0
  32. package/lib/module/api.js +146 -0
  33. package/lib/module/api.js.map +1 -0
  34. package/lib/module/components/ChatHeader.js +105 -0
  35. package/lib/module/components/ChatHeader.js.map +1 -0
  36. package/lib/module/components/ChatInput.js +136 -0
  37. package/lib/module/components/ChatInput.js.map +1 -0
  38. package/lib/module/components/ChatWidget.js +461 -0
  39. package/lib/module/components/ChatWidget.js.map +1 -0
  40. package/lib/module/components/MessageBubble.js +116 -0
  41. package/lib/module/components/MessageBubble.js.map +1 -0
  42. package/lib/module/components/ProductsList.js +133 -0
  43. package/lib/module/components/ProductsList.js.map +1 -0
  44. package/lib/module/components/SuggestionsRow.js +52 -0
  45. package/lib/module/components/SuggestionsRow.js.map +1 -0
  46. package/lib/module/components/utils.js +29 -0
  47. package/lib/module/components/utils.js.map +1 -0
  48. package/lib/module/index.js +7 -0
  49. package/lib/module/index.js.map +1 -0
  50. package/lib/module/index.native.js +7 -0
  51. package/lib/module/index.native.js.map +1 -0
  52. package/lib/module/index.web.js +23 -0
  53. package/lib/module/index.web.js.map +1 -0
  54. package/lib/module/storage.js +129 -0
  55. package/lib/module/storage.js.map +1 -0
  56. package/lib/module/theme.js +22 -0
  57. package/lib/module/theme.js.map +1 -0
  58. package/lib/module/types.js +2 -0
  59. package/lib/module/types.js.map +1 -0
  60. package/lib/typescript/api.d.ts +10 -0
  61. package/lib/typescript/api.d.ts.map +1 -0
  62. package/lib/typescript/components/ChatHeader.d.ts +6 -0
  63. package/lib/typescript/components/ChatHeader.d.ts.map +1 -0
  64. package/lib/typescript/components/ChatInput.d.ts +15 -0
  65. package/lib/typescript/components/ChatInput.d.ts.map +1 -0
  66. package/lib/typescript/components/ChatWidget.d.ts +4 -0
  67. package/lib/typescript/components/ChatWidget.d.ts.map +1 -0
  68. package/lib/typescript/components/MessageBubble.d.ts +13 -0
  69. package/lib/typescript/components/MessageBubble.d.ts.map +1 -0
  70. package/lib/typescript/components/ProductsList.d.ts +9 -0
  71. package/lib/typescript/components/ProductsList.d.ts.map +1 -0
  72. package/lib/typescript/components/SuggestionsRow.d.ts +8 -0
  73. package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -0
  74. package/lib/typescript/components/utils.d.ts +8 -0
  75. package/lib/typescript/components/utils.d.ts.map +1 -0
  76. package/lib/typescript/index.d.ts +6 -0
  77. package/lib/typescript/index.d.ts.map +1 -0
  78. package/lib/typescript/index.native.d.ts +6 -0
  79. package/lib/typescript/index.native.d.ts.map +1 -0
  80. package/lib/typescript/index.web.d.ts +3 -0
  81. package/lib/typescript/index.web.d.ts.map +1 -0
  82. package/lib/typescript/storage.d.ts +28 -0
  83. package/lib/typescript/storage.d.ts.map +1 -0
  84. package/lib/typescript/theme.d.ts +4 -0
  85. package/lib/typescript/theme.d.ts.map +1 -0
  86. package/lib/typescript/types.d.ts +51 -0
  87. package/lib/typescript/types.d.ts.map +1 -0
  88. package/package.json +90 -0
  89. package/src/api.ts +200 -0
  90. package/src/components/ChatHeader.tsx +114 -0
  91. package/src/components/ChatInput.tsx +163 -0
  92. package/src/components/ChatWidget.tsx +679 -0
  93. package/src/components/MessageBubble.tsx +181 -0
  94. package/src/components/ProductsList.tsx +160 -0
  95. package/src/components/SuggestionsRow.tsx +73 -0
  96. package/src/components/utils.ts +43 -0
  97. package/src/index.native.tsx +6 -0
  98. package/src/index.ts +7 -0
  99. package/src/index.web.tsx +33 -0
  100. package/src/storage.ts +142 -0
  101. package/src/theme.ts +21 -0
  102. package/src/types.ts +56 -0
@@ -0,0 +1,28 @@
1
+ interface StorageInterface {
2
+ getItem: (key: string) => Promise<string | null>;
3
+ setItem: (key: string, value: string) => Promise<void>;
4
+ removeItem: (key: string) => Promise<void>;
5
+ }
6
+ /**
7
+ * Initialize storage with AsyncStorage for React Native.
8
+ * Call this once at app startup before using ChatWidget.
9
+ *
10
+ * @example
11
+ * import AsyncStorage from '@react-native-async-storage/async-storage';
12
+ * import { initStorage } from 'vdb-ai-chat';
13
+ * initStorage(AsyncStorage);
14
+ */
15
+ export declare function initStorage(asyncStorage: StorageInterface): void;
16
+ /**
17
+ * Check if storage is properly initialized for the current platform
18
+ */
19
+ export declare function isStorageInitialized(): boolean;
20
+ export declare const Storage: {
21
+ getItem: (key: string) => Promise<string | null>;
22
+ setItem: (key: string, value: string) => Promise<void>;
23
+ removeItem: (key: string) => Promise<void>;
24
+ getJSON: <T = any>(key: string, defaultValue?: T | null) => Promise<T | null>;
25
+ setJSON: (key: string, value: any) => Promise<void>;
26
+ };
27
+ export default Storage;
28
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/storage.ts"],"names":[],"mappings":"AAGA,UAAU,gBAAgB;IACxB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjD,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AA8CD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAGhE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAK9C;AA8BD,eAAO,MAAM,OAAO;mBACG,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;mBAI/B,MAAM,SAAS,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;sBAIlC,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;cAK9B,CAAC,aAAa,MAAM,iBAAgB,CAAC,GAAG,IAAI,KAAU,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;mBAalE,MAAM,SAAS,GAAG,KAAG,OAAO,CAAC,IAAI,CAAC;CAOxD,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ChatTheme } from "./types";
2
+ export declare const defaultTheme: ChatTheme;
3
+ export declare function mergeTheme(overrides?: Partial<ChatTheme>): ChatTheme;
4
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,eAAO,MAAM,YAAY,EAAE,SAc1B,CAAC;AAEF,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAEpE"}
@@ -0,0 +1,51 @@
1
+ export type Role = "user" | "assistant";
2
+ export interface ChatMessage {
3
+ agent_response?: any;
4
+ id: string;
5
+ role: Role;
6
+ text: string;
7
+ createdAt: number;
8
+ isLoading?: boolean;
9
+ suggestions?: string[];
10
+ reaction?: "0" | "1" | "2";
11
+ }
12
+ export interface ChatTheme {
13
+ primaryColor: string;
14
+ backgroundColor: string;
15
+ inputColor: string;
16
+ inputBackgroundColor: string;
17
+ inputBorderRadius: number;
18
+ inputTextColor: string;
19
+ userBubbleColor: string;
20
+ userTextColor: string;
21
+ botBubbleColor: string;
22
+ botTextColor: string;
23
+ borderRadius: number;
24
+ fontFamily?: string;
25
+ fontSize: number;
26
+ listContentBackgroundColor?: string;
27
+ }
28
+ export interface ChatWidgetProps {
29
+ apiUrl: string;
30
+ userId?: string;
31
+ userToken?: string;
32
+ priceMode?: string;
33
+ modalHeight?: number;
34
+ theme?: Partial<ChatTheme>;
35
+ initialMessages?: ChatMessage[];
36
+ placeholder?: string;
37
+ onClose?: () => void;
38
+ onClearChat?: () => void;
39
+ /** Called when "View All" is pressed. Receives the deep link URL and payload. */
40
+ onViewAllPress?: (deepLinkUrl: string, payload: any) => void;
41
+ /** Called when a product item is pressed. Receives the deep link URL and item data. */
42
+ onItemPress?: (deepLinkUrl: string, item: any) => void;
43
+ }
44
+ export interface ChatWidgetRef {
45
+ clearChat: () => Promise<void>;
46
+ }
47
+ export interface ChatWidgetHandlers {
48
+ onViewAllPress?: (deepLinkUrl: string, payload: any) => void;
49
+ onItemPress?: (deepLinkUrl: string, item: any) => void;
50
+ }
51
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,WAAW,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,QAAQ,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3B,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,iFAAiF;IACjF,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7D,uFAAuF;IACvF,WAAW,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7D,WAAW,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;CACxD"}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "vdb-ai-chat",
3
+ "version": "1.0.0",
4
+ "description": "Cross-platform AI chat widget for React Native and Web",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index.native.tsx",
9
+ "source": "src/index.native.tsx",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "dist",
14
+ "!**/__tests__",
15
+ "!**/__fixtures__",
16
+ "!**/__mocks__"
17
+ ],
18
+ "scripts": {
19
+ "start:web": "webpack serve --config webpack.config.js --mode development",
20
+ "build:web": "webpack --config webpack.config.js --mode production",
21
+ "build:lib": "bob build",
22
+ "build": "npm run build:lib && npm run build:web",
23
+ "type-check": "tsc --noEmit",
24
+ "prepare": "npm run build:lib",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "keywords": [
28
+ "react-native",
29
+ "react",
30
+ "chat",
31
+ "widget",
32
+ "ai",
33
+ "chatbot",
34
+ "cross-platform"
35
+ ],
36
+ "author": "",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": ""
41
+ },
42
+ "peerDependencies": {
43
+ "react": ">=17.0.0",
44
+ "react-native": ">=0.64.0",
45
+ "expo-image": ">=1.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "react-native": {
49
+ "optional": true
50
+ },
51
+ "expo-image": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "dependencies": {
56
+ "react-native-web": "^0.19.13"
57
+ },
58
+ "devDependencies": {
59
+ "@babel/core": "^7.26.0",
60
+ "@babel/preset-env": "^7.26.0",
61
+ "@babel/preset-react": "^7.26.0",
62
+ "@babel/preset-typescript": "^7.26.0",
63
+ "@types/react": "^18.3.27",
64
+ "@types/react-dom": "^18.3.1",
65
+ "axios": "^1.13.2",
66
+ "babel-loader": "^9.2.1",
67
+ "react": "^18.3.1",
68
+ "react-dom": "^18.3.1",
69
+ "react-native": "0.76.0",
70
+ "react-native-builder-bob": "^0.23.2",
71
+ "typescript": "^5.6.3",
72
+ "webpack": "^5.96.1",
73
+ "webpack-cli": "^5.1.4",
74
+ "webpack-dev-server": "^5.1.0"
75
+ },
76
+ "react-native-builder-bob": {
77
+ "source": "src",
78
+ "output": "lib",
79
+ "targets": [
80
+ "commonjs",
81
+ "module",
82
+ [
83
+ "typescript",
84
+ {
85
+ "project": "tsconfig.build.json"
86
+ }
87
+ ]
88
+ ]
89
+ }
90
+ }
package/src/api.ts ADDED
@@ -0,0 +1,200 @@
1
+ import { FeedbackAction } from "./components/utils";
2
+ import { Storage } from "./storage";
3
+ import type { ChatMessage } from "./types";
4
+
5
+ // Generic params type reused for downstream APIs (e.g. product search payload)
6
+ export type ChatApiParams = Record<string, any>;
7
+
8
+ async function buildHeaders() {
9
+ const token = (await Storage.getItem("token")) || "";
10
+ const iid = (await Storage.getItem("persist:appState")) || "{}";
11
+ let installationId = "";
12
+ try {
13
+ const parsed = JSON.parse(iid);
14
+ installationId = parsed?.installationIdentifier
15
+ ? JSON.parse(parsed.installationIdentifier)
16
+ : "";
17
+ } catch {
18
+ installationId = "";
19
+ }
20
+ const session = (await Storage.getItem("session_cookie")) || "";
21
+
22
+ return {
23
+ "Content-Type": "application/json",
24
+ Accept: "application/json, text/plain, */*",
25
+ Authorization: `Token token=${token},installation_identifier=${installationId}`,
26
+ Cookie: `vdb-server_session=${session}`,
27
+ };
28
+ }
29
+
30
+ export function normaliseMessages(raw: any): ChatMessage[] {
31
+ const messages = Array.isArray(raw) ? raw : raw?.messages;
32
+ if (!Array.isArray(messages)) return [];
33
+
34
+ return messages.map((m, index) => ({
35
+ id: String(m.id ?? `${m.role}-${index}-${m.created_at ?? Date.now()}`),
36
+ role: m.role === "user" ? "user" : "assistant",
37
+ text: String(m.content ?? m.text ?? ""),
38
+ createdAt:
39
+ typeof m.created_at === "string"
40
+ ? Date.parse(m.created_at)
41
+ : typeof m.createdAt === "number"
42
+ ? m.createdAt
43
+ : Date.now(),
44
+ ...( m.role === "assistant" ? { agent_response: raw?.agent_response ? raw?.agent_response : undefined } : {}),
45
+ // agent_response: m.agent_response,
46
+ suggestions: Array.isArray(m.suggestions) ? m.suggestions : undefined,
47
+ }));
48
+ }
49
+
50
+ export async function fetchInitialMessages(
51
+ apiUrl: string,
52
+ _params?: ChatApiParams,
53
+ priceMode?: any
54
+ ): Promise<any> {
55
+ const conversations = await Storage.getJSON<Record<string, any>>("vdbchat_conversations", {});
56
+ const storedId = conversations?.[priceMode]?.conversation_id ?? null;
57
+ if (!priceMode) return;
58
+
59
+ const body: any = {};
60
+ if (storedId) {
61
+ body.conversation_id = Number.isNaN(Number(storedId))
62
+ ? storedId
63
+ : Number(storedId);
64
+ }
65
+
66
+ const res = await fetch(apiUrl, {
67
+ method: "POST",
68
+ headers: await buildHeaders(),
69
+ body: Object.keys(body).length ? JSON.stringify(body) : undefined,
70
+ });
71
+
72
+ if (!res.ok) {
73
+ throw new Error(`POST ${apiUrl} failed with ${res.status}`);
74
+ }
75
+
76
+ const data = await res.json();
77
+
78
+ // Persist conversation_id returned by the server for future calls
79
+ if (data && data.conversation_id != null && priceMode) {
80
+ const updatedConversations = conversations || {};
81
+ updatedConversations[priceMode] = {
82
+ conversation_id: data.conversation_id,
83
+ };
84
+ await Storage.setJSON("vdbchat_conversations", updatedConversations);
85
+ }
86
+
87
+ return data;
88
+ }
89
+
90
+ export async function sendUserMessage(
91
+ apiUrl: string,
92
+ userMessage: string,
93
+ _params: ChatApiParams,
94
+ _history: ChatMessage[],
95
+ priceMode: any
96
+ ): Promise<any> {
97
+ const conversations = await Storage.getJSON<Record<string, any>>("vdbchat_conversations", {});
98
+ const storedId = conversations?.[priceMode]?.conversation_id ?? null;
99
+
100
+ if (!priceMode) {
101
+ return null;
102
+ }
103
+
104
+ const body: any = {
105
+ query: userMessage,
106
+ };
107
+ if (storedId) {
108
+ body.conversation_id = Number.isNaN(Number(storedId))
109
+ ? storedId
110
+ : Number(storedId);
111
+ }
112
+
113
+ const res = await fetch(apiUrl, {
114
+ method: "POST",
115
+ headers: await buildHeaders(),
116
+ body: JSON.stringify(body),
117
+ });
118
+
119
+ if (!res.ok) {
120
+ throw new Error(`POST ${apiUrl} failed with ${res.status}`);
121
+ }
122
+
123
+ const data = await res.json();
124
+
125
+ if (data && data.conversation_id != null && priceMode) {
126
+ const updatedConversations = conversations || {};
127
+ updatedConversations[priceMode] = {
128
+ conversation_id: data.conversation_id,
129
+ };
130
+ await Storage.setJSON("vdbchat_conversations", updatedConversations);
131
+ }
132
+
133
+ return data;
134
+ }
135
+
136
+ export async function clearChatHistory(apiUrl: string, priceMode: any): Promise<void> {
137
+ const conversations = await Storage.getJSON<Record<string, any>>("vdbchat_conversations", {});
138
+ const storedId = conversations?.[priceMode]?.conversation_id ?? null;
139
+ if (!storedId || !priceMode) return;
140
+
141
+ const url = new URL(apiUrl);
142
+ url.searchParams.set("conversation_id", storedId);
143
+
144
+ const res = await fetch(url.toString(), {
145
+ method: "DELETE",
146
+ headers: await buildHeaders(),
147
+ });
148
+
149
+ if (!res.ok) {
150
+ throw new Error(`DELETE ${apiUrl} failed with ${res.status}`);
151
+ }
152
+
153
+ // Clear from storage as well
154
+ const updatedConversations = conversations || {};
155
+ delete updatedConversations[priceMode];
156
+ await Storage.setJSON("vdbchat_conversations", updatedConversations);
157
+ }
158
+
159
+ export async function getProducts(params: ChatApiParams): Promise<any> {
160
+ const res = await fetch(
161
+ "https://pdpdemo1.demo.customvirtual.app/v3/vdb/search_diamonds",
162
+ {
163
+ method: "POST",
164
+ headers: await buildHeaders(),
165
+ body: JSON.stringify({
166
+ vdb: {
167
+ ...params,
168
+ page_size: 5,
169
+ price_mode: 1,
170
+ vdb_setting: "true",
171
+ },
172
+ }),
173
+ }
174
+ );
175
+
176
+ if (!res.ok) {
177
+ throw new Error(`POST failed with ${res.status}`);
178
+ }
179
+
180
+ const data = await res.json();
181
+ return data;
182
+ }
183
+
184
+ export const handleFeedbackActionApi = async (
185
+ action: FeedbackAction,
186
+ conversation_id: string,
187
+ message_id: string
188
+ ) => {
189
+ const feedbackUrl = `https://pdpdemo1.demo.customvirtual.app/v3/agent_conversations/${conversation_id}/conversation_messages/${message_id}/reaction`;
190
+ const payload = {
191
+ reaction: action,
192
+ };
193
+
194
+ const response = await fetch(feedbackUrl, {
195
+ method: "POST",
196
+ headers: await buildHeaders(),
197
+ body: JSON.stringify(payload),
198
+ });
199
+ return response;
200
+ };
@@ -0,0 +1,114 @@
1
+ import {
2
+ View,
3
+ Text,
4
+ StyleSheet,
5
+ TouchableOpacity,
6
+ Platform,
7
+ Image,
8
+ } from "react-native";
9
+ import React from "react";
10
+
11
+ // Use expo-image on native if available, fallback to RN Image
12
+ let ImageComponent: typeof Image = Image;
13
+ if (Platform.OS !== "web") {
14
+ try {
15
+ const ExpoImage = require("expo-image").Image;
16
+ if (ExpoImage) ImageComponent = ExpoImage;
17
+ } catch {
18
+ // expo-image not installed, use React Native Image
19
+ }
20
+ }
21
+
22
+ const CloseIcon = () => {
23
+ return (
24
+ <ImageComponent
25
+ source={{
26
+ uri: "https://cdn.jsdelivr.net/npm/feather-icons/dist/icons/x.svg",
27
+ }}
28
+ resizeMode="contain"
29
+ style={{ width: 20, height: 20 }}
30
+ />
31
+ );
32
+ };
33
+
34
+ const ChatHeader = ({
35
+ onClose,
36
+ onClearChat,
37
+ }: {
38
+ onClose?: () => void;
39
+ onClearChat?: () => void;
40
+ }) => {
41
+ return (
42
+ <View style={styles.container}>
43
+ <View style={styles.logoContainer}>
44
+ <ImageComponent
45
+ source={{ uri: "https://getvdb.com/mail_images/web_app_favicon.ico" }}
46
+ resizeMode="contain"
47
+ style={{ width: 30, height: 30, marginRight: 10 }}
48
+ />
49
+ <Text style={styles.title}>AI Search</Text>
50
+ </View>
51
+ <View style={styles.buttonContainer}>
52
+ <TouchableOpacity onPress={() => onClearChat?.()}>
53
+ <Text style={styles.clearChatText}>Clear Chat</Text>
54
+ </TouchableOpacity>
55
+ <TouchableOpacity
56
+ style={styles.closeButton}
57
+ onPress={() => onClose?.()}
58
+ >
59
+ <CloseIcon />
60
+ </TouchableOpacity>
61
+ </View>
62
+ </View>
63
+ );
64
+ };
65
+
66
+ const styles = StyleSheet.create({
67
+ container: {
68
+ padding: 16,
69
+ backgroundColor: "#37363F",
70
+ justifyContent: "space-between",
71
+ flexDirection: "row",
72
+ alignItems: "center",
73
+ },
74
+ logoContainer: {
75
+ flexDirection: "row",
76
+ alignItems: "center",
77
+ },
78
+ title: {
79
+ fontSize: 17,
80
+ fontWeight: "500",
81
+ color: "#FFF",
82
+ },
83
+ closeButton: {
84
+ backgroundColor: "#FFF",
85
+ width: 24,
86
+ height: 24,
87
+ borderRadius: 12,
88
+ justifyContent: "center",
89
+ alignItems: "center",
90
+ },
91
+ closeIconText: {
92
+ fontSize: 14,
93
+ fontWeight: "600",
94
+ color: "#37363F",
95
+ textAlign: "center",
96
+ lineHeight: 16,
97
+ },
98
+ closeButtonText: {
99
+ color: "#FFF",
100
+ },
101
+ clearChatText: {
102
+ color: "#FFF",
103
+ fontSize: 14,
104
+ fontWeight: "500",
105
+ textDecorationLine: "none",
106
+ },
107
+ buttonContainer: {
108
+ flexDirection: "row",
109
+ alignItems: "center",
110
+ gap: 16,
111
+ },
112
+ });
113
+
114
+ export default ChatHeader;
@@ -0,0 +1,163 @@
1
+ import React from "react";
2
+ import {
3
+ View,
4
+ TextInput,
5
+ TouchableOpacity,
6
+ Text,
7
+ StyleSheet,
8
+ Platform,
9
+ Image,
10
+ } from "react-native";
11
+ import type { ChatTheme } from "../types";
12
+
13
+ // Use expo-image on native if available, fallback to RN Image
14
+ let ImageComponent: typeof Image = Image;
15
+ if (Platform.OS !== "web") {
16
+ try {
17
+ const ExpoImage = require("expo-image").Image;
18
+ if (ExpoImage) ImageComponent = ExpoImage;
19
+ } catch {
20
+ // expo-image not installed, use React Native Image
21
+ }
22
+ }
23
+
24
+ interface Props {
25
+ value: string;
26
+ onChangeText: (t: string) => void;
27
+ onSend: () => void;
28
+ disabled?: boolean;
29
+ placeholder?: string;
30
+ theme: ChatTheme;
31
+ inputRef?: React.RefObject<TextInput>;
32
+ }
33
+
34
+ const SendIcon = ({
35
+ disabled,
36
+ isEmpty,
37
+ }: {
38
+ disabled?: boolean;
39
+ isEmpty: boolean;
40
+ }) => {
41
+ const isInactive = disabled || isEmpty;
42
+
43
+ return (
44
+ <ImageComponent
45
+ source={{
46
+ uri: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/up.svg",
47
+ }}
48
+ resizeMode="contain"
49
+ style={{ ...styles.buttonIcon, tintColor: isInactive ? "#AAA" : "#FFF" }}
50
+ />
51
+ );
52
+ };
53
+
54
+ export const ChatInput: React.FC<Props> = ({
55
+ value,
56
+ onChangeText,
57
+ onSend,
58
+ disabled,
59
+ placeholder,
60
+ theme,
61
+ inputRef,
62
+ }) => {
63
+ const isInactive = disabled || value.trim() === "";
64
+
65
+ return (
66
+ <View
67
+ style={[
68
+ styles.container,
69
+ Platform.OS === "web" ? styles.webShadow : styles.nativeShadow,
70
+ ]}
71
+ >
72
+ <TextInput
73
+ ref={inputRef}
74
+ // @ts-ignore - supported on web via react-native-web
75
+ id="chat-input"
76
+ style={[styles.input, styles.inputWeb]}
77
+ placeholder={placeholder || "What are you looking for?"}
78
+ placeholderTextColor="#999"
79
+ value={value}
80
+ onChangeText={onChangeText}
81
+ onSubmitEditing={onSend}
82
+ editable={!disabled}
83
+ autoFocus
84
+ />
85
+ <TouchableOpacity
86
+ style={{
87
+ ...styles.button,
88
+ backgroundColor: isInactive ? "#EDEDF2" : "#C000FF",
89
+ }}
90
+ onPress={onSend}
91
+ disabled={disabled}
92
+ >
93
+ <SendIcon disabled={disabled} isEmpty={value.trim() === ""} />
94
+ </TouchableOpacity>
95
+ </View>
96
+ );
97
+ };
98
+
99
+ const styles = StyleSheet.create({
100
+ container: {
101
+ flexDirection: "row",
102
+ height: 48,
103
+ paddingHorizontal: 16,
104
+ paddingVertical: 0,
105
+ justifyContent: "space-between",
106
+ alignItems: "center",
107
+ alignSelf: "stretch",
108
+ borderRadius: 56,
109
+ backgroundColor: "#FFF",
110
+ margin: 8,
111
+ },
112
+ webShadow: {
113
+ ...(Platform.OS === "web"
114
+ ? ({ boxShadow: "2px 8px 24px -6px rgba(55, 54, 64, 0.26)" } as any)
115
+ : {}),
116
+ },
117
+ nativeShadow: {
118
+ shadowColor: "#373640",
119
+ shadowOffset: { width: 2, height: 8 },
120
+ shadowOpacity: 0.26,
121
+ shadowRadius: 12,
122
+ elevation: 3,
123
+ },
124
+ input: {
125
+ flex: 1,
126
+ color: "#4F4E57",
127
+ fontFamily: "Roboto",
128
+ fontSize: 14,
129
+ fontStyle: "normal",
130
+ fontWeight: "400",
131
+ lineHeight: 20,
132
+ minHeight: 20,
133
+ maxHeight: 120,
134
+ paddingHorizontal: 8,
135
+ paddingVertical: 0,
136
+ backgroundColor: "transparent",
137
+ borderWidth: 0,
138
+ },
139
+ inputWeb: {
140
+ ...(Platform.OS === "web"
141
+ ? ({ outlineStyle: "none", outlineWidth: 0, boxShadow: "none" } as any)
142
+ : {}),
143
+ },
144
+ button: {
145
+ marginLeft: 8,
146
+ padding: 4,
147
+ justifyContent: "center",
148
+ alignItems: "center",
149
+ height: 40,
150
+ aspectRatio: 1,
151
+ borderRadius: 40,
152
+ backgroundColor: "#C000FF",
153
+ },
154
+ buttonIcon: {
155
+ height: 16,
156
+ width: 16,
157
+ },
158
+ sendIconText: {
159
+ fontSize: 18,
160
+ fontWeight: "700",
161
+ textAlign: "center",
162
+ },
163
+ });