import React, { useState, useEffect, useCallback } from 'react';
import { useSettings } from '../hooks/useSettings';
import { useModels } from '../hooks/useModels';
import { getServiceHealth } from '../api/orchestration';
export default function SettingsView({ onNavigate, onBack, modelProps }) {
const { settings, saveSetting, saving } = useSettings();
return (
onNavigate('memory')}>Open →}
/>
saveSetting('recentEpisodeLimit', val)}
saving={saving}
/>
saveSetting('semanticLimit', val)}
saving={saving}
/>
saveSetting('scoreThreshold', val)}
saving={saving}
/>
{/* Global system prompt */}
}
/>
v0.1.0}
/>
} />
);
}
// ── Error boundary ───────────────────────────────────────────
class SettingsSectionErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error) {
return { error };
}
render() {
if (this.state.error) {
return (
this.setState({ error: null })}>
Retry
}
/>
);
}
return this.props.children;
}
}
// ── Layout components ────────────────────────────────────────
function SettingsSection({ title, children }) {
return (
);
}
function SettingsRow({ label, description, action }) {
return (
{label}
{description && {description}}
{action}
);
}
function NumberSetting({ label, description, value, min, max, step = 1, onSave, saving }) {
const [local, setLocal] = useState(value ?? '');
const isDirty = local !== '' && Number(local) !== value;
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 ComingSoon() {
return Coming soon;
}
// ── System prompt setting ────────────────────────────────────
function SystemPromptSetting({ settings, saveSetting, saving }) {
const [local, setLocal] = useState(settings?.systemPrompt ?? '');
const [savedPrompt, setSavedPrompt] = useState(settings?.systemPrompt ?? '');
useEffect(() => {
if (settings?.systemPrompt !== undefined) {
setLocal(settings.systemPrompt ?? '');
setSavedPrompt(settings.systemPrompt ?? '');
}
}, [settings?.systemPrompt]);
const isDirty = local !== savedPrompt;
async function handleSave() {
await saveSetting('systemPrompt', local.trim() || null);
setSavedPrompt(local);
}
return (
System Prompt
Default instruction given to the model on every request. Projects can override this.
{isDirty && (
)}
);
}
// ── Service health ───────────────────────────────────────────
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) => (
{svc.label}
{svc.key === 'inference' && svc.detail?.model
? svc.detail.model
: svc.status === 'unreachable' ? 'Unreachable' : ''}
{svc.latency}ms
))}
)}
);
}
// ── Models section ───────────────────────────────────────────
function ModelsSection({ settings, saveSetting, saving, modelProps }) {
const { models, selectedModel, setSelectedModel } = useModels();
const [selectedInfo, setSelectedInfo] = useState(null);
useEffect(() => {
const m = models.find(m => m.value === selectedModel);
setSelectedInfo(m ?? null);
}, [selectedModel, models]);
return (
<>
}
/>
saveSetting('temperature', val)}
saving={saving}
/>
saveSetting('repeatPenalty', val)}
saving={saving}
/>
saveSetting('topP', val)}
saving={saving}
/>
saveSetting('topK', val)}
saving={saving}
/>
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 => (
))}
}
/>
{selectedInfo && (
Model Info
{selectedInfo.description && (
)}
Model loading and parameter configuration coming soon
)}
>
);
}
function InfoLine({ label, value, mono }) {
return (
{label}
{value}
);
}
function ModelsFolderSetting({ settings, saveSetting, saving }) {
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}}
);
}