diff --git a/static/js/cookbookServe.js b/static/js/cookbookServe.js index 189c4ae..6e71791 100644 --- a/static/js/cookbookServe.js +++ b/static/js/cookbookServe.js @@ -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 += ``; panelHtml += ``; + panelHtml += ``; if (_ggufChoices.length > 1) { panelHtml += `
`; panelHtml += ``; @@ -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 = '';