mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-27 12:53:35 +00:00
perf(webui): 前端按页签懒加载 —— 首屏不再拉隐藏页签的数据,空闲请求砍到 1/3
生产实测的问题:renderAll 无论当前在哪个页签,都无条件拉 9 个接口并
渲染全部 7 个页签。首屏 792,933 B 里**约 230KB 花在用户看不见的隐藏
DOM 上** —— /api/v1/kernel(152KB,只有内核页要)与 /api/v1/settings
(72KB,只有设置页要),而 renderKernel() / renderOneSettings() 是在
**隐藏的 tab 容器**里构建 DOM 的。更糟的是每 15s 重来一遍。
### 依赖关系是实测出来的,不是猜的
逐个 render 函数 grep 它读的 state.*:
renderOverview → 无(只读 status/runtime/dom)★ 总览最便宜
renderKernel → state.kernel
renderOneSettings → state.settings / state.meta
renderPlugins → state.kernel / installedPlugins / disabledPlugins
renderAdapters → 无(自拉 /api/v1/adapters)
renderChat → 自建布局;星图/终端/命令是其子面板
于是「切到哪页才拉哪页的数据」写成一张 TABS 表(唯一真相表),
正确性由结构保证,而不是靠一串 if 串联。
### 顺带治掉三个轮询器(浏览器 40s 空载实测)
改前停在总览页 40s 内 37 个请求:
/runtime 17 次、/terminals 8 次、/cmd/history 8 次、/status 3 次
1. **terminals/cmd/history 的 5s 轮询是无条件的** —— 但这两个面板只
存在于**聊天页**(buildChatLayout 里的 chat-panel-terminal/-cmd),
在总览/设置/内核页渲染它们既没人看也只改看不见的 DOM。
改为「仅聊天页可见时才轮询」。
2. **/runtime 有两个消费者各拉一遍**:startRuntimeTicker(3s)与
starmapPullActivity(3s)。星图现在只读 state.runtime,不再自己发请求。
3. 轮询改走共享数据块 starmapFetchBlock(带 3s 节流 + 单一数据源)。
改后 40s 内 17 个请求(-54%),且不再有任何接口用于渲染不可见的面板。
### 首屏
overview 首屏只拉 status/runtime/chat/history/persona/memory-graph,
**不再拉 kernel 与 settings**。
### 刷新语义
区分「活数据」与「近乎不变的数据」:status/runtime 每 3s 允许重拉;
kernel/settings/plugins 首次拉过后**不再每 15s 重拉**(这正是 53MB/h
的主因)。切回页签也不重拉(数据没理由变)。四个变更操作
(源/MCP 的增删)改调 renderAll(true) 强制刷新;启停插件/保存设置那几处
本来就自己 refetch 再局部重渲染,不依赖 renderAll。
### ★ 途中修掉一个被上一提交引入的真 bug
loadChatStarmapData 的空图分支硬写 getElementById("sm-container-chat")。
星图搬到总览后,总览页与独立页签都没有这个 id ⇒ 首页首帧必抛
「Cannot set properties of null」,被 catch 吞掉但 starmapInit 没置上,
于是**永不重试、星图永远空白**。改为取 starmapActiveContainer() 并加
空值保护。浏览器实测:修前 ERRORS 非空,修后 STARTUP + 7 个页签全 clean。
(教训:把 UI 元素挪到新位置后,必须把所有按 id 直取该元素的地方找全 ——
grep 该 id 一次。)
同时删掉被 starmapActiveContainer 取代的死函数 starmapContainer()。
### 验证(浏览器实测,非估算)
- go vet 干净;全量测试通过
- 首屏(overview):只拉 status/runtime/chat-history/persona/memory-graph,
kernel 与 settings 确认**未出现**
- 逐页切换记录请求:每页只拉自己那几项
- 空载 40s:37 → 17 个请求
- 运行态面板仍正常渲染(rt-panel 存在、rt-sec-pipe / rt-sec-topo 均在)
- 启动 + 7 个页签全部无控制台异常
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -493,80 +493,173 @@ function toggleSidebar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===== Tab Render Dispatch =====
|
// ===== Tab Render Dispatch =====
|
||||||
async function renderAll() {
|
|
||||||
try {
|
// TABS 是「哪个页签需要哪些数据」的**唯一真相表**。
|
||||||
var s = await api("/status");
|
//
|
||||||
state.status = s;
|
// 为什么要有这张表(实测依据,非估算):
|
||||||
state.startedAt = s.startedAt ? new Date(s.startedAt).getTime() : null;
|
//
|
||||||
} catch (e) {}
|
// 改前 renderAll 无论当前在哪个页签,都无条件拉 9 个接口并渲染全部
|
||||||
try {
|
// 7 个页签。生产实例首屏 792,933 B 里,**约 230KB 花在用户看不见的
|
||||||
state.kernel = await api("/kernel");
|
// 隐藏 DOM 上**:/api/v1/kernel(152KB,只有「内核」页要)与
|
||||||
} catch (e) {}
|
// /api/v1/settings(72KB,只有「设置」页要)——而 renderKernel() /
|
||||||
try {
|
// renderOneSettings() 是在**隐藏的 tab 容器**里构建 DOM 的。
|
||||||
var s = await api("/settings");
|
//
|
||||||
state.settings = s.settings || {};
|
// 依赖关系是**实测**出来的(逐个 render 函数 grep 它读的 state.*),
|
||||||
state.meta = s.meta || {};
|
// 不是猜的:
|
||||||
state.settingsPlugins = s.plugins || ["core"];
|
// renderOverview → 无(只读 status/runtime/dom)★ 总览最便宜
|
||||||
state.pluginMeta = s.plugin_meta || {};
|
// renderKernel → state.kernel
|
||||||
state.disabledPlugins = s.disabled_plugins || [];
|
// renderOneSettings → state.settings / state.meta
|
||||||
} catch (e) {}
|
// renderPlugins → state.kernel / state.installedPlugins /
|
||||||
try {
|
// state.disabledPlugins / state.pluginInfo
|
||||||
state.installedPlugins = await api("/plugins");
|
// renderAdapters → 无(自拉 /api/v1/adapters)
|
||||||
} catch (e) {}
|
// renderChat → 自建布局;星图/终端/命令是其子面板
|
||||||
try {
|
//
|
||||||
await loadProxyServices();
|
// 于是「切到哪页才拉哪页的数据」成为结构性正确,而不是靠 if 串联。
|
||||||
} catch (e) {}
|
var TABS = {
|
||||||
try {
|
overview: { fetch: ["status", "runtime"], render: ["renderOverview"] },
|
||||||
await loadTerminals();
|
chat: {
|
||||||
} catch (e) {}
|
fetch: ["status", "proxyServices", "terminals", "cmdHistory"],
|
||||||
try {
|
render: ["renderChat"],
|
||||||
await loadCmdHistory();
|
},
|
||||||
} catch (e) {}
|
starmap: { fetch: [], render: ["renderStarmapTab"] },
|
||||||
try {
|
plugins: {
|
||||||
renderOverview();
|
fetch: ["status", "kernel", "plugins", "runtime"],
|
||||||
} catch (e) {
|
render: ["renderPlugins"],
|
||||||
console.error("renderOverview", e);
|
},
|
||||||
|
settings: { fetch: ["settings", "status"], render: ["renderOneSettings"] },
|
||||||
|
adapters: { fetch: ["status"], render: ["renderAdapters"] },
|
||||||
|
kernel: { fetch: ["status", "kernel", "runtime"], render: ["renderKernel"] },
|
||||||
|
};
|
||||||
|
|
||||||
|
// starmapActiveTab 返回当前激活的页签名。
|
||||||
|
function starmapActiveTab() {
|
||||||
|
var el = document.querySelector(".tab-content.active");
|
||||||
|
if (el && el.id) return el.id.replace(/^tab-/, "");
|
||||||
|
return "overview";
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadedOnce 记录每个数据块是否已拉过。刷新语义刻意区分:
|
||||||
|
// - 首次进入某页签:拉
|
||||||
|
// - 之后每 15s 的 renderAll:**不**重拉 kernel/settings(它们几乎不变,
|
||||||
|
// 却占每拍 232KB —— 这是「空闲 53MB/h」的主因)
|
||||||
|
// - 切回页签:仍然不重拉(数据没理由变),只重渲染
|
||||||
|
// 真正需要新鲜的(runtime/status)单独走快通道,见 STALE_EVERY。
|
||||||
|
var _loadedOnce = {};
|
||||||
|
var _lastFetchAt = {};
|
||||||
|
// STALE_EVERY:这几块数据是活的(运行态/状态),每 3s 允许重拉一次。
|
||||||
|
var STALE_EVERY = 3000;
|
||||||
|
|
||||||
|
// starmapFetchBlock 按名字拉一块数据。幂等且带节流。
|
||||||
|
async function starmapFetchBlock(name) {
|
||||||
|
var now = Date.now();
|
||||||
|
var volatile_ = name === "status" || name === "runtime";
|
||||||
|
if (_loadedOnce[name]) {
|
||||||
|
if (!volatile_) return;
|
||||||
|
if (now - (_lastFetchAt[name] || 0) < STALE_EVERY) return;
|
||||||
}
|
}
|
||||||
try {
|
_lastFetchAt[name] = now;
|
||||||
await loadRuntime();
|
switch (name) {
|
||||||
} catch (e) {
|
case "status":
|
||||||
console.error("loadRuntime", e);
|
try {
|
||||||
|
var s = await api("/status");
|
||||||
|
state.status = s;
|
||||||
|
state.startedAt = s.startedAt
|
||||||
|
? new Date(s.startedAt).getTime()
|
||||||
|
: null;
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
|
case "runtime":
|
||||||
|
try {
|
||||||
|
var rt = await api("/runtime");
|
||||||
|
state.runtime = rt;
|
||||||
|
} catch (e) {
|
||||||
|
state.runtime = null;
|
||||||
|
}
|
||||||
|
// 运行态变了就顺手让星图重算一次(读同一个快照,不额外发请求)。
|
||||||
|
if (typeof starmapApplyActivity === "function") starmapApplyActivity();
|
||||||
|
break;
|
||||||
|
case "kernel":
|
||||||
|
try {
|
||||||
|
state.kernel = await api("/kernel");
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
|
case "settings":
|
||||||
|
try {
|
||||||
|
var s2 = await api("/settings");
|
||||||
|
state.settings = s2.settings || {};
|
||||||
|
state.meta = s2.meta || {};
|
||||||
|
state.settingsPlugins = s2.plugins || ["core"];
|
||||||
|
state.pluginMeta = s2.plugin_meta || {};
|
||||||
|
state.disabledPlugins = s2.disabled_plugins || [];
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
|
case "plugins":
|
||||||
|
try {
|
||||||
|
state.installedPlugins = await api("/plugins");
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
|
case "proxyServices":
|
||||||
|
try {
|
||||||
|
await loadProxyServices();
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
|
case "terminals":
|
||||||
|
try {
|
||||||
|
await loadTerminals();
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
|
case "cmdHistory":
|
||||||
|
try {
|
||||||
|
await loadCmdHistory();
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
_loadedOnce[name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderAll(force) {
|
||||||
|
var tab = starmapActiveTab();
|
||||||
|
var spec = TABS[tab] || TABS.overview;
|
||||||
|
// force:变更操作后(保存设置/启停插件)需要重新拉那块数据。
|
||||||
|
if (force) _loadedOnce = {};
|
||||||
|
// 1) 只拉当前页签需要的数据(并发的别串行等)
|
||||||
|
var needs = spec.fetch || [];
|
||||||
|
await Promise.all(
|
||||||
|
needs.map((n) => starmapFetchBlock(n)),
|
||||||
|
);
|
||||||
|
// 2) 只渲染当前页签(+ 星图在多处出现,单独处理)
|
||||||
try {
|
try {
|
||||||
renderChat();
|
spec.render.forEach((fn) => {
|
||||||
|
if (typeof window[fn] === "function") window[fn]();
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("renderChat", e);
|
console.error("renderAll(" + tab + ")", e);
|
||||||
}
|
}
|
||||||
try {
|
// 星图:总览页内嵌 + 独立页签两处都要确保已初始化。
|
||||||
renderChatStarmap();
|
if (tab === "overview" || tab === "starmap" || tab === "chat") {
|
||||||
} catch (e) {
|
try {
|
||||||
console.error("renderChatStarmap", e);
|
renderChatStarmap();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("renderChatStarmap", e);
|
||||||
|
}
|
||||||
|
if (tab === "starmap") {
|
||||||
|
try {
|
||||||
|
renderStarmapTab();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("renderStarmapTab", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
// 3) 聊天页的子面板(终端/运行中命令)只在聊天页重建
|
||||||
renderStarmapTab();
|
if (tab === "chat") {
|
||||||
} catch (e) {
|
try {
|
||||||
console.error("renderStarmapTab", e);
|
renderTerminals();
|
||||||
}
|
renderCmdHistory();
|
||||||
try {
|
} catch (e) {
|
||||||
renderPlugins();
|
console.error("chat sub-panels", e);
|
||||||
} catch (e) {
|
}
|
||||||
console.error("renderPlugins", e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
renderKernel();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("renderKernel", e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
renderOneSettings();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("renderOneSettings", e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
renderAdapters();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("renderAdapters", e);
|
|
||||||
}
|
}
|
||||||
|
// 4) 卡片倾斜高光:只在指针设备上加
|
||||||
try {
|
try {
|
||||||
if (!window.matchMedia("(hover: none)").matches) {
|
if (!window.matchMedia("(hover: none)").matches) {
|
||||||
document
|
document
|
||||||
@ -1758,7 +1851,11 @@ function startRuntimeTicker() {
|
|||||||
state._runtimeTicker = setInterval(() => {
|
state._runtimeTicker = setInterval(() => {
|
||||||
var tab = document.querySelector("#tab-overview");
|
var tab = document.querySelector("#tab-overview");
|
||||||
if (tab && tab.classList.contains("active")) {
|
if (tab && tab.classList.contains("active")) {
|
||||||
loadRuntime();
|
// 走共享数据块(自带 3s 节流 + 单一数据源)而不是直接 loadRuntime:
|
||||||
|
// 星图也读 state.runtime,两个入口拉同一接口就是重复请求。
|
||||||
|
starmapFetchBlock("runtime").then(() => {
|
||||||
|
if (starmapActiveTab() === "overview") renderRuntime();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
@ -2750,6 +2847,13 @@ function renderChatStarmap() {
|
|||||||
async function loadChatStarmapData() {
|
async function loadChatStarmapData() {
|
||||||
try {
|
try {
|
||||||
var resp = await api("/memory/graph");
|
var resp = await api("/memory/graph");
|
||||||
|
// 星图现在有三个容器(总览/独立页签/聊天面板),写提示前必须重新
|
||||||
|
// 拿当前活跃的那个。原代码硬写 getElementById("sm-container-chat"),
|
||||||
|
// 而总览页与独立页签都没有这个 id —— 空图分支一进就抛
|
||||||
|
// 「Cannot set properties of null」(浏览器实测:首页首帧必现)。
|
||||||
|
// 症状是整个函数被 catch 吞掉、state.starmapInit 却没置上,
|
||||||
|
// 于是后续再也不会重试 —— 星图永远是空的。
|
||||||
|
var target = starmapActiveContainer();
|
||||||
if (
|
if (
|
||||||
!resp ||
|
!resp ||
|
||||||
!resp.success ||
|
!resp.success ||
|
||||||
@ -2757,10 +2861,11 @@ async function loadChatStarmapData() {
|
|||||||
!resp.data.nodes ||
|
!resp.data.nodes ||
|
||||||
resp.data.nodes.length === 0
|
resp.data.nodes.length === 0
|
||||||
) {
|
) {
|
||||||
document.getElementById("sm-container-chat").innerHTML =
|
if (target)
|
||||||
'<p style="color:var(--text-muted);padding:20px;text-align:center">' +
|
target.innerHTML =
|
||||||
__("暂无记忆数据", "No memory data") +
|
'<p style="color:var(--text-muted);padding:20px;text-align:center">' +
|
||||||
"</p>";
|
__("暂无记忆数据", "No memory data") +
|
||||||
|
"</p>";
|
||||||
state.starmapInit = true;
|
state.starmapInit = true;
|
||||||
state.starmapLoading = false;
|
state.starmapLoading = false;
|
||||||
return;
|
return;
|
||||||
@ -2772,10 +2877,12 @@ async function loadChatStarmapData() {
|
|||||||
state.starmapLoading = false;
|
state.starmapLoading = false;
|
||||||
initChatStarmap();
|
initChatStarmap();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
document.getElementById("sm-container-chat").innerHTML =
|
var target2 = starmapActiveContainer();
|
||||||
'<p style="color:var(--text-muted);padding:20px;text-align:center">' +
|
if (target2)
|
||||||
__("加载失败", "Load failed") +
|
target2.innerHTML =
|
||||||
"</p>";
|
'<p style="color:var(--text-muted);padding:20px;text-align:center">' +
|
||||||
|
__("加载失败", "Load failed") +
|
||||||
|
"</p>";
|
||||||
state.starmapInit = true;
|
state.starmapInit = true;
|
||||||
state.starmapLoading = false;
|
state.starmapLoading = false;
|
||||||
}
|
}
|
||||||
@ -4573,22 +4680,37 @@ function connectSSE() {
|
|||||||
console.log("[SSE] sync_required received, incremental sync");
|
console.log("[SSE] sync_required received, incremental sync");
|
||||||
syncChatFromHistory().catch(() => {});
|
syncChatFromHistory().catch(() => {});
|
||||||
});
|
});
|
||||||
// Periodically refresh sidebar data
|
// 终端 / 运行中命令的定时刷新。
|
||||||
|
//
|
||||||
|
// ★ 原来这段是**无条件**的 5s 轮询(浏览器实测:停在总览页 40s 内
|
||||||
|
// 打了 8 次 /terminals + 8 次 /cmd/history)。但这两个面板只存在于
|
||||||
|
// **聊天页**(buildChatLayout 里的 chat-panel-terminal / -cmd),
|
||||||
|
// 在总览/设置/内核页渲染它们既没人看也只改看不见的 DOM。
|
||||||
|
//
|
||||||
|
// 改为「仅聊天页可见时才轮询」,且错过的那一拍在切回聊天页时由
|
||||||
|
// renderAll 补上(renderAll 的 terminals/cmdHistory 数据块带节流)。
|
||||||
if (state._sidebarRefresh) clearInterval(state._sidebarRefresh);
|
if (state._sidebarRefresh) clearInterval(state._sidebarRefresh);
|
||||||
state._sidebarRefresh = setInterval(async () => {
|
state._sidebarRefresh = setInterval(() => {
|
||||||
try {
|
var tab = document.querySelector("#tab-chat");
|
||||||
var td = await api("/terminals");
|
if (!tab || !tab.classList.contains("active")) return;
|
||||||
if (td && td.terminals) state.terminals = td.terminals;
|
fetchTerminalsAndCmd().catch(() => {});
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
var ch = await api("/cmd/history");
|
|
||||||
if (ch && ch.history) state.cmdHistory = ch.history;
|
|
||||||
} catch (e) {}
|
|
||||||
renderTerminals();
|
|
||||||
renderCmdHistory();
|
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchTerminalsAndCmd 拉终端与运行中命令并重渲染(只在聊天页需要)。
|
||||||
|
async function fetchTerminalsAndCmd() {
|
||||||
|
try {
|
||||||
|
var td = await api("/terminals");
|
||||||
|
if (td && td.terminals) state.terminals = td.terminals;
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
var ch = await api("/cmd/history");
|
||||||
|
if (ch && ch.history) state.cmdHistory = ch.history;
|
||||||
|
} catch (e) {}
|
||||||
|
renderTerminals();
|
||||||
|
renderCmdHistory();
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Plugins =====
|
// ===== Plugins =====
|
||||||
// 「服务入口」:被反代的插件服务,点一下直接打开。
|
// 「服务入口」:被反代的插件服务,点一下直接打开。
|
||||||
//
|
//
|
||||||
@ -5480,32 +5602,53 @@ function starmapHintTokens(hint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// starmapPullActivity 拉 /runtime,把调度器状态映射成图的整体节奏。
|
// starmapPullActivity 拉 /runtime,把调度器状态映射成图的整体节奏。
|
||||||
|
// starmapPullActivity 用**已有的** /runtime 快照驱动图,绝不自己再发一轮请求。
|
||||||
|
//
|
||||||
|
// ★ 原来它直接 api("/runtime"),而 startRuntimeTicker 也在 3s 拉同一个
|
||||||
|
// 接口 —— 浏览器实测:停在总览页 40s 内 /runtime 被打 **17 次**
|
||||||
|
// (= 3s 一次 × 2 + 15s 的 renderAll)。两个消费者拉同一份数据是纯浪费。
|
||||||
|
//
|
||||||
|
// 现在只读 state.runtime(由 startRuntimeTicker / renderAll 负责刷新)。
|
||||||
|
// 星图慢一拍(最多 3s)无所谓:它只是把调度器状态映射成脉冲。
|
||||||
function starmapPullActivity() {
|
function starmapPullActivity() {
|
||||||
if (!starmapScene) return;
|
if (!starmapScene) return;
|
||||||
api("/runtime")
|
var s0 = state.runtime && state.runtime.scheduler;
|
||||||
.then((rt) => {
|
if (!s0) {
|
||||||
if (!rt || !rt.scheduler) return;
|
// 还没有快照(星图先于运行态轮询初始化):拉一次,建立基线。
|
||||||
var s = rt.scheduler;
|
starmapFetchBlock("runtime").then(() => {
|
||||||
var prev = state.starmapActivity;
|
starmapApplyActivity();
|
||||||
state.starmapActivity = s;
|
});
|
||||||
var pend =
|
return;
|
||||||
(s.ready_queue_depth || 0) +
|
}
|
||||||
(s.pending_interrupts || 0) +
|
starmapApplyActivity();
|
||||||
(s.suspend_stack || 0);
|
}
|
||||||
// 有任务在排队/中断 ⇒ 脉冲,让图「绷紧」。
|
|
||||||
if (pend > 0) starmapPulse("stage", null);
|
// starmapApplyActivity 把 state.runtime 里的调度器计数映射成星图脉冲。
|
||||||
// 中断或抢占计数上升 ⇒ 一次强脉冲(高优先级插入)。
|
function starmapApplyActivity() {
|
||||||
if (prev) {
|
var rt = state.runtime;
|
||||||
var dInt =
|
if (!rt || !rt.scheduler) return;
|
||||||
(s.interrupts_by_level || []).reduce((a, b) => a + b, 0) -
|
var s = rt.scheduler;
|
||||||
(prev.interrupts_by_level || []).reduce((a, b) => a + b, 0);
|
var prev = state.starmapActivity;
|
||||||
var dPre =
|
state.starmapActivity = s;
|
||||||
(s.preempts_by_level || []).reduce((a, b) => a + b, 0) -
|
var pend =
|
||||||
(prev.preempts_by_level || []).reduce((a, b) => a + b, 0);
|
(s.ready_queue_depth || 0) +
|
||||||
if (dInt > 0 || dPre > 0) starmapPulse("output", null);
|
(s.pending_interrupts || 0) +
|
||||||
}
|
(s.suspend_stack || 0);
|
||||||
})
|
// 有任务在排队/中断 ⇒ 脉冲,让图「绷紧」。
|
||||||
.catch(() => {});
|
if (pend > 0) starmapPulse("stage", null);
|
||||||
|
if (!prev) {
|
||||||
|
smUpdateStat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 中断或抢占计数上升 ⇒ 一次强脉冲(高优先级插入)。
|
||||||
|
var sum = (a) => (a || []).reduce((x, y) => x + y, 0);
|
||||||
|
if (
|
||||||
|
sum(s.interrupts_by_level) - sum(prev.interrupts_by_level) > 0 ||
|
||||||
|
sum(s.preempts_by_level) - sum(prev.preempts_by_level) > 0
|
||||||
|
) {
|
||||||
|
starmapPulse("output", null);
|
||||||
|
}
|
||||||
|
smUpdateStat();
|
||||||
}
|
}
|
||||||
|
|
||||||
// starmapPullPulse 拉轻量活动端点,把新长出来的节点标记为「生长」。
|
// starmapPullPulse 拉轻量活动端点,把新长出来的节点标记为「生长」。
|
||||||
@ -5689,13 +5832,6 @@ function starmapLabelShow(mesh) {
|
|||||||
function starmapLabelHide() {
|
function starmapLabelHide() {
|
||||||
if (starmapLabelEl) starmapLabelEl.style.display = "none";
|
if (starmapLabelEl) starmapLabelEl.style.display = "none";
|
||||||
}
|
}
|
||||||
// starmapContainer 返回当前星图所在的容器(主页或聊天面板任一)。
|
|
||||||
function starmapContainer() {
|
|
||||||
return (
|
|
||||||
document.getElementById("sm-container-home") ||
|
|
||||||
document.getElementById("sm-container-chat")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// starmapActiveContainer 选出「该把星图画在哪」的容器。
|
// starmapActiveContainer 选出「该把星图画在哪」的容器。
|
||||||
//
|
//
|
||||||
@ -6517,7 +6653,7 @@ function showAddSourceDialog() {
|
|||||||
'" ' +
|
'" ' +
|
||||||
__("已创建,请配置各项参数", "created, please configure parameters"),
|
__("已创建,请配置各项参数", "created, please configure parameters"),
|
||||||
);
|
);
|
||||||
renderAll();
|
renderAll(true);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
toast(__("创建失败: ", "Create failed: ") + e.message, true);
|
toast(__("创建失败: ", "Create failed: ") + e.message, true);
|
||||||
@ -6548,7 +6684,7 @@ async function deleteSource(name) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
toast(__("源", "Source") + ' "' + name + '" ' + __("已删除", "deleted"));
|
toast(__("源", "Source") + ' "' + name + '" ' + __("已删除", "deleted"));
|
||||||
renderAll();
|
renderAll(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast(__("删除失败: ", "Delete failed: ") + e.message, true);
|
toast(__("删除失败: ", "Delete failed: ") + e.message, true);
|
||||||
}
|
}
|
||||||
@ -6586,7 +6722,7 @@ function addMCPSource() {
|
|||||||
'" ' +
|
'" ' +
|
||||||
__("已创建", "created"),
|
__("已创建", "created"),
|
||||||
);
|
);
|
||||||
renderAll();
|
renderAll(true);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
toast(__("创建失败: ", "Create failed: ") + e.message, true);
|
toast(__("创建失败: ", "Create failed: ") + e.message, true);
|
||||||
@ -6622,7 +6758,7 @@ async function deleteMCPServer(name) {
|
|||||||
'" ' +
|
'" ' +
|
||||||
__("已删除", "deleted"),
|
__("已删除", "deleted"),
|
||||||
);
|
);
|
||||||
renderAll();
|
renderAll(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast(__("删除失败: ", "Delete failed: ") + e.message, true);
|
toast(__("删除失败: ", "Delete failed: ") + e.message, true);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user