d1876cb6e0
- Introduced `observation-metadata.ts` to define valid observation types and concepts, along with their corresponding emoji mappings. - Updated `context-hook.ts` to utilize new constants for observation types and concepts, enhancing maintainability. - Refactored `worker-service.ts` to validate observation types and concepts against the new centralized constants. - Consolidated settings management in `Sidebar.tsx` to streamline state handling for context settings. - Improved error handling and logging for context loading failures.
413 lines
22 KiB
TypeScript
413 lines
22 KiB
TypeScript
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<Settings>({
|
|
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 (
|
|
<div className={`sidebar ${isOpen ? 'open' : ''}`}>
|
|
<div className="sidebar-header">
|
|
<h1>Settings</h1>
|
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
<span className={`status-dot ${isConnected ? 'connected' : ''}`} />
|
|
<span style={{ fontSize: '11px', opacity: 0.5, fontWeight: 300 }}>{isConnected ? 'Connected' : 'Disconnected'}</span>
|
|
</div>
|
|
<button onClick={handleSave} disabled={isSaving}>
|
|
{isSaving ? 'Saving...' : 'Save'}
|
|
</button>
|
|
<button
|
|
onClick={onClose}
|
|
title="Close settings"
|
|
style={{
|
|
background: 'transparent',
|
|
border: '1px solid #404040',
|
|
padding: '8px',
|
|
width: '36px',
|
|
height: '36px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center'
|
|
}}
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<a
|
|
href="https://discord.gg/J4wttp9vDu"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="sidebar-community-btn"
|
|
title="Join our Discord community"
|
|
>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style={{ marginRight: '6px' }}>
|
|
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"/>
|
|
</svg>
|
|
<span>Community</span>
|
|
</a>
|
|
<div className="sidebar-social-links">
|
|
<a
|
|
href="https://docs.claude-mem.ai"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
title="Documentation"
|
|
className="icon-link"
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
|
|
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
|
|
</svg>
|
|
</a>
|
|
<a
|
|
href="https://github.com/thedotmack/claude-mem/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
title="GitHub"
|
|
className="icon-link"
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
</svg>
|
|
</a>
|
|
<a
|
|
href="https://x.com/Claude_Memory"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
title="X (Twitter)"
|
|
className="icon-link"
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
<div className="sidebar-project-filter">
|
|
<label htmlFor="sidebar-project-select">Filter by Project</label>
|
|
<select
|
|
id="sidebar-project-select"
|
|
value={currentFilter}
|
|
onChange={e => onFilterChange(e.target.value)}
|
|
>
|
|
<option value="">All Projects</option>
|
|
{projects.map(project => (
|
|
<option key={project} value={project}>{project}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="stats-scroll">
|
|
|
|
<div className="settings-section">
|
|
<h3>Environment Variables</h3>
|
|
<div className="form-group">
|
|
<label htmlFor="model">CLAUDE_MEM_MODEL</label>
|
|
<div className="setting-description">
|
|
Model used for AI compression of tool observations. Haiku is fast and cheap, Sonnet offers better quality, Opus is most capable but expensive.
|
|
</div>
|
|
<select
|
|
id="model"
|
|
value={formState.CLAUDE_MEM_MODEL}
|
|
onChange={e => updateFormState('CLAUDE_MEM_MODEL', e.target.value)}
|
|
>
|
|
<option value="claude-haiku-4-5">claude-haiku-4-5</option>
|
|
<option value="claude-sonnet-4-5">claude-sonnet-4-5</option>
|
|
<option value="claude-opus-4">claude-opus-4</option>
|
|
</select>
|
|
</div>
|
|
<div className="form-group">
|
|
<label htmlFor="contextObs">CLAUDE_MEM_CONTEXT_OBSERVATIONS</label>
|
|
<div className="setting-description">
|
|
Number of recent observations to inject at session start. Higher values provide more context but increase token usage. Default: 50
|
|
</div>
|
|
<input
|
|
type="number"
|
|
id="contextObs"
|
|
min="1"
|
|
max="200"
|
|
value={formState.CLAUDE_MEM_CONTEXT_OBSERVATIONS}
|
|
onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_OBSERVATIONS', e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="form-group">
|
|
<label htmlFor="workerPort">CLAUDE_MEM_WORKER_PORT</label>
|
|
<div className="setting-description">
|
|
Port number for the background worker service. Change only if port 37777 conflicts with another service.
|
|
</div>
|
|
<input
|
|
type="number"
|
|
id="workerPort"
|
|
min="1024"
|
|
max="65535"
|
|
value={formState.CLAUDE_MEM_WORKER_PORT}
|
|
onChange={e => updateFormState('CLAUDE_MEM_WORKER_PORT', e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Token Economics Display */}
|
|
<div className="form-group">
|
|
<label>Token Economics Display</label>
|
|
<div className="setting-description">
|
|
Choose which token metrics to show in session start context.
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={formState.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS === 'true'} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS', e.target.checked ? 'true' : 'false')} />
|
|
Show read tokens
|
|
</label>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={formState.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS === 'true'} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS', e.target.checked ? 'true' : 'false')} />
|
|
Show work tokens
|
|
</label>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={formState.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT === 'true'} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT', e.target.checked ? 'true' : 'false')} />
|
|
Show savings amount
|
|
</label>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={formState.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT === 'true'} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT', e.target.checked ? 'true' : 'false')} />
|
|
Show savings percentage
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Display Configuration */}
|
|
<div className="form-group">
|
|
<label>Display Configuration</label>
|
|
<div className="setting-description">
|
|
Control how observations are displayed in the timeline.
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px', marginTop: '8px' }}>
|
|
<div>
|
|
<label htmlFor="fullCount" style={{ display: 'block', marginBottom: '4px', fontSize: '13px' }}>
|
|
Full observation count (0-20)
|
|
</label>
|
|
<input type="number" id="fullCount" min="0" max="20" value={formState.CLAUDE_MEM_CONTEXT_FULL_COUNT} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_FULL_COUNT', e.target.value)} style={{ width: '100%' }} />
|
|
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
|
Number of most recent observations to show with full details
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="fullField" style={{ display: 'block', marginBottom: '4px', fontSize: '13px' }}>
|
|
Full observation field
|
|
</label>
|
|
<select id="fullField" value={formState.CLAUDE_MEM_CONTEXT_FULL_FIELD} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_FULL_FIELD', e.target.value)} style={{ width: '100%' }}>
|
|
<option value="narrative">Narrative</option>
|
|
<option value="facts">Facts</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="sessionCount" style={{ display: 'block', marginBottom: '4px', fontSize: '13px' }}>
|
|
Session summary count (1-50)
|
|
</label>
|
|
<input type="number" id="sessionCount" min="1" max="50" value={formState.CLAUDE_MEM_CONTEXT_SESSION_COUNT} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_SESSION_COUNT', e.target.value)} style={{ width: '100%' }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Feature Toggles */}
|
|
<div className="form-group">
|
|
<label>Context Features</label>
|
|
<div className="setting-description">
|
|
Toggle additional features in session start context.
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={formState.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY === 'true'} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY', e.target.checked ? 'true' : 'false')} />
|
|
Show last session summary
|
|
</label>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={formState.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE === 'true'} onChange={e => updateFormState('CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE', e.target.checked ? 'true' : 'false')} />
|
|
Include last session message
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{saveStatus && (
|
|
<div className="save-status">{saveStatus}</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="settings-section">
|
|
<h3>MCP Search Server</h3>
|
|
<div className="form-group">
|
|
<label htmlFor="mcpEnabled" style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input
|
|
type="checkbox"
|
|
id="mcpEnabled"
|
|
checked={mcpEnabled}
|
|
onChange={e => handleMcpToggle(e.target.checked)}
|
|
disabled={mcpToggling}
|
|
style={{ cursor: mcpToggling ? 'not-allowed' : 'pointer' }}
|
|
/>
|
|
Enable MCP Search Server
|
|
</label>
|
|
<div className="setting-description">
|
|
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.
|
|
</div>
|
|
{mcpStatus && (
|
|
<div className="save-status">{mcpStatus}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-section">
|
|
<h3>Worker Stats</h3>
|
|
<div className="stats-grid">
|
|
<div className="stat">
|
|
<div className="stat-label">Version</div>
|
|
<div className="stat-value">{stats.worker?.version || '-'}</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-label">Uptime</div>
|
|
<div className="stat-value">{formatUptime(stats.worker?.uptime)}</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-label">Active Sessions</div>
|
|
<div className="stat-value">{stats.worker?.activeSessions || '0'}</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-label">SSE Clients</div>
|
|
<div className="stat-value">{stats.worker?.sseClients || '0'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-section">
|
|
<h3>Database Stats</h3>
|
|
<div className="stats-grid">
|
|
<div className="stat">
|
|
<div className="stat-label">DB Size</div>
|
|
<div className="stat-value">{formatBytes(stats.database?.size)}</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-label">Observations</div>
|
|
<div className="stat-value">{stats.database?.observations || '0'}</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-label">Sessions</div>
|
|
<div className="stat-value">{stats.database?.sessions || '0'}</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-label">Summaries</div>
|
|
<div className="stat-value">{stats.database?.summaries || '0'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|