From 5a8fda955af348445a7eec91ff46c1e366fcd280 Mon Sep 17 00:00:00 2001 From: jianf <2198972886@qq.com> Date: Mon, 17 Aug 2026 17:35:47 +0800 Subject: [PATCH] =?UTF-8?q?gui:=20=E6=80=A7=E8=83=BD=E4=BF=AE=E5=A4=8D(?= =?UTF-8?q?=E6=98=9F=E5=9B=BE/reasoning/=E6=B8=B2=E6=9F=93=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF)+=E8=AE=BE=E5=A4=87=E6=A1=A5=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=A7=A3=E8=80=A6(=E7=8B=AC=E7=AB=8Bprefs+IPC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 性能(卡死修复, 已部署本机验证 GPU 91%->14%): - reasoning_content 懒加载: 折叠正文不再 marked.parse, 点击展开时才解析 - 星图动画仅"星图"子面板激活才跑, 12fps限制, 关抗锯齿+pixelRatio=1, 粒子3000->1200 - renderAll/refreshAll 非chat视图跳过聊天整块重建 - switchView(chat) 多段延迟滚动到底部(修复进入停在顶部) 设备桥(配置与连接解耦, 为远程homed被控做准备): - 设备桥改由 gui-prefs.json 的 deviceBridge{enabled,gateway,token} 驱动, 不再依赖 type=device 连接 - 新增 IPC device-bridge:get/set (状态查询+动态启停), preload 暴露 deviceBridge API - whenReady 按 prefs 启动设备桥 待后续: 设备页UI入口(renderDevices 本机卡片仍被 conn.type!==device 条件挡住, 需改为始终显示+调用 device-bridge:set) --- cmd/gui/main.js | 69 ++++++++++++++++++++++++++++++++---- cmd/gui/preload.js | 4 +++ cmd/gui/renderer/app.js | 78 ++++++++++++++++++++++++++++------------- 3 files changed, 120 insertions(+), 31 deletions(-) diff --git a/cmd/gui/main.js b/cmd/gui/main.js index 408d2b9..52e9870 100644 --- a/cmd/gui/main.js +++ b/cmd/gui/main.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow, ipcMain, dialog, Menu } = require("electron"); +const { app, BrowserWindow, ipcMain, Menu } = require("electron"); const path = require("path"); const fs = require("fs"); const { spawn } = require("child_process"); @@ -1032,11 +1032,17 @@ app.whenReady().then(async () => { console.error("homed failed to start within timeout"); } } - // 设备桥:启动接入 remotedevice + // 设备桥:由 GUI 偏好(deviceBridge 开关+网关+token)驱动,独立于连接 try { - var _devs = loadConnections().connections || []; - var _dev = _devs.filter((c) => c.type === "device"); - if (_dev.length > 0) startDeviceBridge(_dev[_dev.length - 1]); + const _prefs = loadGuiPrefs(); + const _db = _prefs.deviceBridge || {}; + if (_db.enabled && _db.gateway && _db.token) { + startDeviceBridge({ url: _db.gateway, apiKey: _db.token }); + } else if (_db.enabled) { + console.error( + "[device-bridge] enabled but missing gateway/token, skipped", + ); + } } catch (e) { console.error("device bridge init: " + e.message); } @@ -1096,14 +1102,25 @@ function loadGuiPrefs() { try { if (fs.existsSync(GUI_PREFS_FILE)) { const d = JSON.parse(fs.readFileSync(GUI_PREFS_FILE, "utf-8")); + const db = d.deviceBridge || {}; return { autoLaunch: !!d.autoLaunch, silentStart: !!d.silentStart, exitToTray: d.exitToTray === undefined ? true : !!d.exitToTray, + deviceBridge: { + enabled: !!db.enabled, + gateway: db.gateway || "", + token: db.token || "", + }, }; } } catch (e) {} - return { autoLaunch: false, silentStart: false, exitToTray: true }; + return { + autoLaunch: false, + silentStart: false, + exitToTray: true, + deviceBridge: { enabled: false, gateway: "", token: "" }, + }; } function saveGuiPrefs(p) { @@ -1131,7 +1148,45 @@ function applyAutoLaunch(enabled) { // 全局偏好缓存(供 createWindow 静默判断使用) let guiPrefs = loadGuiPrefs(); -// IPC:本机设备身份(设备桥登记的设备 ID 与状态) +// IPC:本机设备桥状态(启用+网关+token+连接状态) +ipcMain.handle("device-bridge:get", () => { + const p = loadGuiPrefs(); + const db = p.deviceBridge || {}; + return { + enabled: !!db.enabled, + gateway: db.gateway || "", + tokenSet: !!(db.token || ""), + connected: !!deviceBridge, + deviceId: deviceBridgeId, + address: deviceBridgeAddr, + }; +}); + +// IPC:配置本机设备桥(开关+网关+token),保存并动态启停 +ipcMain.handle("device-bridge:set", (_, cfg) => { + const cur = loadGuiPrefs(); + const db = Object.assign({}, cur.deviceBridge || {}, cfg || {}); + const next = Object.assign({}, cur, { deviceBridge: db }); + guiPrefs = saveGuiPrefs(next); + // 动态应用:停止现有桥,按新配置启动 + try { + stopDeviceBridge(); + } catch (e) {} + if (db.enabled && db.gateway && db.token) { + try { + startDeviceBridge({ url: db.gateway, apiKey: db.token }); + } catch (e) { + console.error("[device-bridge] start failed: " + e.message); + } + } + return { + enabled: !!db.enabled, + gateway: db.gateway || "", + connected: !!deviceBridge, + deviceId: deviceBridgeId, + address: deviceBridgeAddr, + }; +}); // IPC:读取偏好 ipcMain.handle("prefs:get", () => { diff --git a/cmd/gui/preload.js b/cmd/gui/preload.js index bfa7023..926485a 100644 --- a/cmd/gui/preload.js +++ b/cmd/gui/preload.js @@ -41,4 +41,8 @@ contextBridge.exposeInMainWorld("homeagent", { device: { identity: () => ipcRenderer.invoke("device:identity"), }, + deviceBridge: { + get: () => ipcRenderer.invoke("device-bridge:get"), + set: (cfg) => ipcRenderer.invoke("device-bridge:set", cfg), + }, }); diff --git a/cmd/gui/renderer/app.js b/cmd/gui/renderer/app.js index 694bb7b..9338959 100644 --- a/cmd/gui/renderer/app.js +++ b/cmd/gui/renderer/app.js @@ -475,16 +475,24 @@ function switchView(n) { state.currentView = n; if (n === "chat") { state.chatStick = true; - var msgsEl = document.getElementById("chat-msgs"); - if (msgsEl) { + var scrollToBottom = () => { + var msgsEl = document.getElementById("chat-msgs"); + if (!msgsEl) return; try { - msgsEl.scrollTo({ top: msgsEl.scrollHeight, behavior: "smooth" }); - } catch (e) { msgsEl.scrollTop = msgsEl.scrollHeight; - } - } + } catch (e) {} + }; + // 立即滚 + 渲染完成后/延迟再滚(DOM 重建后会重置滚动位置) + scrollToBottom(); + setTimeout(scrollToBottom, 60); + setTimeout(scrollToBottom, 300); } renderAll(); + if (n === "chat") { + // renderAll(含异步 refreshAll)完成后确保仍在底部 + setTimeout(scrollToBottom, 600); + setTimeout(scrollToBottom, 1500); + } } // ===== Tab Render Dispatch ===== @@ -499,15 +507,14 @@ function renderAll() { console.error("renderOverview", e); } try { - renderChat(); + // 聊天视图未激活时不重建聊天 DOM(避免每次轮询/切换全量重建→卡死) + if (!state.currentView || state.currentView === "chat") { + renderChat(); + renderChatStarmap(); + } } catch (e) { console.error("renderChat", e); } - try { - renderChatStarmap(); - } catch (e) { - console.error("renderChatStarmap", e); - } try { renderPlugins(); } catch (e) { @@ -593,15 +600,14 @@ async function refreshAll() { console.error("renderOverview", e); } try { - renderChat(); + // 聊天视图未激活时不重建聊天 DOM(避免每次轮询/切换全量重建→卡死) + if (!state.currentView || state.currentView === "chat") { + renderChat(); + renderChatStarmap(); + } } catch (e) { console.error("renderChat", e); } - try { - renderChatStarmap(); - } catch (e) { - console.error("renderChatStarmap", e); - } try { renderPlugins(); } catch (e) { @@ -1028,9 +1034,7 @@ function renderReasoningCard(text, isStreaming, idx) { ? '