Files
HomeAgent/internal/plugins/webui/dashboard.html
JianFeeeee c33199cc5a fix(webui,plugin): 内核控制按钮晚绑定 + 按钮布局;plugin_start 报错给候选名
1. 内核控制通道晚绑定(真机 503 实测)
   POST /api/v1/kernel/restart 一直返回 503:webui 由 plugin-host 在装配期
   拉起,而 main() 里 SetKernelControl 还没执行 ⇒ 插件 Start 拿到 nil。
   依赖图保证不了 main 里的语句顺序,所以改成让插件拿**解析器**
   (lazyKernelControl),调用那一刻再取真实通道;授权仍在每次调用时
   按 kernelControlAllowed/kernelRestartAllowed 复核,不因晚绑定放宽。

2. WebUI 重启/关机按钮布局
   原先用 .theme-btn(顶栏图标样式:pill + margin-right + 无文字)混在
   侧栏底部的文字按钮里 ⇒ 大小不一、挤在一起、关机看不出是危险操作。
   改为与「退出登录」同族的 btn-sm,带图标+文字,关机用 danger 悬停。

3. plugin_start 失败报错
   原来只说加载出错,或插件不存在,一个可用名字都不给,模型只能盲猜
   (实测把 files 写成 file)。现在给:失败原因分类(目录不存在/没清单/
   自身加载失败)、最接近的名字(Levenshtein ≤2)、已安装清单(最多 12)。

4. scripts/migrate-plugins-to-org.sh:插件迁到 GitHub 组织独立仓的脚本收口

测试:go build ./... 通过;go test ./internal/plugin ./internal/plugins/webui 全绿。
2026-10-27 01:43:19 +08:00

