diff --git a/packages/tracker/meshnet_tracker/dashboard.html b/packages/tracker/meshnet_tracker/dashboard.html index ea30ac6..d95c61e 100644 --- a/packages/tracker/meshnet_tracker/dashboard.html +++ b/packages/tracker/meshnet_tracker/dashboard.html @@ -1223,11 +1223,9 @@ function switchDashboardTab(name) { const button = $("tab-" + tabName); if (button) button.classList.toggle("active", tabName === dashboardTab); } - if (name === "chat") { - const promptEl = $("chat-prompt"); - if (promptEl) promptEl.focus(); - } - refreshActiveTab(true); + void refreshActiveTab(true).then(() => { + if (dashboardTab === "chat") $("chat-prompt")?.focus(); + }); } function updateSectionVisibility() { @@ -1636,7 +1634,7 @@ async function loadAccountSummary(force) { } if (!r.ok) { setSession(null); renderAuthForms(); return false; } applyAccountSummary(r.data, force); - if (dashboardTab === "chat" && !refreshBlocked()) renderChatModels(force); + if (availableModels.length) renderChatModels(Boolean(force)); return true; } @@ -1830,6 +1828,9 @@ function bindChatPromptShortcuts() { promptEl.style.height = "auto"; promptEl.style.height = Math.min(promptEl.scrollHeight, 200) + "px"; }); + promptEl.addEventListener("blur", () => { + if (pendingChatModelRefresh) renderChatModels(true); + }); } async function cancelProxyRequest(requestId) { @@ -1866,6 +1867,21 @@ function applyAvailableModels(models, map) { }).filter(model => model.id); } +async function preloadChatModels(forceRender) { + const [map, models, routing] = await Promise.all([ + fetchJson("/v1/network/map"), + fetchJson("/v1/models"), + fetchJson("/v1/routing"), + ]); + if (routing) lastRouting = routing; + applyAvailableModels(models, map); + if (forceRender !== false) { + panelSig.chatModels = null; + renderChatModels(true); + } + return { map, models, routing }; +} + function markRefreshed() { $("refreshed").textContent = "refreshed " + new Date().toLocaleTimeString(); } @@ -1893,20 +1909,10 @@ async function fetchOverviewTab() { } async function fetchChatTab() { - const fetches = [ - fetchJson("/v1/network/map"), - fetchJson("/v1/models"), - fetchJson("/v1/routing"), - ]; - if (isLoggedIn) fetches.push(apiCall("/v1/account")); - const [map, models, routing, accountResp] = await Promise.all(fetches); - lastRouting = routing; - applyAvailableModels(models, map); - if (accountResp && accountResp.ok) applyAccountSummary(accountResp.data, false); - if (!refreshBlocked()) { - renderChatModels(true); - } else { - pendingChatModelRefresh = true; + await preloadChatModels(true); + if (isLoggedIn) { + const accountResp = await apiCall("/v1/account"); + if (accountResp && accountResp.ok) applyAccountSummary(accountResp.data, false); } } @@ -2003,10 +2009,12 @@ bindChatPromptShortcuts(); (async () => { if (sessionToken) await loadAccountSummary(true); else renderAuthForms(); - await refreshActiveTab(true); + await Promise.all([ + preloadChatModels(true), + refreshActiveTab(true), + ]); renderChatHistory(true); renderChatAuthHint(); - if (dashboardTab === "chat") renderChatModels(true); })(); setInterval(pollCallWallIfIdle, CALL_WALL_POLL_MS); diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index a43d8f9..b37a472 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -50,6 +50,8 @@ def test_dashboard_chat_uses_streaming_fetch(): assert ".body.getReader()" in html assert '=== "[DONE]"' in html assert 'console.error("chat stream failed", err)' in html + assert "preloadChatModels" in html + assert "renderChatModels(true)" in html def test_network_map_includes_node_friendly_name():