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.
- package/README.md +153 -0
- package/dist/chat-widget.js +2 -0
- package/dist/chat-widget.js.LICENSE.txt +29 -0
- package/lib/commonjs/api.js +157 -0
- package/lib/commonjs/api.js.map +1 -0
- package/lib/commonjs/components/ChatHeader.js +111 -0
- package/lib/commonjs/components/ChatHeader.js.map +1 -0
- package/lib/commonjs/components/ChatInput.js +144 -0
- package/lib/commonjs/components/ChatInput.js.map +1 -0
- package/lib/commonjs/components/ChatWidget.js +469 -0
- package/lib/commonjs/components/ChatWidget.js.map +1 -0
- package/lib/commonjs/components/MessageBubble.js +122 -0
- package/lib/commonjs/components/MessageBubble.js.map +1 -0
- package/lib/commonjs/components/ProductsList.js +139 -0
- package/lib/commonjs/components/ProductsList.js.map +1 -0
- package/lib/commonjs/components/SuggestionsRow.js +59 -0
- package/lib/commonjs/components/SuggestionsRow.js.map +1 -0
- package/lib/commonjs/components/utils.js +37 -0
- package/lib/commonjs/components/utils.js.map +1 -0
- package/lib/commonjs/index.js +70 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/index.native.js +70 -0
- package/lib/commonjs/index.native.js.map +1 -0
- package/lib/commonjs/index.web.js +30 -0
- package/lib/commonjs/index.web.js.map +1 -0
- package/lib/commonjs/storage.js +136 -0
- package/lib/commonjs/storage.js.map +1 -0
- package/lib/commonjs/theme.js +29 -0
- package/lib/commonjs/theme.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/api.js +146 -0
- package/lib/module/api.js.map +1 -0
- package/lib/module/components/ChatHeader.js +105 -0
- package/lib/module/components/ChatHeader.js.map +1 -0
- package/lib/module/components/ChatInput.js +136 -0
- package/lib/module/components/ChatInput.js.map +1 -0
- package/lib/module/components/ChatWidget.js +461 -0
- package/lib/module/components/ChatWidget.js.map +1 -0
- package/lib/module/components/MessageBubble.js +116 -0
- package/lib/module/components/MessageBubble.js.map +1 -0
- package/lib/module/components/ProductsList.js +133 -0
- package/lib/module/components/ProductsList.js.map +1 -0
- package/lib/module/components/SuggestionsRow.js +52 -0
- package/lib/module/components/SuggestionsRow.js.map +1 -0
- package/lib/module/components/utils.js +29 -0
- package/lib/module/components/utils.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.native.js +7 -0
- package/lib/module/index.native.js.map +1 -0
- package/lib/module/index.web.js +23 -0
- package/lib/module/index.web.js.map +1 -0
- package/lib/module/storage.js +129 -0
- package/lib/module/storage.js.map +1 -0
- package/lib/module/theme.js +22 -0
- package/lib/module/theme.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/api.d.ts +10 -0
- package/lib/typescript/api.d.ts.map +1 -0
- package/lib/typescript/components/ChatHeader.d.ts +6 -0
- package/lib/typescript/components/ChatHeader.d.ts.map +1 -0
- package/lib/typescript/components/ChatInput.d.ts +15 -0
- package/lib/typescript/components/ChatInput.d.ts.map +1 -0
- package/lib/typescript/components/ChatWidget.d.ts +4 -0
- package/lib/typescript/components/ChatWidget.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble.d.ts +13 -0
- package/lib/typescript/components/MessageBubble.d.ts.map +1 -0
- package/lib/typescript/components/ProductsList.d.ts +9 -0
- package/lib/typescript/components/ProductsList.d.ts.map +1 -0
- package/lib/typescript/components/SuggestionsRow.d.ts +8 -0
- package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -0
- package/lib/typescript/components/utils.d.ts +8 -0
- package/lib/typescript/components/utils.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/index.native.d.ts +6 -0
- package/lib/typescript/index.native.d.ts.map +1 -0
- package/lib/typescript/index.web.d.ts +3 -0
- package/lib/typescript/index.web.d.ts.map +1 -0
- package/lib/typescript/storage.d.ts +28 -0
- package/lib/typescript/storage.d.ts.map +1 -0
- package/lib/typescript/theme.d.ts +4 -0
- package/lib/typescript/theme.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +51 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +90 -0
- package/src/api.ts +200 -0
- package/src/components/ChatHeader.tsx +114 -0
- package/src/components/ChatInput.tsx +163 -0
- package/src/components/ChatWidget.tsx +679 -0
- package/src/components/MessageBubble.tsx +181 -0
- package/src/components/ProductsList.tsx +160 -0
- package/src/components/SuggestionsRow.tsx +73 -0
- package/src/components/utils.ts +43 -0
- package/src/index.native.tsx +6 -0
- package/src/index.ts +7 -0
- package/src/index.web.tsx +33 -0
- package/src/storage.ts +142 -0
- package/src/theme.ts +21 -0
- 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 @@
|
|
|
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
|
+
});
|