UNPKG

@jeanmemory/react

Version:

React SDK for Jean Memory - Build personalized AI chatbots in 5 lines of code

225 lines (224 loc) 8.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JeanProvider = JeanProvider; exports.useJean = useJean; const jsx_runtime_1 = require("react/jsx-runtime"); /** * Jean Memory React SDK - Provider Component * Manages authentication state and API client */ const react_1 = require("react"); const config_1 = require("./config"); const mcp_1 = require("./mcp"); const JEAN_API_BASE_OLD = 'https://jean-memory-api-virginia.onrender.com'; const JeanContext = (0, react_1.createContext)(null); // Generate code verifier and challenge for PKCE function generatePKCE() { const verifier = generateRandomString(128); const encoder = new TextEncoder(); const data = encoder.encode(verifier); return crypto.subtle.digest('SHA-256', data).then(digest => { const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/=/g, '') .replace(/\+/g, '-') .replace(/\//g, '_'); return { verifier, challenge }; }); } function generateRandomString(length) { const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; const values = crypto.getRandomValues(new Uint8Array(length)); return Array.from(values) .map(x => charset[x % charset.length]) .join(''); } function JeanProvider({ apiKey, children }) { const [user, setUser] = (0, react_1.useState)(null); const [messages, setMessages] = (0, react_1.useState)([]); const [isLoading, setIsLoading] = (0, react_1.useState)(false); const [rawError, setRawError] = (0, react_1.useState)(null); // Validate API key on mount (0, react_1.useEffect)(() => { if (!apiKey) { setRawError('API key is required'); return; } if (!apiKey.startsWith('jean_sk_')) { setRawError('Invalid API key format'); return; } // API key will be validated on first request console.log('✅ Jean Memory SDK initialized'); }, [apiKey]); // Load user from localStorage on mount (0, react_1.useEffect)(() => { const storedUser = localStorage.getItem('jean_user'); if (storedUser) { try { setUser(JSON.parse(storedUser)); } catch (e) { localStorage.removeItem('jean_user'); } } }, []); const sendMessage = async (message, options = {}) => { if (!user) { throw new Error('User not authenticated'); } setIsLoading(true); setRawError(null); try { // Add user message to conversation const userMessage = { id: Date.now().toString(), role: 'user', content: message, timestamp: new Date() }; setMessages(prev => [...prev, userMessage]); const response = await (0, mcp_1.makeMCPRequest)(user, apiKey, options.tool || 'jean_memory', { user_message: message, is_new_conversation: messages.length <= 1, needs_context: true }); if (response.error) { throw new Error(response.error.message); } const assistantMessage = { id: (Date.now() + 1).toString(), role: 'assistant', content: response.result?.content?.[0]?.text || 'I understood and saved that information.', timestamp: new Date() }; setMessages(prev => [...prev, assistantMessage]); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to send message'; setRawError(errorMessage); throw err; } finally { setIsLoading(false); } }; const storeDocument = async (title, content) => { if (!user) { throw new Error("User not authenticated"); } const response = await (0, mcp_1.makeMCPRequest)(user, apiKey, 'store_document', { title, content, document_type: 'markdown' }); if (response.error) { throw new Error(response.error.message); } }; const connect = (service) => { if (!user) { throw new Error('User not authenticated'); } // Open OAuth flow for external service integration const integrationUrl = `${config_1.JEAN_API_BASE}/api/v1/integrations/${service}/connect?user_token=${user.access_token}`; // Open in new window for better UX const popup = window.open(integrationUrl, `connect-${service}`, 'width=600,height=700,scrollbars=yes,resizable=yes'); // Listen for completion const checkClosed = setInterval(() => { if (popup?.closed) { clearInterval(checkClosed); console.log(`${service} integration window closed`); // Could refresh integrations list here } }, 1000); }; const clearConversation = () => { setMessages([]); }; // Direct memory tools const tools = { add_memory: async (content) => { if (!user) { throw new Error('User not authenticated'); } const response = await (0, mcp_1.makeMCPRequest)(user, apiKey, 'add_memory', { content }); if (response.error) { throw new Error(response.error.message); } return response.result; }, search_memory: async (query) => { if (!user) { throw new Error('User not authenticated'); } const response = await (0, mcp_1.makeMCPRequest)(user, apiKey, 'search_memory', { query }); if (response.error) { throw new Error(response.error.message); } return response.result; } }; const signIn = async () => { setIsLoading(true); try { // Generate PKCE parameters const { verifier, challenge } = await generatePKCE(); const state = generateRandomString(32); // Store for callback sessionStorage.setItem('jean_oauth_state', state); sessionStorage.setItem('jean_oauth_verifier', verifier); // Build OAuth URL const params = new URLSearchParams({ response_type: 'code', client_id: apiKey || 'default_client', redirect_uri: window.location.origin + window.location.pathname, state, code_challenge: challenge, code_challenge_method: 'S256', scope: 'read write' }); // Redirect to OAuth provider window.location.href = `${config_1.JEAN_API_BASE}/oauth/authorize?${params.toString()}`; } catch (error) { setIsLoading(false); const errorMessage = error instanceof Error ? error.message : 'Sign in failed'; setRawError(errorMessage); } }; const handleSetUser = (newUser) => { setUser(newUser); localStorage.setItem('jean_user', JSON.stringify(newUser)); }; const signOut = () => { setUser(null); setMessages([]); localStorage.removeItem('jean_user'); }; const contextValue = { // Essential state isAuthenticated: !!user, isLoading, user, messages, error: rawError, // Essential methods signIn, signOut, sendMessage, storeDocument, connect, clearConversation, setUser: handleSetUser, // Tools tools }; return ((0, jsx_runtime_1.jsx)(JeanContext.Provider, { value: contextValue, children: children })); } function useJean() { const context = (0, react_1.useContext)(JeanContext); if (!context) { throw new Error('useJean must be used within a JeanProvider'); } return context; }