memory settings implementation
This commit is contained in:
@@ -172,4 +172,20 @@ export async function getEpisodes({ limit = API_DEFAULTS.EPISODE_LIMIT, offset =
|
|||||||
export async function deleteEpisode(id) {
|
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();
|
||||||
}
|
}
|
||||||
@@ -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 (0–1)"
|
||||||
|
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={{
|
||||||
|
|||||||
25
packages/chat-client/src/hooks/useSettings.js
Normal file
25
packages/chat-client/src/hooks/useSettings.js
Normal 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 };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user