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) {
|
||||
const res = await fetch(`${BASE_URL}/episodes/${id}`, { method: 'DELETE' });
|
||||
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 }) {
|
||||
const { settings, saveSetting, saving } = useSettings();
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden', background: 'var(--bg-base)' }}>
|
||||
|
||||
{/* Header */}
|
||||
<div className="panel-header" style={{ padding: '0 24px' }}>
|
||||
<span className="text-base" style={{ fontWeight: 500, color: 'var(--text-secondary)' }}>Settings</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 scroll-y" style={{ padding: '24px' }}>
|
||||
|
||||
{/* Memory */}
|
||||
<SettingsSection title="Memory">
|
||||
<SettingsRow
|
||||
label="Memory Viewer"
|
||||
@@ -19,58 +19,45 @@ export default function SettingsView({ onNavigate }) {
|
||||
action={<button className="btn-primary" style={{ padding: '6px 14px', fontSize: '13px' }}
|
||||
onClick={() => onNavigate('memory')}>Open →</button>}
|
||||
/>
|
||||
<SettingsRow
|
||||
label="Semantic Search Results"
|
||||
description="Max episodes retrieved per query"
|
||||
action={<ComingSoon />}
|
||||
/>
|
||||
<SettingsRow
|
||||
label="Episode Context Window"
|
||||
<NumberSetting
|
||||
label="Recent Episode Limit"
|
||||
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>
|
||||
|
||||
{/* Models */}
|
||||
<SettingsSection title="Models">
|
||||
<SettingsRow
|
||||
label="Active Model"
|
||||
description="Model used for inference"
|
||||
action={<ComingSoon />}
|
||||
/>
|
||||
<SettingsRow
|
||||
label="Temperature"
|
||||
description="Response creativity / randomness"
|
||||
action={<ComingSoon />}
|
||||
/>
|
||||
<SettingsRow
|
||||
label="Context Window"
|
||||
description="Max tokens per request (-c flag)"
|
||||
action={<ComingSoon />}
|
||||
/>
|
||||
<SettingsRow label="Active Model" description="Model used for inference" 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>
|
||||
|
||||
{/* About */}
|
||||
<SettingsSection title="About">
|
||||
<SettingsRow
|
||||
label="Service Health"
|
||||
description="Ping all four services"
|
||||
action={<ComingSoon />}
|
||||
/>
|
||||
<SettingsRow
|
||||
label="Version"
|
||||
description="NexusAI"
|
||||
action={<span className="text-sm text-muted">v0.1.0</span>}
|
||||
/>
|
||||
<SettingsRow label="Service Health" description="Ping all four services" action={<ComingSoon />} />
|
||||
<SettingsRow label="Version" description="NexusAI" action={<span className="text-sm text-muted">v0.1.0</span>} />
|
||||
</SettingsSection>
|
||||
|
||||
{/* Appearance */}
|
||||
<SettingsSection title="Appearance">
|
||||
<SettingsRow
|
||||
label="Theme"
|
||||
description="UI colour scheme"
|
||||
action={<ComingSoon />}
|
||||
/>
|
||||
<SettingsRow label="Theme" description="UI colour scheme" action={<ComingSoon />} />
|
||||
</SettingsSection>
|
||||
|
||||
</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 }) {
|
||||
return (
|
||||
<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