Reduce cookbook startup polling

This commit is contained in:
pewdiepie-archdaemon
2026-06-27 13:50:21 +00:00
parent 45ee5a71f4
commit 4222039b67
6 changed files with 66 additions and 26 deletions
+5 -3
View File
@@ -1541,9 +1541,9 @@ import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composer
<div class="thinking-header-left"><span class="live-think-header-text">Thinking\u2026</span></div>
<span class="live-think-spinner-slot" style="flex-shrink:0;margin-left:auto;"></span>
<span class="live-think-timer" style="font-size:11px;opacity:0.4;font-variant-numeric:tabular-nums;margin-left:6px;margin-right:5px;"></span>
<span class="thinking-toggle live-think-toggle" id="${_liveThinkDomId}-toggle"></span>
<span class="thinking-toggle live-think-toggle expanded" id="${_liveThinkDomId}-toggle"></span>
</div>
<div class="thinking-content" id="${_liveThinkDomId}">
<div class="thinking-content expanded" id="${_liveThinkDomId}">
<div class="thinking-content-inner live-think-inner"></div>
</div>
</div>`;
@@ -1589,13 +1589,15 @@ import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composer
_liveThinkTimerEl.textContent = _formatThinkStats(_elapsedLive, _liveThinkTokenCount);
}
// Keep thinking box scrolled to bottom, but let user scroll up
var _followThinking = true;
var thinkBox = _liveThinkInner.closest('.thinking-content');
if (thinkBox) {
var nearBottom = thinkBox.scrollHeight - thinkBox.clientHeight - thinkBox.scrollTop < 80;
if (nearBottom) thinkBox.scrollTop = thinkBox.scrollHeight;
_followThinking = nearBottom;
}
}
uiModule.scrollHistory();
if (_followThinking) uiModule.scrollHistory();
continue;
} else if (!hasUnclosedThink && isThinking) {
isThinking = false;
+16 -7
View File
@@ -561,8 +561,9 @@ function _ollamaToHwfitRows(libModels, vramAvail, ramAvail) {
return out;
}
export async function _hwfitFetch(fresh = false) {
export async function _hwfitFetch(fresh = false, opts = {}) {
const _tk = ++_hwfitFetchToken;
const allowNetwork = fresh || opts.allowNetwork !== false;
const useCase = document.getElementById('hwfit-usecase')?.value || '';
const search = document.getElementById('hwfit-search')?.value?.trim() || '';
const remoteHost = _envState.remoteHost || '';
@@ -588,6 +589,13 @@ export async function _hwfitFetch(fresh = false) {
}
_hwfitRenderList(list, _applyEngineFilter(_cached.models));
} else {
if (!allowNetwork) {
_hwfitCache = null;
_hwfitRenderHw(hw, null);
list.innerHTML = '<div class="hwfit-loading" style="flex-direction:column;gap:6px;text-align:center;"><div>No cached scan yet</div><div style="font-size:11px;opacity:0.55;max-width:420px;line-height:1.4;">Press Scan to test hardware and rank models for this server.</div></div>';
try { wp.destroy(); } catch {}
return;
}
// Show spinner while scanning — stack the spinner above a text label
// (the .hwfit-loading class is a centered flex ROW, so force column here).
const loadingDiv = document.createElement('div');
@@ -606,6 +614,10 @@ export async function _hwfitFetch(fresh = false) {
list.appendChild(loadingDiv);
_hwfitCache = null; // no instant paint — clear until the fetch returns
}
if (!allowNetwork) {
try { wp.destroy(); } catch {}
return;
}
// Only fetch cached model IDs when server changes, not on every search/sort
const remoteKey = _currentServerValue();
if (!_cachedModelIds || _lastCacheHost() !== remoteKey) {
@@ -2168,15 +2180,12 @@ export function _hwfitInit() {
}
});
});
// Auto-test when host or port blur
// Manual connectivity test after editing host or port. Existing saved
// servers are not auto-tested on panel open; unreachable hosts can stall the
// Cookbook UI and make opening the panel feel blocked.
entry.querySelectorAll('.cookbook-srv-host, .cookbook-srv-port').forEach(el => {
el.addEventListener('blur', () => _testServerConnection(entry));
});
// Initial test for pre-filled rows (existing servers on tab load)
if (entry.querySelector('.cookbook-srv-host')?.value?.trim() && !entry.dataset.tested) {
entry.dataset.tested = '1';
_testServerConnection(entry);
}
// Cancel button on a brand-new server entry: discard it (no confirm — it's
// unsaved) and re-sync so the dropped blank server doesn't linger.
const cancelBtn = entry.querySelector('.cookbook-server-cancel-btn');
+5 -5
View File
@@ -1640,10 +1640,10 @@ function _wireTabEvents(body) {
});
if (backend === 'Search') {
_hwfitInit();
_hwfitFetch();
_hwfitFetch(false, { allowNetwork: false });
}
if (backend === 'Serve') {
_fetchCachedModels();
_fetchCachedModels(false, { allowNetwork: false });
}
if (backend === 'Dependencies') {
_fetchDependencies();
@@ -1746,7 +1746,7 @@ function _wireTabEvents(body) {
_applyServerSelection(dlServer.value);
// Reset toggle state (no flicker) so the new server's hardware re-renders.
_resetGpuToggleState();
_hwfitFetch();
_hwfitFetch(false, { allowNetwork: false });
});
}
@@ -1783,7 +1783,7 @@ function _wireTabEvents(body) {
if (settingsTab) settingsTab.click();
});
}
_fetchCachedModels();
_fetchCachedModels(false, { allowNetwork: false });
});
}
@@ -2863,7 +2863,7 @@ function _renderRecipes() {
// Auto-init What Fits
_hwfitInit();
_hwfitFetch();
_hwfitFetch(false, { allowNetwork: false });
}
// ── Public API ──
+27 -9
View File
@@ -1862,6 +1862,7 @@ export function _renderRunningTab() {
body.querySelectorAll('.cookbook-group').forEach(g => {
g.classList.toggle('hidden', g.dataset.backendGroup !== 'Running');
});
setTimeout(() => _renderRunningTab(), 0);
});
} else if (runTab) {
const _errCount2 = tasks.filter(t => t.status === 'error' || t.status === 'crashed').length;
@@ -2712,7 +2713,7 @@ export function _renderRunningTab() {
// responds; without this, the user opens the Running tab and sees
// only the placeholder ("Launched by scheduled task …") because
// _reconnectTask never fires for status 'ready'/'loading'/'warming'.
if (['running', 'ready', 'loading', 'warming', 'starting'].includes(task.status)) {
if (_isRunningTabVisible() && ['running', 'ready', 'loading', 'warming', 'starting'].includes(task.status)) {
_reconnectTask(el, task);
}
}
@@ -3438,6 +3439,23 @@ async function _reconnectTask(el, task) {
let _bgMonitorInterval = null;
function _hasLiveTasks(tasks = null) {
const list = tasks || _loadTasks();
return list.some(t =>
t.status === 'running'
|| t.status === 'queued'
|| t.status === 'ready'
|| _downloadOutputLooksActive(t)
);
}
function _isRunningTabVisible() {
const modal = document.getElementById('cookbook-modal');
if (!modal || modal.classList.contains('hidden')) return false;
const activeTab = modal.querySelector('.cookbook-tab.active')?.dataset?.backend || '';
return activeTab === 'Running';
}
// Reachability check for running serve tasks. The tmux pane can stay alive
// while the model server inside it has crashed (so no "Process exited" line
// ever appears) — leaving the card showing "running" forever. So we actively
@@ -3656,7 +3674,9 @@ export function _startBackgroundMonitor() {
// crashed/etc. whose tmux session is actually still running, and flip
// them back to running. Internally throttled to 8s so a manual call from
// the open path or a fast invocation doesn't double up.
_selfHealStaleTasks().catch(() => {});
if (_hasLiveTasks() || _isRunningTabVisible()) {
_selfHealStaleTasks().catch(() => {});
}
}, BG_MONITOR_INTERVAL_MS);
_pollBackgroundStatus();
_checkServeReachability();
@@ -3972,17 +3992,15 @@ export function initRunning(shared) {
_detectModelOptimizations = shared._detectModelOptimizations;
_buildServeCmd = shared._buildServeCmd;
// App boot: pull authoritative state from server, then auto-start
// the background monitor unconditionally. Used to gate on "already
// has running tasks" but that meant when the agent (or anyone)
// added a task after boot, the UI never noticed. 10s poll of a
// small status endpoint is cheap and gives the agent + the UI a
// shared live picture.
// App boot: pull authoritative state from server, but don't start the
// running-task monitor unless there is real work to watch. Starting it
// unconditionally made a plain Cookbook open keep probing stale tmux/SSH
// sessions, which is expensive when a saved remote host is unreachable.
(async () => {
try {
await _syncFromServer();
} catch {}
_startBackgroundMonitor();
if (_hasLiveTasks()) _startBackgroundMonitor();
})();
}
+9 -1
View File
@@ -3663,9 +3663,10 @@ function _renderCachedModelsData(list, data, host) {
_rerenderCachedModels();
}
export async function _fetchCachedModels(fresh = false) {
export async function _fetchCachedModels(fresh = false, opts = {}) {
const list = document.getElementById('hwfit-cached-list');
if (!list) return;
const allowNetwork = fresh || opts.allowNetwork !== false;
list.innerHTML = '';
const _dlWp = spinnerModule.createWhirlpool(22);
@@ -3740,6 +3741,13 @@ export async function _fetchCachedModels(fresh = false) {
_renderCachedModelsData(list, cached, host);
return;
}
if (!allowNetwork) {
_dlWp.destroy();
list.innerHTML = '<div class="hwfit-loading" style="flex-direction:column;gap:6px;text-align:center;"><div>No cached model scan yet</div><div style="font-size:11px;opacity:0.55;max-width:420px;line-height:1.4;">Press Scan to check this server\'s model cache.</div></div>';
const tagContainer = document.getElementById('serve-tags');
if (tagContainer) tagContainer.innerHTML = '';
return;
}
const res = await fetch(`/api/model/cached${params}`);
if (!res.ok) {
const body = await res.text().catch(() => '');
+4 -1
View File
@@ -2,7 +2,7 @@
// This module handles all session-related operations
import Storage from './storage.js';
import uiModule, { styledPrompt } from './ui.js';
import uiModule, { autoResize, styledPrompt } from './ui.js';
import markdownModule from './markdown.js';
import chatRenderer from './chatRenderer.js';
import { providerLogo } from './providers.js';
@@ -1859,6 +1859,9 @@ export async function selectSession(id, { keepSidebar = false, showLoading = tru
if (msgInput) {
msgInput.disabled = false;
msgInput.value = '';
msgInput.style.height = '';
msgInput.style.overflow = '';
autoResize(msgInput);
}
const sendBtn2 = document.querySelector('.send-btn');
if (sendBtn2) {