<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LLM Model Switcher</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"></div>
<script type="module">
import { h, render } from 'https://esm.sh/preact';
import { useState, useEffect } from 'https://esm.sh/preact/hooks';
import htm from 'https://esm.sh/htm';
const html = htm.bind(h);
function App() {
const [models, setModels] = useState([]);
const [currentModel, setCurrentModel] = useState(null);
const [selectedModel, setSelectedModel] = useState(null);
const [loading, setLoading] = useState(true);
const [switching, setSwitching] = useState(false);
const [message, setMessage] = useState({ text: '', type: '' });
// Log state
const [logs, setLogs] = useState([]);
const [loadingLogs, setLoadingLogs] = useState(false);
const fetchModels = async () => {
try {
setLoading(true);
const response = await fetch('/api/models');
const data = await response.json();
if (data.error) throw new Error(data.error);
setModels(data.models || []);
setCurrentModel(data.current);
if (data.current) setSelectedModel(data.current);
} catch (err) {
setMessage({ text: 'Failed to load models: ' + err.message, type: 'error' });
} finally {
setLoading(false);
}
};
const fetchLogs = async () => {
try {
setLoadingLogs(true);
const response = await fetch('/api/logs');
const data = await response.json();
if (data.error) throw new Error(data.error);
setLogs(data.logs || []);
} catch (err) {
// Just log to console or show a small error in the log section?
// We'll put a fake log entry for error
setLogs(['Error fetching logs: ' + err.message]);
} finally {
setLoadingLogs(false);
}
};
const switchModel = async () => {
if (!selectedModel || selectedModel === currentModel) return;
try {
setSwitching(true);
setMessage({ text: 'Switching model and restarting service...', type: 'info' });
const response = await fetch('/api/switch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: selectedModel })
});
const data = await response.json();
if (data.error) throw new Error(data.error);
setMessage({ text: 'Model switched successfully!', type: 'success' });
setCurrentModel(selectedModel);
// Refresh logs after switch
setTimeout(fetchLogs, 2000);
} catch (err) {
setMessage({ text: 'Failed to switch model: ' + err.message, type: 'error' });
} finally {
setSwitching(false);
}
};
useEffect(() => {
fetchModels();
fetchLogs();
}, []);
if (loading) return html`<div class="container"><p>Loading models...</p></div>`;
return html`
<div class="container">
<header>
<h1>LLM Model Switcher</h1>
<p class="status">
Current Model: <strong>${currentModel || 'None'}</strong>
</p>
</header>
<main>
<div class="model-list">
${models.length === 0 ? html`<p>No models found in <code>/etc/llama.cpp.d</code></p>` :
models.map(model => html`
<label class="model-item ${selectedModel === model ? 'selected' : ''} ${currentModel === model ? 'active' : ''}">
<input
type="radio"
name="model"
value="${model}"
checked="${selectedModel === model}"
onChange="${() => setSelectedModel(model)}"
disabled="${switching}"
/>
<span class="model-name">${model}</span>
${currentModel === model && html`<span class="badge">Active</span>`}
</label>
`)
}
</div>
<div class="actions">
<button
onClick="${switchModel}"
disabled="${switching || !selectedModel || selectedModel === currentModel}"
class="${switching ? 'loading' : ''}"
>
${switching ? 'Restarting...' : 'Apply Selection'}
</button>
<button class="secondary" onClick="${fetchModels}" disabled="${switching}">Refresh Models</button>
</div>
${message.text && html`
<div class="message ${message.type}">
${message.text}
</div>
`}
<div class="log-section">
<div class="log-header">
<h2>Service Logs</h2>
<button class="secondary" style="width: auto; padding: 0.4rem 0.8rem; font-size: 0.8rem;" onClick="${fetchLogs}" disabled="${loadingLogs}">
${loadingLogs ? 'Refreshing...' : 'Refresh Logs'}
</button>
</div>
<div class="log-viewer">
${logs.length === 0
? html`<div class="log-entry">No logs available or service not found.</div>`
: logs.map((line, i) => html`<div class="log-entry" key="${i}">${line}</div>`)
}
</div>
</div>
</main>
</div>
`;
}
render(html`<${App} />`, document.getElementById('app'));
</script>
</body>
</html>