UNPKG

@jeanmemory/react

Version:

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

846 lines (845 loc) 34.5 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const jsxRuntime = require("react/jsx-runtime"); const react = require("react"); const JEAN_API_BASE = "https://jean-memory-api-virginia.onrender.com"; const JEAN_OAUTH_BASE = "https://jeanmemory.com"; let requestId = 0; async function makeMCPRequest(user, apiKey, toolName, arguments_, clientName = "react-sdk") { const id = ++requestId; const mcpRequest = { jsonrpc: "2.0", id, method: "tools/call", params: { name: toolName, arguments: arguments_ } }; const response = await fetch(`${JEAN_API_BASE}/mcp/${clientName}/messages/${user.user_id}`, { method: "POST", headers: { "Content-Type": "application/json", "X-User-Id": user.user_id, "X-Client-Name": clientName, "X-API-Key": apiKey }, body: JSON.stringify(mcpRequest) }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`MCP request failed: ${response.statusText} - ${errorBody}`); } return response.json(); } const JeanContext = react.createContext(null); function generatePKCE$1() { const verifier = generateRandomString$1(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$1(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] = react.useState(null); const [messages, setMessages] = react.useState([]); const [isLoading, setIsLoading] = react.useState(false); const [rawError, setRawError] = react.useState(null); react.useEffect(() => { if (!apiKey) { setRawError("API key is required"); return; } if (!apiKey.startsWith("jean_sk_")) { setRawError("Invalid API key format"); return; } console.log("✅ Jean Memory SDK initialized"); }, [apiKey]); react.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 { const userMessage = { id: Date.now().toString(), role: "user", content: message, timestamp: /* @__PURE__ */ new Date() }; setMessages((prev) => [...prev, userMessage]); const response = await 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: /* @__PURE__ */ 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 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"); } const integrationUrl = `${JEAN_API_BASE}/api/v1/integrations/${service}/connect?user_token=${user.access_token}`; const popup = window.open( integrationUrl, `connect-${service}`, "width=600,height=700,scrollbars=yes,resizable=yes" ); const checkClosed = setInterval(() => { if (popup?.closed) { clearInterval(checkClosed); console.log(`${service} integration window closed`); } }, 1e3); }; const signIn = async () => { setIsLoading(true); try { const { verifier, challenge } = await generatePKCE$1(); const state = generateRandomString$1(32); sessionStorage.setItem("jean_oauth_state", state); sessionStorage.setItem("jean_oauth_verifier", verifier); 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" }); window.location.href = `${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 clearConversation = () => { setMessages([]); }; const tools = { add_memory: async (content) => { if (!user) { throw new Error("User not authenticated"); } const response = await 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 makeMCPRequest( user, apiKey, "search_memory", { query } ); if (response.error) { throw new Error(response.error.message); } return response.result; } }; const contextValue = { // Essential state isAuthenticated: !!user, isLoading, user, messages, error: rawError, // Essential methods signIn, signOut, sendMessage, storeDocument, connect, clearConversation, setUser: handleSetUser, // Tools tools }; return /* @__PURE__ */ jsxRuntime.jsx(JeanContext.Provider, { value: contextValue, children }); } function useJean() { const context = react.useContext(JeanContext); if (!context) { throw new Error("useJean must be used within a JeanProvider"); } return context; } /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); const mergeClasses = (...classes) => classes.filter((className, index, array) => { return Boolean(className) && array.indexOf(className) === index; }).join(" "); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ var defaultAttributes = { xmlns: "http://www.w3.org/2000/svg", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }; /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Icon = react.forwardRef( ({ color = "currentColor", size = 24, strokeWidth = 2, absoluteStrokeWidth, className = "", children, iconNode, ...rest }, ref) => { return react.createElement( "svg", { ref, ...defaultAttributes, width: size, height: size, stroke: color, strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth, className: mergeClasses("lucide", className), ...rest }, [ ...iconNode.map(([tag, attrs]) => react.createElement(tag, attrs)), ...Array.isArray(children) ? children : [children] ] ); } ); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const createLucideIcon = (iconName, iconNode) => { const Component = react.forwardRef( ({ className, ...props }, ref) => react.createElement(Icon, { ref, iconNode, className: mergeClasses(`lucide-${toKebabCase(iconName)}`, className), ...props }) ); Component.displayName = `${iconName}`; return Component; }; /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Bot = createLucideIcon("Bot", [ ["path", { d: "M12 8V4H8", key: "hb8ula" }], ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2", key: "enze0r" }], ["path", { d: "M2 14h2", key: "vft8re" }], ["path", { d: "M20 14h2", key: "4cs60a" }], ["path", { d: "M15 13v2", key: "1xurst" }], ["path", { d: "M9 13v2", key: "rq6x2g" }] ]); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const LogOut = createLucideIcon("LogOut", [ ["path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4", key: "1uf3rs" }], ["polyline", { points: "16 17 21 12 16 7", key: "1gabdz" }], ["line", { x1: "21", x2: "9", y1: "12", y2: "12", key: "1uyos4" }] ]); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Moon = createLucideIcon("Moon", [ ["path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z", key: "a7tn18" }] ]); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Send = createLucideIcon("Send", [ ["path", { d: "m22 2-7 20-4-9-9-4Z", key: "1q3vgg" }], ["path", { d: "M22 2 11 13", key: "nzbqef" }] ]); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Sun = createLucideIcon("Sun", [ ["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }], ["path", { d: "M12 2v2", key: "tus03m" }], ["path", { d: "M12 20v2", key: "1lh1kg" }], ["path", { d: "m4.93 4.93 1.41 1.41", key: "149t6j" }], ["path", { d: "m17.66 17.66 1.41 1.41", key: "ptbguv" }], ["path", { d: "M2 12h2", key: "1t8f8n" }], ["path", { d: "M20 12h2", key: "1q8mjw" }], ["path", { d: "m6.34 17.66-1.41 1.41", key: "1m8zz5" }], ["path", { d: "m19.07 4.93-1.41 1.41", key: "1shlcs" }] ]); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Trash2 = createLucideIcon("Trash2", [ ["path", { d: "M3 6h18", key: "d0wm0j" }], ["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }], ["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }], ["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }], ["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }] ]); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const User = createLucideIcon("User", [ ["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", key: "975kel" }], ["circle", { cx: "12", cy: "7", r: "4", key: "17ys0d" }] ]); /** * @license lucide-react v0.395.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const X = createLucideIcon("X", [ ["path", { d: "M18 6 6 18", key: "1bl5f8" }], ["path", { d: "m6 6 12 12", key: "d8bk6v" }] ]); function JeanChat({ className = "", showHeader = true, placeholder = "Type your message..." }) { const agent = useJean(); const [input, setInput] = react.useState(""); const messagesEndRef = react.useRef(null); const [isDark, setIsDark] = react.useState(false); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; react.useEffect(() => { scrollToBottom(); }, [agent.messages]); react.useEffect(() => { if (typeof window !== "undefined") { const storedTheme = localStorage.getItem("jean-chat-theme"); if (storedTheme === "dark") { setIsDark(true); } } }, []); react.useEffect(() => { if (isDark) { document.documentElement.classList.add("dark"); localStorage.setItem("jean-chat-theme", "dark"); } else { document.documentElement.classList.remove("dark"); localStorage.setItem("jean-chat-theme", "light"); } }, [isDark]); const handleSubmit = async (e) => { e.preventDefault(); if (!input.trim() || agent.isLoading) return; const message = input.trim(); setInput(""); try { await agent.sendMessage(message); } catch (err) { console.error("Failed to send message:", err); } }; if (!agent.isAuthenticated) { return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center justify-center h-full ${className} bg-gray-50 dark:bg-gray-900`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8 max-w-md w-full", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-16 h-16 mx-auto mb-6 rounded-2xl bg-white dark:bg-gray-800 flex items-center justify-center border border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx( "path", { d: "M12 2L13.09 8.26L20 9L13.09 15.74L12 22L10.91 15.74L4 9L10.91 8.26L12 2Z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-gray-600 dark:text-gray-400" } ) }) }), /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-semibold text-gray-900 dark:text-white mb-2", children: "Connect with Jean Memory" }), /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 dark:text-gray-400 text-sm mb-6", children: "Access your personalized AI assistant with persistent memory across all your applications." }), /* @__PURE__ */ jsxRuntime.jsxs( "button", { onClick: agent.signIn, className: "inline-flex items-center justify-center gap-2.5 px-5 py-2.5 bg-black text-white font-medium text-sm border border-transparent rounded-lg hover:bg-gray-800 transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:focus:ring-gray-300 shadow-md hover:shadow-lg disabled:opacity-60 disabled:cursor-not-allowed", children: [ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx( "path", { d: "M12 2L13.09 8.26L20 9L13.09 15.74L12 22L10.91 15.74L4 9L10.91 8.26L12 2Z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" } ) }), /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Sign In with Jean" }) ] } ) ] }) }); } return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col h-full bg-white dark:bg-gray-900 text-gray-900 dark:text-white ${className}`, children: [ showHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center px-4 py-3 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800", children: [ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-10 h-10 rounded-full bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(Bot, { size: 20, className: "text-white" }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Jean Memory Assistant" }), /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: [ "Connected as ", agent.user?.email ] }) ] }) ] }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [ /* @__PURE__ */ jsxRuntime.jsx( "button", { onClick: () => setIsDark(!isDark), className: "p-2 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full transition-colors", children: isDark ? /* @__PURE__ */ jsxRuntime.jsx(Sun, { size: 16 }) : /* @__PURE__ */ jsxRuntime.jsx(Moon, { size: 16 }) } ), /* @__PURE__ */ jsxRuntime.jsx( "button", { onClick: () => window.location.reload(), disabled: agent.messages.length === 0, className: "p-2 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full disabled:opacity-50 disabled:cursor-not-allowed transition-colors", title: "Clear Conversation", children: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { size: 16 }) } ), /* @__PURE__ */ jsxRuntime.jsx( "button", { onClick: agent.signOut, className: "p-2 text-red-500 hover:bg-red-100 dark:hover:bg-red-900/50 rounded-full transition-colors", title: "Sign Out", children: /* @__PURE__ */ jsxRuntime.jsx(LogOut, { size: 16 }) } ) ] }) ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto p-6 space-y-6", children: agent.messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center text-gray-500 dark:text-gray-400", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-20 h-20 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", className: "text-gray-400 dark:text-gray-500", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2L13.09 8.26L20 9L13.09 15.74L12 22L10.91 15.74L4 9L10.91 8.26L12 2Z" }) }) }), /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium text-gray-800 dark:text-gray-200 mb-1", children: "Start a conversation" }), /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm max-w-sm", children: "Ask me anything! I have access to your personal context and can help with a wide range of topics." }) ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ agent.messages.map((message) => /* @__PURE__ */ jsxRuntime.jsxs( "div", { className: `flex items-start gap-3 ${message.role === "user" ? "justify-end" : "justify-start"}`, children: [ message.role !== "user" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 rounded-full bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(Bot, { size: 18, className: "text-white" }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `max-w-[80%]`, children: [ /* @__PURE__ */ jsxRuntime.jsx( "div", { className: `px-4 py-2.5 rounded-2xl ${message.role === "user" ? "bg-blue-600 text-white rounded-br-lg" : "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-bl-lg"}`, children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm whitespace-pre-wrap leading-relaxed", children: message.content }) } ), /* @__PURE__ */ jsxRuntime.jsx("div", { className: `mt-1.5 px-1 ${message.role === "user" ? "text-right" : "text-left"}`, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500", children: message.timestamp.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) }) }) ] }), message.role === "user" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(User, { size: 18, className: "text-gray-600 dark:text-gray-300" }) }) ] }, message.id )), agent.isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3 justify-start", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 rounded-full bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(Bot, { size: 18, className: "text-white" }) }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 px-4 py-2.5 rounded-2xl rounded-bl-lg", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center space-x-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex space-x-1", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce" }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce", style: { animationDelay: "0.1s" } }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce", style: { animationDelay: "0.2s" } }) ] }) }) }) ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef }) ] }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-gray-200 dark:border-gray-700 p-4 bg-gray-50 dark:bg-gray-800", children: [ agent.error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 p-3 bg-red-100 dark:bg-red-900/30 border border-red-200 dark:border-red-800/50 rounded-lg flex items-start space-x-3", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-red-600 dark:text-red-400 mt-0.5", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }), /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" }) ] }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-700 dark:text-red-300 text-sm font-medium", children: "Error" }), /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-600 dark:text-red-400 text-sm", children: agent.error }) ] }), /* @__PURE__ */ jsxRuntime.jsx( "button", { onClick: () => window.location.reload(), className: "p-1 text-red-600 dark:text-red-400 hover:bg-red-200 dark:hover:bg-red-900/50 rounded-full", children: /* @__PURE__ */ jsxRuntime.jsx(X, { size: 16 }) } ) ] }), /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "relative", children: [ /* @__PURE__ */ jsxRuntime.jsx( "textarea", { value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(e); } }, placeholder, disabled: agent.isLoading, className: "w-full px-4 py-3 pr-12 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-2xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 resize-none text-sm", rows: 1 } ), /* @__PURE__ */ jsxRuntime.jsx( "button", { type: "submit", disabled: agent.isLoading || !input.trim(), className: "absolute right-3 top-1/2 -translate-y-1/2 w-8 h-8 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center transition-colors", children: agent.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(Send, { size: 16 }) } ) ] }) ] }) ] }); } 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 SignInWithJean({ onSuccess, onError, apiKey, className = "", children }) { const [isLoading, setIsLoading] = react.useState(false); react.useEffect(() => { const params = new URLSearchParams(window.location.search); const code = params.get("code"); const state = params.get("state"); if (code && state) { handleOAuthCallback(code, state); } }, []); const handleOAuthCallback = async (code, state) => { try { const storedState = sessionStorage.getItem("jean_oauth_state"); const verifier = sessionStorage.getItem("jean_oauth_verifier"); if (state !== storedState) { throw new Error("State mismatch - possible CSRF attack"); } if (!verifier) { throw new Error("Missing PKCE verifier"); } const response = await fetch(`${JEAN_API_BASE}/oauth/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "authorization_code", code, redirect_uri: window.location.origin + window.location.pathname, code_verifier: verifier, client_id: apiKey || "default_client" }) }); if (!response.ok) { throw new Error("Failed to exchange code for token"); } const data = await response.json(); const userResponse = await fetch(`${JEAN_API_BASE}/api/v1/user/me`, { headers: { "Authorization": `Bearer ${data.access_token}` } }); if (!userResponse.ok) { throw new Error("Failed to get user info"); } const user = await userResponse.json(); user.access_token = data.access_token; sessionStorage.removeItem("jean_oauth_state"); sessionStorage.removeItem("jean_oauth_verifier"); const url = new URL(window.location.href); url.searchParams.delete("code"); url.searchParams.delete("state"); window.history.replaceState({}, "", url.toString()); onSuccess(user); } catch (error) { console.error("OAuth callback error:", error); if (onError) { onError(error instanceof Error ? error : new Error("OAuth callback failed")); } } }; const handleSignIn = async () => { setIsLoading(true); try { const { verifier, challenge } = await generatePKCE(); const state = generateRandomString(32); sessionStorage.setItem("jean_oauth_state", state); sessionStorage.setItem("jean_oauth_verifier", verifier); 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" }); window.location.href = `${JEAN_OAUTH_BASE}/oauth/authorize?${params.toString()}`; } catch (error) { setIsLoading(false); console.error("Sign in error:", error); if (onError) { onError(error instanceof Error ? error : new Error("Sign in failed")); } } }; return /* @__PURE__ */ jsxRuntime.jsx( "button", { onClick: handleSignIn, disabled: isLoading, className: `inline-flex items-center justify-center gap-2.5 px-5 py-2.5 bg-black text-white font-medium text-sm border border-transparent rounded-lg hover:bg-gray-800 transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:focus:ring-gray-300 shadow-md hover:shadow-lg disabled:opacity-60 disabled:cursor-not-allowed ${className}`, children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 border-2 border-white dark:border-black border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Signing in..." }) ] }) : children || /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx( "path", { d: "M12 2L13.09 8.26L20 9L13.09 15.74L12 22L10.91 15.74L4 9L10.91 8.26L12 2Z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" } ) }), /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Sign In with Jean" }) ] }) } ); } function useJeanMCP({ apiKey, clientName = "react-app" }) { const callJeanMemory = react.useCallback( async (user, message, isNewConversation = false) => { const response = await makeMCPRequest( user, apiKey, "jean_memory", { user_message: message, is_new_conversation: isNewConversation, needs_context: true }, clientName ); if (response.error) { throw new Error(`MCP Error: ${response.error.message}`); } return response.result?.content?.[0]?.text || "No response from tool"; }, [apiKey, clientName] ); const addMemory = react.useCallback( async (user, content) => { const response = await makeMCPRequest( user, apiKey, "add_memories", { text: content }, clientName ); if (response.error) { throw new Error(`MCP Error: ${response.error.message}`); } return response.result?.content?.[0]?.text || "No response from tool"; }, [apiKey, clientName] ); const searchMemory = react.useCallback( async (user, query) => { const response = await makeMCPRequest( user, apiKey, "search_memory", { query }, clientName ); if (response.error) { throw new Error(`MCP Error: ${response.error.message}`); } return response.result?.content?.[0]?.text || "No response from tool"; }, [apiKey, clientName] ); const storeDocument = react.useCallback( async (user, title, content, type = "markdown") => { const response = await makeMCPRequest( user, apiKey, "store_document", { title, content, document_type: type }, clientName ); if (response.error) { throw new Error(`MCP Error: ${response.error.message}`); } return response.result?.content?.[0]?.text || "No response from tool"; }, [apiKey, clientName] ); return { callJeanMemory, addMemory, searchMemory, storeDocument }; } exports.JeanChat = JeanChat; exports.JeanProvider = JeanProvider; exports.SignInWithJean = SignInWithJean; exports.useJean = useJean; exports.useJeanMCP = useJeanMCP; //# sourceMappingURL=index.cjs.map