mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-12 12:37:32 +00:00
Reduce cookbook startup polling
This commit is contained in:
+5
-3
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
@@ -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();
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(() => '');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user