diff --git a/internal/plugins/webui/dashboard.js b/internal/plugins/webui/dashboard.js index b056dca..a58dc02 100644 --- a/internal/plugins/webui/dashboard.js +++ b/internal/plugins/webui/dashboard.js @@ -1,6293 +1,6783 @@ - // ===== State ===== - const state = { - status: {}, - kernel: null, - settings: {}, - meta: {}, - runtime: null, // /api/v1/runtime 的运行态小快照(调度器/驻留子/通道) - pluginMeta: {}, - disabledPlugins: [], - settingsPlugins: ["core"], - selectedSection: "core", - messages: [], - chatLoading: false, - chatStage: "", - pipelinePhase: "", // 当前阶段(SSE stage 事件驱动总览页) - pipelineTimer: null, - stageTrail: [], // 本轮已发生的事件轨迹(工具/输出调用,供阶段管道展示循环) - toolFlash: false, // 本轮新到一条工具调用:本轮渲染后给「工具」格放一次滑入动画 - healthResult: null, - starmapInit: false, - starmapLoading: false, - starmapData: null, - // ===== 星图:跟随 agent 活动 ===== - // 三路活动信号(均已在页面上,无需新数据源): - // 1. toolPulseMs —— SSE tool_call/stage 触发的「脉冲」,实时 - // 2. activity —— /runtime 调度器计数(排队/中断/抢占),3s 轮询 - // 3. grownIds —— /memory/graph/pulse 检出「新长出来」的节点,10s 轮询 - starmapPulses: [], // { mesh, until, kind } 活动脉冲队列 - starmapGrown: {}, // nodeId -> 生长动画截止时间戳 - starmapActivity: null, // /runtime 的调度器快照 - starmapPulseTimer: null, // /memory/graph/pulse 轮询 id - starmapActivityTimer: null, // /runtime 轮询 id - starmapLastPulseAt: 0, // 最后一次活动时间(ms),驱动全局呼吸 - starmapPulseSince: 0, // 下次 pulse 请求的回看起点(unix 秒) - chatHistory: [], - terminals: [], - cmdHistory: [], - termScreens: {}, - chatStick: true, - pendingTools: [], - eventSource: null, - chatFinalIdx: -1, - chatOffset: 0, // 分段历史:当前已加载消息在服务端全量中的起始下标 - chatTotal: 0, // 服务端历史总条数 - chatHasMore: false, // 是否还有更早历史可向上加载 - chatLastSeq: 0, // 增量轮询游标:已合并到本地的最大消息 seq - lang: localStorage.getItem("ha-lang") || "zh", - }; - - // ===== I18n ===== - window._i18n = { - navOverview: ["概览", "Overview"], - navChat: ["对话", "Chat"], - navStarmap: ["星图", "Star Map"], - navPlugins: ["插件", "Plugins"], - navSettings: ["设置", "Settings"], - navAdapters: ["适配器", "Adapters"], - navKernel: ["内核", "Kernel"], - navLogout: ["退出登录", "Logout"], - themeToggle: ["切换亮色/暗色模式", "Toggle theme"], - }; - - function __(zh, en) { - return state.lang === "en" ? en : zh; - } - function L() { - return state.lang; - } - - function toggleLang() { - state.lang = state.lang === "zh" ? "en" : "zh"; - localStorage.setItem("ha-lang", state.lang); - document.querySelectorAll("[data-i18n]").forEach((el) => { - var k = el.getAttribute("data-i18n"); - var m = window._i18n && window._i18n[k]; - if (m) el.textContent = __(m[0], m[1]); - }); - renderAll(); - } - - function applyI18n() { - var lang = state.lang; - var btn = document.getElementById("lang-btn"); - if (btn) btn.textContent = lang === "zh" ? "EN" : "中"; - document.querySelectorAll("[data-i18n]").forEach((el) => { - var k = el.getAttribute("data-i18n"); - var m = window._i18n && window._i18n[k]; - if (m) el.textContent = lang === "en" ? m[1] : m[0]; - }); - } - - // ===== Theme ===== - var ICON_SUN = - ''; - var ICON_MOON = - ''; - - function setTheme(name) { - document.documentElement.setAttribute("data-theme", name); - var btn = document.getElementById("theme-btn"); - if (btn) btn.innerHTML = name === "light" ? ICON_SUN : ICON_MOON; - } - - function saveTheme(name) { - localStorage.setItem("ha-theme", name); - setTheme(name); - } - - function toggleTheme() { - var cur = document.documentElement.getAttribute("data-theme"); - saveTheme(cur === "light" ? "dark" : "light"); - } - - var PALETTES = { - // mono 放第一位:它现在是默认配色(黑白单调),也应当是选择器里最显眼那个。 - mono: "#c2c7d0", - sakura: "#ff7fac", - cyan: "#2dd4bf", - violet: "#a78bfa", - emerald: "#34d399", - amber: "#fbbf24", - blue: "#60a5fa", - }; - - function setColor(name) { - document.documentElement.setAttribute("data-color", name); - localStorage.setItem("ha-color", name); - // 配色变了要强制重画运行态:拓扑的归属配色跟着 data-color 走, - // 而数据签名没变,不置空 _rtSig 的话图会一直停在旧色。 - _rtSig = null; - if (document.getElementById("rt-sec-topo")) { - try { - renderRuntime(); - } catch (e) {} - } - var pop = document.getElementById("palette-pop"); - if (!pop) return; - var btns = pop.querySelectorAll("button.cdot"); - for (var i = 0; i < btns.length; i++) { - btns[i].className = - btns[i].getAttribute("data-c") === name ? "cdot on" : "cdot"; - } - } - - function applyBgImg(url) { - url = (url || "").trim(); - if (!url) { - document.documentElement.style.setProperty("--bg-img", "none"); - localStorage.removeItem("ha-bg-img"); - return; - } - document.documentElement.style.setProperty( - "--bg-img", - 'url("' + url.replace(/"/g, '\\"') + '")', - ); - localStorage.setItem("ha-bg-img", url); - } - - function applyBgBlur(n) { - n = Math.max(0, Math.min(30, Number(n) || 0)); - document.documentElement.style.setProperty("--bg-blur", String(n)); - localStorage.setItem("ha-bg-blur", String(n)); - var v = document.getElementById("bg-blur-val"); - if (v) v.textContent = n + "px"; - var r = document.getElementById("bg-blur-range"); - if (r) r.value = String(n); - } - - function toggleAppearance() { - var pop = document.getElementById("palette-pop"); - if (!pop) return; - var on = pop.classList.contains("on"); - if (!pop.querySelector("button.cdot")) { - var cur = localStorage.getItem("ha-color") || "mono"; - var img = localStorage.getItem("ha-bg-img") || ""; - var blur = localStorage.getItem("ha-bg-blur") || "0"; - var dots = ""; - Object.keys(PALETTES).forEach((k) => { - dots += - '"; - }); - pop.innerHTML = - "
" + escHtml(text) + ""; - } - if ( - typeof DOMPurify !== "undefined" && - typeof DOMPurify.sanitize === "function" - ) { - try { - return DOMPurify.sanitize(html, { USE_PROFILES: { html: true } }); - } catch (e) {} - } - return html - .replace(/