From cab3361073aba2cfcf26d9e38bed87f029ad6deb Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 4 Oct 2026 11:11:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(plugins):=20quota=20=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=97=B6=E4=B8=8D=E5=86=8D=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8E=A2=E6=B5=8B=E5=85=A8=E9=83=A8=E4=B8=8A=E6=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 首屏 19.1 秒 → 1.0 秒(生产实测)。 症状是"页面出来了但数据卡在加载"。量出来的原因不是渲染,也不是后端: 所有接口都在 0.4–2ms,而 Network 事件显示页面在头 70 毫秒内并发发出了 21 个 /api/plugins/quota/call/collect_source —— 每个源一次。CPU 采样 98% 空闲,说明 主线程不是在算,是在等。 插件页面的脚本里有 mount(); refresh();,而 refresh 会为每个源发一次探测。 探测要出网,且每次调用都持插件写锁,于是 21 次串行排队;冷启动实测 100 秒, 有缓存后 22 秒。WebUI 的首屏就这么被堵住了。 改动: - 页面加载时不再自动探测。运维没要求的余额不值得用它换首屏。 - 改为先渲染上次的快照(pluginAPI.fetchState),标题栏标注"快照 HH:MM(点刷新 取实时)"。点刷新才真探测。这样打开页面立刻有内容,实时数字在后面更新。 - billing 页面同样有 refresh(),但它只读本机 state(1.5ms),无害,保留。 判据:首屏填充时间实测 19.1s → 1.0s,且首屏请求里不再出现 collect_source。 Co-Authored-By: ModelRouter (cherry picked from commit c17a851ec16c28aca85346e0c3ee00ccaff21d4d) (cherry picked from commit 9823773330bb5ea11fc3568af1ceffb616bbb092) --- internal/lua/plugins/quota.lua | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/internal/lua/plugins/quota.lua b/internal/lua/plugins/quota.lua index c72ce47..6317b21 100644 --- a/internal/lua/plugins/quota.lua +++ b/internal/lua/plugins/quota.lua @@ -682,6 +682,23 @@ PLUGIN.ui = { if(meta.length) h+='
'+meta.map(escH).join(" · ")+'
'; return h+""; } + // paintSnapshot renders the persisted snapshot WITHOUT touching the network. + // Called on load so the page has content immediately. + async function showSnapshot(){ + try{ + var st = await pluginAPI.fetchState("quota"); + var rs = (st && st.reports) || []; + if(!rs.length) return false; + var real = rs.filter(function(r){ return r && !r.skipped; }); + var skipped = rs.filter(function(r){ return r && r.skipped; }).map(function(r){ return r.name; }); + render(real, skipped); + var sub=document.getElementById("qa-sub"); + if(sub && st.updated_at){ + sub.textContent = real.length+" 个源 · 快照 "+new Date(st.updated_at).toLocaleTimeString()+"(点刷新取实时)"; + } + return true; + }catch(e){ return false; } + } function render(reports, skipped){ var host=document.getElementById("qa-list"); if(!host) return; var real=(reports||[]).filter(function(r){return !r.skipped;}); @@ -754,7 +771,18 @@ PLUGIN.ui = { var b=document.getElementById("qa-refresh"); if(b && !b.__qaWired){ b.__qaWired=true; b.addEventListener("click",refresh); } } - mount(); refresh(); + mount(); + showSnapshot(); + // Do NOT auto-refresh on load. + // + // Probing is network-bound and every request takes the plugin's write lock, so + // a page open that fires one call per source serialises them: measured 19s to + // first paint and 22-100s to finish, with the whole WebUI held in its loading + // state meanwhile. A balance the operator did not ask for is not worth the + // first paint of the page they opened. + // + // What IS shown automatically is the last snapshot, so an operator who hits + // refresh once sees a card immediately and a fresh number behind it. if (window.pluginAPI && pluginAPI.onTabShown) pluginAPI.onTabShown(mount); window.__qaRefresh=refresh; window.__qaRender=render; })();