import React, { useState, useEffect, useCallback } from 'react'; import { useSettings } from '../hooks/useSettings'; import { getServiceHealth } from '../api/orchestration'; import { useModels } from '../hooks/useModels'; export default function SettingsView({ onNavigate }) { const { settings, saveSetting, saving } = useSettings(); return (
Settings
onNavigate('memory')}>Open →} /> saveSetting('recentEpisodeLimit', val)} saving={saving} /> saveSetting('semanticLimit', val)} saving={saving} /> saveSetting('scoreThreshold', val)} saving={saving} /> } /> v0.1.0} /> } />
); } function SettingsSection({ title, children }) { return (

{title}

{children}
); } function NumberSetting({ label, description, value, min, max, step = 1, onSave, saving }) { const [local, setLocal] = useState(value ?? ''); const isDirty = local !== '' && Number(local) !== value; // Sync when settings load from API useEffect(() => { if (value !== undefined) setLocal(value); }, [value]); return ( setLocal(e.target.value)} style={{ width: '64px', padding: '5px 8px', textAlign: 'center', background: 'var(--bg-elevated)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', color: 'var(--text-primary)', fontSize: '13px', outline: 'none', }} /> {isDirty && ( )} } /> ); } function SettingsRow({ label, description, action }) { return (
{label} {description && {description}}
{action}
); } function ComingSoon() { return Coming soon; } function ServiceHealth() { const [services, setServices] = useState(null); const [loading, setLoading] = useState(false); const [lastChecked, setLastChecked] = useState(null); const check = useCallback(async () => { setLoading(true); try { setServices(await getServiceHealth()); setLastChecked(new Date()); } catch (err) { console.error('[ServiceHealth]', err.message); } finally { setLoading(false); } }, []); return (
{lastChecked && ( {lastChecked.toLocaleTimeString()} )}
{services && (
{services.map((svc, i) => (
{/* Status dot */}
{/* Label */} {svc.label} {/* Detail — show model for inference, nothing extra for others */} {svc.key === 'inference' && svc.detail?.model ? svc.detail.model : svc.status === 'unreachable' ? 'Unreachable' : ''} {/* Latency */} {svc.latency}ms
))}
)}
); } function ModelsSection({ onNavigate }) { const { models, selectedModel, setSelectedModel } = useModels(); const [selectedInfo, setSelectedInfo] = useState(null); // Sync info panel when selection changes useEffect(() => { const m = models.find(m => m.value === selectedModel); setSelectedInfo(m ?? null); }, [selectedModel, models]); return ( <> } /> setSelectedModel(e.target.value)} style={{ padding: '6px 10px', fontSize: '13px', background: 'var(--bg-elevated)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', color: 'var(--text-primary)', cursor: 'pointer', outline: 'none', maxWidth: '220px', }} > {models.map(m => ( ))} } /> {/* Model info panel */} {selectedInfo && (

Model Info

{selectedInfo.description && ( )}

Model loading and parameter configuration coming soon

)} ); } function InfoLine({ label, value, mono }) { return (
{label} {value}
); } function ModelsFolderSetting() { const { settings, saveSetting, saving } = useSettings(); const [local, setLocal] = useState(''); const [error, setError] = useState(null); useEffect(() => { if (settings?.modelsFolderPath) setLocal(settings.modelsFolderPath); }, [settings?.modelsFolderPath]); const isDirty = local !== '' && local !== settings?.modelsFolderPath; async function handleSave() { setError(null); try { await saveSetting('modelsFolderPath', local); } catch (err) { setError('Path not accessible'); } } return (
{ setLocal(e.target.value); setError(null); }} style={{ width: '220px', padding: '5px 8px', fontSize: '12px', fontFamily: 'monospace', background: 'var(--bg-elevated)', border: `1px solid ${error ? '#e74c3c' : 'var(--border)'}`, borderRadius: 'var(--radius-md)', color: 'var(--text-primary)', outline: 'none', }} /> {isDirty && ( )}
{error && {error}}
); }