235 lines
8.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HologramAgent Dashboard</title>
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<script
src="/static/three.min.js"
onerror="window._THREE_FAILED = true"
></script>
<script
src="/static/OrbitControls.js"
onerror="window._THREE_FAILED = true"
></script>
<script
src="/static/marked.min.js"
onerror="console.warn('marked CDN failed')"
></script>
<script
src="/static/purify.min.js"
onerror="console.warn('DOMPurify CDN failed')"
></script>
<script>
// 兜底:本地库是毫秒级加载,3 秒足够;仍超时说明真的出了问题
// (比如二进制没带上 static/),标记失败好让页面给出明确提示
// 而不是一直转圈。
setTimeout(function () {
if (!window.THREE) window._THREE_FAILED = true;
}, 3000);
</script>
<style>
{{DASHBOARD_CSS}}
</style>
</head>
<body>
<div id="bg-layer"></div>
<div class="palette-pop" id="palette-pop"></div>
<nav id="sidebar">
<h1>
<img
src="/logo.svg"
alt="HologramAgent"
style="
height: 22px;
width: 22px;
vertical-align: middle;
margin-right: 6px;
flex-shrink: 0;
border-radius: 6px;
"
/>HologramAgent
</h1>
<div class="nav-links">
<!--
导航项:图标 + 标签。
为什么把 data-i18n 挪到内层 span:切语言时那段代码是 `el.textContent = …`,
挂在 <a> 上会把图标一起抹掉 —— 只有把标签单独包起来才两全。
图标一律 24x24 stroke(currentColor),跟随 active/hover 变色,不额外加一套资源。
-->
<a class="active" onclick="switchTab('overview')">
<svg class="nav-ico" viewBox="0 0 24 24"><path d="M3 3h7v7H3zM14 3h7v5h-7zM14 12h7v9h-7zM3 14h7v7H3z"/></svg>
<span data-i18n="navOverview">概览</span>
</a>
<a onclick="switchTab('chat')">
<svg class="nav-ico" viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 9.4 9.4 0 0 1-3.3-.6L3 21l1.7-5A8.4 8.4 0 0 1 4 12.5 8.4 8.4 0 0 1 12.5 4h.5a8.4 8.4 0 0 1 8 7.5z"/></svg>
<span data-i18n="navChat">对话</span>
</a>
<a onclick="switchTab('starmap')">
<svg class="nav-ico" viewBox="0 0 24 24"><path d="M12 2l2.2 6.3H21l-5.4 4 2.1 6.4L12 15.2 6.3 18.7l2.1-6.4-5.4-4h6.8z"/></svg>
<span data-i18n="navStarmap">星图</span>
</a>
<a onclick="switchTab('plugins')">
<svg class="nav-ico" viewBox="0 0 24 24"><path d="M21 8l-9-5-9 5 9 5 9-5zM3 8v8l9 5 9-5V8"/></svg>
<span data-i18n="navPlugins">插件</span>
</a>
<a onclick="switchTab('settings')">
<svg class="nav-ico" viewBox="0 0 24 24"><path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6"/></svg>
<span data-i18n="navSettings">设置</span>
</a>
<a onclick="switchTab('adapters')">
<svg class="nav-ico" viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></svg>
<span data-i18n="navAdapters">适配器</span>
</a>
<a onclick="switchTab('kernel')">
<svg class="nav-ico" viewBox="0 0 24 24"><path d="M5 5h14v14H5zM9.5 9.5h5v5h-5zM9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3"/></svg>
<span data-i18n="navKernel">内核</span>
</a>
</div>
<div class="nav-footer">
<div style="display: flex; align-items: center; gap: 4px">
<button
class="theme-btn"
onclick="toggleTheme()"
id="theme-btn"
title="切换亮色/暗色模式"
>
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
>
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
</svg>
</button>
<button
class="theme-btn"
onclick="toggleAppearance()"
id="palette-btn"
title="外观:主题色 / 背景图"
>
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M12 22a10 10 0 1 1 10-10c0 2.2-1.8 4-4 4h-2a3 3 0 0 0-3 3c0 1.1.9 2 2 2 1.4 0 2.5-1.1 3.6-2.1C19.3 18.5 20 20 21.5 20.5A1 1 0 0 0 22 19c0-4-3.6-7-10-7-5.5 0-9 3.8-9 9z"
/>
<circle cx="7.5" cy="10.5" r="1" />
<circle cx="12" cy="7.5" r="1" />
<circle cx="16.5" cy="10.5" r="1" />
</svg>
</button>
<button
class="btn btn-ghost btn-sm"
onclick="toggleLang()"
id="lang-btn"
title="切换语言/Language"
style="font-size: 13px; min-width: 28px"
>
EN
</button>
<!-- 内核控制:重启 / 关机。
放在导航底部而不是页面里,是因为它们作用于**整个服务**,
不属于任何一个标签页。
★ 2026-10-26:原先是两个只有图标的 .theme-btn(那是**顶栏**的样式:
有 margin-right、pill 圆角、无文字),混在侧栏底部的文字按钮里
⇒ 大小不一、挤在一起、且用户看不出哪个是关机。
现在改成与"退出登录"同族的 btn-sm,带图标 + 文字,
关机额外用 danger 样式(红色悬停)区别于重启。 -->
<div class="nav-kernel">
<button
class="btn btn-ghost btn-sm"
id="kernel-restart-btn"
onclick="kernelRestart()"
title="重启内核(服务断线数秒后自动重连;进行中的对话可能中断)"
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
aria-hidden="true">
<path d="M21 12a9 9 0 1 1-2.6-6.4" />
<path d="M21 3v6h-6" />
</svg>
重启
</button>
<button
class="btn btn-ghost btn-sm nav-danger"
id="kernel-shutdown-btn"
onclick="kernelShutdown()"
title="关闭内核(不会自动重启,需人工 systemctl start)"
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
aria-hidden="true">
<path d="M12 3v9" />
<path d="M6.3 6.3a8 8 0 1 0 11.4 0" />
</svg>
关机
</button>
</div>
<button
class="btn btn-ghost btn-sm"
onclick="logout()"
data-i18n="navLogout"
>
退出登录
</button>
</div>
</div>
</nav>
<div class="main-wrap">
<header class="topbar">
<button
class="sidebar-toggle"
onclick="toggleSidebar()"
id="sidebar-toggle"
title="切换侧边栏"
aria-label="切换侧边栏"
>
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
>
<path d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<div class="breadcrumb" id="breadcrumb">
<span class="crumb-home">HologramAgent</span>
<span class="crumb-sep">/</span>
<span class="crumb-current" data-i18n="navOverview">概览</span>
</div>
</header>
<div class="container" id="app">
<div id="tab-overview" class="tab-content active"></div>
<div id="tab-chat" class="tab-content"></div>
<div id="tab-starmap" class="tab-content"></div>
<div id="tab-plugins" class="tab-content"></div>
<div id="tab-settings" class="tab-content"></div>
<div id="tab-adapters" class="tab-content"></div>
<div id="tab-kernel" class="tab-content"></div>
</div>
</div>
<div id="toast" class="toast"></div>
<script>
{{DASHBOARD_JS}}
</script>
</body>
</html>