feat: show serve runtime readiness (#1209)

This commit is contained in:
spooky
2026-06-03 01:01:00 +10:00
committed by GitHub
parent e72b9a8a95
commit 37f5635f8f

View File

@@ -93,6 +93,67 @@ function _allGpuIds(count) {
return Array.from({ length: Math.floor(n) }, (_, i) => String(i)).join(',');
}
function _selectedServeTarget(panel) {
const select = document.getElementById('hwfit-server-select') || document.getElementById('hwfit-dl-server');
const servers = Array.isArray(_envState.servers) ? _envState.servers : [];
let host = _envState.remoteHost || '';
let server = host ? servers.find(s => s.host === host) : null;
if (select && select.value != null) {
if (select.value === 'local') {
host = '';
server = servers.find(s => !s.host || s.host === 'local') || null;
} else {
const idx = /^\d+$/.test(String(select.value)) ? parseInt(select.value, 10) : -1;
server = servers.find(s => s.host === select.value) || (idx >= 0 ? servers[idx] : null) || null;
host = server?.host || '';
}
}
const venv = panel?.querySelector('[data-field="venv"]')?.value?.trim() || server?.envPath || _envState.envPath || '';
const label = host
? (server?.name ? `${server.name} (${host})` : host)
: (server?.name || 'local server');
return {
host,
port: host ? (_getPort(host) || server?.port || '') : '',
venv,
label,
};
}
async function _fetchServeRuntimePackage(panel, backend) {
const packageByBackend = {
vllm: 'vllm',
sglang: 'sglang',
llamacpp: 'llama_cpp',
diffusers: 'diffusers',
};
const packageName = packageByBackend[backend];
if (!packageName) return null;
const target = _selectedServeTarget(panel);
const params = new URLSearchParams();
if (target.host) {
params.set('host', target.host);
if (target.port) params.set('ssh_port', target.port);
if (target.venv) params.set('venv', target.venv);
}
const res = await fetch('/api/cookbook/packages' + (params.toString() ? '?' + params.toString() : ''), { credentials: 'same-origin' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
const pkg = (data.packages || []).find(p => p.name === packageName);
return { pkg, target };
}
function _runtimeNoteText(backend, pkg, target) {
const labels = { vllm: 'vLLM', sglang: 'SGLang', llamacpp: 'llama.cpp', diffusers: 'Diffusers' };
const label = labels[backend] || backend;
if (!pkg) return `${label} readiness unavailable for ${target.label}.`;
const note = pkg.status_note || pkg.update_note || '';
if (pkg.installed) {
return note ? `${label} ready on ${target.label}: ${note}` : `${label} ready on ${target.label}.`;
}
return note ? `${label} missing on ${target.label}: ${note}` : `${label} missing on ${target.label}.`;
}
// ── Filter/sort cached model list ──
function _filterCachedList() {
@@ -399,7 +460,9 @@ function _rerenderCachedModels() {
// Toggle — close if already open
if (item.classList.contains('doclib-card-expanded')) {
item.querySelector('.hwfit-serve-panel')?.remove();
const existingPanel = item.querySelector('.hwfit-serve-panel');
existingPanel?._cleanupRuntimeReadiness?.();
existingPanel?.remove();
item.classList.remove('doclib-card-expanded');
item.style.flexDirection = '';
item.style.alignItems = '';
@@ -410,7 +473,9 @@ function _rerenderCachedModels() {
// Collapse any other expanded
list.querySelectorAll('.doclib-card-expanded').forEach(c => {
c.querySelector('.hwfit-serve-panel')?.remove();
const openPanel = c.querySelector('.hwfit-serve-panel');
openPanel?._cleanupRuntimeReadiness?.();
openPanel?.remove();
c.classList.remove('doclib-card-expanded');
c.style.flexDirection = '';
c.style.alignItems = '';
@@ -509,6 +574,7 @@ function _rerenderCachedModels() {
}
panelHtml += `<label>${_l('GPUs','Toggle which GPUs to use')}<div class="cookbook-gpu-group">${_gpuBtnsHtml}</div><input type="hidden" class="hwfit-sf" data-field="gpus" value="${esc(defaultGpus)}" /></label>`;
panelHtml += `</div>`;
panelHtml += `<div class="hwfit-serve-runtime-note" style="display:none;font-size:11px;line-height:1.35;color:var(--fg-muted);margin-top:-4px;"></div>`;
if (_ggufChoices.length > 1) {
panelHtml += `<div class="hwfit-serve-row hwfit-backend-llamacpp">`;
panelHtml += `<label class="hwfit-backend-llamacpp">${_l('GGUF File','Choose the exact GGUF artifact to serve from this cached model folder.')}<select class="hwfit-sf hwfit-sf-wide" data-field="gguf_file">${_ggufOptions}</select></label>`;
@@ -856,6 +922,38 @@ function _rerenderCachedModels() {
}
updateBackendVisibility();
async function updateRuntimeReadinessNote() {
const note = panel.querySelector('.hwfit-serve-runtime-note');
if (!note) return;
const backend = panel.querySelector('[data-field="backend"]')?.value || 'vllm';
if (!['vllm', 'sglang', 'llamacpp', 'diffusers'].includes(backend)) {
note.style.display = 'none';
note.textContent = '';
return;
}
const seq = (panel._runtimeReadinessSeq || 0) + 1;
panel._runtimeReadinessSeq = seq;
note.style.display = '';
note.textContent = 'Checking runtime on selected server...';
try {
const { pkg, target } = await _fetchServeRuntimePackage(panel, backend);
if (panel._runtimeReadinessSeq !== seq) return;
note.textContent = _runtimeNoteText(backend, pkg, target);
note.style.color = pkg?.installed ? 'var(--fg-muted)' : 'var(--red)';
} catch (err) {
if (panel._runtimeReadinessSeq !== seq) return;
note.textContent = `Runtime readiness unavailable: ${err?.message || err}`;
note.style.color = 'var(--fg-muted)';
}
}
updateRuntimeReadinessNote();
const runtimeServerSelect = document.getElementById('hwfit-server-select') || document.getElementById('hwfit-dl-server');
if (runtimeServerSelect) {
const refreshRuntimeOnServerChange = () => updateRuntimeReadinessNote();
runtimeServerSelect.addEventListener('change', refreshRuntimeOnServerChange);
panel._cleanupRuntimeReadiness = () => runtimeServerSelect.removeEventListener('change', refreshRuntimeOnServerChange);
}
// Wire save slots
function _loadSlotIntoPanel(slotIdx) {
const presets = _loadPresets();
@@ -939,6 +1037,7 @@ function _rerenderCachedModels() {
const _gf = panel.querySelector('[data-field="gpus"]');
if (_gf) _gf.value = activeGpus.join(',');
updateBackendVisibility();
updateRuntimeReadinessNote();
updateCmd();
panel.querySelectorAll('.cookbook-slot-btn').forEach(b => b.classList.remove('active'));
panel.querySelector(`.cookbook-slot-btn[data-slot="${slotIdx}"]`)?.classList.add('active');
@@ -1478,6 +1577,10 @@ function _rerenderCachedModels() {
const extraEl = panel.querySelector('[data-field="extra"]');
if (extraEl) extraEl.value = '';
updateBackendVisibility();
updateRuntimeReadinessNote();
}
if (e.target.dataset.field === 'venv') {
updateRuntimeReadinessNote();
}
updateCmd();
});
@@ -1509,6 +1612,7 @@ function _rerenderCachedModels() {
// "back out" affordance next to Launch.
panel.querySelector('.hwfit-serve-cancel')?.addEventListener('click', (ev) => {
ev.stopPropagation();
panel._cleanupRuntimeReadiness?.();
panel.remove();
item.classList.remove('doclib-card-expanded');
item.style.flexDirection = '';