implementing model selector and info panel
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
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();
|
||||
@@ -47,9 +48,7 @@ export default function SettingsView({ onNavigate }) {
|
||||
</SettingsSection>
|
||||
|
||||
<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 />} />
|
||||
<ModelsSection />
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title="About">
|
||||
@@ -238,3 +237,124 @@ function ServiceHealth() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<SettingsRow
|
||||
label="Models Folder"
|
||||
description="Path to folder containing .gguf files"
|
||||
action={<ModelsFolderSetting />}
|
||||
/>
|
||||
<SettingsRow
|
||||
label="Active Model"
|
||||
description="Model used for inference"
|
||||
action={
|
||||
<select
|
||||
value={selectedModel}
|
||||
onChange={e => 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 => (
|
||||
<option key={m.value} value={m.value}>{m.label}</option>
|
||||
))}
|
||||
</select>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Model info panel */}
|
||||
{selectedInfo && (
|
||||
<div style={{
|
||||
margin: '0', padding: '14px 16px',
|
||||
borderTop: '1px solid var(--border)',
|
||||
background: 'var(--bg-elevated)',
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
}}>
|
||||
<p className="label-upper" style={{ color: 'var(--text-muted)' }}>Model Info</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<InfoLine label="File" value={selectedInfo.value} mono />
|
||||
<InfoLine label="Size" value={selectedInfo.size ?? '—'} />
|
||||
{selectedInfo.description && (
|
||||
<InfoLine label="Description" value={selectedInfo.description} />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted" style={{ marginTop: 4, fontStyle: 'italic' }}>
|
||||
Model loading and parameter configuration coming soon
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function InfoLine({ label, value, mono }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'baseline' }}>
|
||||
<span className="text-xs text-muted" style={{ minWidth: 72, flexShrink: 0 }}>{label}</span>
|
||||
<span style={{
|
||||
fontSize: 12, color: 'var(--text-secondary)',
|
||||
fontFamily: mono ? 'monospace' : 'inherit',
|
||||
wordBreak: 'break-all',
|
||||
}}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'flex-end' }}>
|
||||
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
||||
<input
|
||||
value={local}
|
||||
onChange={e => { 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 && (
|
||||
<button className="btn-primary" style={{ padding: '5px 10px', fontSize: '12px' }}
|
||||
disabled={saving} onClick={handleSave}>
|
||||
Save
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{error && <span className="text-xs" style={{ color: '#e74c3c' }}>{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user