import React, { useState, useEffect } from 'react'; import { Settings, Stats } from '../types'; import { DEFAULT_SETTINGS } from '../constants/settings'; import { formatUptime, formatBytes } from '../utils/formatters'; interface SidebarProps { isOpen: boolean; settings: Settings; stats: Stats; isSaving: boolean; saveStatus: string; isConnected: boolean; onSave: (settings: Settings) => void; onClose: () => void; onRefreshStats: () => void; } export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConnected, onSave, onClose, onRefreshStats }: SidebarProps) { // Settings form state const [model, setModel] = useState(settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL); const [contextObs, setContextObs] = useState(settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS); const [workerPort, setWorkerPort] = useState(settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT); // MCP toggle state (separate from settings) const [mcpEnabled, setMcpEnabled] = useState(true); const [mcpToggling, setMcpToggling] = useState(false); const [mcpStatus, setMcpStatus] = useState(''); // Branch switching state interface BranchInfo { branch: string | null; isBeta: boolean; isGitRepo: boolean; isDirty: boolean; canSwitch: boolean; error?: string; } const [branchInfo, setBranchInfo] = useState(null); const [branchSwitching, setBranchSwitching] = useState(false); const [branchStatus, setBranchStatus] = useState(''); // Update settings form state when settings change useEffect(() => { setModel(settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL); setContextObs(settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS); setWorkerPort(settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT); }, [settings]); // Fetch MCP status on mount useEffect(() => { fetch('/api/mcp/status') .then(res => res.json()) .then(data => setMcpEnabled(data.enabled)) .catch(error => console.error('Failed to load MCP status:', error)); }, []); // Fetch branch status on mount useEffect(() => { fetch('/api/branch/status') .then(res => res.json()) .then(data => setBranchInfo(data)) .catch(error => console.error('Failed to load branch status:', error)); }, []); // Refresh stats when sidebar opens useEffect(() => { if (isOpen) { onRefreshStats(); } }, [isOpen, onRefreshStats]); const handleSave = () => { onSave({ CLAUDE_MEM_MODEL: model, CLAUDE_MEM_CONTEXT_OBSERVATIONS: contextObs, CLAUDE_MEM_WORKER_PORT: workerPort }); }; const handleMcpToggle = async (enabled: boolean) => { setMcpToggling(true); setMcpStatus('Toggling...'); try { const response = await fetch('/api/mcp/toggle', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled }) }); const result = await response.json(); if (result.success) { setMcpEnabled(result.enabled); setMcpStatus('✓ Updated (restart Claude Code to apply)'); setTimeout(() => setMcpStatus(''), 3000); } else { setMcpStatus(`✗ Error: ${result.error}`); setTimeout(() => setMcpStatus(''), 3000); } } catch (error) { setMcpStatus(`✗ Error: ${error instanceof Error ? error.message : 'Unknown error'}`); setTimeout(() => setMcpStatus(''), 3000); } finally { setMcpToggling(false); } }; const handleBranchSwitch = async (targetBranch: string) => { setBranchSwitching(true); setBranchStatus('Switching branches...'); try { const response = await fetch('/api/branch/switch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ branch: targetBranch }) }); const result = await response.json(); if (result.success) { setBranchStatus(`✓ ${result.message}`); // Worker will restart, page will refresh setTimeout(() => { setBranchStatus('Restarting worker...'); }, 1000); } else { setBranchStatus(`✗ Error: ${result.error}`); setTimeout(() => setBranchStatus(''), 5000); setBranchSwitching(false); } } catch (error) { setBranchStatus(`✗ Error: ${error instanceof Error ? error.message : 'Unknown error'}`); setTimeout(() => setBranchStatus(''), 5000); setBranchSwitching(false); } }; const handleBranchUpdate = async () => { setBranchSwitching(true); setBranchStatus('Checking for updates...'); try { const response = await fetch('/api/branch/update', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { setBranchStatus(`✓ ${result.message}`); // Worker will restart, page will refresh setTimeout(() => { setBranchStatus('Restarting worker...'); }, 1000); } else { setBranchStatus(`✗ Error: ${result.error}`); setTimeout(() => setBranchStatus(''), 5000); setBranchSwitching(false); } } catch (error) { setBranchStatus(`✗ Error: ${error instanceof Error ? error.message : 'Unknown error'}`); setTimeout(() => setBranchStatus(''), 5000); setBranchSwitching(false); } }; return (

Settings

{isConnected ? 'Connected' : 'Disconnected'}

Environment Variables

Model used for AI compression of tool observations. Haiku is fast and cheap, Sonnet offers better quality, Opus is most capable but expensive.
Number of recent observations to inject at session start. Higher values provide more context but increase token usage. Default: 50
setContextObs(e.target.value)} />
Port number for the background worker service. Change only if port 37777 conflicts with another service.
setWorkerPort(e.target.value)} />
{saveStatus && (
{saveStatus}
)}

MCP Search Server

claude-mem suggests using skill-based search (saves ~2,500 tokens at session start), but some users prefer MCP. Disable to only use skill-based search. Requires Claude Code restart to apply changes.
{mcpStatus && (
{mcpStatus}
)}

Version Channel

{branchInfo ? ( <>
{branchInfo.isBeta ? 'Beta' : 'Stable'} {branchInfo.branch || 'main'}
{branchInfo.isBeta ? ( <>
You're running the beta with Endless Mode. Your memory data is preserved when switching versions.
) : ( <>
Try the beta to access experimental features like Endless Mode. Your memory data is preserved when switching.
)} {branchStatus && (
{branchStatus}
)} ) : (
Loading branch info...
)}

Worker Stats

Version
{stats.worker?.version || '-'}
Uptime
{formatUptime(stats.worker?.uptime)}
Active Sessions
{stats.worker?.activeSessions || '0'}
SSE Clients
{stats.worker?.sseClients || '0'}

Database Stats

DB Size
{formatBytes(stats.database?.size)}
Observations
{stats.database?.observations || '0'}
Sessions
{stats.database?.sessions || '0'}
Summaries
{stats.database?.summaries || '0'}
); }