memory settings implementation

This commit is contained in:
Storme-bit
2026-04-17 23:18:48 -07:00
parent 77275cf476
commit afae2af85b
3 changed files with 116 additions and 45 deletions

View File

@@ -173,3 +173,19 @@ export async function deleteEpisode(id) {
const res = await fetch(`${BASE_URL}/episodes/${id}`, { method: 'DELETE' }); const res = await fetch(`${BASE_URL}/episodes/${id}`, { method: 'DELETE' });
if (!res.ok) throw new Error(`Failed to delete episode: ${res.status}`); if (!res.ok) throw new Error(`Failed to delete episode: ${res.status}`);
} }
export async function getSettings() {
const res = await fetch(`${BASE_URL}/settings`);
if (!res.ok) throw new Error(`Failed to fetch settings: ${res.status}`);
return res.json();
}
export async function updateSettings(updates) {
const res = await fetch(`${BASE_URL}/settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
if (!res.ok) throw new Error(`Failed to update settings: ${res.status}`);
return res.json();
}

View File

@@ -1,17 +1,17 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import { useSettings } from '../hooks/useSettings';
export default function SettingsView({ onNavigate }) { export default function SettingsView({ onNavigate }) {
const { settings, saveSetting, saving } = useSettings();
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden', background: 'var(--bg-base)' }}> <div style={{ display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden', background: 'var(--bg-base)' }}>
{/* Header */}
<div className="panel-header" style={{ padding: '0 24px' }}> <div className="panel-header" style={{ padding: '0 24px' }}>
<span className="text-base" style={{ fontWeight: 500, color: 'var(--text-secondary)' }}>Settings</span> <span className="text-base" style={{ fontWeight: 500, color: 'var(--text-secondary)' }}>Settings</span>
</div> </div>
<div className="flex-1 scroll-y" style={{ padding: '24px' }}> <div className="flex-1 scroll-y" style={{ padding: '24px' }}>
{/* Memory */}
<SettingsSection title="Memory"> <SettingsSection title="Memory">
<SettingsRow <SettingsRow
label="Memory Viewer" label="Memory Viewer"
@@ -19,58 +19,45 @@ export default function SettingsView({ onNavigate }) {
action={<button className="btn-primary" style={{ padding: '6px 14px', fontSize: '13px' }} action={<button className="btn-primary" style={{ padding: '6px 14px', fontSize: '13px' }}
onClick={() => onNavigate('memory')}>Open </button>} onClick={() => onNavigate('memory')}>Open </button>}
/> />
<SettingsRow <NumberSetting
label="Semantic Search Results" label="Recent Episode Limit"
description="Max episodes retrieved per query"
action={<ComingSoon />}
/>
<SettingsRow
label="Episode Context Window"
description="Recent episodes injected into each prompt" description="Recent episodes injected into each prompt"
action={<ComingSoon />} value={settings?.recentEpisodeLimit}
min={1} max={20}
onSave={val => saveSetting('recentEpisodeLimit', val)}
saving={saving}
/>
<NumberSetting
label="Semantic Search Limit"
description="Max episodes retrieved via vector search per query"
value={settings?.semanticLimit}
min={1} max={20}
onSave={val => saveSetting('semanticLimit', val)}
saving={saving}
/>
<NumberSetting
label="Score Threshold"
description="Minimum similarity score for semantic results (01)"
value={settings?.scoreThreshold}
min={0} max={1} step={0.05}
onSave={val => saveSetting('scoreThreshold', val)}
saving={saving}
/> />
</SettingsSection> </SettingsSection>
{/* Models */}
<SettingsSection title="Models"> <SettingsSection title="Models">
<SettingsRow <SettingsRow label="Active Model" description="Model used for inference" action={<ComingSoon />} />
label="Active Model" <SettingsRow label="Temperature" description="Response creativity / randomness" action={<ComingSoon />} />
description="Model used for inference" <SettingsRow label="Context Window" description="Max tokens per request (-c flag)" action={<ComingSoon />} />
action={<ComingSoon />}
/>
<SettingsRow
label="Temperature"
description="Response creativity / randomness"
action={<ComingSoon />}
/>
<SettingsRow
label="Context Window"
description="Max tokens per request (-c flag)"
action={<ComingSoon />}
/>
</SettingsSection> </SettingsSection>
{/* About */}
<SettingsSection title="About"> <SettingsSection title="About">
<SettingsRow <SettingsRow label="Service Health" description="Ping all four services" action={<ComingSoon />} />
label="Service Health" <SettingsRow label="Version" description="NexusAI" action={<span className="text-sm text-muted">v0.1.0</span>} />
description="Ping all four services"
action={<ComingSoon />}
/>
<SettingsRow
label="Version"
description="NexusAI"
action={<span className="text-sm text-muted">v0.1.0</span>}
/>
</SettingsSection> </SettingsSection>
{/* Appearance */}
<SettingsSection title="Appearance"> <SettingsSection title="Appearance">
<SettingsRow <SettingsRow label="Theme" description="UI colour scheme" action={<ComingSoon />} />
label="Theme"
description="UI colour scheme"
action={<ComingSoon />}
/>
</SettingsSection> </SettingsSection>
</div> </div>
@@ -96,6 +83,49 @@ function SettingsSection({ 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 (
<SettingsRow
label={label}
description={description}
action={
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<input
type="number"
value={local}
min={min} max={max} step={step}
onChange={e => 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 && (
<button
className="btn-primary"
style={{ padding: '5px 10px', fontSize: '12px' }}
disabled={saving}
onClick={() => onSave(Number(local))}
>
Save
</button>
)}
</div>
}
/>
);
}
function SettingsRow({ label, description, action }) { function SettingsRow({ label, description, action }) {
return ( return (
<div style={{ <div style={{

View File

@@ -0,0 +1,25 @@
import { useState, useEffect } from 'react';
import { getSettings, updateSettings } from '../api/orchestration';
export function useSettings() {
const [settings, setSettings] = useState(null);
const [saving, setSaving] = useState(false);
useEffect(() => {
getSettings().then(setSettings).catch(console.error);
}, []);
async function saveSetting(key, value) {
setSaving(true);
try {
const updated = await updateSettings({ [key]: value });
setSettings(updated);
} catch (err) {
console.error('[useSettings] Save failed:', err.message);
} finally {
setSaving(false);
}
}
return { settings, saveSetting, saving };
}