@enhearten/knowledge-base-module 1.0.1 → 1.0.3
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/components/DataUploadModal.d.ts.map +1 -1
- package/dist/components/DataUploadModal.js +37 -36
- package/dist/components/SettingsModal.d.ts +8 -0
- package/dist/components/SettingsModal.d.ts.map +1 -0
- package/dist/components/SettingsModal.js +84 -0
- package/dist/hooks/useServiceConnection.d.ts +1 -0
- package/dist/hooks/useServiceConnection.d.ts.map +1 -1
- package/dist/hooks/useServiceConnection.js +16 -3
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/services/api.d.ts +9 -3
- package/dist/services/api.d.ts.map +1 -1
- package/dist/services/api.js +28 -15
- package/package.json +3 -3
- package/src/components/DataUploadModal.tsx +93 -91
- package/src/components/SettingsModal.tsx +155 -0
- package/src/hooks/useServiceConnection.ts +15 -3
- package/src/index.ts +1 -0
- package/src/services/api.ts +31 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataUploadModal.d.ts","sourceRoot":"","sources":["../../src/components/DataUploadModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,UAAU,oBAAoB;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,WAAW,GAAG,UAAU,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAChC;AAmCD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,
|
|
1
|
+
{"version":3,"file":"DataUploadModal.d.ts","sourceRoot":"","sources":["../../src/components/DataUploadModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,UAAU,oBAAoB;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,WAAW,GAAG,UAAU,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAChC;AAmCD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA+K1D,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { Button, Card, Text, XStack, YStack, Spinner, Sheet } from 'tamagui';
|
|
2
|
+
import { Button, Card, Text, XStack, YStack, Spinner, Sheet, Theme } from 'tamagui';
|
|
3
3
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
4
4
|
import * as DocumentPicker from 'expo-document-picker';
|
|
5
5
|
import { uploadUserData } from '../services/api';
|
|
@@ -96,39 +96,40 @@ export const DataUploadModal = ({ open, onClose, serviceName, entityId, onUpload
|
|
|
96
96
|
onClose();
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
|
-
return (React.createElement(
|
|
100
|
-
React.createElement(Sheet
|
|
101
|
-
|
|
102
|
-
React.createElement(Sheet.
|
|
103
|
-
|
|
104
|
-
React.createElement(
|
|
105
|
-
React.createElement(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
React.createElement(
|
|
109
|
-
React.createElement(
|
|
110
|
-
|
|
111
|
-
React.createElement(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
React.createElement(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
React.createElement(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
React.createElement(
|
|
128
|
-
|
|
129
|
-
React.createElement(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
React.createElement(
|
|
133
|
-
|
|
99
|
+
return (React.createElement(Theme, { name: "light" },
|
|
100
|
+
React.createElement(Sheet, { modal: true, open: open, onOpenChange: (isOpen) => !isOpen && handleClose(), snapPoints: [85], dismissOnSnapToBottom: true },
|
|
101
|
+
React.createElement(Sheet.Overlay, null),
|
|
102
|
+
React.createElement(Sheet.Frame, { padding: "$4", backgroundColor: "white" },
|
|
103
|
+
React.createElement(Sheet.Handle, null),
|
|
104
|
+
React.createElement(YStack, { space: "$4", paddingTop: "$4" },
|
|
105
|
+
React.createElement(XStack, { alignItems: "center", space: "$2" },
|
|
106
|
+
React.createElement(MaterialCommunityIcons, { name: instructions.icon, size: 28, color: serviceName === 'instagram' ? '#E4405F' : '#25D366' }),
|
|
107
|
+
React.createElement(Text, { fontSize: "$6", fontWeight: "bold" }, instructions.title)),
|
|
108
|
+
React.createElement(Card, { bordered: true, padding: "$3", backgroundColor: "$gray2" },
|
|
109
|
+
React.createElement(YStack, { space: "$2" },
|
|
110
|
+
React.createElement(Text, { fontSize: "$4", fontWeight: "600" }, "How to download your data:"),
|
|
111
|
+
instructions.steps.map((step, index) => (React.createElement(XStack, { key: index, space: "$2", alignItems: "flex-start" },
|
|
112
|
+
React.createElement(Text, { fontSize: "$3", color: "$gray11", minWidth: 20 },
|
|
113
|
+
index + 1,
|
|
114
|
+
"."),
|
|
115
|
+
React.createElement(Text, { fontSize: "$3", color: "$gray11", flex: 1 }, step)))))),
|
|
116
|
+
React.createElement(YStack, { gap: "$3" },
|
|
117
|
+
React.createElement(Button, { size: "$4", onPress: handlePickFiles, disabled: uploading, icon: React.createElement(MaterialCommunityIcons, { name: "file-upload", size: 20, color: "white" }) }, selectedFiles.length > 0
|
|
118
|
+
? `${selectedFiles.length} file(s) selected`
|
|
119
|
+
: 'Select File(s)'),
|
|
120
|
+
selectedFiles.length > 0 && (React.createElement(Card, { bordered: true, padding: "$2", backgroundColor: "$green2" },
|
|
121
|
+
React.createElement(YStack, { space: "$1" }, selectedFiles.map((file, index) => (React.createElement(XStack, { key: index, alignItems: "center", space: "$2" },
|
|
122
|
+
React.createElement(MaterialCommunityIcons, { name: "file-check", size: 16, color: "green" }),
|
|
123
|
+
React.createElement(Text, { fontSize: "$2", flex: 1, numberOfLines: 1 }, file.name),
|
|
124
|
+
React.createElement(Text, { fontSize: "$1", color: "$gray10" },
|
|
125
|
+
(file.size ? file.size / 1024 / 1024 : 0).toFixed(2),
|
|
126
|
+
" MB"))))))),
|
|
127
|
+
error && (React.createElement(Card, { bordered: true, padding: "$2", backgroundColor: "$red2", borderColor: "$red8" },
|
|
128
|
+
React.createElement(Text, { color: "$red10", fontSize: "$3" }, error))),
|
|
129
|
+
uploadProgress && (React.createElement(XStack, { alignItems: "center", space: "$2" },
|
|
130
|
+
React.createElement(Spinner, { size: "small" }),
|
|
131
|
+
React.createElement(Text, { fontSize: "$3", color: "$gray10" }, uploadProgress))),
|
|
132
|
+
React.createElement(XStack, { gap: "$3", justifyContent: "flex-end" },
|
|
133
|
+
React.createElement(Button, { size: "$4", variant: "outlined", onPress: handleClose, disabled: uploading }, "Cancel"),
|
|
134
|
+
React.createElement(Button, { size: "$4", themeInverse: true, onPress: handleUpload, disabled: uploading || selectedFiles.length === 0, icon: uploading ? React.createElement(Spinner, { size: "small", color: "white" }) : undefined }, uploading ? 'Uploading...' : 'Upload Data'))))))));
|
|
134
135
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsModal.d.ts","sourceRoot":"","sources":["../../src/components/SettingsModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAMnD,UAAU,kBAAkB;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+ItD,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Button, Input, Label, Sheet, Text, XStack, YStack, Theme, Spinner } from 'tamagui';
|
|
3
|
+
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
4
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
5
|
+
import { configure } from '../services/api';
|
|
6
|
+
export const SettingsModal = ({ open, onClose }) => {
|
|
7
|
+
const [backendUrl, setBackendUrl] = useState('');
|
|
8
|
+
const [composioKey, setComposioKey] = useState('');
|
|
9
|
+
const [googleKey, setGoogleKey] = useState('');
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [saving, setSaving] = useState(false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (open) {
|
|
14
|
+
loadSettings();
|
|
15
|
+
}
|
|
16
|
+
}, [open]);
|
|
17
|
+
const loadSettings = async () => {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
try {
|
|
20
|
+
const storedUrl = await AsyncStorage.getItem('kb_backend_url');
|
|
21
|
+
const storedComposioKey = await AsyncStorage.getItem('kb_composio_key');
|
|
22
|
+
const storedGoogleKey = await AsyncStorage.getItem('kb_google_key');
|
|
23
|
+
if (storedUrl)
|
|
24
|
+
setBackendUrl(storedUrl);
|
|
25
|
+
if (storedComposioKey)
|
|
26
|
+
setComposioKey(storedComposioKey);
|
|
27
|
+
if (storedGoogleKey)
|
|
28
|
+
setGoogleKey(storedGoogleKey);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error('Failed to load settings', error);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
setLoading(false);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const handleSave = async () => {
|
|
38
|
+
setSaving(true);
|
|
39
|
+
try {
|
|
40
|
+
// Remove trailing slash from URL if present
|
|
41
|
+
const cleanUrl = backendUrl.replace(/\/$/, '');
|
|
42
|
+
await AsyncStorage.setItem('kb_backend_url', cleanUrl);
|
|
43
|
+
await AsyncStorage.setItem('kb_composio_key', composioKey);
|
|
44
|
+
await AsyncStorage.setItem('kb_google_key', googleKey);
|
|
45
|
+
configure({
|
|
46
|
+
backendUrl: cleanUrl,
|
|
47
|
+
headers: {
|
|
48
|
+
'x-composio-key': composioKey,
|
|
49
|
+
'x-google-key': googleKey
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
onClose();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error('Failed to save settings', error);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
setSaving(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return (React.createElement(Theme, { name: "light" },
|
|
62
|
+
React.createElement(Sheet, { modal: true, open: open, onOpenChange: (isOpen) => !isOpen && onClose(), snapPoints: [85], dismissOnSnapToBottom: true },
|
|
63
|
+
React.createElement(Sheet.Overlay, null),
|
|
64
|
+
React.createElement(Sheet.Frame, { padding: "$4", backgroundColor: "white" },
|
|
65
|
+
React.createElement(Sheet.Handle, null),
|
|
66
|
+
React.createElement(YStack, { space: "$4", paddingTop: "$4" },
|
|
67
|
+
React.createElement(XStack, { alignItems: "center", space: "$2" },
|
|
68
|
+
React.createElement(MaterialCommunityIcons, { name: "cog", size: 28, color: "#333" }),
|
|
69
|
+
React.createElement(Text, { fontSize: "$6", fontWeight: "bold" }, "Settings")),
|
|
70
|
+
React.createElement(Text, { color: "$gray11" }, "Configure your Cloudflare Worker URL and API keys."),
|
|
71
|
+
loading ? (React.createElement(Spinner, { size: "large", color: "$blue10" })) : (React.createElement(YStack, { space: "$4" },
|
|
72
|
+
React.createElement(YStack, { space: "$2" },
|
|
73
|
+
React.createElement(Label, { htmlFor: "backendUrl", fontWeight: "600" }, "Backend URL (Cloudflare Worker)"),
|
|
74
|
+
React.createElement(Input, { id: "backendUrl", placeholder: "https://knowledge-base-worker.your-subdomain.workers.dev", value: backendUrl, onChangeText: setBackendUrl, autoCapitalize: "none", autoCorrect: false })),
|
|
75
|
+
React.createElement(YStack, { space: "$2" },
|
|
76
|
+
React.createElement(Label, { htmlFor: "composioKey", fontWeight: "600" }, "Composio API Key"),
|
|
77
|
+
React.createElement(Input, { id: "composioKey", placeholder: "Enter your Composio API Key", value: composioKey, onChangeText: setComposioKey, secureTextEntry: true })),
|
|
78
|
+
React.createElement(YStack, { space: "$2" },
|
|
79
|
+
React.createElement(Label, { htmlFor: "googleKey", fontWeight: "600" }, "Google Gemini API Key"),
|
|
80
|
+
React.createElement(Input, { id: "googleKey", placeholder: "Enter your Google Gemini API Key", value: googleKey, onChangeText: setGoogleKey, secureTextEntry: true })),
|
|
81
|
+
React.createElement(XStack, { gap: "$3", justifyContent: "flex-end", marginTop: "$4" },
|
|
82
|
+
React.createElement(Button, { size: "$4", variant: "outlined", onPress: onClose, disabled: saving }, "Cancel"),
|
|
83
|
+
React.createElement(Button, { size: "$4", themeInverse: true, onPress: handleSave, disabled: saving, icon: saving ? React.createElement(Spinner, { size: "small", color: "white" }) : undefined }, saving ? 'Saving...' : 'Save Settings')))))))));
|
|
84
|
+
};
|
|
@@ -2,6 +2,7 @@ export declare const useServiceConnection: (appName: string, entityId: string, s
|
|
|
2
2
|
connected: boolean;
|
|
3
3
|
loading: boolean;
|
|
4
4
|
disconnecting: boolean;
|
|
5
|
+
error: string | null;
|
|
5
6
|
handleConnect: () => Promise<void>;
|
|
6
7
|
handleCancel: () => void;
|
|
7
8
|
handleDisconnect: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useServiceConnection.d.ts","sourceRoot":"","sources":["../../src/hooks/useServiceConnection.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,EAAE,cAAa,MAAM,EAAc
|
|
1
|
+
{"version":3,"file":"useServiceConnection.d.ts","sourceRoot":"","sources":["../../src/hooks/useServiceConnection.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,EAAE,cAAa,MAAM,EAAc;;;;;;;;CA4FxG,CAAC"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { Linking } from 'react-native';
|
|
3
|
-
import { startConnection, getConnectionStatus, disconnectApp } from '../services/api';
|
|
2
|
+
import { Linking, Alert } from 'react-native';
|
|
3
|
+
import { startConnection, getConnectionStatus, disconnectApp, isConfigured } from '../services/api';
|
|
4
4
|
export const useServiceConnection = (appName, entityId, searchTerms = [appName]) => {
|
|
5
5
|
const [loading, setLoading] = useState(false);
|
|
6
6
|
const [disconnecting, setDisconnecting] = useState(false);
|
|
7
7
|
const [connected, setConnected] = useState(false);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
8
9
|
const pollingInterval = useRef(null);
|
|
9
10
|
const checkStatus = async () => {
|
|
11
|
+
if (!isConfigured())
|
|
12
|
+
return false;
|
|
10
13
|
try {
|
|
11
14
|
const data = await getConnectionStatus(entityId);
|
|
12
15
|
const isConnected = data.connectedApps && data.connectedApps.some((a) => searchTerms.some(term => a.toLowerCase().includes(term.toLowerCase())));
|
|
@@ -28,8 +31,14 @@ export const useServiceConnection = (appName, entityId, searchTerms = [appName])
|
|
|
28
31
|
};
|
|
29
32
|
}, [entityId]);
|
|
30
33
|
const handleConnect = async () => {
|
|
34
|
+
if (!isConfigured()) {
|
|
35
|
+
setError('Configuration missing. Please set up the API URL and keys.');
|
|
36
|
+
Alert.alert('Configuration Missing', 'Please configure the backend URL and API keys in settings.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
31
39
|
try {
|
|
32
40
|
setLoading(true);
|
|
41
|
+
setError(null);
|
|
33
42
|
const data = await startConnection(appName, entityId);
|
|
34
43
|
if (data.url)
|
|
35
44
|
await Linking.openURL(data.url);
|
|
@@ -54,6 +63,7 @@ export const useServiceConnection = (appName, entityId, searchTerms = [appName])
|
|
|
54
63
|
catch (error) {
|
|
55
64
|
console.error(error);
|
|
56
65
|
console.error(`Failed to connect ${appName}`);
|
|
66
|
+
setError(`Failed to connect ${appName}`);
|
|
57
67
|
setLoading(false);
|
|
58
68
|
}
|
|
59
69
|
};
|
|
@@ -65,6 +75,8 @@ export const useServiceConnection = (appName, entityId, searchTerms = [appName])
|
|
|
65
75
|
setLoading(false);
|
|
66
76
|
};
|
|
67
77
|
const handleDisconnect = async () => {
|
|
78
|
+
if (!isConfigured())
|
|
79
|
+
return;
|
|
68
80
|
try {
|
|
69
81
|
setDisconnecting(true);
|
|
70
82
|
await disconnectApp(appName, entityId);
|
|
@@ -72,10 +84,11 @@ export const useServiceConnection = (appName, entityId, searchTerms = [appName])
|
|
|
72
84
|
}
|
|
73
85
|
catch (error) {
|
|
74
86
|
console.error(`Failed to disconnect ${appName}:`, error);
|
|
87
|
+
setError(`Failed to disconnect ${appName}`);
|
|
75
88
|
}
|
|
76
89
|
finally {
|
|
77
90
|
setDisconnecting(false);
|
|
78
91
|
}
|
|
79
92
|
};
|
|
80
|
-
return { connected, loading, disconnecting, handleConnect, handleCancel, handleDisconnect };
|
|
93
|
+
return { connected, loading, disconnecting, error, handleConnect, handleCancel, handleDisconnect };
|
|
81
94
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,7 @@ export { ConnectInstagram } from './components/ConnectInstagram';
|
|
|
9
9
|
export { ConnectLinkedIn } from './components/ConnectLinkedIn';
|
|
10
10
|
export { ConnectWhatsApp } from './components/ConnectWhatsApp';
|
|
11
11
|
export { DataUploadModal } from './components/DataUploadModal';
|
|
12
|
+
export { SettingsModal } from './components/SettingsModal';
|
|
12
13
|
export { useServiceConnection } from './hooks/useServiceConnection';
|
|
13
14
|
export { configure, startConnection, executeAction, getConnectionStatus, disconnectApp, uploadUserData, getUserDataStatus, deleteUserData } from './services/api';
|
|
14
15
|
export { searchPerson, getGiftIdeas, draftEmail, summarizeRecent } from './services/api';
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { ConnectInstagram } from './components/ConnectInstagram';
|
|
|
9
9
|
export { ConnectLinkedIn } from './components/ConnectLinkedIn';
|
|
10
10
|
export { ConnectWhatsApp } from './components/ConnectWhatsApp';
|
|
11
11
|
export { DataUploadModal } from './components/DataUploadModal';
|
|
12
|
+
export { SettingsModal } from './components/SettingsModal';
|
|
12
13
|
export { useServiceConnection } from './hooks/useServiceConnection';
|
|
13
14
|
export { configure, startConnection, executeAction, getConnectionStatus, disconnectApp, uploadUserData, getUserDataStatus, deleteUserData } from './services/api';
|
|
14
15
|
export { searchPerson, getGiftIdeas, draftEmail, summarizeRecent } from './services/api';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,EACH,SAAS,EACT,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,cAAc,EACjB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACH,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,eAAe,EAClB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EACR,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACjB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { ConnectInstagram } from './components/ConnectInstagram';
|
|
|
10
10
|
export { ConnectLinkedIn } from './components/ConnectLinkedIn';
|
|
11
11
|
export { ConnectWhatsApp } from './components/ConnectWhatsApp';
|
|
12
12
|
export { DataUploadModal } from './components/DataUploadModal';
|
|
13
|
+
export { SettingsModal } from './components/SettingsModal';
|
|
13
14
|
// Hooks
|
|
14
15
|
export { useServiceConnection } from './hooks/useServiceConnection';
|
|
15
16
|
// Core API functions
|
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ export { ConnectInstagram } from './components/ConnectInstagram';
|
|
|
10
10
|
export { ConnectLinkedIn } from './components/ConnectLinkedIn';
|
|
11
11
|
export { ConnectWhatsApp } from './components/ConnectWhatsApp';
|
|
12
12
|
export { DataUploadModal } from './components/DataUploadModal';
|
|
13
|
+
export { SettingsModal } from './components/SettingsModal';
|
|
13
14
|
// Hooks
|
|
14
15
|
export { useServiceConnection } from './hooks/useServiceConnection';
|
|
15
16
|
// Core API functions
|
package/dist/services/api.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Configure the backend URL for the knowledge base API.
|
|
3
|
-
* Call this before using any other functions if your backend is not at localhost:3000
|
|
4
|
-
*
|
|
2
|
+
* Configure the backend URL and optional headers for the knowledge base API.
|
|
3
|
+
* Call this before using any other functions if your backend is not at localhost:3000
|
|
4
|
+
* or if you need to pass specific API keys.
|
|
5
|
+
* @param options - The configuration options
|
|
5
6
|
*/
|
|
6
7
|
export declare const configure: (options: {
|
|
7
8
|
backendUrl: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
8
10
|
}) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Check if the API is configured (has a backend URL set).
|
|
13
|
+
*/
|
|
14
|
+
export declare const isConfigured: () => boolean;
|
|
9
15
|
export declare const startConnection: (appName: string, entityId: string) => Promise<any>;
|
|
10
16
|
export declare const executeAction: (prompt: string, entityId: string) => Promise<any>;
|
|
11
17
|
export declare const getConnectionStatus: (entityId: string) => Promise<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/services/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/services/api.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,SAK1F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,eAExB,CAAC;AASF,eAAO,MAAM,eAAe,GAAU,SAAS,MAAM,EAAE,UAAU,MAAM,iBAatE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,QAAQ,MAAM,EAAE,UAAU,MAAM,iBAanE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAU,UAAU,MAAM,iBAYzD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,SAAS,MAAM,EAAE,UAAU,MAAM,iBAapE,CAAC;AAEF,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,GACvB,aAAa,WAAW,GAAG,UAAU,EACrC,UAAU,MAAM,EAChB,MAAM,cAAc,iBAgCvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,UAAU,MAAM,EAAE,aAAa,MAAM,iBAY5E,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,aAAa,MAAM,EAAE,UAAU,MAAM,iBAezE,CAAC;AAMF,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAU,SAAS,mBAAmB,iBAM9D,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,SAAS,gBAAgB,iBAO3D,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAU,SAAS,iBAAiB,iBAM1D,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,sBAAsB,iBAOpE,CAAC"}
|
package/dist/services/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
let _backendUrl = null;
|
|
3
|
+
let _headers = {};
|
|
3
4
|
const getBackendUrl = () => {
|
|
4
5
|
if (_backendUrl)
|
|
5
6
|
return _backendUrl;
|
|
@@ -9,20 +10,34 @@ const getBackendUrl = () => {
|
|
|
9
10
|
});
|
|
10
11
|
};
|
|
11
12
|
/**
|
|
12
|
-
* Configure the backend URL for the knowledge base API.
|
|
13
|
-
* Call this before using any other functions if your backend is not at localhost:3000
|
|
14
|
-
*
|
|
13
|
+
* Configure the backend URL and optional headers for the knowledge base API.
|
|
14
|
+
* Call this before using any other functions if your backend is not at localhost:3000
|
|
15
|
+
* or if you need to pass specific API keys.
|
|
16
|
+
* @param options - The configuration options
|
|
15
17
|
*/
|
|
16
18
|
export const configure = (options) => {
|
|
17
19
|
_backendUrl = options.backendUrl;
|
|
20
|
+
if (options.headers) {
|
|
21
|
+
_headers = { ..._headers, ...options.headers };
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Check if the API is configured (has a backend URL set).
|
|
26
|
+
*/
|
|
27
|
+
export const isConfigured = () => {
|
|
28
|
+
return _backendUrl !== null;
|
|
29
|
+
};
|
|
30
|
+
const getHeaders = () => {
|
|
31
|
+
return {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
..._headers
|
|
34
|
+
};
|
|
18
35
|
};
|
|
19
36
|
export const startConnection = async (appName, entityId) => {
|
|
20
37
|
try {
|
|
21
38
|
const response = await fetch(`${getBackendUrl()}/start-connection`, {
|
|
22
39
|
method: 'POST',
|
|
23
|
-
headers:
|
|
24
|
-
'Content-Type': 'application/json',
|
|
25
|
-
},
|
|
40
|
+
headers: getHeaders(),
|
|
26
41
|
body: JSON.stringify({ appName, entityId }),
|
|
27
42
|
});
|
|
28
43
|
const data = await response.json();
|
|
@@ -37,9 +52,7 @@ export const executeAction = async (prompt, entityId) => {
|
|
|
37
52
|
try {
|
|
38
53
|
const response = await fetch(`${getBackendUrl()}/execute`, {
|
|
39
54
|
method: 'POST',
|
|
40
|
-
headers:
|
|
41
|
-
'Content-Type': 'application/json',
|
|
42
|
-
},
|
|
55
|
+
headers: getHeaders(),
|
|
43
56
|
body: JSON.stringify({ prompt, entityId }),
|
|
44
57
|
});
|
|
45
58
|
const data = await response.json();
|
|
@@ -52,7 +65,9 @@ export const executeAction = async (prompt, entityId) => {
|
|
|
52
65
|
};
|
|
53
66
|
export const getConnectionStatus = async (entityId) => {
|
|
54
67
|
try {
|
|
55
|
-
const response = await fetch(`${getBackendUrl()}/connection-status?entityId=${entityId}
|
|
68
|
+
const response = await fetch(`${getBackendUrl()}/connection-status?entityId=${entityId}`, {
|
|
69
|
+
headers: getHeaders()
|
|
70
|
+
});
|
|
56
71
|
if (!response.ok)
|
|
57
72
|
throw new Error('Failed to fetch status');
|
|
58
73
|
const data = await response.json();
|
|
@@ -67,9 +82,7 @@ export const disconnectApp = async (appName, entityId) => {
|
|
|
67
82
|
try {
|
|
68
83
|
const response = await fetch(`${getBackendUrl()}/disconnect`, {
|
|
69
84
|
method: 'POST',
|
|
70
|
-
headers:
|
|
71
|
-
'Content-Type': 'application/json',
|
|
72
|
-
},
|
|
85
|
+
headers: getHeaders(),
|
|
73
86
|
body: JSON.stringify({ appName, entityId }),
|
|
74
87
|
});
|
|
75
88
|
const data = await response.json();
|
|
@@ -148,7 +161,7 @@ export const searchPerson = async (options) => {
|
|
|
148
161
|
const { entityId, personName, context } = options;
|
|
149
162
|
const prompt = context
|
|
150
163
|
? `Search all my connected accounts for information about ${personName}. Context: ${context}`
|
|
151
|
-
: `Search all my connected accounts for information about ${personName}. Look for
|
|
164
|
+
: `Search all my connected accounts for information about ${personName}. Look for any relevant information that could help me understand my relationship with this person, their interests, and recent interactions.`;
|
|
152
165
|
return executeAction(prompt, entityId);
|
|
153
166
|
};
|
|
154
167
|
/**
|
|
@@ -158,7 +171,7 @@ export const searchPerson = async (options) => {
|
|
|
158
171
|
*/
|
|
159
172
|
export const getGiftIdeas = async (options) => {
|
|
160
173
|
const { entityId, personName, occasion, budget } = options;
|
|
161
|
-
let prompt = `Based on my
|
|
174
|
+
let prompt = `Based on my connected accounts, suggest gift ideas for ${personName}.`;
|
|
162
175
|
if (occasion)
|
|
163
176
|
prompt += ` The occasion is: ${occasion}.`;
|
|
164
177
|
if (budget)
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enhearten/knowledge-base-module",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Connect accounts (Gmail, GitHub, Discord, etc.) and search across them with AI to execute actions",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
10
11
|
"import": "./dist/index.mjs",
|
|
11
|
-
"require": "./dist/index.js"
|
|
12
|
-
"types": "./dist/index.d.ts"
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"./backend": {
|
|
15
15
|
"require": "./backend/app.js"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { Button, Card, Text, XStack, YStack, Spinner, Sheet } from 'tamagui';
|
|
2
|
+
import { Button, Card, Text, XStack, YStack, Spinner, Sheet, Theme } from 'tamagui';
|
|
3
3
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
4
4
|
import * as DocumentPicker from 'expo-document-picker';
|
|
5
5
|
import { uploadUserData } from '../services/api';
|
|
@@ -120,102 +120,104 @@ export const DataUploadModal: React.FC<DataUploadModalProps> = ({
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
return (
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<Sheet.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<Card bordered padding="$3" backgroundColor="$gray2">
|
|
145
|
-
<YStack space="$2">
|
|
146
|
-
<Text fontSize="$4" fontWeight="600">How to download your data:</Text>
|
|
147
|
-
{instructions.steps.map((step, index) => (
|
|
148
|
-
<XStack key={index} space="$2" alignItems="flex-start">
|
|
149
|
-
<Text fontSize="$3" color="$gray11" minWidth={20}>{index + 1}.</Text>
|
|
150
|
-
<Text fontSize="$3" color="$gray11" flex={1}>{step}</Text>
|
|
151
|
-
</XStack>
|
|
152
|
-
))}
|
|
153
|
-
</YStack>
|
|
154
|
-
</Card>
|
|
155
|
-
|
|
156
|
-
<YStack gap="$3">
|
|
157
|
-
<Button
|
|
158
|
-
size="$4"
|
|
159
|
-
onPress={handlePickFiles}
|
|
160
|
-
disabled={uploading}
|
|
161
|
-
icon={<MaterialCommunityIcons name="file-upload" size={20} color="white" />}
|
|
162
|
-
>
|
|
163
|
-
{selectedFiles.length > 0
|
|
164
|
-
? `${selectedFiles.length} file(s) selected`
|
|
165
|
-
: 'Select File(s)'}
|
|
166
|
-
</Button>
|
|
167
|
-
|
|
168
|
-
{selectedFiles.length > 0 && (
|
|
169
|
-
<Card bordered padding="$2" backgroundColor="$green2">
|
|
170
|
-
<YStack space="$1">
|
|
171
|
-
{selectedFiles.map((file, index) => (
|
|
172
|
-
<XStack key={index} alignItems="center" space="$2">
|
|
173
|
-
<MaterialCommunityIcons name="file-check" size={16} color="green" />
|
|
174
|
-
<Text fontSize="$2" flex={1} numberOfLines={1}>{file.name}</Text>
|
|
175
|
-
<Text fontSize="$1" color="$gray10">
|
|
176
|
-
{(file.size ? file.size / 1024 / 1024 : 0).toFixed(2)} MB
|
|
177
|
-
</Text>
|
|
178
|
-
</XStack>
|
|
179
|
-
))}
|
|
180
|
-
</YStack>
|
|
181
|
-
</Card>
|
|
182
|
-
)}
|
|
183
|
-
|
|
184
|
-
{error && (
|
|
185
|
-
<Card bordered padding="$2" backgroundColor="$red2" borderColor="$red8">
|
|
186
|
-
<Text color="$red10" fontSize="$3">{error}</Text>
|
|
187
|
-
</Card>
|
|
188
|
-
)}
|
|
189
|
-
|
|
190
|
-
{uploadProgress && (
|
|
191
|
-
<XStack alignItems="center" space="$2">
|
|
192
|
-
<Spinner size="small" />
|
|
193
|
-
<Text fontSize="$3" color="$gray10">{uploadProgress}</Text>
|
|
194
|
-
</XStack>
|
|
195
|
-
)}
|
|
123
|
+
<Theme name="light">
|
|
124
|
+
<Sheet
|
|
125
|
+
modal
|
|
126
|
+
open={open}
|
|
127
|
+
onOpenChange={(isOpen: boolean) => !isOpen && handleClose()}
|
|
128
|
+
snapPoints={[85]}
|
|
129
|
+
dismissOnSnapToBottom
|
|
130
|
+
>
|
|
131
|
+
<Sheet.Overlay />
|
|
132
|
+
<Sheet.Frame padding="$4" backgroundColor="white">
|
|
133
|
+
<Sheet.Handle />
|
|
134
|
+
|
|
135
|
+
<YStack space="$4" paddingTop="$4">
|
|
136
|
+
<XStack alignItems="center" space="$2">
|
|
137
|
+
<MaterialCommunityIcons
|
|
138
|
+
name={instructions.icon}
|
|
139
|
+
size={28}
|
|
140
|
+
color={serviceName === 'instagram' ? '#E4405F' : '#25D366'}
|
|
141
|
+
/>
|
|
142
|
+
<Text fontSize="$6" fontWeight="bold">{instructions.title}</Text>
|
|
143
|
+
</XStack>
|
|
196
144
|
|
|
197
|
-
<
|
|
145
|
+
<Card bordered padding="$3" backgroundColor="$gray2">
|
|
146
|
+
<YStack space="$2">
|
|
147
|
+
<Text fontSize="$4" fontWeight="600">How to download your data:</Text>
|
|
148
|
+
{instructions.steps.map((step, index) => (
|
|
149
|
+
<XStack key={index} space="$2" alignItems="flex-start">
|
|
150
|
+
<Text fontSize="$3" color="$gray11" minWidth={20}>{index + 1}.</Text>
|
|
151
|
+
<Text fontSize="$3" color="$gray11" flex={1}>{step}</Text>
|
|
152
|
+
</XStack>
|
|
153
|
+
))}
|
|
154
|
+
</YStack>
|
|
155
|
+
</Card>
|
|
156
|
+
|
|
157
|
+
<YStack gap="$3">
|
|
198
158
|
<Button
|
|
199
159
|
size="$4"
|
|
200
|
-
|
|
201
|
-
onPress={handleClose}
|
|
160
|
+
onPress={handlePickFiles}
|
|
202
161
|
disabled={uploading}
|
|
162
|
+
icon={<MaterialCommunityIcons name="file-upload" size={20} color="white" />}
|
|
203
163
|
>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
size="$4"
|
|
208
|
-
themeInverse
|
|
209
|
-
onPress={handleUpload}
|
|
210
|
-
disabled={uploading || selectedFiles.length === 0}
|
|
211
|
-
icon={uploading ? <Spinner size="small" color="white" /> : undefined}
|
|
212
|
-
>
|
|
213
|
-
{uploading ? 'Uploading...' : 'Upload Data'}
|
|
164
|
+
{selectedFiles.length > 0
|
|
165
|
+
? `${selectedFiles.length} file(s) selected`
|
|
166
|
+
: 'Select File(s)'}
|
|
214
167
|
</Button>
|
|
215
|
-
|
|
168
|
+
|
|
169
|
+
{selectedFiles.length > 0 && (
|
|
170
|
+
<Card bordered padding="$2" backgroundColor="$green2">
|
|
171
|
+
<YStack space="$1">
|
|
172
|
+
{selectedFiles.map((file, index) => (
|
|
173
|
+
<XStack key={index} alignItems="center" space="$2">
|
|
174
|
+
<MaterialCommunityIcons name="file-check" size={16} color="green" />
|
|
175
|
+
<Text fontSize="$2" flex={1} numberOfLines={1}>{file.name}</Text>
|
|
176
|
+
<Text fontSize="$1" color="$gray10">
|
|
177
|
+
{(file.size ? file.size / 1024 / 1024 : 0).toFixed(2)} MB
|
|
178
|
+
</Text>
|
|
179
|
+
</XStack>
|
|
180
|
+
))}
|
|
181
|
+
</YStack>
|
|
182
|
+
</Card>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{error && (
|
|
186
|
+
<Card bordered padding="$2" backgroundColor="$red2" borderColor="$red8">
|
|
187
|
+
<Text color="$red10" fontSize="$3">{error}</Text>
|
|
188
|
+
</Card>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{uploadProgress && (
|
|
192
|
+
<XStack alignItems="center" space="$2">
|
|
193
|
+
<Spinner size="small" />
|
|
194
|
+
<Text fontSize="$3" color="$gray10">{uploadProgress}</Text>
|
|
195
|
+
</XStack>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
<XStack gap="$3" justifyContent="flex-end">
|
|
199
|
+
<Button
|
|
200
|
+
size="$4"
|
|
201
|
+
variant="outlined"
|
|
202
|
+
onPress={handleClose}
|
|
203
|
+
disabled={uploading}
|
|
204
|
+
>
|
|
205
|
+
Cancel
|
|
206
|
+
</Button>
|
|
207
|
+
<Button
|
|
208
|
+
size="$4"
|
|
209
|
+
themeInverse
|
|
210
|
+
onPress={handleUpload}
|
|
211
|
+
disabled={uploading || selectedFiles.length === 0}
|
|
212
|
+
icon={uploading ? <Spinner size="small" color="white" /> : undefined}
|
|
213
|
+
>
|
|
214
|
+
{uploading ? 'Uploading...' : 'Upload Data'}
|
|
215
|
+
</Button>
|
|
216
|
+
</XStack>
|
|
217
|
+
</YStack>
|
|
216
218
|
</YStack>
|
|
217
|
-
</
|
|
218
|
-
</Sheet
|
|
219
|
-
</
|
|
219
|
+
</Sheet.Frame>
|
|
220
|
+
</Sheet>
|
|
221
|
+
</Theme>
|
|
220
222
|
);
|
|
221
223
|
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Button, Card, Input, Label, Sheet, Text, XStack, YStack, Theme, Spinner } from 'tamagui';
|
|
3
|
+
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
4
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
5
|
+
import { configure } from '../services/api';
|
|
6
|
+
|
|
7
|
+
interface SettingsModalProps {
|
|
8
|
+
open: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const SettingsModal: React.FC<SettingsModalProps> = ({ open, onClose }) => {
|
|
13
|
+
const [backendUrl, setBackendUrl] = useState('');
|
|
14
|
+
const [composioKey, setComposioKey] = useState('');
|
|
15
|
+
const [googleKey, setGoogleKey] = useState('');
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
const [saving, setSaving] = useState(false);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (open) {
|
|
21
|
+
loadSettings();
|
|
22
|
+
}
|
|
23
|
+
}, [open]);
|
|
24
|
+
|
|
25
|
+
const loadSettings = async () => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
try {
|
|
28
|
+
const storedUrl = await AsyncStorage.getItem('kb_backend_url');
|
|
29
|
+
const storedComposioKey = await AsyncStorage.getItem('kb_composio_key');
|
|
30
|
+
const storedGoogleKey = await AsyncStorage.getItem('kb_google_key');
|
|
31
|
+
|
|
32
|
+
if (storedUrl) setBackendUrl(storedUrl);
|
|
33
|
+
if (storedComposioKey) setComposioKey(storedComposioKey);
|
|
34
|
+
if (storedGoogleKey) setGoogleKey(storedGoogleKey);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Failed to load settings', error);
|
|
37
|
+
} finally {
|
|
38
|
+
setLoading(false);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleSave = async () => {
|
|
43
|
+
setSaving(true);
|
|
44
|
+
try {
|
|
45
|
+
// Remove trailing slash from URL if present
|
|
46
|
+
const cleanUrl = backendUrl.replace(/\/$/, '');
|
|
47
|
+
|
|
48
|
+
await AsyncStorage.setItem('kb_backend_url', cleanUrl);
|
|
49
|
+
await AsyncStorage.setItem('kb_composio_key', composioKey);
|
|
50
|
+
await AsyncStorage.setItem('kb_google_key', googleKey);
|
|
51
|
+
|
|
52
|
+
configure({
|
|
53
|
+
backendUrl: cleanUrl,
|
|
54
|
+
headers: {
|
|
55
|
+
'x-composio-key': composioKey,
|
|
56
|
+
'x-google-key': googleKey
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
onClose();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Failed to save settings', error);
|
|
63
|
+
} finally {
|
|
64
|
+
setSaving(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Theme name="light">
|
|
70
|
+
<Sheet
|
|
71
|
+
modal
|
|
72
|
+
open={open}
|
|
73
|
+
onOpenChange={(isOpen: boolean) => !isOpen && onClose()}
|
|
74
|
+
snapPoints={[85]}
|
|
75
|
+
dismissOnSnapToBottom
|
|
76
|
+
>
|
|
77
|
+
<Sheet.Overlay />
|
|
78
|
+
<Sheet.Frame padding="$4" backgroundColor="white">
|
|
79
|
+
<Sheet.Handle />
|
|
80
|
+
|
|
81
|
+
<YStack space="$4" paddingTop="$4">
|
|
82
|
+
<XStack alignItems="center" space="$2">
|
|
83
|
+
<MaterialCommunityIcons name="cog" size={28} color="#333" />
|
|
84
|
+
<Text fontSize="$6" fontWeight="bold">Settings</Text>
|
|
85
|
+
</XStack>
|
|
86
|
+
|
|
87
|
+
<Text color="$gray11">
|
|
88
|
+
Configure your Cloudflare Worker URL and API keys.
|
|
89
|
+
</Text>
|
|
90
|
+
|
|
91
|
+
{loading ? (
|
|
92
|
+
<Spinner size="large" color="$blue10" />
|
|
93
|
+
) : (
|
|
94
|
+
<YStack space="$4">
|
|
95
|
+
<YStack space="$2">
|
|
96
|
+
<Label htmlFor="backendUrl" fontWeight="600">Backend URL (Cloudflare Worker)</Label>
|
|
97
|
+
<Input
|
|
98
|
+
id="backendUrl"
|
|
99
|
+
placeholder="https://knowledge-base-worker.your-subdomain.workers.dev"
|
|
100
|
+
value={backendUrl}
|
|
101
|
+
onChangeText={setBackendUrl}
|
|
102
|
+
autoCapitalize="none"
|
|
103
|
+
autoCorrect={false}
|
|
104
|
+
/>
|
|
105
|
+
</YStack>
|
|
106
|
+
|
|
107
|
+
<YStack space="$2">
|
|
108
|
+
<Label htmlFor="composioKey" fontWeight="600">Composio API Key</Label>
|
|
109
|
+
<Input
|
|
110
|
+
id="composioKey"
|
|
111
|
+
placeholder="Enter your Composio API Key"
|
|
112
|
+
value={composioKey}
|
|
113
|
+
onChangeText={setComposioKey}
|
|
114
|
+
secureTextEntry
|
|
115
|
+
/>
|
|
116
|
+
</YStack>
|
|
117
|
+
|
|
118
|
+
<YStack space="$2">
|
|
119
|
+
<Label htmlFor="googleKey" fontWeight="600">Google Gemini API Key</Label>
|
|
120
|
+
<Input
|
|
121
|
+
id="googleKey"
|
|
122
|
+
placeholder="Enter your Google Gemini API Key"
|
|
123
|
+
value={googleKey}
|
|
124
|
+
onChangeText={setGoogleKey}
|
|
125
|
+
secureTextEntry
|
|
126
|
+
/>
|
|
127
|
+
</YStack>
|
|
128
|
+
|
|
129
|
+
<XStack gap="$3" justifyContent="flex-end" marginTop="$4">
|
|
130
|
+
<Button
|
|
131
|
+
size="$4"
|
|
132
|
+
variant="outlined"
|
|
133
|
+
onPress={onClose}
|
|
134
|
+
disabled={saving}
|
|
135
|
+
>
|
|
136
|
+
Cancel
|
|
137
|
+
</Button>
|
|
138
|
+
<Button
|
|
139
|
+
size="$4"
|
|
140
|
+
themeInverse
|
|
141
|
+
onPress={handleSave}
|
|
142
|
+
disabled={saving}
|
|
143
|
+
icon={saving ? <Spinner size="small" color="white" /> : undefined}
|
|
144
|
+
>
|
|
145
|
+
{saving ? 'Saving...' : 'Save Settings'}
|
|
146
|
+
</Button>
|
|
147
|
+
</XStack>
|
|
148
|
+
</YStack>
|
|
149
|
+
)}
|
|
150
|
+
</YStack>
|
|
151
|
+
</Sheet.Frame>
|
|
152
|
+
</Sheet>
|
|
153
|
+
</Theme>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { Linking } from 'react-native';
|
|
3
|
-
import { startConnection, getConnectionStatus, disconnectApp } from '../services/api';
|
|
2
|
+
import { Linking, Alert } from 'react-native';
|
|
3
|
+
import { startConnection, getConnectionStatus, disconnectApp, isConfigured } from '../services/api';
|
|
4
4
|
|
|
5
5
|
export const useServiceConnection = (appName: string, entityId: string, searchTerms: string[] = [appName]) => {
|
|
6
6
|
const [loading, setLoading] = useState(false);
|
|
7
7
|
const [disconnecting, setDisconnecting] = useState(false);
|
|
8
8
|
const [connected, setConnected] = useState(false);
|
|
9
|
+
const [error, setError] = useState<string | null>(null);
|
|
9
10
|
const pollingInterval = useRef<NodeJS.Timeout | null>(null);
|
|
10
11
|
|
|
11
12
|
const checkStatus = async () => {
|
|
13
|
+
if (!isConfigured()) return false;
|
|
12
14
|
try {
|
|
13
15
|
const data = await getConnectionStatus(entityId);
|
|
14
16
|
const isConnected = data.connectedApps && data.connectedApps.some((a: string) =>
|
|
@@ -32,8 +34,15 @@ export const useServiceConnection = (appName: string, entityId: string, searchTe
|
|
|
32
34
|
}, [entityId]);
|
|
33
35
|
|
|
34
36
|
const handleConnect = async () => {
|
|
37
|
+
if (!isConfigured()) {
|
|
38
|
+
setError('Configuration missing. Please set up the API URL and keys.');
|
|
39
|
+
Alert.alert('Configuration Missing', 'Please configure the backend URL and API keys in settings.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
try {
|
|
36
44
|
setLoading(true);
|
|
45
|
+
setError(null);
|
|
37
46
|
const data = await startConnection(appName, entityId);
|
|
38
47
|
if (data.url) await Linking.openURL(data.url);
|
|
39
48
|
|
|
@@ -57,6 +66,7 @@ export const useServiceConnection = (appName: string, entityId: string, searchTe
|
|
|
57
66
|
} catch (error) {
|
|
58
67
|
console.error(error);
|
|
59
68
|
console.error(`Failed to connect ${appName}`);
|
|
69
|
+
setError(`Failed to connect ${appName}`);
|
|
60
70
|
setLoading(false);
|
|
61
71
|
}
|
|
62
72
|
};
|
|
@@ -70,16 +80,18 @@ export const useServiceConnection = (appName: string, entityId: string, searchTe
|
|
|
70
80
|
};
|
|
71
81
|
|
|
72
82
|
const handleDisconnect = async () => {
|
|
83
|
+
if (!isConfigured()) return;
|
|
73
84
|
try {
|
|
74
85
|
setDisconnecting(true);
|
|
75
86
|
await disconnectApp(appName, entityId);
|
|
76
87
|
setConnected(false);
|
|
77
88
|
} catch (error) {
|
|
78
89
|
console.error(`Failed to disconnect ${appName}:`, error);
|
|
90
|
+
setError(`Failed to disconnect ${appName}`);
|
|
79
91
|
} finally {
|
|
80
92
|
setDisconnecting(false);
|
|
81
93
|
}
|
|
82
94
|
};
|
|
83
95
|
|
|
84
|
-
return { connected, loading, disconnecting, handleConnect, handleCancel, handleDisconnect };
|
|
96
|
+
return { connected, loading, disconnecting, error, handleConnect, handleCancel, handleDisconnect };
|
|
85
97
|
};
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { ConnectInstagram } from './components/ConnectInstagram';
|
|
|
10
10
|
export { ConnectLinkedIn } from './components/ConnectLinkedIn';
|
|
11
11
|
export { ConnectWhatsApp } from './components/ConnectWhatsApp';
|
|
12
12
|
export { DataUploadModal } from './components/DataUploadModal';
|
|
13
|
+
export { SettingsModal } from './components/SettingsModal';
|
|
13
14
|
|
|
14
15
|
// Hooks
|
|
15
16
|
export { useServiceConnection } from './hooks/useServiceConnection';
|
package/src/services/api.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
|
|
3
3
|
let _backendUrl: string | null = null;
|
|
4
|
+
let _headers: Record<string, string> = {};
|
|
4
5
|
|
|
5
6
|
const getBackendUrl = () => {
|
|
6
7
|
if (_backendUrl) return _backendUrl;
|
|
@@ -11,21 +12,37 @@ const getBackendUrl = () => {
|
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* Configure the backend URL for the knowledge base API.
|
|
15
|
-
* Call this before using any other functions if your backend is not at localhost:3000
|
|
16
|
-
*
|
|
15
|
+
* Configure the backend URL and optional headers for the knowledge base API.
|
|
16
|
+
* Call this before using any other functions if your backend is not at localhost:3000
|
|
17
|
+
* or if you need to pass specific API keys.
|
|
18
|
+
* @param options - The configuration options
|
|
17
19
|
*/
|
|
18
|
-
export const configure = (options: { backendUrl: string }) => {
|
|
20
|
+
export const configure = (options: { backendUrl: string; headers?: Record<string, string> }) => {
|
|
19
21
|
_backendUrl = options.backendUrl;
|
|
22
|
+
if (options.headers) {
|
|
23
|
+
_headers = { ..._headers, ...options.headers };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if the API is configured (has a backend URL set).
|
|
29
|
+
*/
|
|
30
|
+
export const isConfigured = () => {
|
|
31
|
+
return _backendUrl !== null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getHeaders = () => {
|
|
35
|
+
return {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
..._headers
|
|
38
|
+
};
|
|
20
39
|
};
|
|
21
40
|
|
|
22
41
|
export const startConnection = async (appName: string, entityId: string) => {
|
|
23
42
|
try {
|
|
24
43
|
const response = await fetch(`${getBackendUrl()}/start-connection`, {
|
|
25
44
|
method: 'POST',
|
|
26
|
-
headers:
|
|
27
|
-
'Content-Type': 'application/json',
|
|
28
|
-
},
|
|
45
|
+
headers: getHeaders(),
|
|
29
46
|
body: JSON.stringify({ appName, entityId }),
|
|
30
47
|
});
|
|
31
48
|
const data = await response.json();
|
|
@@ -40,9 +57,7 @@ export const executeAction = async (prompt: string, entityId: string) => {
|
|
|
40
57
|
try {
|
|
41
58
|
const response = await fetch(`${getBackendUrl()}/execute`, {
|
|
42
59
|
method: 'POST',
|
|
43
|
-
headers:
|
|
44
|
-
'Content-Type': 'application/json',
|
|
45
|
-
},
|
|
60
|
+
headers: getHeaders(),
|
|
46
61
|
body: JSON.stringify({ prompt, entityId }),
|
|
47
62
|
});
|
|
48
63
|
const data = await response.json();
|
|
@@ -55,7 +70,9 @@ export const executeAction = async (prompt: string, entityId: string) => {
|
|
|
55
70
|
|
|
56
71
|
export const getConnectionStatus = async (entityId: string) => {
|
|
57
72
|
try {
|
|
58
|
-
const response = await fetch(`${getBackendUrl()}/connection-status?entityId=${entityId}
|
|
73
|
+
const response = await fetch(`${getBackendUrl()}/connection-status?entityId=${entityId}`, {
|
|
74
|
+
headers: getHeaders()
|
|
75
|
+
});
|
|
59
76
|
if (!response.ok) throw new Error('Failed to fetch status');
|
|
60
77
|
const data = await response.json();
|
|
61
78
|
return data;
|
|
@@ -69,9 +86,7 @@ export const disconnectApp = async (appName: string, entityId: string) => {
|
|
|
69
86
|
try {
|
|
70
87
|
const response = await fetch(`${getBackendUrl()}/disconnect`, {
|
|
71
88
|
method: 'POST',
|
|
72
|
-
headers:
|
|
73
|
-
'Content-Type': 'application/json',
|
|
74
|
-
},
|
|
89
|
+
headers: getHeaders(),
|
|
75
90
|
body: JSON.stringify({ appName, entityId }),
|
|
76
91
|
});
|
|
77
92
|
const data = await response.json();
|
|
@@ -176,7 +191,7 @@ export const searchPerson = async (options: SearchPersonOptions) => {
|
|
|
176
191
|
const { entityId, personName, context } = options;
|
|
177
192
|
const prompt = context
|
|
178
193
|
? `Search all my connected accounts for information about ${personName}. Context: ${context}`
|
|
179
|
-
: `Search all my connected accounts for information about ${personName}. Look for
|
|
194
|
+
: `Search all my connected accounts for information about ${personName}. Look for any relevant information that could help me understand my relationship with this person, their interests, and recent interactions.`;
|
|
180
195
|
return executeAction(prompt, entityId);
|
|
181
196
|
};
|
|
182
197
|
|
|
@@ -194,7 +209,7 @@ export interface GiftIdeasOptions {
|
|
|
194
209
|
*/
|
|
195
210
|
export const getGiftIdeas = async (options: GiftIdeasOptions) => {
|
|
196
211
|
const { entityId, personName, occasion, budget } = options;
|
|
197
|
-
let prompt = `Based on my
|
|
212
|
+
let prompt = `Based on my connected accounts, suggest gift ideas for ${personName}.`;
|
|
198
213
|
if (occasion) prompt += ` The occasion is: ${occasion}.`;
|
|
199
214
|
if (budget) prompt += ` Budget: ${budget}.`;
|
|
200
215
|
prompt += ` Look for their interests, hobbies, recent conversations, and anything that might hint at what they would appreciate.`;
|