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; projects: string[]; currentFilter: string; onFilterChange: (filter: string) => void; onSave: (settings: Settings) => void; onClose: () => void; onRefreshStats: () => void; } export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConnected, projects, currentFilter, onFilterChange, onSave, onClose, onRefreshStats }: SidebarProps) { // Consolidated settings form state const [formState, setFormState] = useState({ CLAUDE_MEM_MODEL: settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL, CLAUDE_MEM_CONTEXT_OBSERVATIONS: settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS, CLAUDE_MEM_WORKER_PORT: settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT, CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS, CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS, CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: settings.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT, CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: settings.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT, CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: settings.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES, CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: settings.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS, CLAUDE_MEM_CONTEXT_FULL_COUNT: settings.CLAUDE_MEM_CONTEXT_FULL_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_COUNT, CLAUDE_MEM_CONTEXT_FULL_FIELD: settings.CLAUDE_MEM_CONTEXT_FULL_FIELD || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_FIELD, CLAUDE_MEM_CONTEXT_SESSION_COUNT: settings.CLAUDE_MEM_CONTEXT_SESSION_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SESSION_COUNT, CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: settings.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY, CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: settings.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE, }); // MCP toggle state (separate from settings) const [mcpEnabled, setMcpEnabled] = useState(true); const [mcpToggling, setMcpToggling] = useState(false); const [mcpStatus, setMcpStatus] = useState(''); // Helper to update form state const updateFormState = (field: keyof Settings, value: string) => { setFormState(prev => ({ ...prev, [field]: value })); }; // Update settings form state when settings prop changes useEffect(() => { setFormState({ CLAUDE_MEM_MODEL: settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL, CLAUDE_MEM_CONTEXT_OBSERVATIONS: settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS, CLAUDE_MEM_WORKER_PORT: settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT, CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS, CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS, CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: settings.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT, CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: settings.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT, CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: settings.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES, CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: settings.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS, CLAUDE_MEM_CONTEXT_FULL_COUNT: settings.CLAUDE_MEM_CONTEXT_FULL_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_COUNT, CLAUDE_MEM_CONTEXT_FULL_FIELD: settings.CLAUDE_MEM_CONTEXT_FULL_FIELD || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_FIELD, CLAUDE_MEM_CONTEXT_SESSION_COUNT: settings.CLAUDE_MEM_CONTEXT_SESSION_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SESSION_COUNT, CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: settings.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY, CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: settings.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE, }); }, [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)); }, []); // Refresh stats when sidebar opens useEffect(() => { if (isOpen) { onRefreshStats(); } }, [isOpen, onRefreshStats]); const handleSave = () => { onSave(formState); }; 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); } }; return (

Settings

{isConnected ? 'Connected' : 'Disconnected'}
Community

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
updateFormState('CLAUDE_MEM_CONTEXT_OBSERVATIONS', e.target.value)} />
Port number for the background worker service. Change only if port 37777 conflicts with another service.
updateFormState('CLAUDE_MEM_WORKER_PORT', e.target.value)} />
{/* Token Economics Display */}
Choose which token metrics to show in session start context.
{/* Display Configuration */}
Control how observations are displayed in the timeline.
updateFormState('CLAUDE_MEM_CONTEXT_FULL_COUNT', e.target.value)} style={{ width: '100%' }} />
Number of most recent observations to show with full details
updateFormState('CLAUDE_MEM_CONTEXT_SESSION_COUNT', e.target.value)} style={{ width: '100%' }} />
{/* Feature Toggles */}
Toggle additional features in session start context.
{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}
)}

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'}
); }