auto-save 2026-05-11 18:34 (~5)
This commit is contained in:
@@ -1,12 +1,5 @@
|
|||||||
{
|
{
|
||||||
"entries": [
|
"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,
|
"files_changed": 1,
|
||||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-10 09:37 (~1)",
|
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-10 09:37 (~1)",
|
||||||
@@ -3268,6 +3261,13 @@
|
|||||||
"type": "session-heartbeat",
|
"type": "session-heartbeat",
|
||||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-11 18:23 (~1)",
|
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-11 18:23 (~1)",
|
||||||
"files_changed": 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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
11
RULES.md
11
RULES.md
@@ -14,13 +14,14 @@
|
|||||||
- 飞书事件回调(`cli_a97764e101b95be9`):https://hermes.kang-kang.com/feishu/events/cli_a97764e101b95be9
|
- 飞书事件回调(`cli_a97764e101b95be9`):https://hermes.kang-kang.com/feishu/events/cli_a97764e101b95be9
|
||||||
- 飞书主动通知:https://hermes.kang-kang.com/feishu/notify
|
- 飞书主动通知:https://hermes.kang-kang.com/feishu/notify
|
||||||
- 飞书机器人列表:https://hermes.kang-kang.com/feishu/apps(展示 App ID / 回调地址,不含 Secret / Token)
|
- 飞书机器人列表: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`
|
- 飞书事件消息回复默认走 Feishu `im/v1/messages/{message_id}/reply`;主动通知仍走 `/feishu/notify`
|
||||||
- 爱马仕前端「仪表盘」同步了上游 Hermes 的快捷入口板块,个人版展示主站、API、飞书机器人列表、文档/解析入口
|
- 爱马仕前端「工作区」同步了上游 Hermes 的快捷入口板块,个人版展示主站、API、飞书机器人列表、文档/解析入口
|
||||||
- 爱马仕前端「仪表盘」活动热力图已重做为带摘要、月份标尺、紧凑格子和细分色阶的活动卡片
|
- 爱马仕前端「工作区」活动热力图已重做为带摘要、月份标尺、紧凑格子和细分色阶的活动卡片
|
||||||
- 爱马仕前端「设置 → 连接」可自助维护 API 地址 / API Key 并测试连接;「对话 → 存周报」和「设置 → 周报记录」会在本地保存任务描述、上下文片段和最终周报
|
- 爱马仕前端「设置 → 连接」可自助维护 API 地址 / API Key 并测试连接;「对话 → 存周报」和「设置 → 周报记录」会在本地保存任务描述、上下文片段和最终周报
|
||||||
- 爱马仕前端「模型」可维护 AI 模型 Profiles、Provider、Base URL 和 LXC 内 `/opt/hermes-agent/config.yaml` 的 `model` 块,保存后重启 Docker `hermes-agent`
|
- 爱马仕前端「模型」可维护 AI 模型 Profiles,用于给不同 Agent 绑定不同模型、Provider、Base URL 和服务器端 Key 引用
|
||||||
- 爱马仕前端「工具集 → MCP 工具接入」可维护 LXC 内 `/opt/hermes-agent/config.yaml` 的 `mcp_servers` 块,保存后重启 Docker `hermes-agent`
|
- 爱马仕前端「提供商」可维护 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` 静默续期
|
- 飞书、模型、MCP、共享 Agent/Profile 等 `/feishu/*` 管理接口复用网页登录 cookie,并在前端 401 时走 `/_auth/verify` 静默续期
|
||||||
- 当前前端不再启用 Service Worker 静态壳缓存;`sw.js` 仅用于清理旧 `hermes-ui-*` 缓存并注销旧注册
|
- 当前前端不再启用 Service Worker 静态壳缓存;`sw.js` 仅用于清理旧 `hermes-ui-*` 缓存并注销旧注册
|
||||||
- 文档 / 解析:https://styles.kang-kang.com
|
- 文档 / 解析:https://styles.kang-kang.com
|
||||||
|
|||||||
97
src/app.js
97
src/app.js
@@ -110,7 +110,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
safeBoot("绑定导航", bindTabs);
|
safeBoot("绑定导航", bindTabs);
|
||||||
safeBoot("绑定对话", bindChat);
|
safeBoot("绑定对话", bindChat);
|
||||||
safeBoot("绑定搜索", bindSearch);
|
safeBoot("绑定搜索", bindSearch);
|
||||||
safeBoot("绑定 Skill Studio", bindStudio);
|
safeBoot("绑定技能库", bindStudio);
|
||||||
safeBoot("渲染侧栏", renderSidebar);
|
safeBoot("渲染侧栏", renderSidebar);
|
||||||
safeBoot("渲染对话", renderChat);
|
safeBoot("渲染对话", renderChat);
|
||||||
safeBoot("渲染智能体", renderAgents);
|
safeBoot("渲染智能体", renderAgents);
|
||||||
@@ -223,7 +223,7 @@ function updateModelDisplay(modelValue, providerValue = "") {
|
|||||||
const statSub = document.getElementById("statModelSub");
|
const statSub = document.getElementById("statModelSub");
|
||||||
const aboutModel = document.getElementById("aboutModelValue");
|
const aboutModel = document.getElementById("aboutModelValue");
|
||||||
if (stat) stat.textContent = label;
|
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;
|
if (aboutModel) aboutModel.textContent = provider ? model + " · " + provider : model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,9 +626,19 @@ function bindTabs() {
|
|||||||
function restoreActiveTab() {
|
function restoreActiveTab() {
|
||||||
const saved = localStorage.getItem(LS_TAB);
|
const saved = localStorage.getItem(LS_TAB);
|
||||||
if (!saved) return;
|
if (!saved) return;
|
||||||
if (!document.querySelector(`.side-item[data-tab="${CSS.escape(saved)}"]`)) return;
|
const aliases = {
|
||||||
if (!document.getElementById("tab-" + saved)) return;
|
research: "sessions",
|
||||||
switchTab(saved, { persist: false });
|
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;
|
let _dashboardDirty = true;
|
||||||
function markDashboardDirty() { _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(".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));
|
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 === "chat") setTimeout(() => document.getElementById("chatInput")?.focus(), 50);
|
||||||
if (name === "studio") {
|
if (name === "sessions") {
|
||||||
|
renderSessionsPanel();
|
||||||
|
refreshMemory();
|
||||||
|
}
|
||||||
|
if (name === "skills") {
|
||||||
renderStudioLibrary();
|
renderStudioLibrary();
|
||||||
renderStudioCanvas();
|
renderStudioCanvas();
|
||||||
}
|
}
|
||||||
if (name === "cron") refreshCron();
|
if (name === "schedules") refreshCron();
|
||||||
if (name === "memory") refreshMemory();
|
if (name === "soul") refreshMemory();
|
||||||
if (name === "models") {
|
if (name === "models") {
|
||||||
renderModelProfiles();
|
renderModelProfiles();
|
||||||
refreshUiConfig().catch((error) => {
|
refreshUiConfig().catch((error) => {
|
||||||
setSharedConfigStatus("共享配置读取失败: " + (error.message || error), true);
|
setSharedConfigStatus("共享配置读取失败: " + (error.message || error), true);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (name === "providers") {
|
||||||
refreshHermesConfig();
|
refreshHermesConfig();
|
||||||
}
|
}
|
||||||
if (name === "tools") {
|
if (name === "tools") {
|
||||||
refreshTools();
|
refreshTools();
|
||||||
refreshHermesConfig();
|
refreshHermesConfig();
|
||||||
}
|
}
|
||||||
if (name === "integrations") refreshFeishuApps();
|
if (name === "gateway") refreshFeishuApps();
|
||||||
if (name === "settings") {
|
if (name === "settings") {
|
||||||
renderWeeklyReports();
|
renderWeeklyReports();
|
||||||
}
|
}
|
||||||
if (name === "runs") setTimeout(() => document.getElementById("runPrompt")?.focus(), 50);
|
if (name === "runs") setTimeout(() => document.getElementById("runPrompt")?.focus(), 50);
|
||||||
if (name === "dashboard" && _dashboardDirty) {
|
if (name === "office" && _dashboardDirty) {
|
||||||
// 推迟到下一帧,避免阻塞切换动画
|
// 推迟到下一帧,避免阻塞切换动画
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
refreshDashboard();
|
refreshDashboard();
|
||||||
@@ -2103,7 +2119,7 @@ function renderDashStats() {
|
|||||||
if (avatarEl) avatarEl.textContent = a.emoji || "🤖";
|
if (avatarEl) avatarEl.textContent = a.emoji || "🤖";
|
||||||
} else {
|
} else {
|
||||||
setText("stTopAgent", "还未使用智能体");
|
setText("stTopAgent", "还未使用智能体");
|
||||||
setText("stTopAgentSub", "去 Agent 面板创建一个开始对话");
|
setText("stTopAgentSub", "去档案面板创建一个开始对话");
|
||||||
if (avatarEl) avatarEl.textContent = "—";
|
if (avatarEl) avatarEl.textContent = "—";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2204,7 +2220,7 @@ function renderHeatmap() {
|
|||||||
|
|
||||||
// 窗口尺寸变化时重新渲染热力图
|
// 窗口尺寸变化时重新渲染热力图
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
if (document.getElementById("tab-dashboard")?.classList.contains("active")) {
|
if (document.getElementById("tab-office")?.classList.contains("active")) {
|
||||||
renderHeatmap();
|
renderHeatmap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4227,22 +4243,62 @@ function handleRunEvent(evt) {
|
|||||||
addRunEvent(shortType, body, cls);
|
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 = '<div class="history-empty">还没有本地会话</div>';
|
||||||
|
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 = `
|
||||||
|
<div class="session-row-title"></div>
|
||||||
|
<div class="session-row-meta">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span class="session-row-action">→ 打开</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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() {
|
async function refreshMemory() {
|
||||||
const soulEl = document.getElementById("memorySoul");
|
const soulEl = document.getElementById("memorySoul");
|
||||||
const sessEl = document.getElementById("memorySessions");
|
const sessEl = document.getElementById("memorySessions");
|
||||||
const cntEl = document.getElementById("memorySessCount");
|
const cntEl = document.getElementById("memorySessCount");
|
||||||
if (!soulEl || !sessEl) return;
|
if (!soulEl && !sessEl) return;
|
||||||
try {
|
try {
|
||||||
const [soulRes, sessRes] = await Promise.all([
|
const [soulRes, sessRes] = await Promise.all([
|
||||||
fetch("/memory/SOUL.md", { cache: "no-store" }),
|
soulEl ? fetch("/memory/SOUL.md", { cache: "no-store" }) : Promise.resolve(null),
|
||||||
fetch("/memory/sessions.json", { cache: "no-store" }),
|
sessEl ? fetch("/memory/sessions.json", { cache: "no-store" }) : Promise.resolve(null),
|
||||||
]);
|
]);
|
||||||
soulEl.textContent = soulRes.ok ? await soulRes.text() : "(无 SOUL.md)";
|
if (soulEl) soulEl.textContent = soulRes?.ok ? await soulRes.text() : "(无 SOUL.md)";
|
||||||
if (sessRes.ok) {
|
if (sessEl && sessRes?.ok) {
|
||||||
const data = await sessRes.json();
|
const data = await sessRes.json();
|
||||||
const list = data.sessions || [];
|
const list = data.sessions || [];
|
||||||
cntEl.textContent = list.length;
|
if (cntEl) cntEl.textContent = list.length;
|
||||||
sessEl.innerHTML = "";
|
sessEl.innerHTML = "";
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
sessEl.innerHTML = '<div class="history-empty">还没有会话</div>';
|
sessEl.innerHTML = '<div class="history-empty">还没有会话</div>';
|
||||||
@@ -4272,7 +4328,8 @@ async function refreshMemory() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
soulEl.textContent = "加载失败: " + (e.message || e);
|
if (soulEl) soulEl.textContent = "加载失败: " + (e.message || e);
|
||||||
|
if (sessEl) sessEl.innerHTML = '<div class="history-empty">加载失败: ' + escapeHTML(e.message || e) + '</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
192
src/index.html
192
src/index.html
@@ -11,7 +11,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="./icon.svg">
|
<link rel="icon" type="image/svg+xml" href="./icon.svg">
|
||||||
<link rel="apple-touch-icon" href="./icon.svg">
|
<link rel="apple-touch-icon" href="./icon.svg">
|
||||||
<title>爱马仕 · AI</title>
|
<title>爱马仕 · AI</title>
|
||||||
<link rel="stylesheet" href="./styles.css?v=20260511-settings-ia-v35">
|
<link rel="stylesheet" href="./styles.css?v=20260511-modules-ia-v36">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -617,13 +617,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB: 记忆 (真实 Hermes SOUL.md + sessions 快照) -->
|
<!-- TAB: 人格 (真实 Hermes SOUL.md) -->
|
||||||
<section class="tab-panel" id="tab-memory">
|
<section class="tab-panel" id="tab-soul">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<div class="panel-head-row">
|
<div class="panel-head-row">
|
||||||
<div>
|
<div>
|
||||||
<h2>记忆</h2>
|
<h2>人格</h2>
|
||||||
<p>Hermes 后端 <code>SOUL.md</code> + 会话历史快照 · 每分钟自动同步</p>
|
<p>通过 <code>SOUL.md</code> 定义 Agent 的人格、语气和长期指令。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-head-actions">
|
<div class="panel-head-actions">
|
||||||
<button class="glass-btn-sm" onclick="refreshMemory()">
|
<button class="glass-btn-sm" onclick="refreshMemory()">
|
||||||
@@ -639,8 +639,102 @@
|
|||||||
<pre class="memory-body" id="memorySoul">加载中…</pre>
|
<pre class="memory-body" id="memorySoul">加载中…</pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="memory-pane">
|
<div class="memory-pane">
|
||||||
<div class="memory-pane-head">会话历史 · <span id="memorySessCount">0</span></div>
|
<div class="memory-pane-head">说明</div>
|
||||||
<div class="memory-sessions" id="memorySessions">加载中…</div>
|
<div class="memory-sessions">
|
||||||
|
<div class="settings-help">每次对话会加载这份长期指令。编辑能力暂未开放在前端,当前页面用于读取线上 Hermes 同步快照。</div>
|
||||||
|
<button class="glass-btn-sm" onclick="switchTab('chat');fillPrompt('请总结当前 SOUL.md 的人格设定和约束')">让 Agent 解读人格</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- TAB: 记忆 -->
|
||||||
|
<section class="tab-panel" id="tab-memory">
|
||||||
|
<div class="panel-head">
|
||||||
|
<div class="panel-head-row">
|
||||||
|
<div>
|
||||||
|
<h2>记忆</h2>
|
||||||
|
<p>Hermes 在不同会话之间沉淀的偏好、事实和可检索上下文。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="memory-grid">
|
||||||
|
<div class="memory-pane">
|
||||||
|
<div class="memory-pane-head">代理记忆</div>
|
||||||
|
<div class="memory-sessions">
|
||||||
|
<div class="settings-help">当前个人版已同步 SOUL.md 和会话快照;更细的外部记忆 Provider 仍由 Hermes 后端配置。</div>
|
||||||
|
<button class="glass-btn-sm" onclick="switchTab('soul')">查看人格</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="memory-pane">
|
||||||
|
<div class="memory-pane-head">相关入口</div>
|
||||||
|
<div class="memory-sessions">
|
||||||
|
<button class="glass-btn-sm" onclick="switchTab('sessions')">查看会话</button>
|
||||||
|
<button class="glass-btn-sm" onclick="switchTab('tools');fillPrompt('列出当前可用的记忆相关工具')">查看记忆工具</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- TAB: 提供商 -->
|
||||||
|
<section class="tab-panel" id="tab-providers">
|
||||||
|
<div class="panel-head">
|
||||||
|
<div class="panel-head-row">
|
||||||
|
<div>
|
||||||
|
<h2>提供商</h2>
|
||||||
|
<p>配置 LLM Provider、运行模型、Base URL 和服务器端 API Key 引用。</p>
|
||||||
|
</div>
|
||||||
|
<div class="panel-head-actions">
|
||||||
|
<button class="glass-btn-sm" onclick="refreshHermesConfig(true)">
|
||||||
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 15.5-6.3L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15.5 6.3L3 16"/><path d="M3 21v-5h5"/></svg>
|
||||||
|
读取配置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="models-scroll settings-scroll">
|
||||||
|
<div class="settings-group wide">
|
||||||
|
<div class="settings-group-head">
|
||||||
|
<div class="settings-group-icon">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 7h.01"/><path d="M7.5 7h3.75a3.75 3.75 0 0 1 0 7.5H9L6 18v-3.5H5A3.5 3.5 0 0 1 5 7h2.5z"/><path d="M15 14.5h1a3.5 3.5 0 0 0 0-7h-1"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="settings-group-title">运行 Provider</div>
|
||||||
|
<div class="settings-group-desc">线上 Hermes agent 当前默认调用的模型和 Provider 配置</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-group-body">
|
||||||
|
<div class="settings-grid-3">
|
||||||
|
<div class="settings-field">
|
||||||
|
<label for="hermesModelDefault">默认模型 ID</label>
|
||||||
|
<input type="text" id="hermesModelDefault" placeholder="google/gemini-3.1-pro-preview" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="settings-field">
|
||||||
|
<label for="hermesModelProvider">Provider</label>
|
||||||
|
<input type="text" id="hermesModelProvider" placeholder="openrouter" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="settings-field">
|
||||||
|
<label for="hermesModelBaseUrl">Base URL</label>
|
||||||
|
<input type="text" id="hermesModelBaseUrl" placeholder="https://openrouter.ai/api/v1" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-grid-3">
|
||||||
|
<div class="settings-field">
|
||||||
|
<label for="hermesModelApiKeyRef">AI API Key</label>
|
||||||
|
<input type="password" id="hermesModelApiKeyRef" placeholder="服务器环境变量,不在浏览器显示" disabled>
|
||||||
|
<div class="settings-help">真实 Key 仍只放在服务器环境变量;这里不保存、不回显密钥。多 Agent 的专属 Key 引用在「模型」Profile 里维护。</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-actions">
|
||||||
|
<button class="glass-btn-sm" onclick="refreshHermesConfig(true)">读取 Provider 配置</button>
|
||||||
|
<button class="glass-btn-sm primary" id="hermesModelSaveBtn" onclick="saveModelConfig()">
|
||||||
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/></svg>
|
||||||
|
保存 Provider 并重启
|
||||||
|
</button>
|
||||||
|
<div class="settings-help" id="hermesModelStatus">打开提供商页后自动读取。</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -856,14 +950,18 @@
|
|||||||
<thead><tr><th>Tab</th><th>调用</th><th>数据源</th></tr></thead>
|
<thead><tr><th>Tab</th><th>调用</th><th>数据源</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>对话</td><td><code>POST /v1/chat/completions</code> (SSE 流式)</td><td>实时 · 前端 localStorage 存会话</td></tr>
|
<tr><td>对话</td><td><code>POST /v1/chat/completions</code> (SSE 流式)</td><td>实时 · 前端 localStorage 存会话</td></tr>
|
||||||
<tr><td>研究</td><td>跳回对话 + 预填 prompt</td><td>纯前端</td></tr>
|
<tr><td>会话</td><td>本地对话列表 + <code>/memory/sessions.json</code> + <code>/memory/sessions/{id}.json</code></td><td>localStorage + systemd timer sync</td></tr>
|
||||||
<tr><td>Agent</td><td>前端 CRUD + system prompt 组装</td><td>localStorage (hermes-ui-agents-v1)</td></tr>
|
<tr><td>档案</td><td>前端 CRUD + system prompt 组装</td><td>localStorage (hermes-ui-agents-v1)</td></tr>
|
||||||
<tr><td>Skill Studio</td><td><code>GET /hermes-skills/</code> autoindex JSON</td><td>docker cp 快照 (78 个真实 SKILL.md)</td></tr>
|
<tr><td>工作区</td><td><code>GET /v1/models</code> + localStorage 聚合</td><td>实时 + 本地</td></tr>
|
||||||
<tr><td>定时任务</td><td><code>GET/POST/PATCH/DELETE /api/jobs</code></td><td>Hermes 后端实时</td></tr>
|
<tr><td>模型</td><td><code>GET/PUT /feishu/shared-config</code></td><td>服务器共享模型 Profiles</td></tr>
|
||||||
<tr><td>记忆</td><td><code>GET /memory/SOUL.md</code> + <code>/memory/sessions.json</code> + <code>/memory/sessions/{id}.json</code></td><td>systemd timer 每 1 分钟 sync</td></tr>
|
<tr><td>提供商</td><td><code>GET/PUT /feishu/hermes-config</code> 的 <code>model</code> 块</td><td>Hermes 运行模型配置</td></tr>
|
||||||
<tr><td>工具</td><td><code>GET /memory/tools.txt</code></td><td><code>hermes tools list</code> 输出每分钟刷新</td></tr>
|
<tr><td>技能</td><td><code>GET /hermes-skills/</code> autoindex JSON</td><td>docker cp 快照 (78 个真实 SKILL.md)</td></tr>
|
||||||
|
<tr><td>人格</td><td><code>GET /memory/SOUL.md</code></td><td>systemd timer sync</td></tr>
|
||||||
|
<tr><td>记忆</td><td><code>GET /memory/sessions.json</code> 入口聚合</td><td>systemd timer sync</td></tr>
|
||||||
|
<tr><td>工具</td><td><code>GET /memory/tools.txt</code> + <code>GET/PUT /feishu/hermes-config</code> 的 <code>mcp_servers</code> 块</td><td><code>hermes tools list</code> + MCP 配置</td></tr>
|
||||||
|
<tr><td>计划任务</td><td><code>GET/POST/PATCH/DELETE /api/jobs</code></td><td>Hermes 后端实时</td></tr>
|
||||||
|
<tr><td>网关</td><td><code>GET/POST/DELETE /feishu/apps</code></td><td>外部消息入口</td></tr>
|
||||||
<tr><td>异步任务</td><td><code>POST /v1/runs</code> + EventSource <code>/v1/runs/{id}/events</code></td><td>实时 SSE</td></tr>
|
<tr><td>异步任务</td><td><code>POST /v1/runs</code> + EventSource <code>/v1/runs/{id}/events</code></td><td>实时 SSE</td></tr>
|
||||||
<tr><td>仪表盘</td><td><code>GET /v1/models</code> + localStorage 聚合</td><td>实时 + 本地</td></tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -879,7 +977,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="help-card">
|
<div class="help-card">
|
||||||
<h3>🧠 Skill Studio 编排原理</h3>
|
<h3>🧠 技能编排原理</h3>
|
||||||
<p>不是真的在后端运行编排,而是前端<b>组装复合 system prompt</b>:</p>
|
<p>不是真的在后端运行编排,而是前端<b>组装复合 system prompt</b>:</p>
|
||||||
<pre class="md-pre"><code>Agent.stages.pre → 阶段一 · 前置(理解目标)
|
<pre class="md-pre"><code>Agent.stages.pre → 阶段一 · 前置(理解目标)
|
||||||
Agent.stages.exec → 阶段二 · 执行(工具调用 / 生成)
|
Agent.stages.exec → 阶段二 · 执行(工具调用 / 生成)
|
||||||
@@ -928,47 +1026,39 @@ git push # Gitea kangwan/hermes-glass-ui-personal
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- TAB: 研究 -->
|
<!-- TAB: 会话 -->
|
||||||
<section class="tab-panel" id="tab-research">
|
<section class="tab-panel" id="tab-sessions">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h2>研究</h2>
|
<div class="panel-head-row">
|
||||||
<p>爱马仕能帮你深度调研、解读文档、调用工具。</p>
|
<div>
|
||||||
|
<h2>会话</h2>
|
||||||
|
<p>查看本地对话和 Hermes 后端同步的会话快照,可导入继续聊。</p>
|
||||||
|
</div>
|
||||||
|
<div class="panel-head-actions">
|
||||||
|
<button class="glass-btn-sm primary" onclick="newChat()">新建聊天</button>
|
||||||
|
<button class="glass-btn-sm" onclick="renderSessionsPanel();refreshMemory()">刷新</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-cards">
|
<div class="memory-grid">
|
||||||
<div class="card">
|
<div class="memory-pane">
|
||||||
<div class="card-icon">🔍</div>
|
<div class="memory-pane-head">本地会话</div>
|
||||||
<div class="card-title">深度研究</div>
|
<div class="memory-sessions" id="localSessionsList">加载中…</div>
|
||||||
<div class="card-desc">给一个主题,自动拆解 → 搜索 → 总结 → 输出报告。</div>
|
|
||||||
<button class="glass-btn-sm" onclick="startResearch()">开始</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="memory-pane">
|
||||||
<div class="card-icon">📄</div>
|
<div class="memory-pane-head">云端会话 · <span id="memorySessCount">0</span></div>
|
||||||
<div class="card-title">文档问答</div>
|
<div class="memory-sessions" id="memorySessions">加载中…</div>
|
||||||
<div class="card-desc">粘贴长文档或 URL,就文档内容提问。</div>
|
|
||||||
<button class="glass-btn-sm" onclick="switchTab('chat');fillPrompt('请粘贴文档内容或 URL,我来帮你解读')">去对话</button>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-icon">🛠</div>
|
|
||||||
<div class="card-title">工具调用</div>
|
|
||||||
<div class="card-desc">浏览器 / 文件 / 终端 — Hermes 的工具集通过后端执行。</div>
|
|
||||||
<button class="glass-btn-sm" onclick="switchTab('chat');fillPrompt('列出你现在有哪些工具可以用')">查询工具</button>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-icon">🧠</div>
|
|
||||||
<div class="card-title">记忆体系</div>
|
|
||||||
<div class="card-desc">对话历史沉淀进 SQLite FTS5,未来 skill 可自动提炼。</div>
|
|
||||||
<button class="glass-btn-sm" onclick="openLog()">查看日志</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- TAB: 仪表盘 -->
|
<!-- TAB: 工作区 -->
|
||||||
<section class="tab-panel" id="tab-dashboard">
|
<section class="tab-panel" id="tab-office">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<div class="panel-head-row">
|
<div class="panel-head-row">
|
||||||
<div>
|
<div>
|
||||||
<h2>仪表盘</h2>
|
<h2>工作区</h2>
|
||||||
<p>用量、系统状态、快捷入口和实时日志。</p>
|
<p>集中查看运行状态、快捷入口、活动热力图和工作节奏。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-head-actions">
|
<div class="panel-head-actions">
|
||||||
<button class="glass-btn-sm" onclick="refreshDashboard()">
|
<button class="glass-btn-sm" onclick="refreshDashboard()">
|
||||||
@@ -1074,7 +1164,7 @@ git push # Gitea kangwan/hermes-glass-ui-personal
|
|||||||
<div class="stat-body">
|
<div class="stat-body">
|
||||||
<div class="stat-label">当前模型</div>
|
<div class="stat-label">当前模型</div>
|
||||||
<div class="stat-value" id="statModel">google/gemini-3.1-pro-preview</div>
|
<div class="stat-value" id="statModel">google/gemini-3.1-pro-preview</div>
|
||||||
<div class="stat-sub" id="statModelSub">Provider 以设置为准</div>
|
<div class="stat-sub" id="statModelSub">Provider 以提供商页为准</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat stat-lg">
|
<div class="stat stat-lg">
|
||||||
@@ -1166,10 +1256,10 @@ git push # Gitea kangwan/hermes-glass-ui-personal
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- TAB: 集成 -->
|
<!-- TAB: 网关 -->
|
||||||
<section class="tab-panel" id="tab-integrations">
|
<section class="tab-panel" id="tab-gateway">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h2>机器人集成</h2>
|
<h2>网关</h2>
|
||||||
<p>统一管理飞书、微信、WhatsApp、Telegram、Discord 等外部消息入口。</p>
|
<p>统一管理飞书、微信、WhatsApp、Telegram、Discord 等外部消息入口。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1455,6 +1545,6 @@ git push # Gitea kangwan/hermes-glass-ui-personal
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="./app.js?v=20260511-settings-ia-v35"></script>
|
<script src="./app.js?v=20260511-modules-ia-v36"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3563,6 +3563,7 @@ a { color: var(--orange-3); text-decoration: none; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
#tab-models,
|
#tab-models,
|
||||||
|
#tab-providers,
|
||||||
#tab-tools {
|
#tab-tools {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user