import { useState, useEffect, useCallback } from "react"; import { useNavigate } from "react-router"; import { useConfig } from "#/hooks/query/use-config"; import { useAppMode } from "#/hooks/use-app-mode"; import SettingsService from "#/api/settings-service/settings-service.api"; import { Button } from "#/components/ui/Button"; import { Input } from "#/components/ui/Input"; import { EmptyState } from "#/components/ui/EmptyState"; import { Footer } from "#/components/ui/Footer"; import { ErrorBoundary } from "#/components/ErrorBoundary"; import ApiKeysService, { PROVIDER_METADATA, ROLE_PROVIDER_PREFS, } from "#/api/api-keys-service/api-keys-service.api"; type SettingsCategory = | "application" | "profile" | "appearance" | "llm" | "integrations" | "api-keys" | "secrets"; const CATEGORIES: { id: SettingsCategory; label: string; description: string; }[] = [ { id: "application", label: "Application", description: "General application preferences", }, { id: "profile", label: "Profile", description: "Your personal information" }, { id: "appearance", label: "Appearance", description: "Theme and layout density", }, { id: "llm", label: "LLM", description: "Configure your language model provider", }, { id: "integrations", label: "Integrations", description: "Connect your git providers", }, { id: "api-keys", label: "API Keys", description: "Manage your programmatic keys", }, { id: "secrets", label: "Secrets", description: "Manage your stored credentials", }, ]; export default function SettingsPage() { return ( ); } function SettingsContent() { const navigate = useNavigate(); const { data: config } = useConfig(); const { isCloud } = useAppMode(); const [active, setActive] = useState("application"); const [theme, setTheme] = useState<"dark" | "light" | "system">("dark"); const [density, setDensity] = useState<"comfortable" | "compact" | "normal">( "comfortable", ); const [displayName, setDisplayName] = useState(""); const [llmModel, setLlmModel] = useState(""); const [llmApiKey, setLlmApiKey] = useState(""); const [saving, setSaving] = useState(false); const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">( "idle", ); // Load user settings on mount useEffect(() => { let cancelled = false; SettingsService.getSettings() .then((data) => { if (!cancelled) { if (data.ui_theme) setTheme(data.ui_theme); if (data.ui_density) setDensity(data.ui_density); if (data.display_name) setDisplayName(data.display_name); if (data.llm_model) setLlmModel(data.llm_model); if (data.llm_api_key) setLlmApiKey(data.llm_api_key); } }) .catch(() => {}); return () => { cancelled = true; }; }, []); // Apply theme to document useEffect(() => { document.documentElement.classList.toggle("light", theme === "light"); document.documentElement.classList.toggle("dark", theme === "dark"); localStorage.setItem("wren_theme", theme); }, [theme]); const handleSave = useCallback(async () => { setSaving(true); setSaveStatus("idle"); try { await SettingsService.saveSettings({ ui_theme: theme, ui_density: density, display_name: displayName, llm_model: llmModel, llm_api_key: llmApiKey || undefined, }); setSaveStatus("success"); setTimeout(() => setSaveStatus("idle"), 2000); } catch { setSaveStatus("error"); setTimeout(() => setSaveStatus("idle"), 3000); } finally { setSaving(false); } }, [theme, density, displayName, llmModel, llmApiKey]); return (
{/* Page Header */}

Settings

Configure your Wren experience

{/* ── Two-Column Layout ── */}
{/* Sidebar Navigation */} {/* Content Panel */}
{/* Application */} {active === "application" && (
)} {/* Profile */} {active === "profile" && ( setDisplayName(e.target.value)} placeholder="Your name" className="max-w-md" />
)} {/* Appearance */} {active === "appearance" && (
)} {/* LLM */} {active === "llm" && ( setLlmApiKey(e.target.value)} placeholder="sk-... or your provider key" className="max-w-md" /> )} {/* Integrations */} {active === "integrations" && (
{["github", "gitlab", "bitbucket"].map((provider) => ( navigate( `/settings/integrations?provider=${provider}`, ) } /> ))}
)} {/* API Keys */} {active === "api-keys" && } {/* Secrets */} {active === "secrets" && ( } title="No secrets stored" description="Add secrets to securely use them in your conversations" action={{ label: "Add secret", onClick: () => {}, variant: "primary", }} /> )} {/* Save Status */} {(saveStatus === "success" || saveStatus === "error") && (
{saving ? ( <> Saving... ) : saveStatus === "success" ? ( ) : ( )} {saveStatus === "success" ? "Settings saved" : "Failed to save settings"}
)} {/* Footer (Ft2) */}
); } /* ── Settings Section ── */ function SettingsSection({ title, description, children, }: { title: string; description: string; children: React.ReactNode; }) { return (

{title}

{description}

{children}
); } /* ── Setting Group ── */ function SettingGroup({ children }: { children: React.ReactNode }) { return
{children}
; } /* ── Setting Item ── */ function SettingItem({ label, description, children, }: { label: string; description: string; children: React.ReactNode; }) { return (

{description}

{children}
); } /* ── API Keys Settings (inline) ── */ function ApiKeysSettings() { const navigate = useNavigate(); const [providers, setProviders] = useState(() => ApiKeysService.getAll()); // Re-read on mount useEffect(() => { setProviders(ApiKeysService.getAll()); }, []); const roleAvailability = ApiKeysService.getRoleAvailability(); const rolesEnabled = Object.values(roleAvailability).filter( (r) => r.available, ).length; const handleRemove = (pid: string) => { ApiKeysService.remove(pid); setProviders(ApiKeysService.getAll()); }; if (providers.length === 0) { return ( } title="No API keys configured" description="Add your LLM provider API keys to enable multi-agent model routing. Each agent role (Planner, Researcher, Writer, Reviewer) uses the best model available." action={{ label: "Configure API Keys", onClick: () => navigate("/api-keys"), variant: "primary", }} /> ); } return ( {/* Status summary */}

{providers.length} provider{providers.length !== 1 ? "s" : ""}

Configured

{rolesEnabled}/4 roles

Active

{/* Provider list */}
{providers.map((p) => { const meta = PROVIDER_METADATA[p.provider]; const rolesForProvider = Object.entries(ROLE_PROVIDER_PREFS) .filter(([, info]) => info.preferredProviders.includes(p.provider), ) .map(([role]) => role); return (
{(meta?.name || p.provider).charAt(0)}

{meta?.name || p.provider}

{p.model}

{/* Role tags */}
{rolesForProvider.map((role) => ( {role} ))}
); })}
{/* Add more button */}
); } /* ── Provider Card ── */ function ProviderCard({ name, connected, onConnect, }: { name: string; connected: boolean; onConnect: () => void; }) { const icons: Record = { GitHub: ( ), GitLab: ( ), Bitbucket: ( ), }; return (
{icons[name as keyof typeof icons]}

{name}

{connected ? "Connected" : "Not connected"}

); }