@enhearten/knowledge-base-module 1.0.2 → 1.0.4
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/backend/.env +1 -1
- 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 +38 -5
- 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 +27 -14
- package/package.json +3 -3
- package/src/components/SettingsModal.tsx +155 -0
- package/src/hooks/useServiceConnection.ts +36 -4
- package/src/index.ts +1 -0
- package/src/services/api.ts +30 -15
package/backend/.env
CHANGED
|
@@ -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;;;;;;;;CAgHxG,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,11 +31,34 @@ 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
|
+
}
|
|
39
|
+
// SAFARI FIX: Open window immediately while still in user gesture context
|
|
40
|
+
// This must happen BEFORE any async operations to avoid popup blocker
|
|
41
|
+
let popup = null;
|
|
42
|
+
if (typeof window !== 'undefined') {
|
|
43
|
+
popup = window.open('about:blank', '_blank');
|
|
44
|
+
}
|
|
31
45
|
try {
|
|
32
46
|
setLoading(true);
|
|
47
|
+
setError(null);
|
|
33
48
|
const data = await startConnection(appName, entityId);
|
|
34
|
-
if (data.url)
|
|
35
|
-
|
|
49
|
+
if (data.url) {
|
|
50
|
+
// Redirect the pre-opened popup, or fallback to Linking
|
|
51
|
+
if (popup) {
|
|
52
|
+
popup.location.href = data.url;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await Linking.openURL(data.url);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (popup) {
|
|
59
|
+
// Close popup if no URL was returned
|
|
60
|
+
popup.close();
|
|
61
|
+
}
|
|
36
62
|
// Start polling for connection status
|
|
37
63
|
let attempts = 0;
|
|
38
64
|
const maxAttempts = 60; // 2 minutes approx (2s interval)
|
|
@@ -52,8 +78,12 @@ export const useServiceConnection = (appName, entityId, searchTerms = [appName])
|
|
|
52
78
|
}, 2000);
|
|
53
79
|
}
|
|
54
80
|
catch (error) {
|
|
81
|
+
// Close the pre-opened popup on error
|
|
82
|
+
if (popup)
|
|
83
|
+
popup.close();
|
|
55
84
|
console.error(error);
|
|
56
85
|
console.error(`Failed to connect ${appName}`);
|
|
86
|
+
setError(`Failed to connect ${appName}`);
|
|
57
87
|
setLoading(false);
|
|
58
88
|
}
|
|
59
89
|
};
|
|
@@ -65,6 +95,8 @@ export const useServiceConnection = (appName, entityId, searchTerms = [appName])
|
|
|
65
95
|
setLoading(false);
|
|
66
96
|
};
|
|
67
97
|
const handleDisconnect = async () => {
|
|
98
|
+
if (!isConfigured())
|
|
99
|
+
return;
|
|
68
100
|
try {
|
|
69
101
|
setDisconnecting(true);
|
|
70
102
|
await disconnectApp(appName, entityId);
|
|
@@ -72,10 +104,11 @@ export const useServiceConnection = (appName, entityId, searchTerms = [appName])
|
|
|
72
104
|
}
|
|
73
105
|
catch (error) {
|
|
74
106
|
console.error(`Failed to disconnect ${appName}:`, error);
|
|
107
|
+
setError(`Failed to disconnect ${appName}`);
|
|
75
108
|
}
|
|
76
109
|
finally {
|
|
77
110
|
setDisconnecting(false);
|
|
78
111
|
}
|
|
79
112
|
};
|
|
80
|
-
return { connected, loading, disconnecting, handleConnect, handleCancel, handleDisconnect };
|
|
113
|
+
return { connected, loading, disconnecting, error, handleConnect, handleCancel, handleDisconnect };
|
|
81
114
|
};
|
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();
|
|
@@ -163,7 +176,7 @@ export const getGiftIdeas = async (options) => {
|
|
|
163
176
|
prompt += ` The occasion is: ${occasion}.`;
|
|
164
177
|
if (budget)
|
|
165
178
|
prompt += ` Budget: ${budget}.`;
|
|
166
|
-
prompt +=
|
|
179
|
+
prompt += `.`;
|
|
167
180
|
return executeAction(prompt, entityId);
|
|
168
181
|
};
|
|
169
182
|
/**
|
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.4",
|
|
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"
|
|
@@ -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,10 +34,35 @@ 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
|
+
|
|
43
|
+
// SAFARI FIX: Open window immediately while still in user gesture context
|
|
44
|
+
// This must happen BEFORE any async operations to avoid popup blocker
|
|
45
|
+
let popup: Window | null = null;
|
|
46
|
+
if (typeof window !== 'undefined') {
|
|
47
|
+
popup = window.open('about:blank', '_blank');
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
try {
|
|
36
51
|
setLoading(true);
|
|
52
|
+
setError(null);
|
|
37
53
|
const data = await startConnection(appName, entityId);
|
|
38
|
-
|
|
54
|
+
|
|
55
|
+
if (data.url) {
|
|
56
|
+
// Redirect the pre-opened popup, or fallback to Linking
|
|
57
|
+
if (popup) {
|
|
58
|
+
popup.location.href = data.url;
|
|
59
|
+
} else {
|
|
60
|
+
await Linking.openURL(data.url);
|
|
61
|
+
}
|
|
62
|
+
} else if (popup) {
|
|
63
|
+
// Close popup if no URL was returned
|
|
64
|
+
popup.close();
|
|
65
|
+
}
|
|
39
66
|
|
|
40
67
|
// Start polling for connection status
|
|
41
68
|
let attempts = 0;
|
|
@@ -55,8 +82,11 @@ export const useServiceConnection = (appName: string, entityId: string, searchTe
|
|
|
55
82
|
}, 2000);
|
|
56
83
|
|
|
57
84
|
} catch (error) {
|
|
85
|
+
// Close the pre-opened popup on error
|
|
86
|
+
if (popup) popup.close();
|
|
58
87
|
console.error(error);
|
|
59
88
|
console.error(`Failed to connect ${appName}`);
|
|
89
|
+
setError(`Failed to connect ${appName}`);
|
|
60
90
|
setLoading(false);
|
|
61
91
|
}
|
|
62
92
|
};
|
|
@@ -70,16 +100,18 @@ export const useServiceConnection = (appName: string, entityId: string, searchTe
|
|
|
70
100
|
};
|
|
71
101
|
|
|
72
102
|
const handleDisconnect = async () => {
|
|
103
|
+
if (!isConfigured()) return;
|
|
73
104
|
try {
|
|
74
105
|
setDisconnecting(true);
|
|
75
106
|
await disconnectApp(appName, entityId);
|
|
76
107
|
setConnected(false);
|
|
77
108
|
} catch (error) {
|
|
78
109
|
console.error(`Failed to disconnect ${appName}:`, error);
|
|
110
|
+
setError(`Failed to disconnect ${appName}`);
|
|
79
111
|
} finally {
|
|
80
112
|
setDisconnecting(false);
|
|
81
113
|
}
|
|
82
114
|
};
|
|
83
115
|
|
|
84
|
-
return { connected, loading, disconnecting, handleConnect, handleCancel, handleDisconnect };
|
|
116
|
+
return { connected, loading, disconnecting, error, handleConnect, handleCancel, handleDisconnect };
|
|
85
117
|
};
|
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();
|
|
@@ -197,7 +212,7 @@ export const getGiftIdeas = async (options: GiftIdeasOptions) => {
|
|
|
197
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
|
-
prompt +=
|
|
215
|
+
prompt += `.`;
|
|
201
216
|
return executeAction(prompt, entityId);
|
|
202
217
|
};
|
|
203
218
|
|