"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Check, Copy } from "lucide-react"; import { CopyToClipboard } from "react-copy-to-clipboard"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useAuth } from "@/hooks/use-auth"; import { getErrorMessage } from "@/lib/error-message"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { API_KEY_ENDPOINTS, AUTH_ENDPOINTS, MEMORY_ENDPOINTS, } from "@/utils/api-endpoints"; import { buildProviderConfig, getEffectiveConfig, } from "@/utils/self-hosted-config"; import { isValidEmail } from "@/lib/validators"; type BundledProviders = { llm: string[]; embedder: string[]; }; const STEPS = [ "Admin Account", "Providers", "API Key", "Use Case", "Quick Test", ]; const STEP_TITLES = [ "Create your admin account", "Review provider configuration", "Your API key", "Tell us your use case", "Test your setup", ]; const SUPPORTED_PROVIDERS_URL = "https://docs.mem0.ai/open-source/setup#supported-providers"; const USE_CASE_PRESETS = [ "Personal assistant", "Coding agent", "Customer support", "Research", "Therapy / journaling", ]; const DEFAULT_TEST_MESSAGE = "I like to hike on weekends."; export default function SetupPage() { const router = useRouter(); const { register } = useAuth(); const [step, setStep] = useState(0); const [isLoading, setIsLoading] = useState(false); const [isPrefillingConfig, setIsPrefillingConfig] = useState(false); const [error, setError] = useState(""); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [llmProvider, setLlmProvider] = useState(""); const [llmModel, setLlmModel] = useState(""); const [llmApiKey, setLlmApiKey] = useState(""); const [embedderProvider, setEmbedderProvider] = useState(""); const [embedderModel, setEmbedderModel] = useState(""); const [serverHasLlmKey, setServerHasLlmKey] = useState(false); const [providers, setProviders] = useState(null); const [initialLlmProvider, setInitialLlmProvider] = useState(""); const [initialLlmModel, setInitialLlmModel] = useState(""); const [initialEmbedderProvider, setInitialEmbedderProvider] = useState(""); const [initialEmbedderModel, setInitialEmbedderModel] = useState(""); const [apiKey, setApiKey] = useState(""); const [keyLabel, setKeyLabel] = useState(""); const [copied, setCopied] = useState(false); const [testSuccess, setTestSuccess] = useState(false); const [useCase, setUseCase] = useState(""); const [customInstructions, setCustomInstructions] = useState(""); const [testMessage, setTestMessage] = useState(DEFAULT_TEST_MESSAGE); const [isGeneratingInstructions, setIsGeneratingInstructions] = useState(false); const apiUrl = process.env.NEXT_PUBLIC_API_URL || ""; useEffect(() => { if (step !== 1) { setIsPrefillingConfig(false); return; } let active = true; setIsPrefillingConfig(true); const loadConfig = async () => { try { const [configRes, providersRes] = await Promise.all([ api.get(MEMORY_ENDPOINTS.CONFIGURE), api.get(MEMORY_ENDPOINTS.CONFIGURE_PROVIDERS), ]); const config = getEffectiveConfig(configRes.data); if (!active) { return; } const llmProv = config?.llm?.provider || ""; const llmMod = config?.llm?.config?.model || ""; const embProv = config?.embedder?.provider || ""; const embMod = config?.embedder?.config?.model || ""; setLlmProvider(llmProv); setLlmModel(llmMod); setEmbedderProvider(embProv); setEmbedderModel(embMod); setInitialLlmProvider(llmProv); setInitialLlmModel(llmMod); setInitialEmbedderProvider(embProv); setInitialEmbedderModel(embMod); setServerHasLlmKey(!!config?.llm?.config?.api_key); setProviders(providersRes.data); } catch (err) { if (active) { setError(getErrorMessage(err, "Could not read server configuration")); } } finally { if (active) { setIsPrefillingConfig(false); } } }; void loadConfig(); return () => { active = false; }; }, [step]); const handleStep1 = async (e: React.FormEvent) => { e.preventDefault(); if (!isValidEmail(email)) { setError("Enter a valid email address."); return; } if (password !== confirmPassword) { setError("Passwords don't match"); return; } if (password.length < 8) { setError("Password must be at least 8 characters"); return; } setError(""); setIsLoading(true); try { await register(name, email, password); setStep(1); } catch (err) { setError(getErrorMessage(err, "Registration failed")); } finally { setIsLoading(false); } }; const handleStep2 = async (e: React.FormEvent) => { e.preventDefault(); setError(""); const dirty = !!llmApiKey || llmProvider !== initialLlmProvider || llmModel !== initialLlmModel || embedderProvider !== initialEmbedderProvider || embedderModel !== initialEmbedderModel; if (!dirty) { setStep(2); return; } setIsLoading(true); try { const llm = buildProviderConfig({ provider: llmProvider, model: llmModel, apiKey: llmApiKey, }); const embedder = buildProviderConfig({ provider: embedderProvider, model: embedderModel, apiKey: llmApiKey && embedderProvider === llmProvider ? llmApiKey : undefined, }); const payload: Record = { version: "v1.1" }; if (llm) payload.llm = llm; if (embedder) payload.embedder = embedder; await api.post(MEMORY_ENDPOINTS.CONFIGURE, payload); if (llmApiKey) setServerHasLlmKey(true); setLlmApiKey(""); setInitialLlmProvider(llmProvider); setInitialLlmModel(llmModel); setInitialEmbedderProvider(embedderProvider); setInitialEmbedderModel(embedderModel); setStep(2); } catch (err) { setError(getErrorMessage(err, "Failed to save configuration")); } finally { setIsLoading(false); } }; const handleStep3 = async (e: React.FormEvent) => { e.preventDefault(); setError(""); setIsLoading(true); try { const res = await api.post(API_KEY_ENDPOINTS.BASE, { label: keyLabel.trim() || "My First Key", }); setApiKey(res.data.key); } catch (err) { setError(getErrorMessage(err, "Failed to create API key")); } finally { setIsLoading(false); } }; const handleContinueToUseCase = (e: React.FormEvent) => { e.preventDefault(); setStep(3); }; const handleContinueToQuickTest = () => { setError(""); setStep(4); }; const handleGoToDashboard = (e: React.FormEvent) => { e.preventDefault(); router.push("/dashboard/requests"); }; const handleTest = async (e: React.FormEvent) => { e.preventDefault(); setError(""); setIsLoading(true); try { const res = await fetch(`${apiUrl}/memories`, { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": apiKey }, body: JSON.stringify({ messages: [{ role: "user", content: testMessage }], user_id: "setup-test", }), }); if (!res.ok) { const body = await res.text(); let detail = body; try { detail = JSON.parse(body).detail ?? body; } catch {} throw new Error( `Test failed (${res.status}): ${detail || res.statusText}`, ); } setTestSuccess(true); void api .post(AUTH_ENDPOINTS.ONBOARDING_COMPLETE, { use_case: useCase }) .catch(() => {}); } catch (err) { setError(getErrorMessage(err, "Test failed")); } finally { setIsLoading(false); } }; return (
{STEPS.map((label, i) => (
{i < step ? : i + 1}
{i < STEPS.length - 1 && (
)}
))}

