auto-save 2026-05-09 18:03 (~6)

This commit is contained in:
2026-05-09 18:03:31 +08:00
parent ef06c99d56
commit fb92072f49
6 changed files with 214 additions and 15 deletions

View File

@@ -343,6 +343,7 @@ function switchTab(name) {
if (name === "cron") refreshCron();
if (name === "memory") refreshMemory();
if (name === "tools") refreshTools();
if (name === "settings") refreshFeishuApps();
if (name === "runs") setTimeout(() => document.getElementById("runPrompt")?.focus(), 50);
if (name === "dashboard" && _dashboardDirty) {
// 推迟到下一帧,避免阻塞切换动画
@@ -353,6 +354,55 @@ function switchTab(name) {
}
}
// ---------- 飞书集成 ----------
let _feishuAppsLoading = false;
async function refreshFeishuApps() {
const box = document.getElementById("feishuApps");
if (!box || _feishuAppsLoading) return;
_feishuAppsLoading = true;
box.innerHTML = '<div class="settings-help">正在读取飞书桥接服务...</div>';
try {
const res = await fetch("/feishu/apps", { cache: "no-store" });
if (!res.ok) throw new Error("HTTP " + res.status);
const data = await res.json();
const apps = Array.isArray(data.apps) ? data.apps : [];
if (!apps.length) {
box.innerHTML = '<div class="settings-help">还没有读取到飞书机器人配置。</div>';
return;
}
box.innerHTML = apps.map(app => {
const appId = escapeHTML(app.app_id || "");
const callbackUrl = escapeHTML(app.callback_url || "");
const isDefault = app.app_id === data.default_app_id;
const tokenCount = Number(app.verification_tokens_count || 0);
return `
<div class="feishu-app-card">
<div class="feishu-app-top">
<div>
<div class="feishu-app-title">${appId}</div>
<div class="feishu-app-meta">${isDefault ? "默认应用" : "独立应用"} · ${tokenCount} 个校验 Token</div>
</div>
<span class="feishu-status">已接入</span>
</div>
<div class="feishu-callback">
<span>${callbackUrl}</span>
<button class="icon-btn-mini" onclick="copyText('${callbackUrl}')" title="复制回调地址">
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
</button>
</div>
<div class="feishu-app-foot">
<span>事件: im.message.receive_v1</span>
<span>通知目标: ${app.has_default_receive_id ? escapeHTML(app.default_receive_id_type || "chat_id") : "按请求传入"}</span>
</div>
</div>`;
}).join("");
} catch (e) {
box.innerHTML = `<div class="settings-help">飞书桥接服务读取失败: ${escapeHTML(e.message || e)}</div>`;
} finally {
_feishuAppsLoading = false;
}
}
// ---------- 带认证续期的 fetch ----------
// nginx 的 hermes_auth cookie 默认 24h 过期;过期后 /api/v1/* 全部 401。
// 这里在 401 时尝试一次静默续期(浏览器若缓存了 Basic Auth 会自动带上),
@@ -3283,6 +3333,9 @@ function startResearch() {
function openLog() {
toast("日志查看暂未实现,可 SSH 到 Mac mini 查看 ~/.hermes/logs/");
}
function copyText(text) {
navigator.clipboard?.writeText(text).then(() => toast("已复制"));
}
function toast(text) {
const el = document.createElement("div");
el.textContent = text;

View File

@@ -1088,6 +1088,31 @@ git push # Gitea kangwan/hermes-glass-ui-personal
</div>
</div>
<!-- 飞书集成 -->
<div class="settings-group wide" id="feishuSettingsGroup">
<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="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/><path d="M8 9h8"/><path d="M8 13h5"/></svg>
</div>
<div>
<div class="settings-group-title">飞书集成</div>
<div class="settings-group-desc">当前已接入的飞书机器人和事件回调地址</div>
</div>
</div>
<div class="settings-group-body">
<div class="feishu-toolbar">
<div class="settings-help">只显示 App ID、回调地址和服务状态Secret 与 Token 只保存在服务器环境文件。</div>
<button class="glass-btn-sm" onclick="refreshFeishuApps()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" 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 class="feishu-apps" id="feishuApps">
<div class="settings-help">打开设置页后自动读取飞书桥接服务。</div>
</div>
</div>
</div>
<!-- 关于 -->
<div class="settings-group wide">
<div class="settings-group-head">

View File

@@ -2296,6 +2296,104 @@ a { color: var(--orange-3); text-decoration: none; }
padding: 10px 16px;
font-size: 12px;
}
.feishu-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.feishu-toolbar .settings-help { flex: 1 1 280px; }
.feishu-apps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 12px;
}
.feishu-app-card {
border: 1px solid var(--line);
background: rgba(255,255,255,0.04);
border-radius: 12px;
padding: 14px;
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0;
}
[data-theme="light"] .feishu-app-card { background: rgba(15,22,40,0.035); }
.feishu-app-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
min-width: 0;
}
.feishu-app-title {
font-family: "SF Mono", ui-monospace, Menlo, monospace;
font-size: 12px;
font-weight: 800;
color: var(--text);
overflow-wrap: anywhere;
}
.feishu-app-meta {
margin-top: 3px;
font-size: 11px;
color: var(--text-dim2);
}
.feishu-status {
flex: 0 0 auto;
border: 1px solid rgba(80,220,140,0.32);
background: rgba(80,220,140,0.1);
color: #68d391;
border-radius: 999px;
padding: 4px 8px;
font-size: 11px;
font-weight: 800;
}
.feishu-callback {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
padding: 9px 10px;
border: 1px solid var(--line);
border-radius: 10px;
background: rgba(0,0,0,0.18);
}
[data-theme="light"] .feishu-callback { background: rgba(15,22,40,0.04); }
.feishu-callback span {
flex: 1 1 auto;
min-width: 0;
font-family: "SF Mono", ui-monospace, Menlo, monospace;
font-size: 11px;
color: var(--text-dim);
overflow-wrap: anywhere;
}
.icon-btn-mini {
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid var(--line-strong);
background: rgba(255,105,0,0.08);
color: var(--orange-3);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.icon-btn-mini:hover { background: rgba(255,105,0,0.16); }
.feishu-app-foot {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: var(--text-dim2);
font-size: 11px;
}
.feishu-app-foot span {
padding: 3px 8px;
border: 1px solid var(--line);
border-radius: 999px;
background: rgba(255,255,255,0.035);
}
.danger-btn {
border-color: rgba(255,93,122,0.3) !important;
color: var(--err) !important;

View File

@@ -1,6 +1,6 @@
// 爱马仕 Hermes · 轻量 Service Worker
// 静态壳走 network-first拿不到再回退缓存API 直通
const CACHE = "hermes-ui-v9";
const CACHE = "hermes-ui-v10";
const ASSETS = [
"./",
"./index.html",
@@ -29,6 +29,7 @@ self.addEventListener("fetch", (e) => {
// API / 鉴权 / skill 索引等动态资源全部直通
if (url.pathname.startsWith("/api/")) return;
if (url.pathname.startsWith("/_auth/")) return;
if (url.pathname.startsWith("/feishu/")) return;
if (url.pathname.startsWith("/hermes-skills/")) return;
if (e.request.method !== "GET") return;