From 405746dd72818dbea21fe7d554c8707f68190edd Mon Sep 17 00:00:00 2001 From: kang Date: Mon, 11 May 2026 18:34:56 +0800 Subject: [PATCH] auto-save 2026-05-11 18:34 (~5) --- .memory/worklog.json | 14 ++-- RULES.md | 11 +-- src/app.js | 97 +++++++++++++++++----- src/index.html | 192 +++++++++++++++++++++++++++++++------------ src/styles.css | 1 + 5 files changed, 232 insertions(+), 83 deletions(-) diff --git a/.memory/worklog.json b/.memory/worklog.json index be85846..a8df40a 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,12 +1,5 @@ { "entries": [ - { - "files_changed": 1, - "hash": "a94dc90", - "message": "auto-save 2026-05-10 09:37 (~1)", - "ts": "2026-05-10T09:37:59+08:00", - "type": "commit" - }, { "files_changed": 1, "message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-10 09:37 (~1)", @@ -3268,6 +3261,13 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-11 18:23 (~1)", "files_changed": 1 + }, + { + "ts": "2026-05-11T18:29:22+08:00", + "type": "commit", + "message": "auto-save 2026-05-11 18:29 (~2)", + "hash": "111dbfe", + "files_changed": 2 } ] } diff --git a/RULES.md b/RULES.md index 4d270c6..1640e10 100644 --- a/RULES.md +++ b/RULES.md @@ -14,13 +14,14 @@ - 飞书事件回调(`cli_a97764e101b95be9`):https://hermes.kang-kang.com/feishu/events/cli_a97764e101b95be9 - 飞书主动通知:https://hermes.kang-kang.com/feishu/notify - 飞书机器人列表:https://hermes.kang-kang.com/feishu/apps(展示 App ID / 回调地址,不含 Secret / Token) -- 爱马仕前端「集成 → 飞书集成」可自助添加 / 更新 / 删除飞书机器人;Secret / Token 只写入服务器 `/etc/hermes-feishu-bridge.env` +- 爱马仕前端「网关 → 飞书集成」可自助添加 / 更新 / 删除飞书机器人;Secret / Token 只写入服务器 `/etc/hermes-feishu-bridge.env` - 飞书事件消息回复默认走 Feishu `im/v1/messages/{message_id}/reply`;主动通知仍走 `/feishu/notify` -- 爱马仕前端「仪表盘」同步了上游 Hermes 的快捷入口板块,个人版展示主站、API、飞书机器人列表、文档/解析入口 -- 爱马仕前端「仪表盘」活动热力图已重做为带摘要、月份标尺、紧凑格子和细分色阶的活动卡片 +- 爱马仕前端「工作区」同步了上游 Hermes 的快捷入口板块,个人版展示主站、API、飞书机器人列表、文档/解析入口 +- 爱马仕前端「工作区」活动热力图已重做为带摘要、月份标尺、紧凑格子和细分色阶的活动卡片 - 爱马仕前端「设置 → 连接」可自助维护 API 地址 / API Key 并测试连接;「对话 → 存周报」和「设置 → 周报记录」会在本地保存任务描述、上下文片段和最终周报 -- 爱马仕前端「模型」可维护 AI 模型 Profiles、Provider、Base URL 和 LXC 内 `/opt/hermes-agent/config.yaml` 的 `model` 块,保存后重启 Docker `hermes-agent` -- 爱马仕前端「工具集 → MCP 工具接入」可维护 LXC 内 `/opt/hermes-agent/config.yaml` 的 `mcp_servers` 块,保存后重启 Docker `hermes-agent` +- 爱马仕前端「模型」可维护 AI 模型 Profiles,用于给不同 Agent 绑定不同模型、Provider、Base URL 和服务器端 Key 引用 +- 爱马仕前端「提供商」可维护 LXC 内 `/opt/hermes-agent/config.yaml` 的 `model` 块,保存后重启 Docker `hermes-agent` +- 爱马仕前端「工具 → MCP 工具接入」可维护 LXC 内 `/opt/hermes-agent/config.yaml` 的 `mcp_servers` 块,保存后重启 Docker `hermes-agent` - 飞书、模型、MCP、共享 Agent/Profile 等 `/feishu/*` 管理接口复用网页登录 cookie,并在前端 401 时走 `/_auth/verify` 静默续期 - 当前前端不再启用 Service Worker 静态壳缓存;`sw.js` 仅用于清理旧 `hermes-ui-*` 缓存并注销旧注册 - 文档 / 解析:https://styles.kang-kang.com diff --git a/src/app.js b/src/app.js index 70bbd58..a409d4b 100644 --- a/src/app.js +++ b/src/app.js @@ -110,7 +110,7 @@ document.addEventListener("DOMContentLoaded", () => { safeBoot("绑定导航", bindTabs); safeBoot("绑定对话", bindChat); safeBoot("绑定搜索", bindSearch); - safeBoot("绑定 Skill Studio", bindStudio); + safeBoot("绑定技能库", bindStudio); safeBoot("渲染侧栏", renderSidebar); safeBoot("渲染对话", renderChat); safeBoot("渲染智能体", renderAgents); @@ -223,7 +223,7 @@ function updateModelDisplay(modelValue, providerValue = "") { const statSub = document.getElementById("statModelSub"); const aboutModel = document.getElementById("aboutModelValue"); if (stat) stat.textContent = label; - if (statSub) statSub.textContent = provider ? "Provider: " + provider : "Provider 以设置为准"; + if (statSub) statSub.textContent = provider ? "Provider: " + provider : "Provider 以提供商页为准"; if (aboutModel) aboutModel.textContent = provider ? model + " · " + provider : model; } @@ -626,9 +626,19 @@ function bindTabs() { function restoreActiveTab() { const saved = localStorage.getItem(LS_TAB); if (!saved) return; - if (!document.querySelector(`.side-item[data-tab="${CSS.escape(saved)}"]`)) return; - if (!document.getElementById("tab-" + saved)) return; - switchTab(saved, { persist: false }); + const aliases = { + research: "sessions", + agent: "agents", + dashboard: "office", + studio: "skills", + cron: "schedules", + integrations: "gateway", + }; + let next = aliases[saved] || saved; + if (!document.querySelector(`.side-item[data-tab="${CSS.escape(next)}"]`) || !document.getElementById("tab-" + next)) { + next = "chat"; + } + switchTab(next, { persist: false }); } let _dashboardDirty = true; function markDashboardDirty() { _dashboardDirty = true; } @@ -638,29 +648,35 @@ function switchTab(name, options = {}) { document.querySelectorAll(".side-item").forEach(t => t.classList.toggle("active", t.dataset.tab === name)); document.querySelectorAll(".tab-panel").forEach(p => p.classList.toggle("active", p.id === "tab-" + name)); if (name === "chat") setTimeout(() => document.getElementById("chatInput")?.focus(), 50); - if (name === "studio") { + if (name === "sessions") { + renderSessionsPanel(); + refreshMemory(); + } + if (name === "skills") { renderStudioLibrary(); renderStudioCanvas(); } - if (name === "cron") refreshCron(); - if (name === "memory") refreshMemory(); + if (name === "schedules") refreshCron(); + if (name === "soul") refreshMemory(); if (name === "models") { renderModelProfiles(); refreshUiConfig().catch((error) => { setSharedConfigStatus("共享配置读取失败: " + (error.message || error), true); }); + } + if (name === "providers") { refreshHermesConfig(); } if (name === "tools") { refreshTools(); refreshHermesConfig(); } - if (name === "integrations") refreshFeishuApps(); + if (name === "gateway") refreshFeishuApps(); if (name === "settings") { renderWeeklyReports(); } if (name === "runs") setTimeout(() => document.getElementById("runPrompt")?.focus(), 50); - if (name === "dashboard" && _dashboardDirty) { + if (name === "office" && _dashboardDirty) { // 推迟到下一帧,避免阻塞切换动画 requestAnimationFrame(() => { refreshDashboard(); @@ -2103,7 +2119,7 @@ function renderDashStats() { if (avatarEl) avatarEl.textContent = a.emoji || "🤖"; } else { setText("stTopAgent", "还未使用智能体"); - setText("stTopAgentSub", "去 Agent 面板创建一个开始对话"); + setText("stTopAgentSub", "去档案面板创建一个开始对话"); if (avatarEl) avatarEl.textContent = "—"; } } @@ -2204,7 +2220,7 @@ function renderHeatmap() { // 窗口尺寸变化时重新渲染热力图 window.addEventListener("resize", () => { - if (document.getElementById("tab-dashboard")?.classList.contains("active")) { + if (document.getElementById("tab-office")?.classList.contains("active")) { renderHeatmap(); } }); @@ -4227,22 +4243,62 @@ function handleRunEvent(evt) { addRunEvent(shortType, body, cls); } -// ========== Memory (真实 Hermes SOUL.md + sessions 快照) ========== +// ========== Sessions / Soul / Memory ========== +function renderSessionsPanel() { + const list = document.getElementById("localSessionsList"); + if (!list) return; + const ids = sortedConvoIds(); + list.innerHTML = ""; + if (!ids.length) { + list.innerHTML = '
还没有本地会话
'; + return; + } + for (const id of ids) { + const c = state.conversations[id]; + if (!c) continue; + const row = document.createElement("div"); + row.className = "session-row clickable"; + row.title = "点击打开这个本地会话"; + const time = new Date(c.updatedAt || c.createdAt || Date.now()).toLocaleString("zh-CN", { + month: "numeric", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + row.innerHTML = ` +
+
+ + + + → 打开 +
+ `; + row.querySelector(".session-row-title").textContent = c.title || "未命名会话"; + const metas = row.querySelectorAll(".session-row-meta span"); + metas[0].textContent = time; + metas[1].textContent = (c.messages?.length || 0) + " 条消息"; + metas[2].textContent = c.agentId && state.agents[c.agentId] ? state.agents[c.agentId].name : "默认档案"; + row.onclick = () => switchConvo(id); + list.appendChild(row); + } +} + async function refreshMemory() { const soulEl = document.getElementById("memorySoul"); const sessEl = document.getElementById("memorySessions"); const cntEl = document.getElementById("memorySessCount"); - if (!soulEl || !sessEl) return; + if (!soulEl && !sessEl) return; try { const [soulRes, sessRes] = await Promise.all([ - fetch("/memory/SOUL.md", { cache: "no-store" }), - fetch("/memory/sessions.json", { cache: "no-store" }), + soulEl ? fetch("/memory/SOUL.md", { cache: "no-store" }) : Promise.resolve(null), + sessEl ? fetch("/memory/sessions.json", { cache: "no-store" }) : Promise.resolve(null), ]); - soulEl.textContent = soulRes.ok ? await soulRes.text() : "(无 SOUL.md)"; - if (sessRes.ok) { + if (soulEl) soulEl.textContent = soulRes?.ok ? await soulRes.text() : "(无 SOUL.md)"; + if (sessEl && sessRes?.ok) { const data = await sessRes.json(); const list = data.sessions || []; - cntEl.textContent = list.length; + if (cntEl) cntEl.textContent = list.length; sessEl.innerHTML = ""; if (!list.length) { sessEl.innerHTML = '
还没有会话
'; @@ -4272,7 +4328,8 @@ async function refreshMemory() { } } } catch (e) { - soulEl.textContent = "加载失败: " + (e.message || e); + if (soulEl) soulEl.textContent = "加载失败: " + (e.message || e); + if (sessEl) sessEl.innerHTML = '
加载失败: ' + escapeHTML(e.message || e) + '
'; } } diff --git a/src/index.html b/src/index.html index 4819743..ea6d696 100644 --- a/src/index.html +++ b/src/index.html @@ -11,7 +11,7 @@ 爱马仕 · AI - + @@ -617,13 +617,13 @@ - -
+ +
-

记忆

-

Hermes 后端 SOUL.md + 会话历史快照 · 每分钟自动同步

+

人格

+

通过 SOUL.md 定义 Agent 的人格、语气和长期指令。

-
会话历史 · 0
-
加载中…
+
说明
+
+
每次对话会加载这份长期指令。编辑能力暂未开放在前端,当前页面用于读取线上 Hermes 同步快照。
+ +
+
+
+
+ + +
+
+
+
+

记忆

+

Hermes 在不同会话之间沉淀的偏好、事实和可检索上下文。

+
+
+
+
+
+
代理记忆
+
+
当前个人版已同步 SOUL.md 和会话快照;更细的外部记忆 Provider 仍由 Hermes 后端配置。
+ +
+
+
+
相关入口
+
+ + +
+
+
+
+ + +
+
+
+
+

提供商

+

配置 LLM Provider、运行模型、Base URL 和服务器端 API Key 引用。

+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
运行 Provider
+
线上 Hermes agent 当前默认调用的模型和 Provider 配置
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
真实 Key 仍只放在服务器环境变量;这里不保存、不回显密钥。多 Agent 的专属 Key 引用在「模型」Profile 里维护。
+
+
+
+ + +
打开提供商页后自动读取。
+
+
@@ -856,14 +950,18 @@ Tab调用数据源 对话POST /v1/chat/completions (SSE 流式)实时 · 前端 localStorage 存会话 - 研究跳回对话 + 预填 prompt纯前端 - Agent前端 CRUD + system prompt 组装localStorage (hermes-ui-agents-v1) - Skill StudioGET /hermes-skills/ autoindex JSONdocker cp 快照 (78 个真实 SKILL.md) - 定时任务GET/POST/PATCH/DELETE /api/jobsHermes 后端实时 - 记忆GET /memory/SOUL.md + /memory/sessions.json + /memory/sessions/{id}.jsonsystemd timer 每 1 分钟 sync - 工具GET /memory/tools.txthermes tools list 输出每分钟刷新 + 会话本地对话列表 + /memory/sessions.json + /memory/sessions/{id}.jsonlocalStorage + systemd timer sync + 档案前端 CRUD + system prompt 组装localStorage (hermes-ui-agents-v1) + 工作区GET /v1/models + localStorage 聚合实时 + 本地 + 模型GET/PUT /feishu/shared-config服务器共享模型 Profiles + 提供商GET/PUT /feishu/hermes-configmodel 块Hermes 运行模型配置 + 技能GET /hermes-skills/ autoindex JSONdocker cp 快照 (78 个真实 SKILL.md) + 人格GET /memory/SOUL.mdsystemd timer sync + 记忆GET /memory/sessions.json 入口聚合systemd timer sync + 工具GET /memory/tools.txt + GET/PUT /feishu/hermes-configmcp_servershermes tools list + MCP 配置 + 计划任务GET/POST/PATCH/DELETE /api/jobsHermes 后端实时 + 网关GET/POST/DELETE /feishu/apps外部消息入口 异步任务POST /v1/runs + EventSource /v1/runs/{id}/events实时 SSE - 仪表盘GET /v1/models + localStorage 聚合实时 + 本地 @@ -879,7 +977,7 @@
-

🧠 Skill Studio 编排原理

+

🧠 技能编排原理

不是真的在后端运行编排,而是前端组装复合 system prompt:

Agent.stages.pre    → 阶段一 · 前置(理解目标)
 Agent.stages.exec   → 阶段二 · 执行(工具调用 / 生成)
@@ -928,47 +1026,39 @@ git push  # Gitea kangwan/hermes-glass-ui-personal
       
- -
+ +
-

研究

-

爱马仕能帮你深度调研、解读文档、调用工具。

+
+
+

会话

+

查看本地对话和 Hermes 后端同步的会话快照,可导入继续聊。

+
+
+ + +
+
-
-
-
🔍
-
深度研究
-
给一个主题,自动拆解 → 搜索 → 总结 → 输出报告。
- +
+
+
本地会话
+
加载中…
-
-
📄
-
文档问答
-
粘贴长文档或 URL,就文档内容提问。
- -
-
-
🛠
-
工具调用
-
浏览器 / 文件 / 终端 — Hermes 的工具集通过后端执行。
- -
-
-
🧠
-
记忆体系
-
对话历史沉淀进 SQLite FTS5,未来 skill 可自动提炼。
- +
+
云端会话 · 0
+
加载中…
- -
+ +
-

仪表盘

-

用量、系统状态、快捷入口和实时日志。

+

工作区

+

集中查看运行状态、快捷入口、活动热力图和工作节奏。

@@ -1166,10 +1256,10 @@ git push # Gitea kangwan/hermes-glass-ui-personal
- -
+ +
-

机器人集成

+

网关

统一管理飞书、微信、WhatsApp、Telegram、Discord 等外部消息入口。

@@ -1455,6 +1545,6 @@ git push # Gitea kangwan/hermes-glass-ui-personal - + diff --git a/src/styles.css b/src/styles.css index d272ce2..6405f11 100644 --- a/src/styles.css +++ b/src/styles.css @@ -3563,6 +3563,7 @@ a { color: var(--orange-3); text-decoration: none; } } #tab-models, +#tab-providers, #tab-tools { overflow: hidden; }