@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.
@@ -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,CA6K1D,CAAC"}
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(Sheet, { modal: true, open: open, onOpenChange: (isOpen) => !isOpen && handleClose(), snapPoints: [85], dismissOnSnapToBottom: true },
100
- React.createElement(Sheet.Overlay, null),
101
- React.createElement(Sheet.Frame, { padding: "$4", backgroundColor: "white" },
102
- React.createElement(Sheet.Handle, null),
103
- React.createElement(YStack, { space: "$4", paddingTop: "$4" },
104
- React.createElement(XStack, { alignItems: "center", space: "$2" },
105
- React.createElement(MaterialCommunityIcons, { name: instructions.icon, size: 28, color: serviceName === 'instagram' ? '#E4405F' : '#25D366' }),
106
- React.createElement(Text, { fontSize: "$6", fontWeight: "bold" }, instructions.title)),
107
- React.createElement(Card, { bordered: true, padding: "$3", backgroundColor: "$gray2" },
108
- React.createElement(YStack, { space: "$2" },
109
- React.createElement(Text, { fontSize: "$4", fontWeight: "600" }, "How to download your data:"),
110
- instructions.steps.map((step, index) => (React.createElement(XStack, { key: index, space: "$2", alignItems: "flex-start" },
111
- React.createElement(Text, { fontSize: "$3", color: "$gray11", minWidth: 20 },
112
- index + 1,
113
- "."),
114
- React.createElement(Text, { fontSize: "$3", color: "$gray11", flex: 1 }, step)))))),
115
- React.createElement(YStack, { gap: "$3" },
116
- React.createElement(Button, { size: "$4", onPress: handlePickFiles, disabled: uploading, icon: React.createElement(MaterialCommunityIcons, { name: "file-upload", size: 20, color: "white" }) }, selectedFiles.length > 0
117
- ? `${selectedFiles.length} file(s) selected`
118
- : 'Select File(s)'),
119
- selectedFiles.length > 0 && (React.createElement(Card, { bordered: true, padding: "$2", backgroundColor: "$green2" },
120
- React.createElement(YStack, { space: "$1" }, selectedFiles.map((file, index) => (React.createElement(XStack, { key: index, alignItems: "center", space: "$2" },
121
- React.createElement(MaterialCommunityIcons, { name: "file-check", size: 16, color: "green" }),
122
- React.createElement(Text, { fontSize: "$2", flex: 1, numberOfLines: 1 }, file.name),
123
- React.createElement(Text, { fontSize: "$1", color: "$gray10" },
124
- (file.size ? file.size / 1024 / 1024 : 0).toFixed(2),
125
- " MB"))))))),
126
- error && (React.createElement(Card, { bordered: true, padding: "$2", backgroundColor: "$red2", borderColor: "$red8" },
127
- React.createElement(Text, { color: "$red10", fontSize: "$3" }, error))),
128
- uploadProgress && (React.createElement(XStack, { alignItems: "center", space: "$2" },
129
- React.createElement(Spinner, { size: "small" }),
130
- React.createElement(Text, { fontSize: "$3", color: "$gray10" }, uploadProgress))),
131
- React.createElement(XStack, { gap: "$3", justifyContent: "flex-end" },
132
- React.createElement(Button, { size: "$4", variant: "outlined", onPress: handleClose, disabled: uploading }, "Cancel"),
133
- 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')))))));
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,8 @@
1
+ import React from 'react';
2
+ interface SettingsModalProps {
3
+ open: boolean;
4
+ onClose: () => void;
5
+ }
6
+ export declare const SettingsModal: React.FC<SettingsModalProps>;
7
+ export {};
8
+ //# sourceMappingURL=SettingsModal.d.ts.map
@@ -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;;;;;;;CAgFxG,CAAC"}
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';
@@ -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;AAG/D,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"}
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
@@ -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
- * @param url - The base URL of the backend (e.g., 'https://api.example.com')
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":"AAYA;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,SAExD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,SAAS,MAAM,EAAE,UAAU,MAAM,iBAetE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,QAAQ,MAAM,EAAE,UAAU,MAAM,iBAenE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAU,UAAU,MAAM,iBAUzD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,SAAS,MAAM,EAAE,UAAU,MAAM,iBAepE,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"}
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"}
@@ -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
- * @param url - The base URL of the backend (e.g., 'https://api.example.com')
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 emails, messages, shared documents, or any other relevant information that could help me understand my relationship with this person, their interests, and recent interactions.`;
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 emails, messages, and other connected accounts, suggest gift ideas for ${personName}.`;
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.1",
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
- <Sheet
124
- modal
125
- open={open}
126
- onOpenChange={(isOpen: boolean) => !isOpen && handleClose()}
127
- snapPoints={[85]}
128
- dismissOnSnapToBottom
129
- >
130
- <Sheet.Overlay />
131
- <Sheet.Frame padding="$4" backgroundColor="white">
132
- <Sheet.Handle />
133
-
134
- <YStack space="$4" paddingTop="$4">
135
- <XStack alignItems="center" space="$2">
136
- <MaterialCommunityIcons
137
- name={instructions.icon}
138
- size={28}
139
- color={serviceName === 'instagram' ? '#E4405F' : '#25D366'}
140
- />
141
- <Text fontSize="$6" fontWeight="bold">{instructions.title}</Text>
142
- </XStack>
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
- <XStack gap="$3" justifyContent="flex-end">
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
- variant="outlined"
201
- onPress={handleClose}
160
+ onPress={handlePickFiles}
202
161
  disabled={uploading}
162
+ icon={<MaterialCommunityIcons name="file-upload" size={20} color="white" />}
203
163
  >
204
- Cancel
205
- </Button>
206
- <Button
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
- </XStack>
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
- </YStack>
218
- </Sheet.Frame>
219
- </Sheet>
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';
@@ -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
- * @param url - The base URL of the backend (e.g., 'https://api.example.com')
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 emails, messages, shared documents, or any other relevant information that could help me understand my relationship with this person, their interests, and recent interactions.`;
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 emails, messages, and other connected accounts, suggest gift ideas for ${personName}.`;
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.`;