implementing model selector and info panel
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useSettings } from '../hooks/useSettings';
|
import { useSettings } from '../hooks/useSettings';
|
||||||
import { getServiceHealth } from '../api/orchestration';
|
import { getServiceHealth } from '../api/orchestration';
|
||||||
|
import { useModels } from '../hooks/useModels';
|
||||||
|
|
||||||
export default function SettingsView({ onNavigate }) {
|
export default function SettingsView({ onNavigate }) {
|
||||||
const { settings, saveSetting, saving } = useSettings();
|
const { settings, saveSetting, saving } = useSettings();
|
||||||
@@ -47,9 +48,7 @@ export default function SettingsView({ onNavigate }) {
|
|||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection title="Models">
|
<SettingsSection title="Models">
|
||||||
<SettingsRow label="Active Model" description="Model used for inference" action={<ComingSoon />} />
|
<ModelsSection />
|
||||||
<SettingsRow label="Temperature" description="Response creativity / randomness" action={<ComingSoon />} />
|
|
||||||
<SettingsRow label="Context Window" description="Max tokens per request (-c flag)" action={<ComingSoon />} />
|
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection title="About">
|
<SettingsSection title="About">
|
||||||
@@ -238,3 +237,124 @@ function ServiceHealth() {
|
|||||||
</div>
|
</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