feat: show serve runtime readiness (#1209)
This commit is contained in:
@@ -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 = '';
|
||||
|
||||
Reference in New Issue
Block a user