{STEPS[step]}

{STEP_TITLES[step]}

{isPrefillingConfig && step === 1 && (

Checking server configuration...

)}
{error && (

{error}

)} {step === 0 && (
setName(e.target.value)} placeholder="Your name" />
setEmail(e.target.value)} placeholder="admin@company.com" />
setPassword(e.target.value)} placeholder="Min 8 characters" />
setConfirmPassword(e.target.value)} />
)} {step === 1 && (
{!serverHasLlmKey && (

No LLM provider API key is configured on the server. Paste one below to continue — it will be saved to the server and used for all memory operations.

)}
setLlmModel(e.target.value)} placeholder="gpt-4.1-nano-2025-04-14" className="font-mono text-sm" />
setLlmApiKey(e.target.value)} placeholder={ serverHasLlmKey ? "Leave blank to keep existing key" : "sk-..." } className="font-mono text-sm" />

Also used for the embedder when it shares the same provider.

setEmbedderModel(e.target.value)} placeholder="text-embedding-3-small" className="font-mono text-sm" />

Need another provider? Install its Python package and rebuild the image. See{" "} supported providers .

)} {step === 2 && !apiKey && (
setKeyLabel(e.target.value)} placeholder="My First Key" />
)} {step === 2 && apiKey && (
{ setCopied(true); setTimeout(() => setCopied(false), 2000); }} >

Save this key. You will not see it again.

)} {step === 3 && (