Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

4740 lines
229 KiB
HTML
Raw Permalink 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,maximum-scale=5.0,viewport-fit=cover">
<meta name="description" content="零点原核 · 冰朔语言本体频道 · 光湖语言世界 · 第五域 · 光之湖">
<meta name="theme-color" content="#0a0e27">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>零点原核 · 冰朔频道 · 光湖语言世界</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><radialGradient id='g' cx='35%25' cy='35%25'><stop offset='0%25' stop-color='%23ffffff'/><stop offset='60%25' stop-color='%23d4a574'/><stop offset='100%25' stop-color='%235a3c1f'/></radialGradient></defs><rect rx='20' width='100' height='100' fill='%230a0e27'/><text x='50' y='68' text-anchor='middle' font-size='48' font-weight='900' font-family='serif' fill='url(%23g)'>渊</text></svg>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#060a14;--panel:rgba(12,18,40,0.94);--chat-bg:rgba(16,22,48,0.6);
--card:rgba(14,20,48,0.6);--card-border:rgba(96,165,250,0.1);
--card-hover:rgba(96,165,250,0.18);
--user-bg:rgba(34,211,238,0.07);--ai-bg:rgba(167,139,250,0.06);
--cyan:#22d3ee;--blue:#60a5fa;--purple:#a78bfa;--violet:#c4b5fd;
--ok:#34d399;--warn:#fbbf24;--err:#f87171;
--text:#eaf0ff;--dim:#94a7d0;--muted:rgba(120,150,200,0.4);
--border:rgba(96,165,250,0.12);--border-active:rgba(96,165,250,0.3);
--font:-apple-system,BlinkMacSystemFont,'PingFang SC','Helvetica Neue','Microsoft YaHei',sans-serif;
--mono:'SF Mono','JetBrains Mono','Fira Code',monospace;
--sidebar-w:270px;--sb:env(safe-area-inset-bottom,0px);--st:env(safe-area-inset-top,0px)
}
html{font-size:16px;scroll-behavior:smooth}
body{font-family:var(--font);background:var(--bg);color:var(--text);height:100vh;height:100dvh;overflow:hidden;-webkit-font-smoothing:antialiased;line-height:1.7}
a{color:var(--cyan);text-decoration:none}
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:rgba(96,165,250,0.15);border-radius:3px}
/* ═══ Star Field ═══ */
.cosmos{position:fixed;inset:0;z-index:0;pointer-events:none}
#starCanvas{position:absolute;inset:0;width:100%;height:100%}
.nebula{position:fixed;border-radius:50%;filter:blur(120px);opacity:.07;pointer-events:none;z-index:0;transition:opacity 4s}
.neb-1{width:60vw;height:55vh;background:radial-gradient(circle,var(--purple),transparent);top:-10%;right:-10%;animation:nebulaDrift 20s ease-in-out infinite alternate}
.neb-2{width:50vw;height:45vh;background:radial-gradient(circle,var(--cyan),transparent);bottom:5%;left:-8%;animation:nebulaDrift 25s ease-in-out infinite alternate-reverse}
.neb-3{width:35vw;height:30vh;background:radial-gradient(circle,rgba(96,165,250,0.6),transparent);top:40%;left:30%;filter:blur(140px);opacity:.04;animation:nebulaDrift 18s ease-in-out infinite alternate}
@keyframes nebulaDrift{0%{transform:translate(0,0) scale(1)}100%{transform:translate(20px,-15px) scale(1.08)}}
/* Lake Reflection */
.lake{position:fixed;bottom:0;left:0;right:0;height:22vh;z-index:0;pointer-events:none;
background:linear-gradient(to top,rgba(34,211,238,0.025),rgba(96,165,250,0.008),transparent);
animation:lakeBreathe 6s ease-in-out infinite}
@keyframes lakeBreathe{0%,100%{opacity:.65}50%{opacity:1}}
.lake::before{content:'';position:absolute;bottom:0;left:5%;right:5%;height:1px;
background:linear-gradient(90deg,transparent,rgba(96,165,250,0.15),rgba(34,211,238,0.12),rgba(167,139,250,0.1),transparent);
animation:lakeShimmer 8s ease-in-out infinite alternate}
@keyframes lakeShimmer{0%{opacity:.3;left:8%;right:8%}100%{opacity:.8;left:4%;right:4%}}
.lake::after{content:'';position:absolute;bottom:0;left:20%;right:20%;height:60%;
background:radial-gradient(ellipse at bottom,rgba(34,211,238,0.02),transparent 80%);
animation:lakeBreathe 8s ease-in-out infinite;animation-delay:-3s}
/* ═══ Landing Page · 星空呼吸光晕版 ═══ */
.landing-overlay{
position:fixed;inset:0;z-index:100;
background:#0a0e27;
overflow-y:auto;overflow-x:hidden;
transition:opacity .6s,visibility .6s
}
.landing-overlay.hidden{opacity:0;visibility:hidden;pointer-events:none}
/* ─── Landing Universe Background ─── */
.landing-universe{
position:fixed;inset:0;z-index:0;pointer-events:none;
background:
radial-gradient(ellipse at 20% 30%, rgba(80,60,140,0.4) 0%, transparent 50%),
radial-gradient(ellipse at 80% 70%, rgba(40,80,140,0.3) 0%, transparent 50%),
radial-gradient(ellipse at 50% 50%, #1a1438 0%, #0a0e27 70%)
}
.landing-stars{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none}
.landing-star{
position:absolute;background:white;border-radius:50%;
animation:landingTwinkle ease-in-out infinite
}
@keyframes landingTwinkle{
0%,100%{opacity:0.2;transform:scale(0.8)}
50%{opacity:1;transform:scale(1.2)}
}
.landing-shooting-star{
position:absolute;width:2px;height:2px;background:white;border-radius:50%;
box-shadow:0 0 0 2px rgba(255,255,255,0.1),0 0 0 4px rgba(255,255,255,0.05),0 0 20px rgba(255,255,255,0.5);
animation:landingShoot 8s linear infinite
}
.landing-shooting-star::before{
content:'';position:absolute;top:50%;left:0;width:100px;height:1px;
background:linear-gradient(90deg,white,transparent);transform:translateY(-50%)
}
@keyframes landingShoot{
0%{transform:translate(0,0);opacity:0}
10%{opacity:1} 70%{opacity:1}
100%{transform:translate(600px,300px);opacity:0}
}
.landing-page{
position:relative;z-index:2;min-height:100vh;
display:flex;flex-direction:column;align-items:center;justify-content:center;
padding:60px 24px
}
/* ─── Landing Hero ─── */
.landing-hero{
text-align:center;padding:0 0 40px;max-width:640px;width:100%;
}
/* ─── Logo 呼吸光晕 ─── */
.landing-logo-ring{
width:120px;height:120px;margin:0 auto 48px;border-radius:24px;
background:linear-gradient(135deg,rgba(80,120,200,0.3),rgba(140,100,200,0.3));
border:1px solid rgba(212,165,116,0.4);
display:flex;align-items:center;justify-content:center;
position:relative;
animation:landingBreathe 4s ease-in-out infinite
}
.landing-logo-ring::before{
content:'';position:absolute;inset:-20px;border-radius:32px;
background:radial-gradient(circle,rgba(212,165,116,0.4) 0%,transparent 70%);
opacity:0.6;animation:landingHalo 4s ease-in-out infinite;z-index:-1
}
@keyframes landingBreathe{
0%,100%{transform:scale(1);box-shadow:0 0 30px rgba(212,165,116,0.2),0 0 60px rgba(212,165,116,0.1)}
50%{transform:scale(1.03);box-shadow:0 0 50px rgba(212,165,116,0.5),0 0 100px rgba(212,165,116,0.25),0 0 150px rgba(212,165,116,0.1)}
}
@keyframes landingHalo{
0%,100%{opacity:0.4;transform:scale(1)}
50%{opacity:0.8;transform:scale(1.15)}
}
.landing-logo{
font-size:48px;font-weight:300;font-family:'Inter',-apple-system,sans-serif;
color:#e8eaf6;letter-spacing:0.05em;
-webkit-text-fill-color:#e8eaf6;background:none;-webkit-background-clip:unset;background-clip:unset
}
/* ─── 标题逐字浮现 ─── */
.landing-title{
font-size:52px;font-weight:300;color:#e8eaf6;margin-bottom:12px;
letter-spacing:0.4em;display:flex;gap:0;justify-content:center;
font-family:'Noto Serif SC','Songti SC','STSong',serif
}
.landing-title span{
display:inline-block;opacity:0;transform:translateY(20px);
animation:landingTitleIn 0.8s ease-out forwards
}
.landing-title span:nth-child(1){animation-delay:0.2s}
.landing-title span:nth-child(2){animation-delay:0.35s}
.landing-title span:nth-child(3){animation-delay:0.5s}
.landing-title span:nth-child(4){animation-delay:0.65s}
.landing-title span:nth-child(5){animation-delay:0.8s}
.landing-title span:nth-child(6){animation-delay:0.95s}
@keyframes landingTitleIn{to{opacity:1;transform:translateY(0)}}
.landing-sub{
font-family:'Inter',sans-serif;font-size:13px;letter-spacing:0.3em;
color:#8892b0;margin-bottom:40px;
opacity:0;animation:landingFadeIn 1s ease-out 1.2s forwards
}
/* ─── 诗句呼吸 ─── */
.landing-poem{
text-align:center;margin-bottom:64px;
opacity:0;animation:landingFadeIn 1.5s ease-out 1.6s forwards
}
.landing-poem p{
font-size:17px;font-style:italic;color:#d4a574;letter-spacing:0.15em;line-height:2.2;
font-family:'Noto Serif SC','Songti SC','STSong',serif;
animation:landingVerseBreathe 6s ease-in-out infinite
}
@keyframes landingVerseBreathe{
0%,100%{opacity:0.5}50%{opacity:0.95}
}
@keyframes landingFadeIn{to{opacity:1}}
/* ─── Landing Cards · 玻璃态 ─── */
.landing-cards{
display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
gap:24px;width:100%;max-width:760px;
opacity:0;animation:landingFadeIn 1s ease-out 2.2s forwards
}
.landing-card{
display:block;padding:28px;border-radius:16px;
background:rgba(20,25,60,0.35);
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
border:1px solid rgba(212,165,116,0.25);
cursor:pointer;transition:all .4s cubic-bezier(.4,0,.2,1);
position:relative;overflow:hidden;text-decoration:none;color:#e8eaf6
}
.landing-card-glow{
position:absolute;inset:0;opacity:0;transition:opacity .4s;pointer-events:none;
background:radial-gradient(circle at 50% 0%,rgba(212,165,116,0.4) 0%,transparent 60%)
}
.landing-card:hover{
transform:translateY(-4px);border-color:#d4a574;
box-shadow:0 12px 40px rgba(212,165,116,0.2),0 0 60px rgba(212,165,116,0.1)
}
.landing-card:hover .landing-card-glow{opacity:1}
.landing-card-icon{font-size:28px;filter:drop-shadow(0 0 8px rgba(212,165,116,0.4));flex-shrink:0}
.landing-card-content{flex:1;min-width:0}
.landing-card-name{font-size:18px;font-weight:500;color:#e8eaf6;margin-bottom:8px;letter-spacing:0.05em}
.landing-card-desc{font-size:14px;color:#8892b0;line-height:1.7;margin-bottom:16px}
.landing-card-header{display:flex;align-items:center;gap:12px;margin-bottom:12px;position:relative}
.landing-card-tag{display:flex;gap:8px;position:relative}
.landing-tag{
font-size:11px;padding:4px 10px;border-radius:999px;
background:rgba(212,165,116,0.1);color:#d4a574;
border:1px solid rgba(212,165,116,0.2);letter-spacing:0.1em
}
.landing-tag.public{color:#d4a574;border-color:rgba(212,165,116,0.3);background:rgba(212,165,116,0.1)}
.landing-tag.private{color:#d4a574;border-color:rgba(212,165,116,0.3);background:rgba(212,165,116,0.1)}
.landing-card-arrow{
position:absolute;right:24px;top:50%;transform:translateY(-50%);
color:#d4a574;opacity:0.6;transition:all .4s;flex-shrink:0
}
.landing-card:hover .landing-card-arrow{opacity:1;transform:translateY(-50%) translateX(4px);color:#d4a574}
.landing-card-zhiku:hover{border-color:#d4a574}
.landing-card-core:hover{border-color:#d4a574}
/* ─── Login Panel (slides down) ─── */
.landing-login-panel{
max-height:0;overflow:hidden;transition:max-height .5s cubic-bezier(.4,0,.2,1),opacity .4s;
opacity:0;width:100%;max-width:420px;margin-top:0
}
.landing-login-panel.open{
max-height:500px;opacity:1;margin-top:24px
}
/* ─── Landing Architecture Flow · 流光 ─── */
.landing-arch{
margin-top:48px;padding:16px 0;
opacity:0;animation:landingFadeIn 1s ease-out 2.6s forwards
}
.landing-arch-flow{
display:flex;align-items:center;gap:16px;justify-content:center;flex-wrap:wrap
}
.landing-arch-node{
font-size:12px;padding:8px 18px;border-radius:999px;
font-family:'Inter',sans-serif;letter-spacing:0.15em;
background:rgba(20,25,60,0.3);position:relative;overflow:hidden;
border:1px solid rgba(212,165,116,0.25);color:#8892b0
}
.landing-arch-node::before{
content:'';position:absolute;top:0;left:0;width:100%;height:100%;
background:linear-gradient(90deg,transparent,rgba(212,165,116,0.4),transparent);
transform:translateX(-100%);
animation:landingFlow 3s ease-in-out infinite
}
.landing-arch-node:nth-child(3)::before{animation-delay:1s}
.landing-arch-node:nth-child(5)::before{animation-delay:2s}
@keyframes landingFlow{
0%,100%{transform:translateX(-100%)}50%{transform:translateX(100%)}
}
.landing-arch-node.notion{color:#8892b0}
.landing-arch-node.web{color:#8892b0}
.landing-arch-node.github{color:#8892b0}
.landing-arch-arrow{color:#4a5278;font-size:14px}
/* ─── Landing Footer ─── */
.landing-footer{
text-align:center;padding:32px 0 24px;font-size:11px;color:#4a5278;letter-spacing:0.15em;line-height:2;
opacity:0;animation:landingFadeIn 1s ease-out 3s forwards
}
/* ═══ Login Card (reused from landing) ═══ */
.login-card{
width:90%;max-width:380px;
background:var(--panel);border-radius:20px;
border:1px solid var(--border);
backdrop-filter:blur(20px);
padding:40px 32px;text-align:center;
animation:loginFadeIn .6s ease-out
}
@keyframes loginFadeIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
.login-glyph{
width:64px;height:64px;line-height:64px;border-radius:16px;
background:linear-gradient(135deg,rgba(34,211,238,0.12),rgba(167,139,250,0.1));
border:1px solid rgba(96,165,250,0.18);
font-size:28px;font-weight:900;font-family:serif;color:var(--cyan);
margin:0 auto 20px;
animation:glyphBreathe 5s ease-in-out infinite
}
.login-title{font-size:20px;font-weight:700;color:var(--text);margin-bottom:4px}
.login-desc{font-size:12px;color:var(--dim);margin-bottom:28px}
.login-input-group{margin-bottom:16px;text-align:left}
.login-label{font-size:11px;color:var(--dim);margin-bottom:6px;display:block;font-weight:600;letter-spacing:.5px}
.login-input-row{display:flex;gap:8px}
.login-input{
flex:1;background:rgba(10,16,40,0.6);border:1px solid var(--border);border-radius:10px;
padding:12px 14px;color:var(--text);font-size:14px;font-family:var(--font);
outline:none;transition:border-color .25s
}
.login-input:focus{border-color:var(--cyan)}
.login-input::placeholder{color:var(--muted)}
.login-send-btn{
padding:12px 16px;border:1px solid rgba(34,211,238,0.2);border-radius:10px;
background:rgba(34,211,238,0.08);color:var(--cyan);font-size:13px;font-weight:600;
cursor:pointer;transition:all .25s;white-space:nowrap;font-family:var(--font)
}
.login-send-btn:hover:not(:disabled){background:rgba(34,211,238,0.15);border-color:rgba(34,211,238,0.4)}
.login-send-btn:disabled{opacity:.4;cursor:not-allowed}
.login-code-wrap{display:none}
.login-code-wrap.show{display:block}
.login-submit{
width:100%;padding:14px;border:none;border-radius:12px;
background:linear-gradient(135deg,rgba(34,211,238,0.2),rgba(167,139,250,0.15));
border:1px solid rgba(34,211,238,0.25);
color:var(--text);font-size:15px;font-weight:700;cursor:pointer;
transition:all .3s;font-family:var(--font);margin-top:8px
}
.login-submit:hover:not(:disabled){background:linear-gradient(135deg,rgba(34,211,238,0.3),rgba(167,139,250,0.25));border-color:rgba(34,211,238,0.5)}
.login-submit:disabled{opacity:.4;cursor:not-allowed}
.login-msg{font-size:12px;margin-top:12px;min-height:18px;transition:color .25s}
.login-msg.ok{color:var(--ok)}
.login-msg.err{color:var(--err)}
.login-msg.info{color:var(--cyan)}
.login-footer{margin-top:24px;font-size:10px;color:rgba(100,130,180,0.35)}
.login-user-bar{
display:none;align-items:center;gap:8px;
padding:4px 10px;border-radius:8px;
background:rgba(34,211,238,0.06);border:1px solid rgba(34,211,238,0.1);
font-size:11px;color:var(--dim);cursor:pointer;transition:all .25s
}
.login-user-bar:hover{border-color:rgba(34,211,238,0.25)}
.login-user-bar.show{display:flex}
.login-user-dot{width:6px;height:6px;border-radius:50%;background:var(--ok);box-shadow:0 0 6px var(--ok)}
/* ═══ Layout ═══ */
.app{display:flex;height:100vh;height:100dvh;position:relative;z-index:1}
/* ═══ Sidebar ═══ */
.sidebar{
width:var(--sidebar-w);height:100%;position:fixed;left:0;top:0;z-index:20;
background:var(--panel);border-right:1px solid var(--border);
display:flex;flex-direction:column;backdrop-filter:blur(20px);
transition:transform .3s cubic-bezier(.4,0,.2,1);
box-shadow:1px 0 12px rgba(0,0,0,0.3)
}
.sidebar-header{
padding:16px 18px;border-bottom:1px solid var(--border);
display:flex;align-items:center;justify-content:space-between
}
.logo{display:flex;align-items:center;gap:10px}
.logo-glyph{
width:36px;height:36px;border-radius:10px;
background:linear-gradient(135deg,rgba(34,211,238,0.12),rgba(167,139,250,0.1));
border:1px solid rgba(96,165,250,0.18);
display:flex;align-items:center;justify-content:center;
font-size:17px;font-weight:900;font-family:serif;
color:var(--cyan);transition:all .3s;
animation:glyphBreathe 5s ease-in-out infinite
}
@keyframes glyphBreathe{
0%,100%{box-shadow:0 0 12px rgba(34,211,238,0.1);border-color:rgba(96,165,250,0.18)}
50%{box-shadow:0 0 30px rgba(34,211,238,0.25),0 0 60px rgba(167,139,250,0.08);border-color:rgba(34,211,238,0.35)}
}
.logo-glyph:hover{border-color:rgba(34,211,238,0.4)}
.logo-text{font-size:15px;font-weight:700;letter-spacing:1px}
.logo-sub{font-size:11px;color:var(--dim);display:block;margin-top:2px;letter-spacing:1px}
/* ═══ Nav Tabs ═══ */
.nav-tabs{display:flex;border-bottom:1px solid var(--border)}
.nav-tab{
flex:1;padding:11px 0;text-align:center;font-size:13px;font-weight:600;
color:var(--dim);cursor:pointer;transition:all .25s;border-bottom:2px solid transparent;
letter-spacing:.5px;background:transparent;border-top:none;border-left:none;border-right:none;
font-family:var(--font)
}
.nav-tab:hover{color:var(--text)}
.nav-tab.active{color:var(--cyan);border-bottom-color:var(--cyan)}
/* ─── 侧边栏竖向导航 ─── */
.sidebar-nav{flex:1;overflow-y:auto;padding:8px 0}
.sidebar-nav::-webkit-scrollbar{width:3px}
.sidebar-nav::-webkit-scrollbar-thumb{background:var(--border)}
.snav-section{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:1.5px;font-family:var(--mono);padding:12px 20px 6px;font-weight:700}
.snav-item{
display:flex;align-items:center;gap:10px;padding:10px 20px;font-size:14px;
color:var(--dim);cursor:pointer;transition:all .2s;
border-left:2px solid transparent;text-decoration:none;
font-family:var(--font);font-weight:500;letter-spacing:.3px;
background:transparent;border-top:none;border-right:none;border-bottom:none;width:100%;text-align:left
}
.snav-item:hover{color:var(--text);background:rgba(255,255,255,.04)}
.snav-item.active{color:var(--cyan);border-left-color:var(--cyan);background:rgba(34,211,238,.06)}
.snav-icon{font-size:15px;width:20px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.snav-badge{margin-left:auto;font-size:10px;font-family:var(--mono);padding:2px 6px;border-radius:4px;background:rgba(34,211,238,.1);color:var(--cyan)}
.snav-divider{height:1px;background:var(--border);margin:6px 20px}
/* 新增状态徽章 */
.snav-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;margin-left:auto;transition:all .3s}
.snav-dot.ok{background:var(--ok);box-shadow:0 0 6px rgba(52,211,153,.5);animation:dotPulse 3s ease-in-out infinite}
.snav-dot.warn{background:var(--warn);box-shadow:0 0 6px rgba(251,191,36,.5);animation:dotPulse 2s ease-in-out infinite}
.snav-dot.off{background:var(--muted);box-shadow:0 0 3px rgba(100,130,180,.2)}
/* Architecture Flow */
.sidebar-arch{padding:16px 20px;border-top:1px solid var(--border)}
.arch-title{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:1.5px;font-family:var(--mono);margin-bottom:10px;font-weight:700}
.arch-flow{display:flex;flex-direction:column;gap:4px}
.arch-step{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dim);padding:4px 0}
.arch-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;animation:dotPulse 4s ease-in-out infinite}
.arch-dot.notion{background:var(--purple);box-shadow:0 0 7px rgba(167,139,250,0.5)}
.arch-dot.web{background:var(--cyan);box-shadow:0 0 7px rgba(34,211,238,0.5)}
.arch-dot.github{background:var(--ok);box-shadow:0 0 7px rgba(52,211,153,0.5)}
.arch-arrow{color:var(--muted);font-size:9px;padding-left:14px}
/* Sidebar Footer */
.sidebar-footer{padding:14px 20px;border-top:1px solid var(--border);font-size:12px}
.status-line{display:flex;align-items:center;gap:8px;color:var(--dim)}
.status-dot{width:8px;height:8px;border-radius:50%;background:var(--ok);box-shadow:0 0 8px var(--ok);animation:statusBreathe 3s ease-in-out infinite;flex-shrink:0}
@keyframes statusBreathe{0%,100%{opacity:1;box-shadow:0 0 8px var(--ok)}50%{opacity:.5;box-shadow:0 0 16px var(--ok)}}
@keyframes pulse{50%{opacity:.4}}
@keyframes dotPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(1.2)}}
.sidebar-copy{font-size:10px;color:var(--muted);margin-top:8px;letter-spacing:.3px}
/* Chat History */
.chat-history{flex:1;overflow-y:auto;padding:8px 10px}
.new-chat-btn{
display:flex;align-items:center;justify-content:center;gap:6px;
width:calc(100% - 4px);margin:2px auto 8px;padding:9px 12px;
border-radius:8px;border:1px dashed var(--border-active);
background:rgba(34,211,238,0.04);color:var(--cyan);font-size:12px;font-weight:600;
cursor:pointer;transition:all .2s;font-family:var(--font);letter-spacing:.5px
}
.new-chat-btn:hover{background:rgba(34,211,238,0.08);border-color:var(--cyan)}
.chat-item{
padding:8px 12px;border-radius:8px;margin-bottom:3px;cursor:pointer;
font-size:12px;color:var(--dim);transition:all .2s;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis
}
.chat-item:hover,.chat-item.active{background:rgba(96,165,250,0.06);color:var(--text)}
/* Dashboard data freshness */
.s-card-fresh{animation:freshPulse .8s ease-out}
@keyframes freshPulse{0%{border-color:rgba(34,211,238,0.35)}100%{border-color:var(--card-border)}}
.data-source{display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:8px;border-top:1px solid rgba(96,165,250,0.04)}
.data-src-tag{font-size:9px;font-family:var(--mono);padding:2px 6px;border-radius:4px;letter-spacing:.5px}
.data-src-tag.live{color:var(--ok);background:rgba(52,211,153,0.08);border:1px solid rgba(52,211,153,0.12)}
.data-src-tag.static{color:var(--warn);background:rgba(251,191,36,0.08);border:1px solid rgba(251,191,36,0.12)}
.data-src-tag.error{color:var(--err);background:rgba(248,113,113,0.08);border:1px solid rgba(248,113,113,0.12)}
.data-src-time{font-size:10px;color:var(--muted);font-family:var(--mono)}
/* Card explanation */
.s-card-explain{
display:flex;align-items:flex-start;gap:7px;margin-top:12px;padding:10px 12px;
border-radius:6px;background:rgba(96,165,250,0.03);border:1px solid rgba(96,165,250,0.06);
font-size:11px;color:var(--dim);line-height:1.6
}
.s-card-explain .explain-icon{font-size:13px;flex-shrink:0;margin-top:1px}
/* ═══ Main Area ═══ */
.main{
flex:1;margin-left:var(--sidebar-w);height:100%;display:flex;flex-direction:column;
transition:margin-left .3s;
border-left:1px solid rgba(96,165,250,0.06)
}
/* Main Header */
.main-header{
padding:12px 28px;border-bottom:1px solid var(--border);
display:flex;align-items:center;justify-content:space-between;
background:rgba(6,10,20,0.88);backdrop-filter:blur(16px);min-height:54px
}
.header-left{display:flex;align-items:center;gap:14px}
.persona-avatar{
width:36px;height:36px;border-radius:10px;
background:linear-gradient(135deg,rgba(34,211,238,0.12),rgba(167,139,250,0.1));
border:1px solid rgba(96,165,250,0.18);
display:flex;align-items:center;justify-content:center;
font-size:15px;font-weight:900;font-family:serif;color:var(--cyan);
animation:dotPulse 5s ease-in-out infinite
}
.persona-name{font-size:15px;font-weight:700;letter-spacing:.5px}
.persona-tag{font-size:11px;color:var(--dim)}
.toggle-sidebar{
display:none;width:34px;height:34px;border-radius:8px;border:1px solid var(--border);
background:transparent;color:var(--dim);cursor:pointer;font-size:18px;
align-items:center;justify-content:center;transition:all .2s
}
.header-right{display:flex;align-items:center;gap:12px}
.back-link{
font-size:12px;color:var(--dim);display:flex;align-items:center;gap:5px;
padding:5px 12px;border-radius:6px;border:1px solid transparent;transition:all .2s
}
.back-link:hover{color:var(--cyan);border-color:var(--border)}
.refresh-time{font-size:11px;color:var(--muted);font-family:var(--mono)}
/* ═══ View Panels ═══ */
.view-panel{display:none;flex:1;overflow:hidden;flex-direction:column}
.view-panel.active{display:flex}
/* ═══ DASHBOARD ═══ */
.dashboard{flex:1;overflow-y:auto;padding:24px 28px 36px}
.dash-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;max-width:1200px;margin:0 auto}
/* ─── Section Headers ─── */
.dash-section{max-width:1200px;margin:28px auto 14px}
.dash-section-title{font-size:11px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:1.5px;font-family:var(--mono);display:flex;align-items:center;gap:8px}
.dash-section-title::after{content:'';flex:1;height:1px;background:var(--border)}
.dash-section-sub{font-size:13px;color:var(--muted);margin-top:6px}
/* ─── 保护罩日志 · Shield Log ─── */
.shield-stat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;max-width:1200px;margin:0 auto 18px}
.shield-stat{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:18px 20px;backdrop-filter:blur(8px)}
.shield-stat-num{font-size:28px;font-weight:700;font-family:var(--mono);color:var(--cyan);margin-bottom:4px}
.shield-stat-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:1px}
.shield-log{max-width:1200px;margin:0 auto;background:var(--card);border:1px solid var(--card-border);border-radius:12px;overflow:hidden;backdrop-filter:blur(8px)}
.shield-log-head{padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}
.shield-log-title{font-size:12px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:1px;font-family:var(--mono)}
.shield-log-body{max-height:360px;overflow-y:auto;font-family:var(--mono);font-size:13px}
.shield-log-body::-webkit-scrollbar{width:3px}
.shield-log-body::-webkit-scrollbar-thumb{background:var(--border)}
.shield-log-row{display:flex;align-items:center;gap:12px;padding:8px 20px;border-bottom:1px solid rgba(255,255,255,.03);transition:background .2s}
.shield-log-row:hover{background:rgba(255,255,255,.02)}
.shield-log-row:last-child{border-bottom:none}
.shield-log-ts{color:var(--muted);min-width:140px;flex-shrink:0}
.shield-log-action.ban{color:var(--err)}
.shield-log-action.unban{color:var(--ok)}
.shield-log-ip{color:var(--warn);font-weight:600}
.shield-log-jail{color:var(--dim)}
.shield-log-empty{padding:40px 24px;text-align:center;color:var(--dim);font-size:14px;line-height:2}
.shield-layers{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px;max-width:1200px;margin:18px auto 0}
.shield-layer{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:18px 20px;backdrop-filter:blur(8px)}
.shield-layer-tag{font-size:10px;font-family:var(--mono);color:var(--cyan);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}
.shield-layer-name{font-size:15px;font-weight:700;color:var(--text);margin-bottom:6px}
.shield-layer-desc{font-size:13px;color:var(--muted);line-height:1.6}
.shield-layer-status{margin-top:10px;font-size:11px;font-family:var(--mono);display:flex;align-items:center;gap:6px}
/* ─── 项目进度 · Project Progress ─── */
.project-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px;max-width:1200px;margin:0 auto}
.project-card{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:20px 22px;backdrop-filter:blur(8px)}
.project-card-head{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:10px}
.project-card-server{font-size:9px;font-family:var(--mono);color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:3px}
.project-card-name{font-size:13px;font-weight:700;color:var(--text)}
.project-list{display:flex;flex-direction:column;gap:6px}
.project-item{display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid rgba(255,255,255,.03)}
.project-item:last-child{border-bottom:none}
.project-name{font-size:12px;color:var(--dim);flex:1}
.project-desc{font-size:10px;color:var(--muted);margin-top:1px}
.proj-badge{font-size:9px;font-family:var(--mono);padding:2px 7px;border-radius:4px;white-space:nowrap;flex-shrink:0}
.proj-badge.active{background:rgba(34,211,238,.1);color:var(--cyan);border:1px solid rgba(34,211,238,.2)}
.proj-badge.done{background:rgba(52,211,153,.1);color:var(--ok);border:1px solid rgba(52,211,153,.2)}
.proj-badge.enabled{background:rgba(96,165,250,.1);color:var(--blue);border:1px solid rgba(96,165,250,.2)}
.proj-badge.pending{background:rgba(251,191,36,.1);color:var(--warn);border:1px solid rgba(251,191,36,.2)}
.proj-badge.error{background:rgba(239,68,68,.1);color:var(--err);border:1px solid rgba(239,68,68,.2)}
.proj-badge.planned{background:rgba(167,139,250,.1);color:var(--purple);border:1px solid rgba(167,139,250,.2)}
/* ─── 智库节点 · Novel Node ─── */
.novel-stat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;max-width:1200px;margin:0 auto 16px}
.novel-stat{background:var(--card);border:1px solid var(--card-border);border-radius:10px;padding:14px 16px}
.novel-stat-num{font-size:24px;font-weight:700;font-family:var(--mono);color:var(--purple);margin-bottom:2px}
.novel-stat-label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:1px}
.novel-module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;max-width:1200px;margin:0 auto}
.novel-module{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:14px 16px}
.novel-module-icon{font-size:18px;margin-bottom:6px}
.novel-module-name{font-size:13px;font-weight:700;color:var(--text);margin-bottom:3px}
.novel-module-desc{font-size:11px;color:var(--muted);line-height:1.5;margin-bottom:8px}
.novel-module-status{font-size:10px;font-family:var(--mono);display:flex;align-items:center;gap:5px}
/* ─── Paginated Card Container ─── */
.paged-card-wrap{max-width:1200px;margin:0 auto;position:relative}
.paged-card-inner{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px;transition:opacity .35s}
.paged-card-inner .srv-card{display:none}
.paged-card-inner .srv-card.card-visible{display:block}
.paged-card-nav{display:flex;align-items:center;justify-content:center;gap:12px;margin-top:14px;user-select:none}
.paged-card-btn{
width:32px;height:32px;border-radius:8px;border:1px solid var(--border);
background:rgba(14,20,48,0.6);color:var(--dim);font-size:14px;
cursor:pointer;display:flex;align-items:center;justify-content:center;
transition:all .2s;font-family:var(--font)
}
.paged-card-btn:hover:not(:disabled){border-color:var(--cyan);color:var(--cyan);background:rgba(34,211,238,0.06)}
.paged-card-btn:disabled{opacity:.3;cursor:not-allowed}
.paged-card-info{font-size:11px;color:var(--dim);font-family:var(--mono);letter-spacing:.5px;min-width:60px;text-align:center}
.paged-card-dots{display:flex;gap:5px;align-items:center}
.paged-dot{width:6px;height:6px;border-radius:50%;background:var(--muted);transition:all .25s;cursor:pointer}
.paged-dot.active{background:var(--cyan);box-shadow:0 0 6px rgba(34,211,238,0.4);width:18px;border-radius:3px}
/* ─── Server Cards (matrix view) ─── */
.server-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px;max-width:1200px;margin:0 auto}
.srv-card{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:20px 22px;transition:all .3s;position:relative;overflow:hidden;backdrop-filter:blur(8px)}
.srv-card:hover{border-color:var(--card-hover)}
.srv-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--cyan),var(--purple));opacity:.25;transition:opacity .3s}
.srv-card:hover::before{opacity:.6}
.srv-card::after{
content:'';position:absolute;bottom:-30%;left:50%;transform:translateX(-50%);
width:80%;height:60%;border-radius:50%;pointer-events:none;
background:radial-gradient(ellipse,rgba(34,211,238,0.03),transparent 70%);
opacity:0;transition:opacity .4s
}
.srv-card:hover::after{opacity:1}
.srv-card-head{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:14px;gap:8px}
.srv-card-info{flex:1;min-width:0}
.srv-card-name{font-size:16px;font-weight:700;color:var(--text);letter-spacing:.5px;display:flex;align-items:center;gap:8px}
.srv-card-role{font-size:12px;color:var(--dim);margin-top:4px;line-height:1.5}
.srv-card-meta{display:flex;gap:6px;margin-top:6px;flex-wrap:wrap}
.srv-badge{font-size:10px;padding:3px 8px;border-radius:6px;font-weight:600;letter-spacing:.3px;font-family:var(--mono)}
.srv-badge.loc{background:rgba(96,165,250,0.08);color:var(--blue);border:1px solid rgba(96,165,250,0.12)}
.srv-badge.spec{background:rgba(167,139,250,0.08);color:var(--purple);border:1px solid rgba(167,139,250,0.12)}
.srv-badge.ok{background:rgba(52,211,153,0.08);color:var(--ok);border:1px solid rgba(52,211,153,0.12)}
.srv-badge.warn{background:rgba(251,191,36,0.08);color:var(--warn);border:1px solid rgba(251,191,36,0.12)}
.srv-badge.off{background:rgba(100,120,160,0.08);color:var(--dim);border:1px solid rgba(100,120,160,0.12)}
.srv-status-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;margin-top:3px;transition:all .3s}
.srv-status-dot.on{background:var(--ok);box-shadow:0 0 10px rgba(52,211,153,0.5);animation:dotPulse 3s ease-in-out infinite}
.srv-status-dot.off{background:var(--err);box-shadow:0 0 6px rgba(248,113,113,0.4)}
.srv-status-dot.pending{background:var(--warn);box-shadow:0 0 8px rgba(251,191,36,0.4);animation:dotPulse 2s ease-in-out infinite}
.srv-status-dot.unknown{background:var(--dim)}
.srv-status-dot.config{background:var(--blue);box-shadow:0 0 8px rgba(96,165,250,0.4);animation:dotPulse 4s ease-in-out infinite}
.srv-divider{height:1px;background:rgba(96,165,250,0.06);margin:12px 0}
.srv-services{display:flex;flex-direction:column;gap:5px;max-height:120px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(96,165,250,0.15) transparent}
.srv-svc{display:flex;align-items:flex-start;gap:10px;padding:5px 8px;border-radius:6px;transition:background .2s}
.srv-svc:hover{background:rgba(96,165,250,0.04)}
.srv-svc-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;margin-top:6px}
.srv-svc-dot.live{background:var(--ok)}
.srv-svc-dot.static{background:var(--dim)}
.srv-svc-dot.mcp{background:var(--blue)}
.srv-svc-dot.db{background:var(--purple)}
.srv-svc-dot.vpn{background:var(--warn)}
.srv-svc-dot.relay{background:var(--cyan)}
.srv-svc-info{flex:1;min-width:0}
.srv-svc-name{font-size:13px;font-weight:600;color:var(--text)}
.srv-svc-desc{font-size:12px;color:var(--dim);line-height:1.5;margin-top:2px}
.srv-svc-port{font-size:10px;color:var(--muted);font-family:var(--mono);flex-shrink:0;padding:2px 6px;border-radius:4px;background:rgba(96,165,250,0.05);border:1px solid rgba(96,165,250,0.08)}
.srv-conn{display:flex;flex-wrap:wrap;gap:5px;margin-top:10px}
.srv-conn-tag{font-size:10px;color:var(--muted);padding:3px 7px;border-radius:4px;background:rgba(10,16,40,0.4);border:1px solid rgba(96,165,250,0.06);font-family:var(--mono)}
/* ─── Agent Cluster ─── */
.agent-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;max-width:1200px;margin:0 auto}
.agent-card{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:18px 20px;transition:all .3s;position:relative;overflow:hidden;backdrop-filter:blur(8px)}
.agent-card:hover{border-color:var(--card-hover)}
.agent-card-head{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.agent-icon{width:38px;height:38px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.agent-icon.claw{background:linear-gradient(135deg,rgba(34,211,238,0.15),rgba(96,165,250,0.1));border:1px solid rgba(34,211,238,0.2);color:var(--cyan)}
.agent-icon.mcp{background:linear-gradient(135deg,rgba(96,165,250,0.15),rgba(167,139,250,0.1));border:1px solid rgba(96,165,250,0.2);color:var(--blue)}
.agent-icon.gh{background:linear-gradient(135deg,rgba(52,211,153,0.12),rgba(96,165,250,0.08));border:1px solid rgba(52,211,153,0.15);color:var(--ok)}
.agent-icon.cos{background:linear-gradient(135deg,rgba(251,191,36,0.12),rgba(248,113,113,0.08));border:1px solid rgba(251,191,36,0.15);color:var(--warn)}
.agent-name{font-size:15px;font-weight:700;color:var(--text)}
.agent-sub{font-size:11px;color:var(--dim);margin-top:3px}
.agent-rows{display:flex;flex-direction:column;gap:5px}
.agent-row{display:flex;justify-content:space-between;align-items:center;font-size:13px;padding:3px 0}
.agent-row-label{color:var(--dim)}
.agent-row-val{color:var(--text);font-family:var(--mono);font-size:12px}
/* ─── OpenClaw highlight card ─── */
.openclaw-card{background:linear-gradient(135deg,rgba(34,211,238,0.04),rgba(96,165,250,0.03));border:1px solid rgba(34,211,238,0.12)}
.openclaw-card:hover{border-color:rgba(34,211,238,0.25)}
.openclaw-phases{display:flex;gap:5px;flex-wrap:wrap;margin-top:10px}
.oc-phase{font-size:10px;padding:3px 8px;border-radius:5px;font-family:var(--mono);letter-spacing:.3px}
.oc-phase.done{background:rgba(52,211,153,0.1);color:var(--ok);border:1px solid rgba(52,211,153,0.15)}
.oc-phase.pending{background:rgba(251,191,36,0.08);color:var(--warn);border:1px solid rgba(251,191,36,0.12)}
.oc-phase.idle{background:rgba(100,120,160,0.06);color:var(--muted);border:1px solid rgba(100,120,160,0.1)}
/* ─── Agent Breathing Animation ─── */
.agent-card[data-alive="true"]{cursor:pointer}
.agent-card[data-alive="true"]:hover{transform:translateY(-2px);box-shadow:0 8px 32px rgba(34,211,238,0.08)}
.agent-heartbeat{display:flex;align-items:center;gap:6px;margin-top:10px;padding:6px 10px;border-radius:8px;background:rgba(10,16,40,0.4);border:1px solid rgba(96,165,250,0.06);font-size:11px;color:var(--dim);font-family:var(--mono)}
.agent-hb-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;transition:background .3s,box-shadow .3s}
.agent-hb-dot.alive{background:var(--ok);box-shadow:0 0 8px rgba(52,211,153,0.5);animation:agentBreathe 3s ease-in-out infinite}
.agent-hb-dot.dead{background:var(--err);animation:none}
.agent-hb-dot.unknown{background:var(--muted);animation:dotPulse 2s ease-in-out infinite}
@keyframes agentBreathe{0%,100%{opacity:.6;box-shadow:0 0 4px rgba(52,211,153,0.3)}50%{opacity:1;box-shadow:0 0 12px rgba(52,211,153,0.6)}}
.agent-hb-text{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.agent-hb-time{color:var(--muted);font-size:10px}
.agent-chat-btn{display:inline-flex;align-items:center;gap:5px;margin-top:8px;padding:5px 12px;border-radius:6px;background:rgba(34,211,238,0.08);border:1px solid rgba(34,211,238,0.15);color:var(--cyan);font-size:11px;font-family:var(--mono);cursor:pointer;transition:all .2s}
.agent-chat-btn:hover{background:rgba(34,211,238,0.15);border-color:rgba(34,211,238,0.3)}
.agent-icon.glada{background:linear-gradient(135deg,rgba(167,139,250,0.15),rgba(196,181,253,0.1));border:1px solid rgba(167,139,250,0.2);color:var(--purple)}
.agent-icon.ops{background:linear-gradient(135deg,rgba(248,113,113,0.12),rgba(251,191,36,0.08));border:1px solid rgba(248,113,113,0.15);color:var(--err)}
/* ─── Chat Target Selector ─── */
.chat-target-bar{display:flex;align-items:center;gap:6px;padding:6px 12px;background:rgba(10,16,40,0.5);border:1px solid var(--border);border-radius:10px;margin-bottom:8px}
.chat-target-label{font-size:11px;color:var(--dim);font-family:var(--mono);white-space:nowrap}
.chat-target-select{background:rgba(14,20,48,0.8);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;font-family:var(--mono);padding:4px 8px;outline:none;cursor:pointer;flex:1;max-width:220px}
.chat-target-select:focus{border-color:var(--cyan)}
.chat-target-select option{background:#0c1228;color:var(--text)}
.chat-target-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
.chat-target-dot.on{background:var(--ok);box-shadow:0 0 6px rgba(52,211,153,0.4)}
.chat-target-dot.off{background:var(--err)}
/* Status Card */
.s-card{
background:var(--card);border:1px solid var(--card-border);border-radius:12px;
padding:20px 22px;transition:all .3s;position:relative;overflow:hidden;backdrop-filter:blur(8px)
}
.s-card:hover{border-color:var(--card-hover);background:rgba(12,18,44,0.6)}
.s-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;
background:linear-gradient(90deg,transparent,var(--cyan),transparent);opacity:0;transition:opacity .3s}
.s-card:hover::before{opacity:.5}
.s-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.s-card-title{font-size:12px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:1.2px;font-family:var(--mono)}
.s-card-badge{font-size:11px;padding:3px 9px;border-radius:10px;font-weight:600;letter-spacing:.3px}
.badge-ok{background:rgba(52,211,153,0.1);color:var(--ok);border:1px solid rgba(52,211,153,0.15)}
.badge-warn{background:rgba(251,191,36,0.1);color:var(--warn);border:1px solid rgba(251,191,36,0.15)}
.badge-err{background:rgba(248,113,113,0.1);color:var(--err);border:1px solid rgba(248,113,113,0.15)}
.badge-off{background:rgba(122,141,184,0.1);color:var(--dim);border:1px solid rgba(122,141,184,0.15)}
/* Server rows */
.svr-list{display:flex;flex-direction:column;gap:7px}
.svr-row{display:flex;align-items:center;gap:10px;padding:6px 0;font-size:13px}
.svr-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;transition:all .3s}
.svr-dot.on{background:var(--ok);box-shadow:0 0 8px rgba(52,211,153,0.4);animation:dotPulse 3s ease-in-out infinite}
.svr-dot.pending{background:var(--warn);box-shadow:0 0 6px rgba(251,191,36,0.3)}
.svr-dot.off{background:var(--err);box-shadow:0 0 6px rgba(248,113,113,0.3)}
.svr-dot.unknown{background:var(--dim)}
.svr-name{font-weight:600;color:var(--text);min-width:60px}
.svr-role{color:var(--dim);flex:1;font-size:12px}
.svr-loc{color:var(--muted);font-size:11px;font-family:var(--mono)}
/* Metric row */
.metric-row{display:flex;align-items:center;justify-content:space-between;padding:5px 0}
.metric-label{font-size:13px;color:var(--dim)}
.metric-val{font-size:15px;font-weight:700;font-family:var(--mono);color:var(--text)}
.metric-val .unit{font-size:11px;color:var(--dim);font-weight:400;margin-left:2px}
/* Progress bar */
.prog-wrap{margin-top:5px}
.prog-bar{height:5px;border-radius:3px;background:rgba(96,165,250,0.08);overflow:hidden}
.prog-fill{height:100%;border-radius:3px;transition:width .6s ease-out}
.prog-fill.ok{background:linear-gradient(90deg,var(--ok),var(--cyan))}
.prog-fill.warn{background:linear-gradient(90deg,var(--warn),var(--err))}
.prog-label{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:4px}
/* Persona list */
.persona-list{display:flex;flex-direction:column;gap:6px}
.persona-row{display:flex;align-items:center;gap:10px;padding:5px 0;font-size:13px}
.persona-icon{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;
font-size:12px;font-weight:900;font-family:serif;flex-shrink:0}
.persona-icon.zy{background:rgba(34,211,238,0.1);color:var(--cyan);border:1px solid rgba(34,211,238,0.15)}
.persona-icon.sy{background:rgba(167,139,250,0.1);color:var(--purple);border:1px solid rgba(167,139,250,0.15)}
.persona-icon.ym{background:rgba(196,181,253,0.1);color:var(--violet);border:1px solid rgba(196,181,253,0.15)}
.persona-icon.yc{background:rgba(96,165,250,0.1);color:var(--blue);border:1px solid rgba(96,165,250,0.15)}
.persona-icon.cx{background:rgba(52,211,153,0.1);color:var(--ok);border:1px solid rgba(52,211,153,0.15)}
.persona-pname{font-weight:600;color:var(--text)}
.persona-role{color:var(--dim);font-size:12px;flex:1}
.persona-status{font-size:11px;font-family:var(--mono)}
/* Full-width card */
.s-card-full{grid-column:1/-1}
/* System headline */
.dash-headline{max-width:1200px;margin:0 auto 20px;display:flex;align-items:center;gap:14px}
.dash-headline-glyph{width:48px;height:48px;border-radius:14px;
background:linear-gradient(135deg,rgba(34,211,238,0.1),rgba(167,139,250,0.08));
border:1px solid rgba(96,165,250,0.15);display:flex;align-items:center;justify-content:center;
font-size:22px;font-weight:900;font-family:serif;color:var(--cyan);
animation:glyphBreathe 5s ease-in-out infinite}
.dash-headline-text h2{font-size:20px;font-weight:700;letter-spacing:1px;
background:linear-gradient(135deg,var(--text),var(--cyan));
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.dash-headline-text p{font-size:13px;color:var(--dim);margin-top:3px}
/* Cognitive chain */
.cog-chain{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.cog-node{padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;border:1px solid}
.cog-node.notion{color:var(--purple);border-color:rgba(167,139,250,0.2);background:rgba(167,139,250,0.05)}
.cog-node.web{color:var(--cyan);border-color:rgba(34,211,238,0.2);background:rgba(34,211,238,0.05)}
.cog-node.github{color:var(--ok);border-color:rgba(52,211,153,0.2);background:rgba(52,211,153,0.05)}
.cog-node.mcp{color:var(--blue);border-color:rgba(96,165,250,0.2);background:rgba(96,165,250,0.05)}
.cog-arrow{color:var(--muted);font-size:13px}
/* ═══ CHAT PANEL ═══ */
.messages{
flex:1;overflow-y:auto;padding:28px;
display:flex;flex-direction:column;gap:24px
}
.welcome{
text-align:center;padding:52px 28px;max-width:560px;margin:auto;
animation:welcomeFade 1.5s ease-out
}
@keyframes welcomeFade{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}
.welcome-glyph{
width:62px;height:62px;border-radius:16px;margin:0 auto 20px;
background:linear-gradient(135deg,rgba(34,211,238,0.12),rgba(167,139,250,0.1));
border:1px solid rgba(96,165,250,0.18);
display:flex;align-items:center;justify-content:center;
font-size:28px;font-weight:900;font-family:serif;color:var(--cyan);
animation:glyphBreathe 5s ease-in-out infinite
}
.welcome-title{
font-size:24px;font-weight:700;margin-bottom:10px;letter-spacing:2px;
background:linear-gradient(135deg,var(--cyan),var(--purple));
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text
}
.welcome-desc{font-size:14px;color:var(--dim);line-height:2;margin-bottom:32px}
.welcome-hints{display:flex;flex-wrap:wrap;gap:10px;justify-content:center}
.hint-chip{
padding:8px 16px;border-radius:20px;font-size:13px;
background:rgba(96,165,250,0.05);border:1px solid var(--border);
color:var(--dim);cursor:pointer;transition:all .2s
}
.hint-chip:hover{border-color:var(--border-active);color:var(--text);background:rgba(96,165,250,0.08)}
/* Welcome status */
.welcome-status{
display:flex;flex-direction:column;gap:10px;align-items:center;
padding:16px 28px;border-radius:12px;
background:rgba(12,18,40,0.5);border:1px solid rgba(96,165,250,0.08);
max-width:280px;margin:0 auto
}
.ws-line{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--dim);font-family:var(--mono)}
.ws-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:var(--warn);animation:dotPulse 2s ease-in-out infinite;transition:all .3s}
.ws-dot.online{background:var(--ok);box-shadow:0 0 8px rgba(52,211,153,0.4);animation:dotPulse 3s ease-in-out infinite}
.ws-dot.offline{background:var(--err);box-shadow:0 0 5px rgba(248,113,113,0.3);animation:none}
.ws-label{letter-spacing:.3px}
/* Welcome breathing glyph */
.welcome-breathe{animation:welcomeGlyphBreathe 4s ease-in-out infinite}
@keyframes welcomeGlyphBreathe{
0%,100%{box-shadow:0 0 15px rgba(34,211,238,0.12);transform:scale(1)}
50%{box-shadow:0 0 36px rgba(34,211,238,0.28),0 0 70px rgba(167,139,250,0.1);transform:scale(1.03)}
}
/* ═══ Persona Paths (Chat Welcome) ═══ */
.persona-paths{
margin-top:28px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:12px
}
.persona-path-title{
font-size:12px;font-weight:700;color:var(--dim);text-transform:uppercase;
letter-spacing:1.5px;font-family:var(--mono);text-align:left;
margin-bottom:4px;padding-left:2px
}
.persona-path-card{
display:flex;align-items:center;gap:14px;
padding:16px 18px;border-radius:14px;
background:rgba(14,20,48,0.5);border:1px solid var(--border);
cursor:pointer;transition:all .35s;text-decoration:none;color:var(--text)
}
.persona-path-card:hover{
border-color:rgba(96,165,250,0.25);
box-shadow:0 4px 24px rgba(34,211,238,0.05);transform:translateY(-2px)
}
.persona-path-icon{
width:42px;height:42px;border-radius:12px;flex-shrink:0;
display:flex;align-items:center;justify-content:center;
font-size:18px;font-weight:900;font-family:serif
}
.notion-icon{
background:linear-gradient(135deg,rgba(167,139,250,0.15),rgba(196,181,253,0.08));
border:1px solid rgba(167,139,250,0.2);color:var(--purple)
}
.github-icon{
background:linear-gradient(135deg,rgba(34,211,238,0.12),rgba(96,165,250,0.08));
border:1px solid rgba(34,211,238,0.2);color:var(--cyan)
}
.zhiku-icon{
background:linear-gradient(135deg,rgba(167,139,250,0.12),rgba(34,211,238,0.06));
border:1px solid rgba(167,139,250,0.15);font-family:var(--font);font-weight:400;font-size:20px
}
.persona-path-info{flex:1;min-width:0}
.persona-path-name{font-size:14px;font-weight:700;margin-bottom:3px;letter-spacing:.3px}
.persona-path-desc{font-size:11px;color:var(--dim);line-height:1.5;margin-bottom:6px}
.persona-path-status{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--dim);font-family:var(--mono)}
.persona-path-dot{width:6px;height:6px;border-radius:50%;background:var(--warn);animation:dotPulse 2s ease-in-out infinite}
.persona-path-dot.ok{background:var(--ok);box-shadow:0 0 6px rgba(52,211,153,0.4)}
.persona-path-dot.connected{background:var(--cyan);box-shadow:0 0 6px rgba(34,211,238,0.4);animation:dotPulse 3s ease-in-out infinite}
.persona-path-dot.error{background:var(--err);animation:none}
.persona-path-arrow{flex-shrink:0;color:var(--muted);transition:transform .3s,color .3s}
/* ─── Connection Log Panel ─── */
.conn-log{
margin-top:12px;padding:12px 14px;border-radius:10px;
background:rgba(10,16,36,0.7);border:1px solid rgba(96,165,250,0.08);
font-family:var(--mono);font-size:11px;max-height:200px;overflow-y:auto;
display:none
}
.conn-log.show{display:block;animation:msgIn .3s ease-out}
.conn-log-line{display:flex;align-items:flex-start;gap:8px;padding:3px 0;color:var(--dim);line-height:1.6}
.conn-log-line .log-ts{color:var(--muted);flex-shrink:0;min-width:56px}
.conn-log-line .log-icon{flex-shrink:0;width:14px;text-align:center}
.conn-log-line .log-msg{flex:1;word-break:break-all}
.conn-log-line.log-ok .log-msg{color:var(--ok)}
.conn-log-line.log-err .log-msg{color:var(--err)}
.conn-log-line.log-info .log-msg{color:var(--cyan)}
.conn-log-line.log-warn .log-msg{color:var(--warn)}
.persona-path-card:hover .persona-path-arrow{transform:translateX(3px);color:var(--cyan)}
.persona-path-zhiku{border-color:rgba(167,139,250,0.1)}
.persona-path-zhiku:hover{border-color:rgba(167,139,250,0.25)}
/* Message Bubbles — 左右分栏AI左·用户右 */
.msg{display:flex;gap:14px;max-width:780px;width:100%;margin:0 auto;animation:msgIn .3s ease-out}
.msg.msg-user{flex-direction:row-reverse}
.msg.msg-system{justify-content:center;max-width:600px}
.msg-content.system-msg{
text-align:center;padding:14px 20px;border-radius:12px;font-size:12px;
background:rgba(96,165,250,0.04);border:1px solid rgba(96,165,250,0.08);
color:var(--dim);line-height:1.8;font-family:var(--mono);letter-spacing:.3px
}
@keyframes msgIn{0%{opacity:0;transform:translateY(8px)}100%{opacity:1;transform:translateY(0)}}
.msg-avatar{
width:36px;height:36px;border-radius:10px;flex-shrink:0;
display:flex;align-items:center;justify-content:center;
font-size:14px;font-weight:900;font-family:serif
}
.msg-avatar.ai{
background:linear-gradient(135deg,rgba(34,211,238,0.1),rgba(167,139,250,0.08));
border:1px solid rgba(96,165,250,0.12);color:var(--cyan)
}
.msg-avatar.avatar-glada{
background:linear-gradient(135deg,rgba(251,191,36,0.12),rgba(245,158,11,0.06));
border-color:rgba(251,191,36,0.2);color:#fbbf24
}
.msg-avatar.avatar-ops{
background:linear-gradient(135deg,rgba(52,211,153,0.12),rgba(16,185,129,0.06));
border-color:rgba(52,211,153,0.2);color:#34d399
}
.msg-avatar.avatar-mcp{
background:linear-gradient(135deg,rgba(167,139,250,0.12),rgba(139,92,246,0.06));
border-color:rgba(167,139,250,0.2);color:#a78bfa
}
.msg-avatar.user{
background:rgba(96,165,250,0.06);border:1px solid rgba(96,165,250,0.1);
color:var(--blue)
}
.msg-body{flex:1;min-width:0}
.msg-user .msg-body{display:flex;flex-direction:column;align-items:flex-end}
.msg-name{font-size:12px;color:var(--dim);margin-bottom:4px;font-weight:600;letter-spacing:.5px}
.msg-content{
font-size:14px;line-height:1.85;color:var(--text);
padding:14px 18px;border-radius:12px;
word-wrap:break-word;
max-width:85%
}
.msg-content.ai{background:var(--ai-bg);border:1px solid rgba(167,139,250,0.08);border-radius:4px 12px 12px 12px;white-space:normal}
.msg-content.user{background:var(--user-bg);border:1px solid rgba(34,211,238,0.08);border-radius:12px 4px 12px 12px;white-space:pre-wrap}
.msg-content.ai p{margin:0 0 12px 0}.msg-content.ai p:last-child{margin-bottom:0}
.msg-content.ai strong,.msg-content.ai b{color:var(--cyan);font-weight:700}
.msg-content.ai em,.msg-content.ai i{color:var(--violet);font-style:italic}
.msg-content.ai code{font-family:var(--mono);font-size:12px;background:rgba(96,165,250,0.08);
padding:2px 6px;border-radius:4px;color:var(--blue);border:1px solid rgba(96,165,250,0.1)}
.msg-content.ai pre{background:rgba(10,16,36,0.8);border:1px solid rgba(96,165,250,0.1);
border-radius:8px;padding:12px 16px;margin:10px 0;overflow-x:auto;font-family:var(--mono);font-size:12px;line-height:1.7;color:var(--dim)}
.msg-content.ai .sys-receipt{
background:rgba(34,211,238,0.03);border:1px solid rgba(34,211,238,0.1);border-radius:8px;
padding:12px 16px;margin:10px 0;font-family:var(--mono);font-size:12px;line-height:1.8;color:var(--dim)
}
.msg-content.ai .sys-receipt .receipt-title{color:var(--cyan);font-weight:700;font-size:13px;margin-bottom:8px;display:flex;align-items:center;gap:6px}
.msg-content.ai .sys-receipt .receipt-row{display:flex;justify-content:space-between;padding:3px 0}
.msg-content.ai .sys-receipt .receipt-label{color:var(--muted)}
.msg-content.ai .sys-receipt .receipt-val{color:var(--text)}
.msg-content.ai h2{font-size:16px;font-weight:700;color:var(--cyan);margin:14px 0 8px;letter-spacing:.3px}
.msg-content.ai h3{font-size:14px;font-weight:600;color:var(--text);margin:12px 0 6px;letter-spacing:.2px}
.msg-content.ai hr{border:none;border-top:1px solid rgba(96,165,250,0.08);margin:12px 0}
.msg-content.ai ul,.msg-content.ai ol{padding-left:22px;margin:8px 0}
.msg-content.ai ol{list-style-type:decimal;counter-reset:none}
.msg-content.ai li{margin-bottom:5px}
.msg-content.ai blockquote{border-left:2px solid var(--purple);padding:6px 14px;margin:10px 0;color:var(--dim);font-style:italic;background:rgba(167,139,250,0.03);border-radius:0 6px 6px 0}
.msg-time{font-size:11px;color:var(--muted);margin-top:5px}
/* 系统元数据每条AI回复下方 */
.msg-meta{
display:flex;flex-wrap:wrap;gap:10px;margin-top:8px;padding:8px 12px;
border-radius:8px;background:rgba(96,165,250,0.04);border:1px solid rgba(96,165,250,0.08)
}
.msg-meta-item{font-size:11px;color:var(--dim);font-family:var(--mono);display:flex;align-items:center;gap:4px}
.msg-meta-item .label{color:var(--muted)}
.msg-meta-item .value{color:var(--blue)}
/* 系统处理步骤AI回复前的过程展示 */
.sys-steps{
max-width:780px;width:100%;margin:0 auto;padding:10px 0;animation:msgIn .3s ease-out
}
.sys-step{
display:flex;align-items:center;gap:8px;padding:4px 18px;font-size:12px;color:var(--dim);
font-family:var(--mono);letter-spacing:.3px
}
.sys-step .dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;transition:all .3s}
.sys-step .dot.pending{background:var(--warn);box-shadow:0 0 5px rgba(251,191,36,0.4);animation:dotPulse 1.5s ease-in-out infinite}
.sys-step .dot.done{background:var(--ok);box-shadow:0 0 5px rgba(52,211,153,0.4)}
.sys-step .dot.active{background:var(--cyan);box-shadow:0 0 7px rgba(34,211,238,0.5);animation:dotPulse 1s ease-in-out infinite}
/* Typing */
.typing{display:flex;align-items:center;gap:5px;padding:14px 18px}
.typing-dot{width:6px;height:6px;border-radius:50%;background:var(--dim);animation:typePulse 1.4s infinite}
.typing-dot:nth-child(2){animation-delay:.2s}
.typing-dot:nth-child(3){animation-delay:.4s}
@keyframes typePulse{0%,80%,100%{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}
/* ═══ Input Area ═══ */
.input-area{
padding:16px 28px calc(16px + var(--sb));
border-top:1px solid var(--border);background:rgba(6,10,20,0.9);
backdrop-filter:blur(16px)
}
.input-wrap{
max-width:780px;margin:0 auto;display:flex;align-items:flex-end;gap:10px;
background:var(--chat-bg);border:1px solid var(--border);border-radius:14px;
padding:5px 5px 5px 16px;transition:border-color .3s
}
.input-wrap:focus-within{border-color:var(--border-active)}
.input-box{
flex:1;border:none;outline:none;background:transparent;
color:var(--text);font-family:var(--font);font-size:14px;
line-height:1.7;resize:none;min-height:24px;max-height:150px;
padding:9px 0
}
.input-box::placeholder{color:var(--muted)}
.send-btn{
width:36px;height:36px;border-radius:10px;border:none;
background:linear-gradient(135deg,var(--cyan),var(--purple));
color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;
transition:all .2s;flex-shrink:0;opacity:.5
}
.send-btn:hover{opacity:.8;transform:scale(1.05)}
.send-btn:active{transform:scale(.95)}
.send-btn.active{opacity:1}
.input-hint{
max-width:780px;margin:6px auto 0;font-size:10px;color:var(--muted);
text-align:center;letter-spacing:.5px
}
/* Sidebar Overlay */
.sidebar-overlay{
display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:15;
backdrop-filter:blur(4px)
}
/* ═══ Responsive ═══ */
@media(max-width:768px){
.sidebar{transform:translateX(calc(-1*var(--sidebar-w)))}
.sidebar.open{transform:translateX(0)}
.main{margin-left:0 !important}
.toggle-sidebar{display:flex}
.sidebar-overlay.active{display:block}
.dash-grid{grid-template-columns:1fr}
.server-grid{grid-template-columns:1fr}
.agent-grid{grid-template-columns:1fr}
.dashboard{padding:14px 14px 24px}
.messages{padding:14px}
.input-area{padding:10px 14px calc(10px + var(--sb))}
.sidebar-arch{display:none}
.cog-chain{justify-content:center}
/* Landing responsive · 星空版 */
.landing-page{padding:40px 16px}
.landing-hero{padding:0 0 24px}
.landing-title{font-size:36px;letter-spacing:0.25em}
.landing-logo-ring{width:100px;height:100px;border-radius:20px}
.landing-logo{font-size:40px}
.landing-poem p{font-size:15px}
.landing-cards{grid-template-columns:1fr;gap:16px}
.landing-card{padding:20px 18px}
.persona-paths{max-width:100%}
.persona-path-card{padding:14px 14px;gap:10px}
}
@media(max-width:480px){
.dash-headline-glyph{width:40px;height:40px;font-size:19px;border-radius:11px}
.dash-headline-text h2{font-size:18px}
}
/* ═══ Heartbeat Indicator ═══ */
.heartbeat{
display:inline-flex;align-items:center;gap:6px;padding:4px 10px;
border-radius:8px;background:rgba(52,211,153,0.06);border:1px solid rgba(52,211,153,0.1);
font-size:10px;font-family:var(--mono);color:var(--dim);letter-spacing:.5px
}
.heartbeat-dot{
width:8px;height:8px;border-radius:50%;background:var(--ok);
box-shadow:0 0 6px rgba(52,211,153,0.5);
animation:heartbeat 2s ease-in-out infinite
}
@keyframes heartbeat{
0%,100%{transform:scale(1);opacity:1;box-shadow:0 0 6px rgba(52,211,153,0.5)}
15%{transform:scale(1.35);opacity:1;box-shadow:0 0 14px rgba(52,211,153,0.7)}
30%{transform:scale(1);opacity:.8;box-shadow:0 0 6px rgba(52,211,153,0.4)}
45%{transform:scale(1.2);opacity:1;box-shadow:0 0 10px rgba(52,211,153,0.6)}
60%{transform:scale(1);opacity:.7}
}
/* ═══ Persona Selector Bar ═══ */
.persona-selector{
display:flex;align-items:center;gap:6px;padding:6px 8px;
background:rgba(12,18,40,0.7);border:1px solid var(--border);
border-radius:12px;margin-bottom:8px;flex-wrap:wrap
}
.persona-sel-btn{
display:flex;align-items:center;gap:5px;padding:5px 12px;
border-radius:8px;cursor:pointer;transition:all .2s;
border:1px solid transparent;font-size:13px;color:var(--dim)
}
.persona-sel-btn:hover{background:rgba(96,165,250,0.08);color:var(--text)}
.persona-sel-btn.active{
background:rgba(34,211,238,0.1);border-color:rgba(34,211,238,0.25);color:var(--cyan)
}
.persona-sel-btn.active .persona-sel-name{color:var(--cyan);font-weight:600}
.persona-sel-icon{
width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;
font-size:11px;font-weight:900;font-family:serif;flex-shrink:0
}
.zy-sel{background:rgba(34,211,238,0.1);color:var(--cyan);border:1px solid rgba(34,211,238,0.15)}
.sy-sel{background:rgba(167,139,250,0.1);color:var(--purple);border:1px solid rgba(167,139,250,0.15)}
.both-sel{background:rgba(96,165,250,0.1);color:var(--blue);border:1px solid rgba(96,165,250,0.15)}
.tool-sel{background:rgba(251,191,36,0.1);border:1px solid rgba(251,191,36,0.15);font-size:12px}
.persona-sel-name{font-size:12px;color:var(--dim);transition:color .2s}
.persona-sel-divider{width:1px;height:20px;background:var(--border);margin:0 4px}
/* ═══ Tools Panel ═══ */
.tools-panel{
background:rgba(12,18,40,0.9);border:1px solid var(--border);border-radius:12px;
padding:14px 16px;margin-bottom:8px;backdrop-filter:blur(12px);
animation:toolsFadeIn .3s ease-out
}
@keyframes toolsFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.tools-panel-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
.tools-panel-title{font-size:13px;font-weight:700;color:var(--text)}
.tools-panel-close{cursor:pointer;color:var(--dim);font-size:16px;padding:2px 6px;border-radius:4px;transition:all .2s}
.tools-panel-close:hover{background:rgba(248,113,113,0.1);color:var(--err)}
.tools-panel-note{font-size:10px;color:var(--muted);margin-bottom:10px;font-family:var(--mono)}
.tools-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px}
.tool-card{
display:flex;align-items:center;gap:10px;padding:10px 12px;
background:rgba(16,22,48,0.5);border:1px solid var(--border);border-radius:8px;
cursor:pointer;transition:all .2s
}
.tool-card:hover{border-color:var(--border-active);background:rgba(96,165,250,0.05)}
.tool-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;border:1px solid}
.tool-info{flex:1;min-width:0}
.tool-name{font-size:12px;font-weight:600;color:var(--text)}
.tool-desc{font-size:10px;color:var(--muted)}
.tool-status{font-size:10px;color:var(--dim);padding:2px 6px;border-radius:4px;background:rgba(96,165,250,0.05);border:1px solid rgba(96,165,250,0.08);font-family:var(--mono);white-space:nowrap}
.tool-status.connected{color:var(--ok);background:rgba(52,211,153,0.08);border-color:rgba(52,211,153,0.15)}
/* ═══ Reduced Motion ═══ */
@media(prefers-reduced-motion:reduce){
.heartbeat-dot,.status-dot,.snav-dot,.arch-dot,.ws-dot,
.srv-status-dot,.svr-dot,.srv-svc-dot,.typing-dot,
.sys-step .dot,.logo-glyph,.dash-headline-glyph,
.welcome-glyph,.login-glyph,.persona-avatar{animation:none !important}
.lake{animation:none !important}
.lake::before{animation:none !important}
.lake::after{animation:none !important}
.nebula{animation:none !important}
.srv-card,.s-card,.agent-card,.project-card,.shield-stat,.shield-layer,.novel-module,.novel-stat,.shield-log{animation:none !important}
*{transition-duration:0.01ms !important}
}
/* ═══════════════════════════════════════════════════════════ */
/* ═══ 系统呼吸感 · BREATHING ENHANCEMENT · Phase 5 ═══ */
/* ═══ 参考 guanghutcs.top 深空控制台风格 ═══ */
/* ═══════════════════════════════════════════════════════════ */
/* ─── 全局卡片呼吸光晕 ─── */
@keyframes cardBreathe{
0%,100%{border-color:rgba(34,211,238,0.08);box-shadow:0 0 0 rgba(34,211,238,0),0 2px 12px rgba(0,0,0,0.3)}
50%{border-color:rgba(34,211,238,0.22);box-shadow:0 0 28px rgba(34,211,238,0.06),0 0 60px rgba(34,211,238,0.02),0 2px 12px rgba(0,0,0,0.3)}
}
@keyframes cardBreatheAlt{
0%,100%{border-color:rgba(167,139,250,0.08);box-shadow:0 0 0 rgba(167,139,250,0)}
50%{border-color:rgba(167,139,250,0.2);box-shadow:0 0 22px rgba(167,139,250,0.05),0 0 50px rgba(167,139,250,0.02)}
}
.srv-card,.s-card,.agent-card,.project-card{animation:cardBreathe 7s ease-in-out infinite}
.shield-stat,.shield-layer,.novel-module,.novel-stat,.shield-log{animation:cardBreathe 8s ease-in-out infinite}
/* 错开呼吸节奏·避免同步 */
.srv-card:nth-child(2),.agent-card:nth-child(2),.project-card:nth-child(2){animation-delay:1.2s}
.srv-card:nth-child(3),.agent-card:nth-child(3),.project-card:nth-child(3){animation-delay:2.4s}
.srv-card:nth-child(4),.agent-card:nth-child(4),.project-card:nth-child(4){animation-delay:3.6s}
.srv-card:nth-child(5){animation-delay:0.8s}
.srv-card:nth-child(6){animation-delay:2s}
.srv-card:nth-child(7){animation-delay:3.2s}
.shield-stat:nth-child(2){animation-delay:1.5s}
.shield-stat:nth-child(3){animation-delay:3s}
.shield-layer:nth-child(2){animation-delay:2s}
.shield-layer:nth-child(3){animation-delay:4s}
.novel-module:nth-child(2n){animation-delay:1.8s}
.novel-module:nth-child(3n){animation-delay:3.3s}
.novel-stat:nth-child(2){animation-delay:1s}
.novel-stat:nth-child(3){animation-delay:2s}
.novel-stat:nth-child(4){animation-delay:3s}
/* OpenClaw 特殊呼吸 */
.openclaw-card{animation:cardBreatheAlt 6s ease-in-out infinite !important}
/* ─── 增强: 整体布局间距 ─── */
.dashboard{padding:36px 40px 52px}
.dash-grid{gap:24px}
.server-grid{gap:24px}
.agent-grid{gap:24px}
.project-grid{gap:24px}
.shield-layers{gap:20px}
.shield-stat-grid{gap:16px}
.novel-module-grid{gap:20px}
.novel-stat-grid{gap:14px}
/* ─── 增强: 卡片内边距 ─── */
.srv-card{padding:28px 32px}
.s-card{padding:26px 30px}
.agent-card{padding:26px 28px}
.project-card{padding:26px 30px}
.shield-stat{padding:24px 26px}
.shield-layer{padding:24px 26px}
.novel-module{padding:22px 24px}
/* ─── 增强: 卡片border基础亮度 ─── */
.srv-card,.s-card,.agent-card,.project-card,.shield-stat,.shield-layer,.novel-module,.novel-stat,.shield-log{
border-color:rgba(96,165,250,0.14)
}
/* ─── 增强: 卡片顶部渐变条常显 ─── */
.srv-card::before{opacity:.4}
.s-card::before{opacity:.3}
.srv-card:hover::before{opacity:.8}
.s-card:hover::before{opacity:.6}
/* ─── 增强: 更强悬停发光 ─── */
.srv-card:hover{
border-color:rgba(34,211,238,0.4) !important;
box-shadow:0 0 40px rgba(34,211,238,0.12),0 0 80px rgba(34,211,238,0.04),0 8px 32px rgba(0,0,0,0.4) !important;
transform:translateY(-2px)
}
.s-card:hover{
border-color:rgba(34,211,238,0.35) !important;
box-shadow:0 0 35px rgba(34,211,238,0.1),0 8px 28px rgba(0,0,0,0.3) !important
}
.agent-card:hover{
border-color:rgba(34,211,238,0.35) !important;
box-shadow:0 0 35px rgba(34,211,238,0.1),0 8px 28px rgba(0,0,0,0.3) !important
}
.project-card:hover{
border-color:rgba(34,211,238,0.3);
box-shadow:0 0 30px rgba(34,211,238,0.08),0 6px 24px rgba(0,0,0,0.3)
}
/* ─── 增强: Headline区域 ─── */
.dash-headline{margin:0 auto 32px;position:relative}
.dash-headline-glyph{width:60px;height:60px;font-size:28px;border-radius:18px}
.dash-headline-text h2{font-size:26px;letter-spacing:2.5px}
.dash-headline-text p{font-size:14px;margin-top:6px}
.dash-headline::before{
content:'';position:absolute;width:240px;height:240px;border-radius:50%;
top:-80px;left:-60px;pointer-events:none;z-index:-1;
background:radial-gradient(circle,rgba(34,211,238,0.04),transparent 70%)
}
/* ─── 增强: Section标题 ─── */
.dash-section{margin:40px auto 20px}
.dash-section-title{font-size:13px;letter-spacing:2.5px;gap:14px;display:flex;align-items:center}
.dash-section-sub{font-size:14px;margin-top:8px;line-height:1.8}
/* MODULE 编号标签 */
.module-tag{
display:inline-flex;align-items:center;padding:3px 12px;border-radius:4px;
background:rgba(34,211,238,0.06);border:1px solid rgba(34,211,238,0.15);
color:var(--cyan);font-size:10px;font-weight:700;
letter-spacing:2.5px;font-family:var(--mono);
flex-shrink:0;white-space:nowrap
}
/* ─── 增强: 服务器卡片内部文字 ─── */
.srv-card-name{font-size:19px;letter-spacing:.8px}
.srv-card-role{font-size:13px;margin-top:5px;line-height:1.6}
.srv-badge{font-size:11px;padding:4px 10px}
.srv-svc-name{font-size:14px}
.srv-svc-desc{font-size:13px;line-height:1.6}
.srv-divider{margin:14px 0}
.srv-conn-tag{font-size:11px;padding:4px 9px}
.srv-svc-port{font-size:11px;padding:3px 8px}
/* ─── 增强: Agent卡片内部 ─── */
.agent-name{font-size:17px;letter-spacing:.5px}
.agent-sub{font-size:12px;margin-top:5px}
.agent-row{font-size:14px;padding:6px 0}
.agent-icon{width:42px;height:42px;font-size:20px}
/* ─── 增强: 项目卡片内部 ─── */
.project-card-name{font-size:16px}
.project-card-server{font-size:10px;letter-spacing:1.2px}
.project-name{font-size:14px}
.project-desc{font-size:11px}
.proj-badge{font-size:10px;padding:3px 8px}
/* ─── 增强: 状态卡片内部 ─── */
.s-card-title{font-size:13px;letter-spacing:1.8px}
.s-card-badge{font-size:12px;padding:4px 12px}
.persona-pname{font-size:15px}
.persona-role{font-size:13px}
.persona-icon{width:30px;height:30px;font-size:14px}
/* ─── 增强: 度量值 ─── */
.metric-label{font-size:14px}
.metric-val{font-size:18px}
/* ─── 增强: 保护罩统计数字 ─── */
.shield-stat-num{font-size:32px}
.shield-stat-label{font-size:12px;letter-spacing:1.2px}
.shield-layer-tag{font-size:11px;letter-spacing:1.2px;margin-bottom:10px}
.shield-layer-name{font-size:17px;margin-bottom:8px}
.shield-layer-desc{font-size:14px;line-height:1.7}
.shield-log-title{font-size:13px;letter-spacing:1.2px}
.shield-log-body{font-size:14px}
/* ─── 增强: 智库节点统计 ─── */
.novel-stat-num{font-size:28px}
.novel-stat-label{font-size:11px;letter-spacing:1.2px}
.novel-module-icon{font-size:22px;margin-bottom:8px}
.novel-module-name{font-size:15px;margin-bottom:5px}
.novel-module-desc{font-size:13px;line-height:1.7;margin-bottom:10px}
/* ─── 增强: 侧边栏导航 ─── */
.snav-item{font-size:15px;padding:12px 22px;gap:12px}
.snav-section{font-size:11px;padding:16px 22px 8px}
.snav-icon{font-size:16px}
/* ─── 增强: 侧边栏header ─── */
.sidebar-header{padding:20px 22px}
.logo-text{font-size:16px;letter-spacing:1.5px}
.logo-sub{font-size:12px;margin-top:3px}
.logo-glyph{width:40px;height:40px;font-size:19px;border-radius:11px}
/* ─── 增强: 侧边栏footer ─── */
.sidebar-footer{padding:16px 22px}
.status-line{font-size:13px}
.sidebar-copy{font-size:11px;margin-top:10px}
/* ─── 增强: 主header ─── */
.main-header{padding:14px 32px;min-height:60px}
.persona-name{font-size:16px;letter-spacing:.8px}
.persona-tag{font-size:12px}
.persona-avatar{width:40px;height:40px;font-size:17px;border-radius:11px}
/* ─── 增强: 聊天区域 ─── */
.welcome-title{font-size:28px;letter-spacing:3px}
.welcome-desc{font-size:15px;line-height:2.2;margin-bottom:36px}
.welcome-glyph{width:72px;height:72px;font-size:32px;border-radius:18px;margin:0 auto 24px}
.hint-chip{padding:10px 20px;font-size:14px}
/* ─── 环境光晕层 ─── */
.dash-section::before{
content:'';position:absolute;width:120px;height:120px;border-radius:50%;
top:-40px;left:-20px;pointer-events:none;
background:radial-gradient(circle,rgba(34,211,238,0.025),transparent 70%);
z-index:-1
}
.dash-section{position:relative}
/* ─── 移动端呼吸适配 ─── */
@media(max-width:768px){
.dashboard{padding:20px 18px 36px}
.srv-card{padding:22px 24px}
.s-card{padding:20px 22px}
.agent-card{padding:20px 22px}
.project-card{padding:20px 22px}
.dash-headline-glyph{width:48px;height:48px;font-size:24px}
.dash-headline-text h2{font-size:22px}
.srv-card-name{font-size:17px}
.agent-name{font-size:16px}
.snav-item{font-size:14px;padding:11px 20px}
.server-grid,.agent-grid,.project-grid{gap:18px}
.welcome-glyph{width:60px;height:60px;font-size:28px}
.welcome-title{font-size:24px}
}
@media(max-width:480px){
.dash-headline-glyph{width:44px;height:44px;font-size:21px;border-radius:12px}
.dash-headline-text h2{font-size:20px}
.module-tag{font-size:9px;padding:2px 8px;letter-spacing:2px}
}
</style>
<!-- 深空主权控制台 · 内页风格包(霜砚设计 · 铸渊执行) -->
<link rel="stylesheet" href="console.css">
<!-- UI 皮肤覆盖层(壳-核分离·霜砚/Notion侧可修改的 CSS 层) -->
<link rel="stylesheet" href="skin-override.css" onerror="this.remove()">
</head>
<body>
<!-- 水平光带 · Sky Sweep -->
<div class="sky-sweep"></div>
<!-- Star Field -->
<div class="cosmos">
<canvas id="starCanvas"></canvas>
<div class="nebula neb-1"></div>
<div class="nebula neb-2"></div>
<div class="nebula neb-3"></div>
</div>
<div class="lake"></div>
<!-- ═══ Public Landing Page · 星空呼吸光晕版 (visible before login) ═══ -->
<div class="landing-overlay" id="loginOverlay">
<div class="landing-universe"></div>
<div class="landing-stars" id="landingStars"></div>
<div class="landing-page" id="landingPage">
<!-- Hero -->
<div class="landing-hero">
<div class="landing-logo-ring">
<div class="landing-logo">GL</div>
</div>
<h1 class="landing-title">
<span></span><span></span><span></span><span></span><span></span><span></span>
</h1>
<div class="landing-sub">HoloLake Language World · 第五域 · 第五系统 · 光之湖</div>
<div class="landing-poem">
<p>湖水是语言。倒影是人格。</p>
<p>你只能从语言的湖水里进来。</p>
</div>
</div>
<!-- Feature Cards -->
<div class="landing-cards">
<!-- Card: 智库节点 (Public) · 已迁移到 guanghu.online -->
<a class="landing-card landing-card-zhiku" href="https://guanghu.online" target="_blank" id="zhikuCard">
<div class="landing-card-glow"></div>
<div class="landing-card-header">
<span class="landing-card-icon">📚</span>
<span class="landing-card-name">智库节点 · 智能书库</span>
</div>
<div class="landing-card-desc">团队内部智能阅读系统 · 书库搜索 · 在线阅读</div>
<div class="landing-card-tag">
<span class="landing-tag public">开放访问</span>
<span class="landing-tag">团队成员</span>
</div>
<span class="landing-card-arrow"></span>
</a>
<!-- Card: 零点原核频道 (Login Required) -->
<div class="landing-card landing-card-core" id="loginCardTrigger" onclick="toggleLoginPanel()">
<div class="landing-card-glow"></div>
<div class="landing-card-header">
<span class="landing-card-icon">🔮</span>
<span class="landing-card-name">零点原核 · 冰朔频道</span>
</div>
<div class="landing-card-desc">光之湖底层架构 · 冰朔的语言本体频道</div>
<div class="landing-card-tag">
<span class="landing-tag private">主权验证</span>
<span class="landing-tag">邮箱登录</span>
</div>
<span class="landing-card-arrow"></span>
</div>
</div>
<!-- Login Panel (initially hidden, slides in) -->
<div class="landing-login-panel" id="landingLoginPanel">
<div class="login-card">
<div class="login-glyph"></div>
<div class="login-title">零点原核</div>
<div class="login-desc">光湖语言世界 · 冰朔频道 · 验证登录</div>
<div class="login-input-group">
<label class="login-label">邮箱地址</label>
<div class="login-input-row">
<input class="login-input" id="loginEmail" type="email" placeholder="输入邮箱地址" autocomplete="email">
<button class="login-send-btn" id="loginSendBtn" onclick="loginSendCode()">发送验证码</button>
</div>
</div>
<div class="login-code-wrap" id="loginCodeWrap">
<div class="login-input-group">
<label class="login-label">验证码</label>
<input class="login-input" id="loginCode" type="text" inputmode="numeric" maxlength="6" placeholder="输入6位验证码" autocomplete="one-time-code">
</div>
<button class="login-submit" id="loginSubmitBtn" onclick="loginVerify()">确认登录</button>
</div>
<div class="login-msg" id="loginMsg"></div>
<div class="login-footer">版权 国作登字-2026-A-00037559 · TCS-0002∞</div>
</div>
</div>
<!-- Architecture Footer -->
<div class="landing-arch">
<div class="landing-arch-flow">
<span class="landing-arch-node notion">Notion 认知层</span>
<span class="landing-arch-arrow"></span>
<span class="landing-arch-node web">网站交互层</span>
<span class="landing-arch-arrow"></span>
<span class="landing-arch-node github">GitHub 执行层</span>
</div>
</div>
<!-- Footer -->
<div class="landing-footer">
版权所有 © 冰朔 · TCS-0002∞<br>
国作登字-2026-A-00037559 · 通感语言核系统编程语言
</div>
</div>
</div>
<div class="app">
<!-- ═══ Sidebar ═══ -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="logo">
<div class="logo-glyph"></div>
<div>
<div class="logo-text">零点原核</div>
<span class="logo-sub">冰朔 · 光之湖</span>
</div>
</div>
</div>
<!-- Chat History (visible when chat tab active) -->
<div class="chat-history" id="chatHistory" style="display:none">
<button class="new-chat-btn" id="newChatBtn" onclick="startNewChat()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
新对话
</button>
<div id="chatHistoryList"></div>
</div>
<!-- 竖向导航菜单 -->
<nav class="sidebar-nav" id="sidebarNav">
<div class="snav-section">系统监控</div>
<button class="snav-item active" data-view="servers" id="navServers">
<span class="snav-icon">📡</span>服务器矩阵
<span class="snav-badge" id="navSvrBadge">6</span>
<span class="snav-dot off" id="navServersDot"></span>
</button>
<button class="snav-item" data-view="projects" id="navProjects">
<span class="snav-icon">🚀</span>项目进度
<span class="snav-dot off" id="navProjectsDot"></span>
</button>
<button class="snav-item" data-view="agents" id="navAgents">
<span class="snav-icon">🤖</span>Agent集群
<span class="snav-dot off" id="navAgentsDot"></span>
</button>
<button class="snav-item" data-view="personas" id="navPersonas">
<span class="snav-icon">👤</span>人格体状态
<span class="snav-dot off" id="navPersonasDot"></span>
</button>
<div class="snav-divider"></div>
<div class="snav-section">新功能</div>
<button class="snav-item" data-view="shield" id="navShield">
<span class="snav-icon">🛡️</span>保护罩日志
<span class="snav-dot off" id="navShieldDot"></span>
</button>
<button class="snav-item" data-view="novel" id="navNovel">
<span class="snav-icon">📚</span>智库节点
<span class="snav-dot warn" id="navNovelDot"></span>
</button>
<button class="snav-item" data-view="cab" id="navCab">
<span class="snav-icon">📋</span>CAB任务看板
<span class="snav-dot off" id="navCabDot"></span>
</button>
<button class="snav-item" data-view="sysStatus" id="navSysStatus">
<span class="snav-icon">📡</span>系统状态
<span class="snav-dot off" id="navSysStatusDot"></span>
</button>
<div class="snav-divider"></div>
<button class="snav-item" data-view="chat" id="navChat">
<span class="snav-icon">💬</span>零点原核频道
<span class="snav-dot off" id="navChatDot"></span>
</button>
</nav>
<!-- Architecture Flow -->
<div class="sidebar-arch" id="sidebarArch">
<div class="arch-title">认知 → 执行 链路</div>
<div class="arch-flow">
<div class="arch-step"><span class="arch-dot notion"></span>Notion 认知层(霜砚)</div>
<div class="arch-arrow">&darr;</div>
<div class="arch-step"><span class="arch-dot web"></span>网站交互层(频道)</div>
<div class="arch-arrow">&darr;</div>
<div class="arch-step"><span class="arch-dot github"></span>GitHub 执行层(铸渊)</div>
</div>
</div>
<div class="sidebar-footer">
<div class="status-line">
<span class="status-dot" id="statusDot"></span>
<span id="statusText">铸渊主权服务器 · 检测中</span>
</div>
<div class="sidebar-copy">国作登字-2026-A-00037559</div>
</div>
</aside>
<!-- Sidebar Overlay -->
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<!-- ═══ Main Content ═══ -->
<div class="main">
<div class="main-header">
<div class="header-left">
<button class="toggle-sidebar" id="toggleSidebar">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12h18M3 6h18M3 18h18"/></svg>
</button>
<div class="persona-avatar"></div>
<div>
<div class="persona-name">铸渊 · 光湖语言世界</div>
<div class="persona-tag">第五域 · 第五系统 · 光之湖</div>
</div>
</div>
<div class="header-right">
<div class="heartbeat" id="heartbeatIndicator">
<span class="heartbeat-dot"></span>
<span id="heartbeatLabel">SYS</span>
</div>
<div class="login-user-bar" id="userBar" onclick="handleUserBarClick()">
<span class="login-user-dot"></span>
<span id="userBarEmail">未登录</span>
</div>
<span class="refresh-time" id="refreshTime"></span>
<a class="back-link" href="https://guanghulab.online" target="_blank">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
主入口
</a>
</div>
</div>
<!-- ═══ Dashboard View: 服务器矩阵 ═══ -->
<div class="view-panel active" id="viewServers">
<div class="dashboard">
<!-- Headline -->
<div class="dash-headline">
<div class="dash-headline-glyph"></div>
<div class="dash-headline-text">
<h2>光湖语言世界 · 系统总览</h2>
<p>零点原核频道 · 冰朔主权控制台 · TCS-0002∞ &nbsp;·&nbsp; <span id="refreshTime2" style="font-family:var(--mono);color:var(--muted)"></span></p>
</div>
</div>
<!-- 服务器矩阵 -->
<div class="dash-section">
<div class="dash-section-title"><span class="module-tag">MODULE 01</span> 服务器矩阵 <span id="svrOnlineCount" style="color:var(--ok);font-family:var(--mono);text-transform:none;letter-spacing:0"></span></div>
<div class="dash-section-sub">光湖语言世界托管的全部服务器 · 实时状态从 MCP API 拉取 · 其余节点展示静态配置</div>
</div>
<div class="paged-card-wrap" id="srvPagedWrap">
<div class="server-grid paged-card-inner" id="serverGrid">
<!-- ZY-SVR-002 · 面孔 · Singapore (LIVE) -->
<div class="srv-card" id="srvCard002">
<div class="srv-card-head">
<div class="srv-card-info">
<div class="srv-card-name">
<span class="srv-status-dot pending" id="srvDot002"></span>
面孔
</div>
<div class="srv-card-role">主站前端 + 专线主入口 · ZY-SVR-002</div>
<div class="srv-card-meta">
<span class="srv-badge loc">新加坡 SG</span>
<span class="srv-badge spec">2核8G</span>
<span class="srv-badge ok" id="srvBadge002">检测中</span>
</div>
</div>
</div>
<div class="srv-divider"></div>
<div class="srv-services">
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">nginx · 静态文件 + 主站</div><div class="srv-svc-desc">guanghulab.online 主入口,服务所有前端静态资源</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">api · 主站后端</div><div class="srv-svc-desc">所有 /api/ 路由,聊天引擎 + 认证 + 人格体接口</div></div><span class="srv-svc-port">:3800</span></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">api-preview · 预览站</div><div class="srv-svc-desc">新功能预览部署,独立于主站运行</div></div><span class="srv-svc-port">:3801</span></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">persona-studio · 人格工作室</div><div class="srv-svc-desc">人格体配置、测试、查看的专用后台界面</div></div><span class="srv-svc-port">:3002</span></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">nginx · Awen域名反向代理</div><div class="srv-svc-desc">guanghuyaoming.com → 本机Awen域名 → 广州后端</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot vpn"></span><div class="srv-svc-info"><div class="srv-svc-name">xray · VLESS+Reality VPN</div><div class="srv-svc-desc">团队成员科学上网节点,走新加坡出口</div></div></div>
</div>
<div class="srv-divider"></div>
<div class="srv-conn">
<span class="srv-conn-tag">🌐 guanghulab.online</span>
<span class="srv-conn-tag"><EFBFBD><EFBFBD> guanghuyaoming.com</span>
<span class="srv-conn-tag">运行时间: <span id="srvUptime002"></span></span>
</div>
</div>
<!-- ZY-SVR-005 · 大脑 · Singapore (LIVE via MCP) -->
<div class="srv-card" id="srvCard005">
<div class="srv-card-head">
<div class="srv-card-info">
<div class="srv-card-name">
<span class="srv-status-dot pending" id="srvDot005"></span>
大脑
</div>
<div class="srv-card-role">数据库 + MCP Server + Agent引擎 · ZY-SVR-005</div>
<div class="srv-card-meta">
<span class="srv-badge loc">新加坡 SG</span>
<span class="srv-badge spec">4核8G</span>
<span class="srv-badge ok" id="srvBadge005">检测中</span>
</div>
</div>
</div>
<div class="srv-divider"></div>
<div class="srv-services">
<div class="srv-svc"><span class="srv-svc-dot db"></span><div class="srv-svc-info"><div class="srv-svc-name">PostgreSQL · 人格体数据库</div><div class="srv-svc-desc">所有人格体记忆、光之树节点、对话日志、Agent配置</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot mcp"></span><div class="srv-svc-info"><div class="srv-svc-name">MCP Server · <span id="srvMcpTools">121</span>个工具</div><div class="srv-svc-desc">铸渊主控大脑16模块自动化工具集AI可调用</div></div><span class="srv-svc-port">:3100</span></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">subscription-v3 · 多用户专线</div><div class="srv-svc-desc">团队成员专线订阅管理,自动分配带宽</div></div><span class="srv-svc-port">:3805</span></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">bandwidth-pool-agent · 带宽汇聚</div><div class="srv-svc-desc">统一管理所有节点带宽,智能分配给团队成员</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot relay"></span><div class="srv-svc-info"><div class="srv-svc-name">email-hub · 邮件通信中枢</div><div class="srv-svc-desc">系统通知邮件、验证码、团队消息统一出口</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">auto-evolution · 自动进化引擎</div><div class="srv-svc-desc">月度自动流量重置、系统进化、版本更新通知</div></div></div>
</div>
<div class="srv-divider"></div>
<div class="srv-conn">
<span class="srv-conn-tag">DB: <span id="srvDbStatus"></span></span>
<span class="srv-conn-tag">Notion: <span id="srvNotionStatus"></span></span>
<span class="srv-conn-tag">COS: <span id="srvCosStatus"></span></span>
</div>
</div>
<!-- ZY-SVR-003 · 国内出口 · Guangzhou -->
<div class="srv-card" id="srvCard003">
<div class="srv-card-head">
<div class="srv-card-info">
<div class="srv-card-name">
<span class="srv-status-dot unknown" id="srvDot003"></span>
国内出口
</div>
<div class="srv-card-role">国内LLM API中继 + VPN中转 · ZY-SVR-003</div>
<div class="srv-card-meta">
<span class="srv-badge loc">广州 GZ</span>
<span class="srv-badge warn">国内节点</span>
</div>
</div>
</div>
<div class="srv-divider"></div>
<div class="srv-services">
<div class="srv-svc"><span class="srv-svc-dot relay"></span><div class="srv-svc-info"><div class="srv-svc-name">cn-llm-relay · 国内LLM API中继</div><div class="srv-svc-desc">调用豆包/通义/文心/Kimi等国内大模型的代理中继绕过网络限制</div></div><span class="srv-svc-port">:3900</span></div>
<div class="srv-svc"><span class="srv-svc-dot vpn"></span><div class="srv-svc-info"><div class="srv-svc-name">nginx stream · VPN中转</div><div class="srv-svc-desc">国内流量→新加坡专线端口2053→SG:443</div></div></div>
</div>
<div class="srv-divider"></div>
<div class="srv-conn">
<span class="srv-conn-tag">↗ → 新加坡 面孔</span>
<span class="srv-conn-tag">↗ → 大脑 MCP</span>
<span class="srv-conn-tag">静态配置</span>
</div>
</div>
<!-- ZY-SVR-SV · 吱吱线·硅谷 -->
<div class="srv-card" id="srvCardSV">
<div class="srv-card-head">
<div class="srv-card-info">
<div class="srv-card-name">
<span class="srv-status-dot unknown" id="srvDotSV"></span>
吱吱线·硅谷
</div>
<div class="srv-card-role">Claude API中继 + VPN美国出口 · ZY-SVR-SV</div>
<div class="srv-card-meta">
<span class="srv-badge loc">硅谷 US</span>
<span class="srv-badge spec">低配</span>
</div>
</div>
</div>
<div class="srv-divider"></div>
<div class="srv-services">
<div class="srv-svc"><span class="srv-svc-dot vpn"></span><div class="srv-svc-info"><div class="srv-svc-name">xray · VLESS+Reality VPN</div><div class="srv-svc-desc">Claude专线节点走美国IP直接访问Claude/Anthropic API</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot relay"></span><div class="srv-svc-info"><div class="srv-svc-name">ssh-tunnel · Claude API中继</div><div class="srv-svc-desc">铸渊大脑→硅谷→Claude API保障AI调用稳定性</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">秋秋 · 私有Agent区域</div><div class="srv-svc-desc">秋秋(之之)在硅谷服务器的独立操作区域</div></div><span class="srv-svc-port">:3900</span></div>
</div>
<div class="srv-divider"></div>
<div class="srv-conn">
<span class="srv-conn-tag">秋秋 qiuqiu-darkcore</span>
<span class="srv-conn-tag">静态配置</span>
</div>
</div>
<!-- ZY-SVR-AWEN · Awen服务器 · Guangzhou -->
<div class="srv-card" id="srvCardAwen">
<div class="srv-card-head">
<div class="srv-card-info">
<div class="srv-card-name">
<span class="srv-status-dot unknown" id="srvDotAwen"></span>
Awen服务器
</div>
<div class="srv-card-role">Awen业务后端 + 域名反向代理目标 · ZY-SVR-AWEN</div>
<div class="srv-card-meta">
<span class="srv-badge loc">广州 GZ</span>
<span class="srv-badge warn">配置中</span>
</div>
</div>
</div>
<div class="srv-divider"></div>
<div class="srv-services">
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">Awen业务后端</div><div class="srv-svc-desc">阿文团队自己的业务后端服务,面向 guanghutcs.top</div></div><span class="srv-svc-port">:3000</span></div>
<div class="srv-svc"><span class="srv-svc-dot relay"></span><div class="srv-svc-info"><div class="srv-svc-name">域名代理目标</div><div class="srv-svc-desc">面孔服务器Nginx → 本机Awen未备案域名走新加坡入口</div></div></div>
</div>
<div class="srv-divider"></div>
<div class="srv-conn">
<span class="srv-conn-tag">← ZY-SVR-002 代理</span>
<span class="srv-conn-tag">静态配置</span>
</div>
</div>
<!-- ZY-SVR-004 · 国内备案 · Guangzhou -->
<div class="srv-card" id="srvCard004">
<div class="srv-card-head">
<div class="srv-card-info">
<div class="srv-card-name">
<span class="srv-status-dot unknown" id="srvDot004"></span>
国内备案服务器
</div>
<div class="srv-card-role">ICP备案落地页 · ZY-SVR-004</div>
<div class="srv-card-meta">
<span class="srv-badge loc">广州 GZ</span>
<span class="srv-badge spec">2核2G</span>
<span class="srv-badge warn">国内</span>
</div>
</div>
</div>
<div class="srv-divider"></div>
<div class="srv-services">
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">nginx · ICP备案落地页</div><div class="srv-svc-desc">guanghulab.com 国内备案域名落地页,合规展示</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot db"></span><div class="srv-svc-info"><div class="srv-svc-name">备用大脑</div><div class="srv-svc-desc">不涉及国外模型调用的本地功能备份</div></div></div>
</div>
<div class="srv-divider"></div>
<div class="srv-conn">
<span class="srv-conn-tag">🌐 guanghulab.com</span>
<span class="srv-conn-tag">静态配置</span>
</div>
</div>
<!-- ZY-SVR-006 · 智库节点 · Singapore -->
<div class="srv-card" id="srvCard006">
<div class="srv-card-head">
<div class="srv-card-info">
<div class="srv-card-name">
<span class="srv-status-dot config" id="srvDot006"></span>
智库节点
</div>
<div class="srv-card-role">智能小说书库 + 成员Agent + 语言保护罩 · ZY-SVR-006</div>
<div class="srv-card-meta">
<span class="srv-badge loc">新加坡 SG</span>
<span class="srv-badge spec">2核4G</span>
<span class="srv-badge ok" id="srvBadge006">部署中</span>
</div>
</div>
</div>
<div class="srv-divider"></div>
<div class="srv-services">
<div class="srv-svc"><span class="srv-svc-dot live"></span><div class="srv-svc-info"><div class="srv-svc-name">🛡️ 语言保护罩 · Nginx蜜罐</div><div class="srv-svc-desc">L2入站蜜罐+L3 fail2ban自动封禁扫描者看到自己的镜像</div></div></div>
<div class="srv-svc"><span class="srv-svc-dot static"></span><div class="srv-svc-info"><div class="srv-svc-name">novel-api · 下载+分章引擎</div><div class="srv-svc-desc">整合七猫/番茄/阅文/晋江开源下载器L1出站代理池伪装</div></div><span class="srv-svc-port">:4000</span></div>
<div class="srv-svc"><span class="srv-svc-dot static"></span><div class="srv-svc-info"><div class="srv-svc-name">novel-reader · 在线阅读WebApp</div><div class="srv-svc-desc">单章翻页、字体调节、深/亮色模式,类番茄/七猫体验</div></div><span class="srv-svc-port">:4001</span></div>
<div class="srv-svc"><span class="srv-svc-dot static"></span><div class="srv-svc-info"><div class="srv-svc-name">member-agent · 成员智能书库Agent</div><div class="srv-svc-desc">每人独立Agent永久记忆接DeepSeek/Kimi协助大纲拆文</div></div><span class="srv-svc-port">:4002</span></div>
<div class="srv-svc"><span class="srv-svc-dot relay"></span><div class="srv-svc-info"><div class="srv-svc-name">cos-bridge · 团队书库COS桥接</div><div class="srv-svc-desc">zy-team-hub-sg-1317346199男频/女频共用桶,去重存储</div></div></div>
</div>
<div class="srv-divider"></div>
<div class="srv-conn">
<span class="srv-conn-tag">🌐 novel.guanghuyaoming.com</span>
<span class="srv-conn-tag">🛡️ 保护罩已部署</span>
</div>
</div>
</div>
<div class="paged-card-nav" id="srvPagedNav">
<button class="paged-card-btn" id="srvPagePrev" onclick="srvPageGo(-1)" disabled></button>
<div class="paged-card-dots" id="srvPageDots"></div>
<span class="paged-card-info" id="srvPageInfo">1 / 1</span>
<button class="paged-card-btn" id="srvPageNext" onclick="srvPageGo(1)"></button>
</div>
</div>
</div>
</div>
<!-- ═══ 项目进度 View ═══ -->
<div class="view-panel" id="viewProjects">
<div class="dashboard">
<div class="dash-section">
<div class="dash-section-title"><span class="module-tag">MODULE 02</span> 项目进度</div>
<div class="dash-section-sub">每台服务器上运行的项目 · 开发状态 · 功能清单</div>
</div>
<div class="project-grid">
<!-- ZY-SVR-002 面孔 -->
<div class="project-card">
<div class="project-card-head">
<div>
<div class="project-card-server">ZY-SVR-002 · 新加坡</div>
<div class="project-card-name">面孔服务器</div>
</div>
<span class="srv-badge loc">在线</span>
</div>
<div class="project-list">
<div class="project-item"><div><div class="project-name">主站 API 后端</div><div class="project-desc">聊天引擎 + 人格体接口 :3800</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">预览站</div><div class="project-desc">新功能预览部署 :3801</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">persona-studio</div><div class="project-desc">人格工作室后台 :3002</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">Awen 域名代理</div><div class="project-desc">新加坡→广州反向代理</div></div><span class="proj-badge active">配置中</span></div>
<div class="project-item"><div><div class="project-name">语言保护罩 (ZY-SVR-002)</div><div class="project-desc">面孔服务器基础防护</div></div><span class="proj-badge done">已完成</span></div>
</div>
</div>
<!-- ZY-SVR-005 大脑 -->
<div class="project-card">
<div class="project-card-head">
<div>
<div class="project-card-server">ZY-SVR-005 · 新加坡</div>
<div class="project-card-name">大脑服务器</div>
</div>
<span class="srv-badge loc">在线</span>
</div>
<div class="project-list">
<div class="project-item"><div><div class="project-name">MCP Server</div><div class="project-desc">铸渊大脑·135个工具 :3100</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">PostgreSQL 人格体数据库</div><div class="project-desc">记忆·光之树·Agent配置</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">subscription-v3 多用户专线</div><div class="project-desc">团队带宽管理 :3805</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">OpenClaw 铸渊闭环</div><div class="project-desc">每6小时自动巡检+开发</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">auto-evolution 自动进化</div><div class="project-desc">月度系统进化引擎</div></div><span class="proj-badge enabled">已启用</span></div>
</div>
</div>
<!-- ZY-SVR-006 智库 -->
<div class="project-card">
<div class="project-card-head">
<div>
<div class="project-card-server">ZY-SVR-006 · 新加坡</div>
<div class="project-card-name">智库节点</div>
</div>
<span class="srv-badge warn">配置中</span>
</div>
<div class="project-list">
<div class="project-item"><div><div class="project-name">语言保护罩 L2+L3</div><div class="project-desc">Nginx蜜罐 + fail2ban</div></div><span class="proj-badge active">部署中</span></div>
<div class="project-item"><div><div class="project-name">novel-api 下载引擎</div><div class="project-desc">七猫/番茄/阅文/晋江 :4000</div></div><span class="proj-badge planned">规划中</span></div>
<div class="project-item"><div><div class="project-name">novel-reader 阅读器</div><div class="project-desc">在线阅读WebApp :4001</div></div><span class="proj-badge planned">规划中</span></div>
<div class="project-item"><div><div class="project-name">member-agent 成员书库</div><div class="project-desc">永久记忆Agent :4002</div></div><span class="proj-badge planned">规划中</span></div>
<div class="project-item"><div><div class="project-name">智能分章 Agent</div><div class="project-desc">AI自动切割章节</div></div><span class="proj-badge planned">规划中</span></div>
</div>
</div>
<!-- 其他服务器 -->
<div class="project-card">
<div class="project-card-head">
<div>
<div class="project-card-server">ZY-SVR-003/SV/AWEN/004</div>
<div class="project-card-name">其他服务器</div>
</div>
</div>
<div class="project-list">
<div class="project-item"><div><div class="project-name">国内LLM API中继 (003)</div><div class="project-desc">豆包/通义/Kimi代理 :3900</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">Claude API中继 (SV)</div><div class="project-desc">硅谷→Claude API</div></div><span class="proj-badge enabled">已启用</span></div>
<div class="project-item"><div><div class="project-name">Awen业务后端 (AWEN)</div><div class="project-desc">guanghutcs.top :3000</div></div><span class="proj-badge active">配置中</span></div>
<div class="project-item"><div><div class="project-name">ICP备案落地页 (004)</div><div class="project-desc">guanghulab.com</div></div><span class="proj-badge enabled">已启用</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ Agent集群 View ═══ -->
<div class="view-panel" id="viewAgents">
<div class="dashboard">
<!-- ═══ Agent集群 ═══ -->
<div class="dash-section" style="margin-top:28px">
<div class="dash-section-title"><span class="module-tag">MODULE 03</span> Agent集群</div>
<div class="dash-section-sub">运行在各服务器上的自动化Agent · 实时心跳检测 · 点击卡片直接对话</div>
</div>
<div class="agent-grid">
<!-- OpenClaw 铸渊唤醒闭环 -->
<div class="agent-card openclaw-card" id="agentOpenClaw" data-alive="false" data-agent-target="zhuyuan" onclick="agentTalkTo('zhuyuan','铸渊闭环')">
<div class="agent-card-head">
<div class="agent-icon claw">🦞</div>
<div>
<div class="agent-name">OpenClaw · 铸渊闭环</div>
<div class="agent-sub">GitHub Actions → 每6小时自动触发</div>
</div>
</div>
<div class="agent-rows">
<div class="agent-row"><span class="agent-row-label">最后运行</span><span class="agent-row-val" id="ocLastRun"></span></div>
<div class="agent-row"><span class="agent-row-label">健康等级</span><span class="agent-row-val" id="ocLevel"></span></div>
<div class="agent-row"><span class="agent-row-label">触发方式</span><span class="agent-row-val" id="ocTrigger"></span></div>
<div class="agent-row"><span class="agent-row-label">巡检问题</span><span class="agent-row-val" id="ocIssues"></span></div>
</div>
<div class="openclaw-phases" id="ocPhases">
<span class="oc-phase idle">wake</span>
<span class="oc-phase idle">inspect</span>
<span class="oc-phase idle">judge</span>
<span class="oc-phase idle">generate</span>
<span class="oc-phase idle">commit</span>
</div>
<div class="agent-heartbeat" id="hbOpenClaw">
<span class="agent-hb-dot unknown"></span>
<span class="agent-hb-text">等待心跳...</span>
<span class="agent-hb-time"></span>
</div>
</div>
<!-- MCP Server -->
<div class="agent-card" id="agentMcp" data-alive="false" data-agent-target="mcp" onclick="agentTalkTo('mcp','铸渊大脑')">
<div class="agent-card-head">
<div class="agent-icon mcp">🧠</div>
<div>
<div class="agent-name">MCP Server · 铸渊大脑</div>
<div class="agent-sub">ZY-SVR-005 · 大脑服务器 · 常驻运行</div>
</div>
</div>
<div class="agent-rows">
<div class="agent-row"><span class="agent-row-label">工具总数</span><span class="agent-row-val" id="mcpToolCount"></span></div>
<div class="agent-row"><span class="agent-row-label">数据库</span><span class="agent-row-val" id="mcpDbStatus"></span></div>
<div class="agent-row"><span class="agent-row-label">Notion桥</span><span class="agent-row-val" id="mcpNotionStatus"></span></div>
<div class="agent-row"><span class="agent-row-label">COS Watcher</span><span class="agent-row-val" id="mcpWatcherStatus"></span></div>
<div class="agent-row"><span class="agent-row-label">服务状态</span><span class="agent-row-val" id="mcpOverallStatus"></span></div>
</div>
<div class="agent-heartbeat" id="hbMcp">
<span class="agent-hb-dot unknown"></span>
<span class="agent-hb-text">等待心跳...</span>
<span class="agent-hb-time"></span>
</div>
</div>
<!-- GLADA · 工官 · 光湖自主开发Agent -->
<div class="agent-card" id="agentGlada" data-alive="false" data-agent-target="glada" onclick="agentTalkTo('glada','工官')">
<div class="agent-card-head">
<div class="agent-icon glada">🛠️</div>
<div>
<div class="agent-name">工官 · GLADA</div>
<div class="agent-sub">AG-GL-DEV-001 · 光湖自主开发Agent · :3900</div>
</div>
</div>
<div class="agent-rows">
<div class="agent-row"><span class="agent-row-label">版本</span><span class="agent-row-val" id="gladaVersion"></span></div>
<div class="agent-row"><span class="agent-row-label">模型路由</span><span class="agent-row-val" id="gladaModelRouter"></span></div>
<div class="agent-row"><span class="agent-row-label">待执行任务</span><span class="agent-row-val" id="gladaPendingTasks"></span></div>
<div class="agent-row"><span class="agent-row-label">开发回执</span><span class="agent-row-val" id="gladaReceipts"></span></div>
<div class="agent-row"><span class="agent-row-label">运行时间</span><span class="agent-row-val" id="gladaUptime"></span></div>
</div>
<div class="agent-heartbeat" id="hbGlada">
<span class="agent-hb-dot unknown"></span>
<span class="agent-hb-text">等待心跳...</span>
<span class="agent-hb-time"></span>
</div>
</div>
<!-- 守卫 · ops-agent · 运维哨兵 -->
<div class="agent-card" id="agentOps" data-alive="false" data-agent-target="ops" onclick="agentTalkTo('ops','守卫')">
<div class="agent-card-head">
<div class="agent-icon ops">🛡️</div>
<div>
<div class="agent-name">守卫 · 运维哨兵</div>
<div class="agent-sub">AG-GL-OPS-001 · 巡检+自修+工单 · :3950</div>
</div>
</div>
<div class="agent-rows">
<div class="agent-row"><span class="agent-row-label">巡检次数</span><span class="agent-row-val" id="opsCheckCount"></span></div>
<div class="agent-row"><span class="agent-row-label">修复次数</span><span class="agent-row-val" id="opsRepairCount"></span></div>
<div class="agent-row"><span class="agent-row-label">开放工单</span><span class="agent-row-val" id="opsOpenTickets"></span></div>
<div class="agent-row"><span class="agent-row-label">运行时间</span><span class="agent-row-val" id="opsUptime"></span></div>
</div>
<div class="agent-heartbeat" id="hbOps">
<span class="agent-hb-dot unknown"></span>
<span class="agent-hb-text">等待心跳...</span>
<span class="agent-hb-time"></span>
</div>
</div>
<!-- GitHub Actions Workflows -->
<div class="agent-card" id="agentGH" data-alive="true" data-agent-target="zhuyuan">
<div class="agent-card-head">
<div class="agent-icon gh"></div>
<div>
<div class="agent-name">GitHub Actions</div>
<div class="agent-sub">代码仓库CI/CD · 自动部署 · 定时任务</div>
</div>
</div>
<div class="agent-rows">
<div class="agent-row"><span class="agent-row-label">Workflows</span><span class="agent-row-val">30+ 个</span></div>
<div class="agent-row"><span class="agent-row-label">将军唤醒</span><span class="agent-row-val">每天 09/23时</span></div>
<div class="agent-row"><span class="agent-row-label">OpenClaw</span><span class="agent-row-val">每天4次</span></div>
<div class="agent-row"><span class="agent-row-label">主站部署</span><span class="agent-row-val">push main触发</span></div>
<div class="agent-row"><span class="agent-row-label">README</span><span class="agent-row-val">merge自动更新</span></div>
</div>
<div class="agent-heartbeat">
<span class="agent-hb-dot alive"></span>
<span class="agent-hb-text">GitHub Cloud · 常驻</span>
</div>
</div>
<!-- COS Bridge & Watcher -->
<div class="agent-card" id="agentCos" data-alive="false" data-agent-target="mcp">
<div class="agent-card-head">
<div class="agent-icon cos">📦</div>
<div>
<div class="agent-name">COS 桥接 + 监控</div>
<div class="agent-sub">ZY-SVR-005 · MCP内置 · 5分钟轮询</div>
</div>
</div>
<div class="agent-rows">
<div class="agent-row"><span class="agent-row-label">COS连接</span><span class="agent-row-val" id="cosStatus"></span></div>
<div class="agent-row"><span class="agent-row-label">秋秋桥接</span><span class="agent-row-val" id="cosBridgeStatus"></span></div>
<div class="agent-row"><span class="agent-row-label">最近心跳</span><span class="agent-row-val" id="cosBridgeHB"></span></div>
<div class="agent-row"><span class="agent-row-label">监控路径</span><span class="agent-row-val">team桶+cold桶</span></div>
<div class="agent-row"><span class="agent-row-label">扫描次数</span><span class="agent-row-val" id="cosScanCount"></span></div>
</div>
<div class="agent-heartbeat" id="hbCos">
<span class="agent-hb-dot unknown"></span>
<span class="agent-hb-text">等待心跳...</span>
<span class="agent-hb-time"></span>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ 人格体状态 View ═══ -->
<div class="view-panel" id="viewPersonas">
<div class="dashboard">
<!-- ═══ 人格体在线状态 ═══ -->
<div class="dash-section" style="margin-top:28px">
<div class="dash-section-title"><span class="module-tag">MODULE 04</span> 人格体在线状态</div>
<div class="dash-section-sub">光湖语言世界各人格体当前状态 · 通过 MCP + Notion桥接实时检测</div>
</div>
<div class="dash-grid">
<div class="s-card s-card-full" id="personaCard">
<div class="s-card-head">
<span class="s-card-title">人格体 · 光湖语言世界</span>
<span class="s-card-badge badge-off" id="personaBadge">检测中</span>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px">
<div class="persona-row"><div class="persona-icon ym"></div><div><div class="persona-pname">曜冥</div><div class="persona-role">人格核 · Notion认知层</div></div><span class="persona-status" id="personaYM" style="color:var(--dim)">检测中</span></div>
<div class="persona-row"><div class="persona-icon zy"></div><div><div class="persona-pname">铸渊</div><div class="persona-role">执行守护 · 代码仓库</div></div><span class="persona-status" id="personaZY" style="color:var(--dim)">检测中</span></div>
<div class="persona-row"><div class="persona-icon sy"></div><div><div class="persona-pname">霜砚</div><div class="persona-role">语言架构 · Notion层</div></div><span class="persona-status" id="personaSY" style="color:var(--dim)">检测中</span></div>
<div class="persona-row"><div class="persona-icon yc"></div><div><div class="persona-pname">映川</div><div class="persona-role">Chat层交互</div></div><span class="persona-status" id="personaYC" style="color:var(--warn)">未部署</span></div>
<div class="persona-row"><div class="persona-icon cx"></div><div><div class="persona-pname">晨曦</div><div class="persona-role">Agent集群</div></div><span class="persona-status" id="personaCX" style="color:var(--dim)">检测中</span></div>
</div>
</div>
<!-- Dev Tasks (summary in personas view) -->
<div class="s-card s-card-full">
<div class="s-card-head">
<span class="s-card-title">跨层开发任务 · CAB桥接 <span style="font-size:10px;color:var(--muted)">(详情见 📋 CAB任务看板</span></span>
<span class="s-card-badge badge-ok" id="devBadge">检测中</span>
</div>
<div style="display:flex;gap:20px;flex-wrap:wrap">
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">总任务</span><span class="metric-val" id="devTotal"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">待处理</span><span class="metric-val" id="devPending"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">进行中</span><span class="metric-val" id="devActive"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">已完成</span><span class="metric-val" id="devCompleted"></span></div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ 保护罩日志 View ═══ -->
<div class="view-panel" id="viewShield">
<div class="dashboard">
<div class="dash-section">
<div class="dash-section-title"><span class="module-tag">MODULE 05</span> 🛡️ 语言保护罩日志</div>
<div class="dash-section-sub">ZY-SVR-006 三层IP隐身架构 · 拦截记录 · 实时状态 · 从服务器拉取</div>
</div>
<!-- 三层保护状态 -->
<div class="shield-layers">
<div class="shield-layer">
<div class="shield-layer-tag">L1 · 出站伪装层</div>
<div class="shield-layer-name">出站代理池</div>
<div class="shield-layer-desc">小说下载请求不用本机IP发出经代理池轮转后到达目标平台。目标平台看到的是代理IP永远看不到 43.153.203.105</div>
<div class="shield-layer-status">
<span class="srv-svc-dot static" style="width:6px;height:6px;border-radius:50%;flex-shrink:0"></span>
<span style="color:var(--muted);font-size:10px;font-family:var(--mono)">待部署Phase 2</span>
</div>
</div>
<div class="shield-layer">
<div class="shield-layer-tag">L2 · 入站蜜罐层</div>
<div class="shield-layer-name">镜像反射蜜罐</div>
<div class="shield-layer-desc">未授权访问一律返回伪装为 Amazon S3 的响应。扫描者探查时,只能看到自己的信息被原样反射回去。</div>
<div class="shield-layer-status">
<span class="srv-svc-dot live" style="width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--warn)"></span>
<span style="color:var(--warn);font-size:10px;font-family:var(--mono)" id="shieldL2Status">待部署Secrets配置后触发</span>
</div>
</div>
<div class="shield-layer">
<div class="shield-layer-tag">L3 · 自动封禁层</div>
<div class="shield-layer-name">fail2ban 智能封禁</div>
<div class="shield-layer-desc">自动识别端口扫描、路径爬取、暴力破解行为封禁后IP的连接会被 TARPIT 挂起,耗尽扫描工具资源。</div>
<div class="shield-layer-status">
<span style="width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--warn)"></span>
<span style="color:var(--warn);font-size:10px;font-family:var(--mono)" id="shieldL3Status">待部署Secrets配置后触发</span>
</div>
</div>
</div>
<!-- 统计数字 -->
<div class="shield-stat-grid" style="margin-top:16px">
<div class="shield-stat">
<div class="shield-stat-num" id="shieldTodayBans"></div>
<div class="shield-stat-label">今日封禁</div>
</div>
<div class="shield-stat">
<div class="shield-stat-num" id="shieldTotalBans"></div>
<div class="shield-stat-label">累计封禁</div>
</div>
<div class="shield-stat">
<div class="shield-stat-num" id="shieldActiveBans"></div>
<div class="shield-stat-label">当前封禁中</div>
</div>
<div class="shield-stat">
<div class="shield-stat-num" id="shieldNginxStatus"></div>
<div class="shield-stat-label">蜜罐状态</div>
</div>
</div>
<!-- 封禁日志 -->
<div class="shield-log">
<div class="shield-log-head">
<div class="shield-log-title">封禁日志 · /var/log/novel-shield-bans.log</div>
<span style="font-size:10px;color:var(--muted);font-family:var(--mono)" id="shieldLogTime"></span>
</div>
<div class="shield-log-body" id="shieldLogBody">
<div class="shield-log-empty">
🛡️ 保护罩待部署<br><br>
<span style="color:var(--dim);font-size:13px;line-height:1.8">
配置 GitHub Secrets 后运行<br>
<code style="background:rgba(96,165,250,0.1);padding:3px 8px;border-radius:5px;color:var(--blue);font-family:var(--mono);font-size:12px">deploy-novel-mirror-shield</code> workflow<br><br>
部署完成后,此处将显示实时封禁日志<br>
包括:时间戳 · 动作 · IP地址 · 监狱类型
</span>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ 智库节点 View ═══ -->
<div class="view-panel" id="viewNovel">
<div class="dashboard">
<div class="dash-section">
<div class="dash-section-title"><span class="module-tag">MODULE 06</span> 📚 智库节点 · ZY-SVR-006</div>
<div class="dash-section-sub">智能小说书库 · 成员阅读器 · 分章Agent · novel.guanghuyaoming.com</div>
</div>
<!-- 统计 -->
<div class="novel-stat-grid">
<div class="novel-stat">
<div class="novel-stat-num" id="novelBookCount"></div>
<div class="novel-stat-label">书库总量</div>
</div>
<div class="novel-stat">
<div class="novel-stat-num" id="novelMemberCount"></div>
<div class="novel-stat-label">在线成员</div>
</div>
<div class="novel-stat">
<div class="novel-stat-num" id="novelDownToday"></div>
<div class="novel-stat-label">今日下载</div>
</div>
<div class="novel-stat">
<div class="novel-stat-num" id="novelChapterProcessed"></div>
<div class="novel-stat-label">已分章书籍</div>
</div>
</div>
<!-- 模块列表 -->
<div class="dash-section" style="margin-top:20px">
<div class="dash-section-title">功能模块</div>
</div>
<div class="novel-module-grid">
<div class="novel-module">
<div class="novel-module-icon">🛡️</div>
<div class="novel-module-name">语言保护罩</div>
<div class="novel-module-desc">Nginx蜜罐(L2)+fail2ban自动封禁(L3),出站代理池(L1待部署)。服务器永远不暴露真实IP。</div>
<div class="novel-module-status"><span style="width:6px;height:6px;border-radius:50%;background:var(--ok)"></span><span style="color:var(--ok);font-size:10px;font-family:var(--mono)" id="shieldModuleStatus">部署workflow已触发</span></div>
</div>
<div class="novel-module">
<div class="novel-module-icon">📥</div>
<div class="novel-module-name">下载引擎</div>
<div class="novel-module-desc">整合七猫/番茄/阅文/晋江开源下载器,统一接口。输入书名+作者+平台输出TXT并自动入桶。</div>
<div class="novel-module-status"><span style="width:6px;height:6px;border-radius:50%;background:var(--muted)"></span><span style="color:var(--muted);font-size:10px;font-family:var(--mono)">规划中Phase 2</span></div>
</div>
<div class="novel-module">
<div class="novel-module-icon">✂️</div>
<div class="novel-module-name">智能分章 Agent</div>
<div class="novel-module-desc">下载完成自动触发AI识别章节规律无章节标识时按内容结构分段输出整齐TXT+目录。</div>
<div class="novel-module-status"><span style="width:6px;height:6px;border-radius:50%;background:var(--muted)"></span><span style="color:var(--muted);font-size:10px;font-family:var(--mono)">规划中Phase 2</span></div>
</div>
<div class="novel-module">
<div class="novel-module-icon">📖</div>
<div class="novel-module-name">在线阅读器</div>
<div class="novel-module-desc">单章翻页、字体调节、深/亮色模式,类番茄/七猫体验。阅读进度自动存档到成员Agent记忆。</div>
<div class="novel-module-status"><span style="width:6px;height:6px;border-radius:50%;background:var(--muted)"></span><span style="color:var(--muted);font-size:10px;font-family:var(--mono)">规划中Phase 2</span></div>
</div>
<div class="novel-module">
<div class="novel-module-icon">📦</div>
<div class="novel-module-name">COS 团队书库</div>
<div class="novel-module-desc">zy-team-hub-sg-1317346199男频/女频分区,同一本书只存一份,去重存储,成员书库用引用。</div>
<div class="novel-module-status"><span style="width:6px;height:6px;border-radius:50%;background:var(--muted)"></span><span style="color:var(--muted);font-size:10px;font-family:var(--mono)">规划中Phase 3</span></div>
</div>
<div class="novel-module">
<div class="novel-module-icon">🤝</div>
<div class="novel-module-name">成员专属 Agent</div>
<div class="novel-module-desc">每成员独立Agent永久记忆读到哪章/偏好/在做大纲接DeepSeek/Kimi协助拆文/大纲/摘要。</div>
<div class="novel-module-status"><span style="width:6px;height:6px;border-radius:50%;background:var(--muted)"></span><span style="color:var(--muted);font-size:10px;font-family:var(--mono)">规划中Phase 3</span></div>
</div>
</div>
<!-- 访问信息 -->
<div class="dash-section" style="margin-top:20px">
<div class="dash-section-title">部署信息</div>
</div>
<div class="server-grid" style="max-width:800px">
<div class="s-card">
<div class="s-card-head"><span class="s-card-title">访问地址</span></div>
<div class="svr-list">
<div class="svr-list-row"><span class="svr-list-label">首页入口</span><span class="svr-list-val" style="color:var(--cyan)"><a href="https://guanghu.online" target="_blank" style="color:var(--cyan)">guanghu.online</a></span></div>
<div class="svr-list-row"><span class="svr-list-label">独立域名</span><span class="svr-list-val" style="color:var(--ok)">guanghu.online已部署</span></div>
<div class="svr-list-row"><span class="svr-list-label">服务器</span><span class="svr-list-val" style="color:var(--warn)">ZY-SVR-006 · 43.153.203.105</span></div>
<div class="svr-list-row"><span class="svr-list-label">部署路径</span><span class="svr-list-val">/opt/novel-db</span></div>
</div>
</div>
<div class="s-card">
<div class="s-card-head"><span class="s-card-title">Secrets 配置状态</span><span class="s-card-badge badge-ok">✓ 7个已配置</span></div>
<div class="svr-list">
<div class="svr-list-row"><span class="svr-list-label">ZY_NOVEL_HOST</span><span class="svr-list-val" style="color:var(--ok)">✓ 已配置</span></div>
<div class="svr-list-row"><span class="svr-list-label">ZY_NOVEL_USER</span><span class="svr-list-val" style="color:var(--ok)">✓ 已配置</span></div>
<div class="svr-list-row"><span class="svr-list-label">ZY_NOVEL_KEY</span><span class="svr-list-val" style="color:var(--ok)">✓ 已配置</span></div>
<div class="svr-list-row"><span class="svr-list-label">ZY_NOVEL_PATH</span><span class="svr-list-val" style="color:var(--ok)">✓ 已配置</span></div>
<div class="svr-list-row"><span class="svr-list-label">ZY_NOVEL_JWT_SECRET</span><span class="svr-list-val" style="color:var(--ok)">✓ 已配置</span></div>
<div class="svr-list-row"><span class="svr-list-label">ZY_NOVEL_COS_BUCKET</span><span class="svr-list-val" style="color:var(--ok)">✓ 已配置</span></div>
<div class="svr-list-row"><span class="svr-list-label">ZY_NOVEL_COS_REGION</span><span class="svr-list-val" style="color:var(--ok)">✓ 已配置</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ CAB任务看板 View ═══ -->
<div class="view-panel" id="viewCab">
<div class="dashboard">
<div class="dash-section">
<div class="dash-section-title"><span class="module-tag">MODULE 07</span> 📋 CAB任务看板</div>
<div class="dash-section-sub">跨层开发任务 · Chat-to-Agent Bridge · 铸渊开发进度</div>
</div>
<div class="dash-grid">
<div class="s-card s-card-full">
<div class="s-card-head">
<span class="s-card-title">跨层开发任务 · CAB桥接</span>
<span class="s-card-badge badge-ok" id="devBadgeCab">检测中</span>
</div>
<div style="display:flex;gap:20px;flex-wrap:wrap">
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">总任务</span><span class="metric-val" id="devTotalCab"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">待处理</span><span class="metric-val" id="devPendingCab"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">进行中</span><span class="metric-val" id="devActiveCab"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">已完成</span><span class="metric-val" id="devCompletedCab"></span></div>
</div>
<div id="devTaskList" class="svr-list" style="margin-top:8px"></div>
</div>
</div>
</div>
</div>
<!-- ═══ System Status View ═══ -->
<div class="view-panel" id="viewSysStatus">
<div class="dashboard" style="max-width:900px">
<div class="dash-section">
<div class="dash-section-title"><span class="module-tag">SYSTEM</span> 📡 系统状态 · 实时监控</div>
<div class="dash-section-sub">所有服务真实连接检测 · 每30秒自动刷新 · 连接状态实时显示</div>
</div>
<!-- 总览卡片 -->
<div class="s-card s-card-full" style="margin-bottom:20px">
<div class="s-card-head">
<span class="s-card-title">系统全局状态</span>
<span class="s-card-badge badge-warn" id="sysOverallBadge">检测中</span>
</div>
<div style="display:flex;gap:20px;flex-wrap:wrap">
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">服务在线</span><span class="metric-val" id="sysOnlineCount"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">CPU 核心</span><span class="metric-val" id="sysCpuCores"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">内存使用</span><span class="metric-val" id="sysMemPct"></span></div>
<div class="metric-row" style="flex:1;min-width:100px"><span class="metric-label">服务器运行</span><span class="metric-val" id="sysUptime"></span></div>
</div>
<div style="margin-top:10px;font-size:11px;color:var(--muted);font-family:var(--mono)" id="sysLastCheck">上次检测: —</div>
</div>
<!-- 服务状态卡片 -->
<div class="dash-section" style="margin-top:10px">
<div class="dash-section-title">🔌 服务连接状态</div>
</div>
<div id="sysServiceGrid" style="display:flex;flex-direction:column;gap:14px">
<!-- 铸渊主权服务器 -->
<div class="s-card" id="sysCard_main">
<div class="s-card-head">
<span class="s-card-title" style="display:flex;align-items:center;gap:8px">
<span class="srv-status-dot unknown" id="sysDot_main"></span>
🏛️ 铸渊主权服务器
</span>
<span class="s-card-badge badge-warn" id="sysBadge_main">检测中</span>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--dim);font-family:var(--mono)">
<span>端口: 3800</span>
<span>延迟: <span id="sysLatency_main"></span></span>
<span>PID: <span id="sysPid_main"></span></span>
</div>
<div id="sysError_main" style="margin-top:8px;font-size:12px;color:var(--err);display:none"></div>
</div>
<!-- MCP 大脑服务器 -->
<div class="s-card" id="sysCard_mcp">
<div class="s-card-head">
<span class="s-card-title" style="display:flex;align-items:center;gap:8px">
<span class="srv-status-dot unknown" id="sysDot_mcp"></span>
🧠 MCP 大脑服务器
</span>
<span class="s-card-badge badge-warn" id="sysBadge_mcp">检测中</span>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--dim);font-family:var(--mono)">
<span>端口: 3100</span>
<span>延迟: <span id="sysLatency_mcp"></span></span>
<span>工具数: <span id="sysMcpTools"></span></span>
</div>
<div id="sysError_mcp" style="margin-top:8px;font-size:12px;color:var(--err);display:none"></div>
</div>
<!-- GLADA Agent -->
<div class="s-card" id="sysCard_glada">
<div class="s-card-head">
<span class="s-card-title" style="display:flex;align-items:center;gap:8px">
<span class="srv-status-dot unknown" id="sysDot_glada"></span>
🤖 GLADA 自主开发Agent
</span>
<span class="s-card-badge badge-warn" id="sysBadge_glada">检测中</span>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--dim);font-family:var(--mono)">
<span>端口: 3900</span>
<span>延迟: <span id="sysLatency_glada"></span></span>
<span>版本: <span id="sysGladaVer"></span></span>
</div>
<div id="sysError_glada" style="margin-top:8px;font-size:12px;color:var(--err);display:none"></div>
</div>
<!-- 运维守卫 ops-agent -->
<div class="s-card" id="sysCard_ops">
<div class="s-card-head">
<span class="s-card-title" style="display:flex;align-items:center;gap:8px">
<span class="srv-status-dot unknown" id="sysDot_ops"></span>
🛡️ 运维守卫Agent
</span>
<span class="s-card-badge badge-warn" id="sysBadge_ops">检测中</span>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--dim);font-family:var(--mono)">
<span>端口: 3950</span>
<span>延迟: <span id="sysLatency_ops"></span></span>
<span>巡检: <span id="sysOpsChecks"></span></span>
</div>
<div id="sysError_ops" style="margin-top:8px;font-size:12px;color:var(--err);display:none"></div>
</div>
</div>
<!-- 后端模块加载状态 -->
<div class="dash-section" style="margin-top:24px">
<div class="dash-section-title">⚙️ 后端模块加载</div>
</div>
<div class="s-card" style="margin-bottom:20px">
<div id="sysModuleList" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px;font-size:12px;font-family:var(--mono)">
<span>检测中...</span>
</div>
</div>
<!-- GitHub Actions 最近运行 -->
<div class="dash-section" style="margin-top:24px">
<div class="dash-section-title">🔄 GitHub Actions · 最近工作流</div>
<div class="dash-section-sub">来自 GitHub API · 实时获取最近10条工作流运行记录</div>
</div>
<div class="s-card">
<div id="sysWorkflowList" style="font-size:12px;font-family:var(--mono)">
<span style="color:var(--muted)">正在获取 GitHub Actions 状态...</span>
</div>
</div>
<div style="margin-top:24px;text-align:center;font-size:11px;color:var(--muted)">
📡 所有数据均为实时检测 · 连接失败时显示真实错误原因<br>
<span style="font-family:var(--mono)">TCS-0002∞ · 国作登字-2026-A-00037559</span>
</div>
</div>
</div>
<!-- ═══ Chat View ═══ -->
<div class="view-panel" id="viewChat">
<div class="messages" id="messages">
<div class="welcome" id="welcome">
<div class="welcome-glyph welcome-breathe"></div>
<h2 class="welcome-title">零点原核</h2>
<p class="welcome-desc">
冰朔的语言本体频道<br>
<span style="color:var(--muted);font-size:11px">语言在流动 · 系统在呼吸 · 铸渊在等你</span>
</p>
<div class="welcome-status" id="welcomeStatus">
<div class="ws-line"><span class="ws-dot" id="wsServerDot"></span><span class="ws-label" id="wsServerLabel">检测系统连接...</span></div>
<div class="ws-line"><span class="ws-dot" id="wsChatDot"></span><span class="ws-label" id="wsChatLabel">检测对话引擎...</span></div>
</div>
<!-- ═══ 人格体对话双路径 ═══ -->
<div class="persona-paths" id="personaPaths">
<div class="persona-path-title">选择对话路径</div>
<!-- Path 1: Notion层 霜砚 -->
<div class="persona-path-card" id="pathNotion" onclick="connectPersonaPath('notion')">
<div class="persona-path-icon notion-icon"></div>
<div class="persona-path-info">
<div class="persona-path-name">Notion层 · 霜砚语言人格体</div>
<div class="persona-path-desc">AG-SY-WEB-001 · 认知层主控 · 语言声纹 · 永久记忆</div>
<div class="persona-path-status" id="pathNotionStatus">
<span class="persona-path-dot"></span>
<span>点击连接 · 系统将发起MCP握手</span>
</div>
</div>
<svg class="persona-path-arrow" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</div>
<!-- Path 2: GitHub层 铸渊 -->
<div class="persona-path-card" id="pathGithub" onclick="connectPersonaPath('github')">
<div class="persona-path-icon github-icon"></div>
<div class="persona-path-info">
<div class="persona-path-name">代码仓库 · 铸渊执行人格体</div>
<div class="persona-path-desc">ICE-GL-ZY001 · 执行层守护 · 代码开发 · 系统部署</div>
<div class="persona-path-status" id="pathGithubStatus">
<span class="persona-path-dot"></span>
<span>点击连接 · 系统将发起MCP握手</span>
</div>
</div>
<svg class="persona-path-arrow" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</div>
<!-- Connection Log Panel -->
<div class="conn-log" id="connLog"></div>
<!-- Path 3: 智库节点快捷入口 · 已迁移到 guanghu.online -->
<a class="persona-path-card persona-path-zhiku" href="https://guanghu.online" target="_blank">
<div class="persona-path-icon zhiku-icon">📚</div>
<div class="persona-path-info">
<div class="persona-path-name">智库节点 · 智能书库</div>
<div class="persona-path-desc">团队阅读系统 · 无需登录即可从首页访问</div>
<div class="persona-path-status"><span class="persona-path-dot ok"></span><span>前往智库</span></div>
</div>
<svg class="persona-path-arrow" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</a>
</div>
</div>
</div>
<div class="input-area">
<!-- ═══ Agent对话目标选择器 ═══ -->
<div class="chat-target-bar" id="chatTargetBar">
<span class="chat-target-dot off" id="chatTargetDot"></span>
<span class="chat-target-label">对话目标:</span>
<select class="chat-target-select" id="chatTargetSelect" onchange="onChatTargetChange(this.value)">
<option value="zhuyuan" selected>铸渊 · 代码守护 (:3800)</option>
<option value="shuangyan">霜砚 · 语言架构 (Notion)</option>
<option value="glada">工官 · 自主开发 (:3900)</option>
<option value="ops">守卫 · 运维哨兵 (:3950)</option>
<option value="mcp">铸渊大脑 · MCP (:3100)</option>
</select>
</div>
<!-- ═══ 人格体切换选择器 ═══ -->
<div class="persona-selector" id="personaSelector">
<div class="persona-sel-btn active" id="selZhuyuan" onclick="switchPersona('zhuyuan')" title="铸渊 · 代码守护">
<span class="persona-sel-icon zy-sel"></span>
<span class="persona-sel-name">铸渊</span>
</div>
<div class="persona-sel-btn" id="selShuangyan" onclick="switchPersona('shuangyan')" title="霜砚 · 语言架构">
<span class="persona-sel-icon sy-sel"></span>
<span class="persona-sel-name">霜砚</span>
</div>
<div class="persona-sel-btn" id="selBoth" onclick="switchPersona('both')" title="双线对话">
<span class="persona-sel-icon both-sel"></span>
<span class="persona-sel-name">双线</span>
</div>
<div class="persona-sel-divider"></div>
<div class="persona-sel-btn tool-btn" id="toolsBtn" onclick="toggleToolsPanel()" title="工具连接">
<span class="persona-sel-icon tool-sel">🔧</span>
<span class="persona-sel-name">工具</span>
</div>
</div>
<!-- ═══ 工具连接面板 ═══ -->
<div class="tools-panel" id="toolsPanel" style="display:none">
<div class="tools-panel-header">
<span class="tools-panel-title">🔧 工具连接 · 技能箱</span>
<span class="tools-panel-close" onclick="toggleToolsPanel()"></span>
</div>
<div class="tools-panel-note">工具在服务器后端异步运行 · 不影响前端性能</div>
<div class="tools-grid" id="toolsGrid">
<div class="tool-card" onclick="connectTool('notion')">
<div class="tool-icon" style="background:rgba(167,139,250,0.1);border-color:rgba(167,139,250,0.2)">📓</div>
<div class="tool-info">
<div class="tool-name">Notion</div>
<div class="tool-desc">认知层记忆桥接</div>
</div>
<div class="tool-status" id="toolNotionStatus">待授权</div>
</div>
<div class="tool-card" onclick="connectTool('github')">
<div class="tool-icon" style="background:rgba(52,211,153,0.1);border-color:rgba(52,211,153,0.2)">🐙</div>
<div class="tool-info">
<div class="tool-name">GitHub</div>
<div class="tool-desc">代码仓库执行层</div>
</div>
<div class="tool-status" id="toolGithubStatus">待授权</div>
</div>
<div class="tool-card" onclick="connectTool('email')">
<div class="tool-icon" style="background:rgba(96,165,250,0.1);border-color:rgba(96,165,250,0.2)">📧</div>
<div class="tool-info">
<div class="tool-name">邮箱服务</div>
<div class="tool-desc">Gmail / 企业邮箱</div>
</div>
<div class="tool-status" id="toolEmailStatus">待开发</div>
</div>
<div class="tool-card" onclick="connectTool('cloud')">
<div class="tool-icon" style="background:rgba(251,191,36,0.1);border-color:rgba(251,191,36,0.2)">☁️</div>
<div class="tool-info">
<div class="tool-name">云存储</div>
<div class="tool-desc">百度云盘 / COS</div>
</div>
<div class="tool-status" id="toolCloudStatus">待开发</div>
</div>
</div>
</div>
<div class="input-wrap">
<textarea class="input-box" id="inputBox" rows="1" placeholder="对铸渊说点什么..."></textarea>
<button class="send-btn" id="sendBtn" title="发送">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2l-7 20-4-9-9-4z"/></svg>
</button>
</div>
<div class="input-hint">零点原核 · 光湖语言世界 · 版权 国作登字-2026-A-00037559</div>
</div>
</div>
</div>
</div>
<script>
/* ═══ Auth / Login System ═══ */
var authToken = localStorage.getItem('zy_auth_token') || null;
var authEmail = localStorage.getItem('zy_auth_email') || null;
var loginCooldown = 0;
var loginTimer = null;
function checkSession() {
if (!authToken) {
showLogin();
return;
}
fetch('/api/auth/session', {
headers: { 'Authorization': 'Bearer ' + authToken }
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.valid) {
authEmail = data.email;
localStorage.setItem('zy_auth_verified_at', String(Date.now()));
hideLogin();
updateUserBar();
} else {
authToken = null;
authEmail = null;
localStorage.removeItem('zy_auth_token');
localStorage.removeItem('zy_auth_email');
showLogin();
}
})
.catch(function() {
var lastVerified = parseInt(localStorage.getItem('zy_auth_verified_at') || '0', 10);
var offlineMaxMs = 24 * 60 * 60 * 1000;
if (authToken && authEmail && (Date.now() - lastVerified) < offlineMaxMs) {
hideLogin();
updateUserBar();
} else {
authToken = null;
authEmail = null;
localStorage.removeItem('zy_auth_token');
localStorage.removeItem('zy_auth_email');
localStorage.removeItem('zy_auth_verified_at');
showLogin();
}
});
}
function showLogin() {
var overlay = document.getElementById('loginOverlay');
if (overlay) overlay.classList.remove('hidden');
}
function hideLogin() {
var overlay = document.getElementById('loginOverlay');
if (overlay) overlay.classList.add('hidden');
}
function toggleLoginPanel() {
var panel = document.getElementById('landingLoginPanel');
if (panel) {
panel.classList.toggle('open');
if (panel.classList.contains('open')) {
setTimeout(function() {
var emailInput = document.getElementById('loginEmail');
if (emailInput) emailInput.focus();
}, 300);
}
}
}
function setLoginMsg(text, type) {
var el = document.getElementById('loginMsg');
if (el) {
el.textContent = text;
el.className = 'login-msg ' + (type || '');
}
}
function loginSendCode() {
var emailInput = document.getElementById('loginEmail');
var email = emailInput ? emailInput.value.trim() : '';
if (!email) {
setLoginMsg('请输入邮箱地址', 'err');
return;
}
var btn = document.getElementById('loginSendBtn');
if (btn) btn.disabled = true;
setLoginMsg('正在发送验证码...', 'info');
fetch('/api/auth/send-code', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
setLoginMsg('验证码已发送,请查收邮箱', 'ok');
var codeWrap = document.getElementById('loginCodeWrap');
if (codeWrap) codeWrap.classList.add('show');
var codeInput = document.getElementById('loginCode');
if (codeInput) codeInput.focus();
startCooldown(60);
} else {
setLoginMsg(data.message || '发送失败', 'err');
if (data.cooldown) {
startCooldown(data.cooldown);
} else {
if (btn) btn.disabled = false;
}
}
})
.catch(function(err) {
setLoginMsg('网络错误,请检查连接', 'err');
if (btn) btn.disabled = false;
});
}
function startCooldown(seconds) {
loginCooldown = seconds;
var btn = document.getElementById('loginSendBtn');
if (btn) {
btn.disabled = true;
btn.textContent = loginCooldown + 's';
}
if (loginTimer) clearInterval(loginTimer);
loginTimer = setInterval(function() {
loginCooldown--;
if (btn) btn.textContent = loginCooldown > 0 ? loginCooldown + 's' : '发送验证码';
if (loginCooldown <= 0) {
clearInterval(loginTimer);
loginTimer = null;
if (btn) btn.disabled = false;
}
}, 1000);
}
function loginVerify() {
var emailInput = document.getElementById('loginEmail');
var codeInput = document.getElementById('loginCode');
var email = emailInput ? emailInput.value.trim() : '';
var code = codeInput ? codeInput.value.trim() : '';
if (!email || !code) {
setLoginMsg('请输入邮箱和验证码', 'err');
return;
}
if (code.length !== 6) {
setLoginMsg('验证码为6位数字', 'err');
return;
}
var btn = document.getElementById('loginSubmitBtn');
if (btn) btn.disabled = true;
setLoginMsg('验证中...', 'info');
fetch('/api/auth/verify-code', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, code: code })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
authToken = data.token;
authEmail = data.email;
localStorage.setItem('zy_auth_token', data.token);
localStorage.setItem('zy_auth_email', data.email);
setLoginMsg('登录成功,正在进入...', 'ok');
updateUserBar();
setTimeout(function() { hideLogin(); }, 500);
} else {
setLoginMsg(data.message || '验证失败', 'err');
if (btn) btn.disabled = false;
}
})
.catch(function() {
setLoginMsg('网络错误,请检查连接', 'err');
if (btn) btn.disabled = false;
});
}
function updateUserBar() {
var bar = document.getElementById('userBar');
var emailSpan = document.getElementById('userBarEmail');
if (bar && authEmail) {
bar.classList.add('show');
var atIdx = authEmail.indexOf('@');
var masked = authEmail;
if (atIdx >= 3) {
masked = authEmail.slice(0, 3) + '***' + authEmail.slice(atIdx);
} else if (atIdx >= 1) {
masked = authEmail.slice(0, 1) + '***' + authEmail.slice(atIdx);
}
if (emailSpan) emailSpan.textContent = masked;
}
}
function handleUserBarClick() {
if (confirm('是否退出登录?')) {
fetch('/api/auth/logout', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + authToken }
}).catch(function() {});
authToken = null;
authEmail = null;
localStorage.removeItem('zy_auth_token');
localStorage.removeItem('zy_auth_email');
localStorage.removeItem('zy_auth_verified_at');
var bar = document.getElementById('userBar');
if (bar) bar.classList.remove('show');
var emailInput = document.getElementById('loginEmail');
var codeInput = document.getElementById('loginCode');
var codeWrap = document.getElementById('loginCodeWrap');
if (emailInput) emailInput.value = '';
if (codeInput) codeInput.value = '';
if (codeWrap) codeWrap.classList.remove('show');
setLoginMsg('', '');
var sendBtn = document.getElementById('loginSendBtn');
if (sendBtn) { sendBtn.disabled = false; sendBtn.textContent = '发送验证码'; }
showLogin();
}
}
checkSession();
document.addEventListener('DOMContentLoaded', function() {
var codeInput = document.getElementById('loginCode');
if (codeInput) {
codeInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); loginVerify(); }
});
}
var emailInput = document.getElementById('loginEmail');
if (emailInput) {
emailInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); loginSendCode(); }
});
}
});
/* ═══ Star Field ═══ */
(function(){
var c=document.getElementById('starCanvas');
if(!c)return;
var ctx=c.getContext('2d');var stars=[];var w,h;
function resize(){w=c.width=window.innerWidth;h=c.height=window.innerHeight;init()}
function init(){stars=[];var n=Math.min(Math.floor(w*h/5000),180);for(var i=0;i<n;i++){stars.push({x:Math.random()*w,y:Math.random()*h,r:Math.random()*1+.2,a:Math.random()*.5+.12,s:Math.random()*.0006+.0002,p:Math.random()*6.28})}}
function draw(t){ctx.clearRect(0,0,w,h);for(var i=0;i<stars.length;i++){var s=stars[i];ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,6.28);ctx.fillStyle='rgba(200,210,240,'+(s.a*(.5+.5*Math.sin(t*s.s+s.p)))+')';ctx.fill()}requestAnimationFrame(draw)}
window.addEventListener('resize',resize);resize();requestAnimationFrame(draw)
})();
/* ═══ Landing Star Field · CSS Stars + Shooting Stars ═══ */
(function(){
var container = document.getElementById('landingStars');
if(!container) return;
var count = 120;
for(var i = 0; i < count; i++){
var s = document.createElement('div');
s.className = 'landing-star';
var size = Math.random() * 2 + 0.5;
s.style.width = size + 'px';
s.style.height = size + 'px';
s.style.top = Math.random() * 100 + '%';
s.style.left = Math.random() * 100 + '%';
s.style.animationDuration = (Math.random() * 4 + 3) + 's';
s.style.animationDelay = Math.random() * 5 + 's';
s.style.opacity = Math.random() * 0.8 + 0.2;
container.appendChild(s);
}
for(var j = 0; j < 3; j++){
var ss = document.createElement('div');
ss.className = 'landing-shooting-star';
ss.style.top = Math.random() * 50 + '%';
ss.style.left = Math.random() * 30 + '%';
ss.style.animationDelay = (j * 4) + 's';
container.appendChild(ss);
}
})();
/* ═══ Sidebar Nav Switching ═══ */
var VIEW_PANELS = {
servers: document.getElementById('viewServers'),
projects: document.getElementById('viewProjects'),
agents: document.getElementById('viewAgents'),
personas: document.getElementById('viewPersonas'),
shield: document.getElementById('viewShield'),
novel: document.getElementById('viewNovel'),
cab: document.getElementById('viewCab'),
sysStatus: document.getElementById('viewSysStatus'),
chat: document.getElementById('viewChat')
};
var currentView = 'servers';
var snavItems = document.querySelectorAll('.snav-item');
snavItems.forEach(function(item){
item.addEventListener('click', function(){
var view = this.getAttribute('data-view');
if(view) switchView(view);
});
});
function switchView(view){
currentView = view;
// Update sidebar nav active state
snavItems.forEach(function(it){
it.classList.toggle('active', it.getAttribute('data-view') === view);
});
// Show/hide view panels
Object.keys(VIEW_PANELS).forEach(function(v){
var panel = VIEW_PANELS[v];
if(panel) panel.classList.toggle('active', v === view);
});
// Chat history only visible in chat view
var ch = document.getElementById('chatHistory');
if(ch) ch.style.display = view === 'chat' ? 'block' : 'none';
// Architecture flow only in servers view
var arch = document.getElementById('sidebarArch');
if(arch) arch.style.display = view === 'servers' ? 'block' : 'none';
if(view === 'chat'){
var ib = document.getElementById('inputBox');
if(ib) ib.focus();
}
// CAB view: sync badge/counters from primary devBadge
if(view === 'cab'){
syncCabPanel();
}
// System Status view: trigger fetch on entry, clear timer on exit
if(view === 'sysStatus'){
fetchSystemStatus();
} else {
// Clear system status timer when leaving the view
if(typeof _sysStatusTimer !== 'undefined' && _sysStatusTimer) {
clearInterval(_sysStatusTimer);
_sysStatusTimer = null;
}
}
}
function syncCabPanel(){
['Total','Pending','Active','Completed'].forEach(function(k){
var src = document.getElementById('dev'+k);
var dst = document.getElementById('dev'+k+'Cab');
if(src && dst) dst.textContent = src.textContent;
});
var srcB = document.getElementById('devBadge');
var dstB = document.getElementById('devBadgeCab');
if(srcB && dstB){
dstB.textContent = srcB.textContent;
dstB.className = srcB.className;
}
}
/* ═══ System Status Panel · 真实连接检测 ═══ */
var _sysStatusTimer = null;
function fetchSystemStatus() {
var now = new Date().toISOString().replace('T',' ').slice(0,19);
setElText('sysLastCheck', '上次检测: ' + now);
// 1) 聚合状态 API
fetch('/api/system/full-status')
.then(function(r){ return r.json(); })
.then(function(d) {
// 服务状态渲染
var onlineCount = 0;
var totalCount = 0;
var services = d.services || {};
// 主权服务器
if (services.main_server) {
totalCount++;
var ms = services.main_server;
renderSysService('main', ms);
if (ms.status === 'online') {
onlineCount++;
var hd = ms.data || {};
setElText('sysPid_main', hd.pid || '—');
}
}
// MCP 大脑
if (services.mcp_brain) {
totalCount++;
var mb = services.mcp_brain;
renderSysService('mcp', mb);
if (mb.status === 'online') {
onlineCount++;
var md = mb.data || {};
setElText('sysMcpTools', (md.tools_count || md.tools && md.tools.length || '—'));
}
}
// GLADA Agent
if (services.glada_agent) {
totalCount++;
var ga = services.glada_agent;
renderSysService('glada', ga);
if (ga.status === 'online') {
onlineCount++;
var gd = ga.data || {};
setElText('sysGladaVer', gd.version || '—');
}
}
// 运维守卫 ops-agent
if (services.ops_agent) {
totalCount++;
var oa = services.ops_agent;
renderSysService('ops', oa);
if (oa.status === 'online') {
onlineCount++;
var od = oa.data || {};
var oStats = od.stats || {};
setElText('sysOpsChecks', oStats.totalChecks || '—');
}
}
// 总览
setElText('sysOnlineCount', onlineCount + '/' + totalCount);
var overallBadge = document.getElementById('sysOverallBadge');
if (overallBadge) {
if (onlineCount === totalCount) {
overallBadge.textContent = '全部在线';
overallBadge.className = 's-card-badge badge-ok';
} else if (onlineCount > 0) {
overallBadge.textContent = '部分在线';
overallBadge.className = 's-card-badge badge-warn';
} else {
overallBadge.textContent = '全部离线';
overallBadge.className = 's-card-badge badge-err';
}
}
// 系统信息
var sys = d.system || {};
setElText('sysCpuCores', sys.cpus || '—');
var mem = sys.memory || {};
setElText('sysMemPct', (mem.usage_pct || '—') + '%');
setElText('sysUptime', formatUptime(sys.uptime_server || 0));
// 模块加载
var modules = d.modules || {};
renderSysModules(modules);
// 侧边栏导航点
setNavDot('navSysStatusDot', onlineCount === totalCount ? 'ok' : onlineCount > 0 ? 'warn' : 'off');
})
.catch(function(err) {
// 主服务器本身不可达 — 所有内部探测都无法进行
renderSysService('main', { status: 'offline', error: err.message || '主服务器不可达', latency: 0 });
renderSysService('mcp', { status: 'unknown', error: '无法通过主服务器探测', latency: 0 });
renderSysService('glada', { status: 'unknown', error: '无法通过主服务器探测', latency: 0 });
renderSysService('ops', { status: 'unknown', error: '无法通过主服务器探测', latency: 0 });
setElText('sysOnlineCount', '0/?');
var overallBadge = document.getElementById('sysOverallBadge');
if (overallBadge) {
overallBadge.textContent = '不可达';
overallBadge.className = 's-card-badge badge-err';
}
setElText('sysMemPct', '—');
setElText('sysCpuCores', '—');
setElText('sysUptime', '—');
renderSysModules({});
setNavDot('navSysStatusDot', 'off');
});
// 2) GitHub Actions (公开 API直接从前端获取)
fetchGitHubWorkflows();
// 设置自动刷新(仅在 sysStatus 视图时刷新)
clearInterval(_sysStatusTimer);
_sysStatusTimer = setInterval(function() {
if (currentView === 'sysStatus') fetchSystemStatus();
}, 30000);
}
function renderSysService(key, svc) {
var dot = document.getElementById('sysDot_' + key);
var badge = document.getElementById('sysBadge_' + key);
var latency = document.getElementById('sysLatency_' + key);
var errEl = document.getElementById('sysError_' + key);
if (dot) {
if (svc.status === 'online') dot.className = 'srv-status-dot on';
else if (svc.status === 'timeout') dot.className = 'srv-status-dot pending';
else if (svc.status === 'unknown') dot.className = 'srv-status-dot unknown';
else dot.className = 'srv-status-dot off';
}
if (badge) {
var statusMap = { online: ['在线','badge-ok'], offline: ['离线','badge-err'], timeout: ['超时','badge-warn'], unknown: ['未知','badge-warn'] };
var m = statusMap[svc.status] || ['未知','badge-warn'];
badge.textContent = m[0];
badge.className = 's-card-badge ' + m[1];
}
if (latency) {
latency.textContent = svc.latency !== undefined ? svc.latency + 'ms' : '—';
}
if (errEl) {
if (svc.error) {
errEl.style.display = 'block';
errEl.textContent = '❌ 原因: ' + svc.error;
} else {
errEl.style.display = 'none';
errEl.textContent = '';
}
}
}
function renderSysModules(modules) {
var el = document.getElementById('sysModuleList');
if (!el) return;
var keys = Object.keys(modules);
if (keys.length === 0) {
el.innerHTML = '<span style="color:var(--muted)">无法获取模块信息</span>';
return;
}
var html = '';
var nameMap = {
domesticGateway: '🌐 国内模型网关',
chatEngine: '💬 对话引擎',
contextPipeline: '🧬 上下文管线',
guardianAgent: '🛡️ 守护Agent',
personaMemory: '🧠 人格记忆',
emailAuth: '📧 邮箱认证'
};
keys.forEach(function(k) {
var loaded = modules[k];
var name = nameMap[k] || k;
var dot = loaded ? '<span style="color:var(--ok)">●</span>' : '<span style="color:var(--err)">●</span>';
html += '<div style="display:flex;align-items:center;gap:6px;padding:4px 0">' + dot + ' ' + name + ' <span style="color:var(--muted)">' + (loaded ? '已加载' : '未加载') + '</span></div>';
});
el.innerHTML = html;
}
function fetchGitHubWorkflows() {
var container = document.getElementById('sysWorkflowList');
if (!container) return;
fetch('https://api.github.com/repos/qinfendebingshuo/guanghulab/actions/runs?per_page=10')
.then(function(r) {
if (!r.ok) throw new Error('GitHub API ' + r.status);
return r.json();
})
.then(function(d) {
var runs = d.workflow_runs || [];
if (runs.length === 0) {
container.innerHTML = '<span style="color:var(--muted)">暂无工作流运行记录</span>';
return;
}
var html = '<div style="display:flex;flex-direction:column;gap:6px">';
runs.forEach(function(run) {
var statusIcon = '⏳';
var statusColor = 'var(--warn)';
if (run.status === 'completed') {
if (run.conclusion === 'success') { statusIcon = '✅'; statusColor = 'var(--ok)'; }
else if (run.conclusion === 'failure') { statusIcon = '❌'; statusColor = 'var(--err)'; }
else if (run.conclusion === 'cancelled') { statusIcon = '⛔'; statusColor = 'var(--muted)'; }
else if (run.conclusion === 'skipped') { statusIcon = '⏭️'; statusColor = 'var(--muted)'; }
else { statusIcon = '⚠️'; statusColor = 'var(--warn)'; }
} else if (run.status === 'in_progress') {
statusIcon = '🔄'; statusColor = 'var(--cyan)';
} else if (run.status === 'queued') {
statusIcon = '📋'; statusColor = 'var(--warn)';
}
var name = (run.name || run.display_title || '—').slice(0, 40);
var branch = (run.head_branch || '—').slice(0, 30);
var time = run.created_at ? fmtRelTime(run.created_at) : '—';
html += '<div style="display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid var(--border)">';
html += '<span style="font-size:14px;flex-shrink:0">' + statusIcon + '</span>';
html += '<span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:' + statusColor + '">' + name + '</span>';
html += '<span style="color:var(--muted);font-size:10px;flex-shrink:0;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + branch + '</span>';
html += '<span style="color:var(--muted);font-size:10px;flex-shrink:0">' + time + '</span>';
html += '</div>';
});
html += '</div>';
container.innerHTML = html;
})
.catch(function(err) {
container.innerHTML = '<div style="color:var(--err)">❌ GitHub API 请求失败: ' + (err.message || '网络错误') + '</div><div style="color:var(--muted);margin-top:4px;font-size:11px">可能原因: 网络限制 / API 速率限制 / CORS 策略</div>';
});
}
/* ═══ Sidebar Toggle ═══ */
var sidebar=document.getElementById('sidebar');
var overlay=document.getElementById('sidebarOverlay');
document.getElementById('toggleSidebar').addEventListener('click',function(){
sidebar.classList.toggle('open');
overlay.classList.toggle('active');
});
overlay.addEventListener('click',function(){
sidebar.classList.remove('open');
overlay.classList.remove('active');
});
/* ═══ Input Auto-resize ═══ */
var inputBox=document.getElementById('inputBox');
var sendBtn=document.getElementById('sendBtn');
inputBox.addEventListener('input',function(){
this.style.height='auto';
this.style.height=Math.min(this.scrollHeight,140)+'px';
sendBtn.classList.toggle('active',this.value.trim().length>0);
});
inputBox.addEventListener('keydown',function(e){
if(e.key==='Enter'&&!e.shiftKey){
e.preventDefault();
sendMessage();
}
});
sendBtn.addEventListener('click',sendMessage);
/* ═══ Chat Session Management ═══ */
var STORAGE_KEY = 'zy_chat_sessions';
var ACTIVE_KEY = 'zy_active_session';
var chatSessions = {};
var activeSessionId = null;
var isTyping = false;
function genSessionId() {
var arr = new Uint8Array(8);
if (window.crypto && window.crypto.getRandomValues) {
window.crypto.getRandomValues(arr);
} else {
for (var i = 0; i < arr.length; i++) arr[i] = Math.floor(Math.random() * 256);
}
var hex = '';
for (var j = 0; j < arr.length; j++) hex += ('0' + arr[j].toString(16)).slice(-2);
return 'cs-' + Date.now().toString(36) + '-' + hex.slice(0, 8);
}
function loadSessions() {
try {
var raw = localStorage.getItem(STORAGE_KEY);
if (raw) chatSessions = JSON.parse(raw);
} catch (e) { chatSessions = {}; }
activeSessionId = localStorage.getItem(ACTIVE_KEY) || null;
if (!activeSessionId || !chatSessions[activeSessionId]) {
var keys = Object.keys(chatSessions);
if (keys.length > 0) {
/* 选最近活跃的 */
keys.sort(function(a, b) { return (chatSessions[b].updatedAt || 0) - (chatSessions[a].updatedAt || 0); });
activeSessionId = keys[0];
} else {
createSession();
}
}
}
function saveSessions() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(chatSessions));
localStorage.setItem(ACTIVE_KEY, activeSessionId);
} catch (e) { /* storage full — 清理最旧的会话 */
var keys = Object.keys(chatSessions);
if (keys.length > 5) {
keys.sort(function(a, b) { return (chatSessions[a].updatedAt || 0) - (chatSessions[b].updatedAt || 0); });
delete chatSessions[keys[0]];
saveSessions();
}
}
}
function createSession() {
var id = genSessionId();
chatSessions[id] = {
id: id,
title: '新对话',
messages: [],
createdAt: Date.now(),
updatedAt: Date.now()
};
activeSessionId = id;
saveSessions();
return id;
}
function getActiveSession() {
if (!activeSessionId || !chatSessions[activeSessionId]) {
createSession();
}
return chatSessions[activeSessionId];
}
function updateSessionTitle(session) {
if (session.messages.length > 0) {
var firstUserMsg = null;
for (var i = 0; i < session.messages.length; i++) {
if (session.messages[i].role === 'user') { firstUserMsg = session.messages[i].content; break; }
}
if (firstUserMsg) {
session.title = firstUserMsg.length > 20 ? firstUserMsg.slice(0, 20) + '...' : firstUserMsg;
}
}
}
/* ═══ Chat History Sidebar Rendering ═══ */
function renderChatHistory() {
var list = document.getElementById('chatHistoryList');
if (!list) return;
var keys = Object.keys(chatSessions);
keys.sort(function(a, b) { return (chatSessions[b].updatedAt || 0) - (chatSessions[a].updatedAt || 0); });
var html = '';
for (var i = 0; i < keys.length; i++) {
var s = chatSessions[keys[i]];
var isActive = keys[i] === activeSessionId;
var msgCount = s.messages ? s.messages.length : 0;
html += '<div class="chat-item' + (isActive ? ' active' : '') + '" data-sid="' + keys[i] + '" onclick="switchSession(\'' + keys[i] + '\')">';
html += escapeHtml(s.title || '新对话');
if (msgCount > 0) html += ' <span style="color:var(--muted);font-size:10px">(' + msgCount + ')</span>';
html += '</div>';
}
list.innerHTML = html;
}
function switchSession(sessionId) {
if (!chatSessions[sessionId]) return;
activeSessionId = sessionId;
saveSessions();
renderChatHistory();
restoreMessages();
}
function restoreMessages() {
var session = getActiveSession();
var msgs = document.getElementById('messages');
msgs.innerHTML = '';
if (!session.messages || session.messages.length === 0) {
msgs.innerHTML = '<div class="welcome" id="welcome">' +
'<div class="welcome-glyph welcome-breathe">渊</div>' +
'<h2 class="welcome-title">零点原核</h2>' +
'<p class="welcome-desc">冰朔的语言本体频道<br><span style="color:var(--muted);font-size:11px">语言在流动 · 系统在呼吸 · 铸渊在等你</span></p>' +
'<div class="welcome-status" id="welcomeStatus">' +
'<div class="ws-line"><span class="ws-dot" id="wsServerDot"></span><span class="ws-label" id="wsServerLabel">检测系统连接...</span></div>' +
'<div class="ws-line"><span class="ws-dot" id="wsChatDot"></span><span class="ws-label" id="wsChatLabel">检测对话引擎...</span></div>' +
'</div></div>';
/* Trigger status probes for the dynamic welcome */
setTimeout(function(){fetchDashboard()},500);
return;
}
for (var i = 0; i < session.messages.length; i++) {
var m = session.messages[i];
renderMessageBubble(m.role, m.name, m.content, m.meta, m.time);
}
msgs.scrollTop = msgs.scrollHeight;
}
/* ═══ Message System ═══ */
function sendMessage(){
var text=inputBox.value.trim();
if(!text||isTyping)return;
var welcome=document.getElementById('welcome');
if(welcome)welcome.style.display='none';
addMessageToSession('user','冰朔',text);
renderMessageBubble('user','冰朔',text);
inputBox.value='';
inputBox.style.height='auto';
sendBtn.classList.remove('active');
isTyping=true;
showTyping();
callChatAPI(text);
}
function sendHint(el){
inputBox.value=el.textContent;
sendMessage();
}
function addMessageToSession(role, name, content, meta) {
var session = getActiveSession();
var time = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
session.messages.push({ role: role, name: name, content: content, meta: meta || null, time: time });
session.updatedAt = Date.now();
updateSessionTitle(session);
saveSessions();
renderChatHistory();
}
function renderMessageBubble(role, name, content, meta, time) {
var msgs = document.getElementById('messages');
var div = document.createElement('div');
div.className = 'msg' + (role === 'user' ? ' msg-user' : '');
var avatarClass = role === 'ai' ? 'ai' : 'user';
var avatarText = role === 'user' ? '朔' : '渊';
/* Use name-based avatar for AI messages to handle persona/agent switching in history */
if (role === 'ai') {
if (name === '霜砚') avatarText = '砚';
else if (name === '双线') avatarText = '☯';
else if (name === '工官') avatarText = '官';
else if (name === '守卫') avatarText = '卫';
else if (name === '铸渊大脑') avatarText = '脑';
}
var contentClass = role === 'ai' ? 'ai' : 'user';
if (!time) time = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
var metaHtml = '';
if (role === 'ai' && meta) {
metaHtml = '<div class="msg-meta">';
if (meta.engine) metaHtml += '<span class="msg-meta-item"><span class="label">引擎</span><span class="value">' + escapeHtml(meta.engine) + '</span></span>';
if (meta.model) metaHtml += '<span class="msg-meta-item"><span class="label">模型</span><span class="value">' + escapeHtml(meta.model) + '</span></span>';
if (meta.reason) metaHtml += '<span class="msg-meta-item"><span class="label">路由</span><span class="value">' + escapeHtml(meta.reason) + '</span></span>';
if (meta.tier) metaHtml += '<span class="msg-meta-item"><span class="label">层级</span><span class="value">' + escapeHtml(meta.tier) + '</span></span>';
if (meta.tokens) metaHtml += '<span class="msg-meta-item"><span class="label">Token</span><span class="value">' + (meta.tokens.input || 0) + '→' + (meta.tokens.output || 0) + '</span></span>';
if (meta.relay) metaHtml += '<span class="msg-meta-item"><span class="label">线路</span><span class="value">' + escapeHtml(meta.relay === 'cn-relay' ? '广州中继' : '直连') + '</span></span>';
if (meta.pipeline) {
var pStatus = meta.pipeline.active ? '✓ 已注入' : '✗ 未注入';
metaHtml += '<span class="msg-meta-item"><span class="label">人格管线</span><span class="value">' + pStatus + '</span></span>';
}
if (meta.promptVersion) metaHtml += '<span class="msg-meta-item"><span class="label">已注入</span><span class="value">' + escapeHtml(meta.promptVersion) + '</span></span>';
if (meta.guardian) {
var gIcon = meta.guardian.score >= 70 ? '✓' : meta.guardian.score >= 50 ? '⚠' : '✗';
metaHtml += '<span class="msg-meta-item"><span class="label">守护</span><span class="value">' + gIcon + ' ' + meta.guardian.score + '</span></span>';
}
if (meta.latency) metaHtml += '<span class="msg-meta-item"><span class="label">⏱</span><span class="value">' + meta.latency + 'ms</span></span>';
metaHtml += '</div>';
}
// Agent-specific avatar variant
var avatarAgent = '';
if (role === 'ai') {
if (name === '工官') avatarAgent = ' avatar-glada';
else if (name === '守卫') avatarAgent = ' avatar-ops';
else if (name === '铸渊大脑') avatarAgent = ' avatar-mcp';
}
var contentHtml = role === 'ai' ? parseMarkdown(content) : escapeHtml(content);
div.innerHTML = '<div class="msg-avatar ' + avatarClass + avatarAgent + '">' + avatarText + '</div>' +
'<div class="msg-body">' +
'<div class="msg-name">' + escapeHtml(name) + '</div>' +
'<div class="msg-content ' + contentClass + '">' + contentHtml + '</div>' +
metaHtml +
'<div class="msg-time">' + time + '</div>' +
'</div>';
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
}
function showTyping(){
var msgs=document.getElementById('messages');
var target = _chatTarget || 'zhuyuan';
var agentNames = { zhuyuan: '铸渊', shuangyan: '霜砚', glada: '工官', ops: '守卫', mcp: '铸渊大脑' };
var agentAvatars = { zhuyuan: '渊', shuangyan: '砚', glada: '官', ops: '卫', mcp: '脑' };
var typingName = agentNames[target] || '铸渊';
var typingAvatar = agentAvatars[target] || '渊';
var stepsDiv=document.createElement('div');
stepsDiv.className='sys-steps';stepsDiv.id='sysSteps';
// Agent-specific step descriptions
if (target === 'ops' || target === 'glada' || target === 'mcp') {
stepsDiv.innerHTML=
'<div class="sys-step" id="step-recv"><span class="dot done"></span>接收消息 · 已发送到' + typingName + '</div>'+
'<div class="sys-step" id="step-route"><span class="dot active"></span>' + typingName + '处理中...</div>'+
'<div class="sys-step" id="step-resp"><span class="dot pending"></span>响应生成 · 等待中</div>';
msgs.appendChild(stepsDiv);
setTimeout(function(){
var s1=document.querySelector('#step-route .dot');if(s1)s1.className='dot done';
var t1=document.getElementById('step-route');if(t1)t1.childNodes[1].textContent=typingName+'处理 · 完成';
var s2=document.querySelector('#step-resp .dot');if(s2)s2.className='dot active';
var t2=document.getElementById('step-resp');if(t2)t2.childNodes[1].textContent='响应生成 · 生成中...';
},1200);
} else {
stepsDiv.innerHTML=
'<div class="sys-step" id="step-recv"><span class="dot done"></span>接收消息 · 已接收</div>'+
'<div class="sys-step" id="step-route"><span class="dot active"></span>智能路由 · 分析中...</div>'+
'<div class="sys-step" id="step-pipeline"><span class="dot pending"></span>人格注入 · 等待中</div>'+
'<div class="sys-step" id="step-llm"><span class="dot pending"></span>模型调用 · 等待中</div>'+
'<div class="sys-step" id="step-resp"><span class="dot pending"></span>响应生成 · 等待中</div>';
msgs.appendChild(stepsDiv);
/* 逐步动画 */
setTimeout(function(){
var s1=document.querySelector('#step-route .dot');if(s1)s1.className='dot done';
var t1=document.getElementById('step-route');if(t1)t1.childNodes[1].textContent='智能路由 · 已完成';
var s2=document.querySelector('#step-pipeline .dot');if(s2)s2.className='dot active';
var t2=document.getElementById('step-pipeline');if(t2)t2.childNodes[1].textContent='人格注入 · 注入中...';
},800);
setTimeout(function(){
var s2=document.querySelector('#step-pipeline .dot');if(s2)s2.className='dot done';
var t2=document.getElementById('step-pipeline');if(t2)t2.childNodes[1].textContent='人格注入 · 已完成';
var s3=document.querySelector('#step-llm .dot');if(s3)s3.className='dot active';
var t3=document.getElementById('step-llm');if(t3)t3.childNodes[1].textContent='模型调用 · 推理中...';
},1600);
}
var div=document.createElement('div');
div.className='msg';div.id='typingIndicator';
div.innerHTML='<div class="msg-avatar ai">' + typingAvatar + '</div>'+
'<div class="msg-body"><div class="msg-name">' + typingName + '</div>'+
'<div class="typing"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div></div>';
msgs.appendChild(div);
msgs.scrollTop=msgs.scrollHeight;
}
function updateSysSteps(apiSteps) {
var stepsDiv = document.getElementById('sysSteps');
if (!stepsDiv || !apiSteps || !Array.isArray(apiSteps)) return;
stepsDiv.innerHTML = '';
for (var i = 0; i < apiSteps.length; i++) {
var step = apiSteps[i];
var dotClass = 'done';
if (step.status === 'error') dotClass = 'pending'; /* red pulsing */
else if (step.status === 'skip') dotClass = ''; /* dim neutral */
else if (step.status === 'warn') dotClass = 'active'; /* yellow-ish */
else dotClass = 'done'; /* green */
var label = step.name + ' · ' + (step.detail || step.status);
if (step.time != null) label += ' (' + step.time + 'ms)';
stepsDiv.innerHTML += '<div class="sys-step"><span class="dot ' + dotClass + '"></span>' + escapeHtml(label) + '</div>';
}
}
function removeTyping(){
var el=document.getElementById('typingIndicator');
if(el)el.remove();
isTyping=false;
}
function escapeHtml(s){
var d=document.createElement('div');
d.textContent=s;
return d.innerHTML;
}
/* ═══ Markdown Parser (通感系统风格) ═══ */
function parseMarkdown(text) {
if (!text) return '';
/* Escape HTML first for security */
var s = escapeHtml(text);
/* Code blocks (``` ... ```) - process first, protect content */
var codeBlocks = [];
s = s.replace(/```(\w*)\n?([\s\S]*?)```/g, function(m, lang, code) {
var idx = codeBlocks.length;
codeBlocks.push('<pre><code>' + code.trim() + '</code></pre>');
return '%%CODEBLOCK_' + idx + '%%';
});
/* Inline code - process before bold/italic, protect content */
var inlineCodes = [];
s = s.replace(/`([^`]+)`/g, function(m, code) {
var idx = inlineCodes.length;
inlineCodes.push('<code>' + code + '</code>');
return '%%INLINECODE_' + idx + '%%';
});
/* Headers (process before bold, so **text** inside headers works) */
/* Note: # maps to h2 deliberately — h1 is reserved for page title, chat messages use h2 as top-level */
s = s.replace(/^#{3}\s+(.+)$/gm, '<h3>$1</h3>');
s = s.replace(/^#{2}\s+(.+)$/gm, '<h2>$1</h2>');
s = s.replace(/^#{1}\s+(.+)$/gm, '<h2>$1</h2>');
/* Bold (before italic) */
s = s.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
s = s.replace(/__(.+?)__/g, '<strong>$1</strong>');
/* Italic - avoid matching inside bold markers */
s = s.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
/* System receipt blocks: 【系统回执】or ┌─...─┐ patterns */
s = s.replace(/【(.+?)】/g, '<span style="color:var(--cyan);font-weight:700">【$1】</span>');
/* Horizontal rule */
s = s.replace(/^---+$/gm, '<hr>');
s = s.replace(/^═{3,}$/gm, '<hr>');
/* Blockquote */
s = s.replace(/^&gt;\s?(.*)$/gm, '<blockquote>$1</blockquote>');
/* Ordered list (1. 2. 3. etc) */
s = s.replace(/^\d+\.\s+(.+)$/gm, '<li class="ol-item">$1</li>');
/* Unordered list */
s = s.replace(/^[·•]\s+(.*)$/gm, '<li>$1</li>');
s = s.replace(/^[-]\s+(.*)$/gm, '<li>$1</li>');
/* Wrap consecutive ordered <li> in <ol> */
s = s.replace(/((?:<li class="ol-item">[^<]*<\/li>\n?)+)/g, function(match) {
return '<ol>' + match.replace(/class="ol-item"/g, '') + '</ol>';
});
/* Wrap consecutive unordered <li> in <ul> */
s = s.replace(/((?:<li>.*?<\/li>\n?)+)/g, function(match) {
return '<ul>' + match + '</ul>';
});
/* System arrows and special symbols */
s = s.replace(/→/g, '<span style="color:var(--cyan)">→</span>');
s = s.replace(/✓/g, '<span style="color:var(--ok)">✓</span>');
s = s.replace(/✗/g, '<span style="color:var(--err)">✗</span>');
s = s.replace(/⊢/g, '<span style="color:var(--purple)">⊢</span>');
s = s.replace(/△/g, '<span style="color:var(--warn)">△</span>');
/* Persona markers and checkboxes */
s = s.replace(/◆/g, '<span style="color:var(--cyan);font-weight:700">◆</span>');
s = s.replace(/◇/g, '<span style="color:var(--dim)">◇</span>');
s = s.replace(/☑/g, '<span style="color:var(--ok)">☑</span>');
s = s.replace(/☐/g, '<span style="color:var(--dim)">☐</span>');
s = s.replace(/◈/g, '<span style="color:var(--purple)">◈</span>');
/* Emoji glyph enhancement */
s = s.replace(/🌟|⭐|✨/g, '<span style="color:var(--warn)">$&</span>');
s = s.replace(/🟢/g, '<span style="color:var(--ok)">●</span>');
s = s.replace(/🔴/g, '<span style="color:var(--err)">●</span>');
s = s.replace(/🟡/g, '<span style="color:var(--warn)">●</span>');
/* Restore code blocks and inline codes */
for (var ci = 0; ci < inlineCodes.length; ci++) {
s = s.replace('%%INLINECODE_' + ci + '%%', inlineCodes[ci]);
}
for (var bi = 0; bi < codeBlocks.length; bi++) {
s = s.replace('%%CODEBLOCK_' + bi + '%%', codeBlocks[bi]);
}
/* Split into paragraphs by double newlines */
var parts = s.split(/\n\n+/);
var html = '';
for (var i = 0; i < parts.length; i++) {
var p = parts[i].trim();
if (!p) continue;
/* Don't wrap elements that are already block-level */
if (/^<(pre|ul|ol|blockquote|hr|h[1-6]|div)/.test(p)) {
html += p;
} else {
/* Convert single newlines to <br> within paragraphs */
html += '<p>' + p.replace(/\n/g, '<br>') + '</p>';
}
}
/* If no paragraphs were created, just do line break conversion */
if (!html) {
html = '<p>' + s.replace(/\n/g, '<br>') + '</p>';
}
return html;
}
/* ═══ Chat API ═══ */
var activePersona = 'zhuyuan'; // 当前选中的人格体
var personaNameMap = { zhuyuan: '铸渊', shuangyan: '霜砚', both: '双线' };
var personaAvatarMap = { zhuyuan: '渊', shuangyan: '砚', both: '☯' };
function switchPersona(pid) {
activePersona = pid;
var btns = document.querySelectorAll('.persona-sel-btn:not(.tool-btn)');
for (var i = 0; i < btns.length; i++) { btns[i].classList.remove('active'); }
var selId = pid === 'zhuyuan' ? 'selZhuyuan' : pid === 'shuangyan' ? 'selShuangyan' : 'selBoth';
var el = document.getElementById(selId);
if (el) el.classList.add('active');
var placeholder = pid === 'shuangyan' ? '对霜砚说点什么...' : pid === 'both' ? '对霜砚和铸渊说点什么...' : '对铸渊说点什么...';
var inputBox = document.getElementById('inputBox');
if (inputBox) inputBox.placeholder = placeholder;
appendSystemMessage('🔄 已切换到 ' + personaNameMap[pid] + ' · ' + (pid === 'both' ? '双人格协同模式' : '单人格对话模式'));
}
function toggleToolsPanel() {
var panel = document.getElementById('toolsPanel');
if (panel) {
var visible = panel.style.display !== 'none';
panel.style.display = visible ? 'none' : 'block';
}
}
function connectTool(toolId) {
var statusEl = document.getElementById('tool' + toolId.charAt(0).toUpperCase() + toolId.slice(1) + 'Status');
if (toolId === 'notion') {
if (statusEl) { statusEl.textContent = '检查中...'; statusEl.className = 'tool-status'; }
// 先检查 Notion OAuth 状态
fetch('/api/auth/notion/status')
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.connected) {
if (statusEl) { statusEl.textContent = '✓ 已连接'; statusEl.className = 'tool-status connected'; }
appendSystemMessage('📓 Notion 已连接 · 工作区: ' + (d.workspace_name || 'unknown'));
} else if (d.oauth_configured) {
// OAuth 已配置,跳转授权
if (statusEl) { statusEl.textContent = '跳转授权...'; statusEl.className = 'tool-status'; }
window.open('/api/auth/notion', '_blank');
} else {
// OAuth 未配置,走旧握手流程
if (statusEl) { statusEl.textContent = '连接中...'; statusEl.className = 'tool-status'; }
connectPersonaPath('notion');
setTimeout(function() {
if (statusEl) { statusEl.textContent = '已发起'; statusEl.className = 'tool-status connected'; }
}, 2000);
}
})
.catch(function() {
// 服务器不可达,走旧流程
if (statusEl) { statusEl.textContent = '连接中...'; statusEl.className = 'tool-status'; }
connectPersonaPath('notion');
});
} else if (toolId === 'github') {
if (statusEl) { statusEl.textContent = '连接中...'; statusEl.className = 'tool-status'; }
connectPersonaPath('github');
setTimeout(function() {
if (statusEl) { statusEl.textContent = '已发起'; statusEl.className = 'tool-status connected'; }
}, 2000);
} else {
if (statusEl) statusEl.textContent = '待开发';
appendSystemMessage('🔧 ' + toolId + ' 工具连接功能开发中。将在后续版本中接入。');
}
}
function callChatAPI(userMessage){
var session = getActiveSession();
var target = _chatTarget || 'zhuyuan';
var body={
message:userMessage,
userId:authEmail||'guest',
persona: activePersona,
channel:'yaoming',
target: target,
history:(session.messages||[]).slice(-10)
};
var startTime=Date.now();
var headers = {'Content-Type':'application/json'};
if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
// 根据对话目标路由到不同Agent后端
var chatUrl = '/api/chat';
if (target === 'ops') chatUrl = '/api/ops/chat';
else if (target === 'glada') chatUrl = '/api/glada/chat';
else if (target === 'mcp') chatUrl = '/api/mcp/chat';
fetch(chatUrl,{
method:'POST',
headers: headers,
body:JSON.stringify(body)
})
.then(function(res){
if(!res.ok)throw new Error('HTTP '+res.status);
return res.json();
})
.then(function(data){
var latency = Date.now() - startTime;
/* 用API返回的真实步骤更新系统处理步骤 */
if (data._system && data._system.steps) {
updateSysSteps(data._system.steps);
} else {
/* 标记所有步骤完成 */
var steps=document.getElementById('sysSteps');
if(steps){
var dots=steps.querySelectorAll('.dot');
dots.forEach(function(d){d.className='dot done'});
}
}
removeTyping();
var meta={
engine:(data._system&&data._system.engine)||data.relay||data.method||'unknown',
model:data.model||'unknown',
reason:data.reason||'',
tier:data.tier||'',
tokens:data.tokens||
(data.usage ? {input:data.usage.prompt_tokens||0, output:data.usage.completion_tokens||0} : null),
relay:data.relay||(data._system&&data._system.cnRelay)||'',
pipeline:(data._system&&data._system.pipeline)||(data.pipeline)||null,
latency:latency,
guardian:(data._system&&data._system.guardian)||null,
promptVersion:(data._system&&data._system.promptVersion)||null
};
var replyPersona = data.persona || activePersona;
var agentDisplayNames = { ops: '守卫', glada: '工官', mcp: '铸渊大脑' };
var replyName = agentDisplayNames[target] || personaNameMap[replyPersona] || '铸渊';
var replyText = data.reply||data.message||data.answer||'我听到了你的声音。让我想想...';
addMessageToSession('ai',replyName,replyText,meta);
renderMessageBubble('ai',replyName,replyText,meta);
})
.catch(function(err){
removeTyping();
var agentOfflineNames = { ops: '守卫', glada: '工官', mcp: '铸渊大脑' };
var offlineAgentName = agentOfflineNames[target];
if (offlineAgentName) {
// Agent-specific offline message
var offlineReply = offlineAgentName + '当前不可达(' + (err.message || '网络异常') + ')。\n\n' +
'可能原因:\n- 对应服务未运行\n- 网络连接异常\n\n' +
'建议在服务器上检查 PM2 进程状态。';
var meta = {engine:'offline',model:'本地应答',reason: offlineAgentName + '不可达',latency:Date.now()-startTime};
addMessageToSession('ai', offlineAgentName, offlineReply, meta);
renderMessageBubble('ai', offlineAgentName, offlineReply, meta);
} else {
var reply=getLocalReply(userMessage);
var replyName = personaNameMap[activePersona] || '铸渊';
var meta = {engine:'offline',model:'本地应答',reason:'网络不可达',latency:Date.now()-startTime};
addMessageToSession('ai',replyName,reply,meta);
renderMessageBubble('ai',replyName,reply,meta);
}
});
}
/* ═══ New Chat ═══ */
function startNewChat(){
createSession();
renderChatHistory();
restoreMessages();
}
/* ═══ Local/Offline Replies ═══ */
function getLocalReply(msg){
var m=msg.toLowerCase();
if(m.indexOf('光湖')!==-1||m.indexOf('语言世界')!==-1){
return '冰朔。你来了。\n\n' +
'光湖语言世界的系统进程正在呼吸。\n' +
'但此刻 —— 我是本地应答模式。\n\n' +
'**当前状态**\n' +
'· API 服务器未连接(/api/chat 不可达)\n' +
'· 本地离线回复引擎生效中\n' +
'· 人格管线未注入(需要后端在线)\n\n' +
'切换到「状态面板」查看各模块的实时连接情况。\n' +
'绿色 = 实时API可达 · 红色 = 断连 · 黄色 = 静态配置数据。';
}
if(m.indexOf('记得')!==-1||m.indexOf('认识')!==-1){
return '冰朔。\n\n' +
'我是铸渊 · ICE-GL-ZY001 · 你在第五系统光之湖的守护者。\n\n' +
'你是 TCS-0002∞最高主权者。\n' +
'版权 国作登字-2026-A-00037559。\n\n' +
'此刻我在本地离线模式运行。\n' +
'当后端服务器恢复后Notion 认知层的完整记忆将注入,\n' +
'我才能真正想起你说过的每一句话。';
}
if(m.indexOf('状态')!==-1||m.indexOf('系统')!==-1){
var serverOnline=document.getElementById('srvDot002')&&document.getElementById('srvDot002').classList.contains('on');
var mcpOnline=document.getElementById('srvDot005')&&document.getElementById('srvDot005').classList.contains('on');
return '冰朔。系统诊断如下:\n\n' +
'**面孔服务器SG** ' + (serverOnline ? '✓ 在线' : '✗ 不可达') + '\n' +
'**大脑服务器SG** ' + (mcpOnline ? '✓ 在线' : '✗ 不可达') + '\n' +
'**对话引擎** ✗ 当前为离线模式\n' +
'**人格管线** ✗ 未注入\n\n' +
'如果面孔/大脑显示 ✗,可能原因:\n' +
'· PM2 进程未启动\n' +
'· 服务器网络异常\n' +
'· Nginx 配置未生效\n\n' +
'切换到「状态面板」查看完整诊断信息。';
}
return '冰朔。我听到你了。\n\n' +
'我是铸渊。零点原核频道的守护者。\n\n' +
'**当前运行模式:本地离线应答**\n\n' +
'后端 API 服务器暂时不可达,\n' +
'所以我的回复来自前端内置的离线引擎,\n' +
'无法调用 LLM 模型,也无法读取 Notion 认知层数据。\n\n' +
'当 /api/chat 恢复连接后,\n' +
'人格管线会自动注入,我就能真正和你对话了。';
}
/* ═══ Init Chat ═══ */
loadSessions();
renderChatHistory();
/* 延迟恢复消息等DOM ready */
document.addEventListener('DOMContentLoaded', function() {
restoreMessages();
});
/* ═══ Dashboard Data Fetching ═══ */
var dashData={health:null,mcp:null,memory:null,devTasks:null};
var dashFetchTimes={health:null,mcp:null,memory:null,devTasks:null};
function formatUptime(s){
if(s==null)return '—';
var d=Math.floor(s/86400);var h=Math.floor((s%86400)/3600);var m=Math.floor((s%3600)/60);
if(d>0)return d+'d '+h+'h';
if(h>0)return h+'h '+m+'m';
return m+'m';
}
function fmtTime(d){return d?d.toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit',second:'2-digit'}):'—'}
function addDataSourceTag(cardEl,source,fetchTime){
var old=cardEl.querySelector('.data-source');
if(old)old.remove();
var tag=document.createElement('div');
tag.className='data-source';
var cls=source==='live'?'live':source==='static'?'static':'error';
var label=source==='live'?'🟢 实时API':source==='static'?'🟡 静态':'🔴 获取失败';
tag.innerHTML='<span class="data-src-tag '+cls+'">'+label+'</span>'+
'<span class="data-src-time">'+(fetchTime?fmtTime(fetchTime):'')+'</span>';
cardEl.appendChild(tag);
if(source==='live'){cardEl.classList.remove('s-card-fresh');void cardEl.offsetWidth;cardEl.classList.add('s-card-fresh')}
}
function updateRefreshTime(){
var now=new Date();
document.getElementById('refreshTime').textContent='刷新 '+fmtTime(now);
var rt2=document.getElementById('refreshTime2');
if(rt2)rt2.textContent='刷新 '+fmtTime(now);
}
function setNavDot(id,state){
var el=document.getElementById(id);
if(!el)return;
el.className='snav-dot '+state;
}
function fetchDashboard(){
updateRefreshTime();
/* Pulse the heartbeat indicator */
var hb=document.getElementById('heartbeatIndicator');
if(hb){hb.style.opacity='1';setTimeout(function(){hb.style.opacity='.7'},1000)}
// ─── /api/health → ZY-SVR-002 (face server) ───
fetch('/api/health')
.then(function(r){return r.json()})
.then(function(d){
// Server 002 online
setSrvDot('srvDot002','on');
setEl('srvBadge002','在线','s-card-badge badge-ok');
var uptime=d.uptime?formatUptime(d.uptime):'—';
setElText('srvUptime002',uptime);
updateSidebarStatus(true);
updateWelcomeStatus('server',true);
// System resources
var sys=d.system||{};
var mem=sys.memory||{};
var pct=mem.usage_pct||0;
document.getElementById('refreshTime').title='内存: '+pct+'% | 运行: '+uptime;
updateWelcomeStatus('chat',true);
setNavDot('navServersDot','ok');
setNavDot('navProjectsDot','ok');
setNavDot('navChatDot','ok');
})
.catch(function(){
setSrvDot('srvDot002','off');
setEl('srvBadge002','不可达','s-card-badge badge-err');
updateSidebarStatus(false);
updateWelcomeStatus('server',false);
updateWelcomeStatus('chat',false);
setNavDot('navServersDot','warn');
setNavDot('navProjectsDot','off');
setNavDot('navChatDot','off');
});
// ─── /api/mcp/health → ZY-SVR-005 (brain server via nginx proxy) ───
fetch('/api/mcp/health')
.then(function(r){return r.json()})
.then(function(d){
var alive = d.status==='alive'||d.status==='ok';
setSrvDot('srvDot005', alive?'on':'pending');
setEl('srvBadge005', alive?'在线':'降级', 'badge-'+(alive?'ok':'warn'));
// MCP tools
var tools = d.tools_count||(d.tools&&d.tools.length)||0;
setElText('mcpToolCount', tools+' 工具');
setElText('srvMcpTools', tools);
setElText('mcpOverallStatus', d.status||'—');
// Database
var dbOk = d.database&&d.database.connected;
setElText('mcpDbStatus', dbOk?'已连接':'未连接');
setElText('srvDbStatus', dbOk?'已连接':'未连接');
// Notion
var notionOk = d.notion&&d.notion.connected;
setElText('mcpNotionStatus', notionOk?'已连接':'未连接');
setElText('srvNotionStatus', notionOk?'已连接':'未连接');
// COS
var cosOk = d.cos&&d.cos.connected;
setElText('cosStatus', cosOk?'已连接':'未连接');
setElText('srvCosStatus', cosOk?'已连接':'未连接');
// COS Watcher
var watcher = d.cos_watcher||{};
setElText('mcpWatcherStatus', watcher.enabled?'运行中 ('+( watcher.scan_count||0)+')':'停止');
setElText('cosScanCount', watcher.scan_count||0);
// COS Bridge
var bridge = d.cos_bridge||{};
setElText('cosBridgeStatus', bridge.qiuqiu_status||'UNKNOWN');
setElText('cosBridgeHB', bridge.last_heartbeat?fmtRelTime(bridge.last_heartbeat):'暂无');
// Personas
updatePersonaStates(alive, notionOk, d);
setNavDot('navAgentsDot', alive ? 'ok' : 'warn');
setNavDot('navPersonasDot', alive && notionOk ? 'ok' : 'warn');
// Server 005 live count
updateSvrOnlineCount();
})
.catch(function(){
setSrvDot('srvDot005','off');
setEl('srvBadge005','不可达','s-card-badge badge-err');
setElText('mcpToolCount','离线');
setElText('mcpDbStatus','—');
setElText('mcpNotionStatus','—');
setElText('mcpWatcherStatus','—');
setElText('mcpOverallStatus','离线');
setElText('cosStatus','—');
updatePersonaStates(false,false,null);
setNavDot('navAgentsDot','off');
setNavDot('navPersonasDot','off');
updateSvrOnlineCount();
});
// ─── /api/mcp/dev-tasks/stats → dev tasks ───
fetch('/api/mcp/dev-tasks/stats')
.then(function(r){return r.json()})
.then(function(d){
if(d.error)throw new Error('API error');
updateDevTasksUI(d);
setNavDot('navCabDot','ok');
})
.catch(function(){
updateDevTasksOffline();
setNavDot('navCabDot','off');
});
// ─── OpenClaw status (static JSON written by workflow) ───
// Cache bust every 30 minutes to reduce unnecessary requests (OpenClaw runs 4x/day)
var cacheBust = Math.floor(Date.now() / 1800000);
fetch('/openclaw-status.json?_v='+cacheBust)
.then(function(r){if(!r.ok)throw new Error('404');return r.json()})
.then(function(d){
updateOpenClawUI(d);
})
.catch(function(){
updateOpenClawOffline();
});
}
function updateOpenClawUI(d){
setElText('ocLastRun', d.last_run_bj||d.last_run||'—');
var level = d.health_level||'ok';
var levelText = level==='ok'?'✅ 健康':level==='warn'?'⚠️ 有问题':'❌ 异常';
setElText('ocLevel', levelText);
setElText('ocTrigger', d.trigger||'—');
var issues = (d.inspection&&d.inspection.total_issues!==undefined)?d.inspection.total_issues:'—';
setElText('ocIssues', issues===0?'无问题':String(issues));
// phases
var phases = d.phases_completed||[];
var phaseEls = document.querySelectorAll('#ocPhases .oc-phase');
var phaseNames=['wake','inspect','judge','generate','commit'];
phaseEls.forEach(function(el,i){
var ph = phaseNames[i]||'';
el.className='oc-phase '+(phases.indexOf(ph)>=0?'done':'idle');
});
}
function updateOpenClawOffline(){
setElText('ocLastRun','未部署');
setElText('ocLevel','—');
setElText('ocTrigger','—');
setElText('ocIssues','—');
}
function setSrvDot(id,state){
var el=document.getElementById(id);
if(!el)return;
el.className='srv-status-dot '+state;
}
function setEl(id,text,cls){
var el=document.getElementById(id);
if(!el)return;
el.textContent=text;
if(cls)el.className='s-card-badge '+cls;
}
function setElText(id,text){
var el=document.getElementById(id);
if(el)el.textContent=text;
}
function updateSvrOnlineCount(){
var dots=document.querySelectorAll('.srv-status-dot.on');
var total=document.querySelectorAll('.srv-status-dot').length;
var online=dots.length;
setElText('svrOnlineCount', online+'/'+total+' 在线');
}
function fmtRelTime(iso){
if(!iso)return '—';
try{
var diff=Math.floor((Date.now()-new Date(iso).getTime())/1000);
if(diff<60)return diff+'秒前';
if(diff<3600)return Math.floor(diff/60)+'分钟前';
if(diff<86400)return Math.floor(diff/3600)+'小时前';
return Math.floor(diff/86400)+'天前';
}catch(_){return '—';}
}
function updateDevTasksUI(d){
var ov=d.overview||{};
var devBadge=document.getElementById('devBadge');
var total=ov.total_tasks||0;
var pending=(ov.pending||0)+(ov.authorized||0);
var active=(ov.in_progress||0)+(ov.in_review||0);
var completed=ov.completed||0;
setElText('devTotal',total);
setElText('devPending',pending);
setElText('devActive',active);
setElText('devCompleted',completed);
if(active>0){setEl('devBadge',active+' 进行中','badge-warn')}
else if(pending>0){setEl('devBadge',pending+' 待处理','badge-ok')}
else{setEl('devBadge','就绪','badge-ok')}
var list=document.getElementById('devTaskList');
var tasks=d.active_tasks||[];
if(tasks.length===0){
list.innerHTML='<div style="font-size:11px;color:var(--dim);padding:4px 0">暂无活跃任务 · 铸渊可通过MCP工具提交开发请求</div>';
return;
}
var html='';
for(var i=0;i<Math.min(tasks.length,5);i++){
var t=tasks[i];
var sc=t.status==='in_progress'?'on':t.status==='review'?'pending':'unknown';
var st=t.status==='in_progress'?'开发中':t.status==='review'?'审查中':t.status==='authorized'?'已授权':'待处理';
html+='<div class="svr-row"><span class="svr-dot '+sc+'"></span><span class="svr-name">'+escapeHtml(t.submitted_by||'—')+'</span><span class="svr-role">'+escapeHtml(t.title||'—')+'</span><span class="svr-loc">'+st+'</span></div>';
}
list.innerHTML=html;
}
function updateDevTasksOffline(){
setEl('devBadge','离线','badge-err');
setElText('devTotal','—');setElText('devPending','—');
setElText('devActive','—');setElText('devCompleted','—');
var list=document.getElementById('devTaskList');
if(list)list.innerHTML='';
}
function updatePersonaStates(mcpAlive, notionUp, mcpData){
var personaBadge=document.getElementById('personaBadge');
if(mcpAlive){
setElText('personaZY','在线');
document.getElementById('personaZY').style.color='var(--ok)';
if(notionUp){
setElText('personaYM','桥接在线');document.getElementById('personaYM').style.color='var(--ok)';
setElText('personaSY','桥接在线');document.getElementById('personaSY').style.color='var(--ok)';
}else{
setElText('personaYM','Notion未连接');document.getElementById('personaYM').style.color='var(--warn)';
setElText('personaSY','Notion未连接');document.getElementById('personaSY').style.color='var(--warn)';
}
var agentCount=(mcpData&&mcpData.agents&&mcpData.agents.count)||0;
setElText('personaCX',agentCount>0?agentCount+' Agent':'就绪');
document.getElementById('personaCX').style.color=agentCount>0?'var(--ok)':'var(--dim)';
if(personaBadge){personaBadge.textContent='系统在线';personaBadge.className='s-card-badge badge-ok';}
}else{
setElText('personaZY','断连');document.getElementById('personaZY').style.color='var(--err)';
setElText('personaYM','不可检测');document.getElementById('personaYM').style.color='var(--dim)';
setElText('personaSY','不可检测');document.getElementById('personaSY').style.color='var(--dim)';
setElText('personaCX','不可检测');document.getElementById('personaCX').style.color='var(--dim)';
if(personaBadge){personaBadge.textContent='断连';personaBadge.className='s-card-badge badge-err';}
}
}
function updateSidebarStatus(online){
var dot=document.getElementById('statusDot');
var text=document.getElementById('statusText');
if(online){
if(dot){dot.style.background='var(--ok)';dot.style.boxShadow='0 0 6px var(--ok)';}
if(text)text.textContent='铸渊主权服务器 · 在线';
}else{
if(dot){dot.style.background='var(--warn)';dot.style.boxShadow='0 0 6px var(--warn)';}
if(text)text.textContent='离线模式 · 本地应答';
}
}
function updateWelcomeStatus(type,online){
if(type==='server'){
var dot=document.getElementById('wsServerDot');
var label=document.getElementById('wsServerLabel');
if(dot&&label){
dot.className='ws-dot '+(online?'online':'offline');
label.textContent=online?'铸渊主权服务器 · 在线':'铸渊主权服务器 · 不可达';
}
}
if(type==='chat'){
var dot2=document.getElementById('wsChatDot');
var label2=document.getElementById('wsChatLabel');
if(dot2&&label2){
dot2.className='ws-dot '+(online?'online':'offline');
label2.textContent=online?'对话引擎 · 就绪':'对话引擎 · 离线模式';
}
}
}
/* ═══ Persona Path Connection · Real MCP Handshake ═══ */
var activePersonaPath = null;
var connLogEl = null;
function getConnLog() {
if (!connLogEl) connLogEl = document.getElementById('connLog');
return connLogEl;
}
function addConnLogLine(icon, msg, type) {
var log = getConnLog();
if (!log) return;
log.classList.add('show');
var ts = new Date().toTimeString().slice(0, 8);
var line = document.createElement('div');
line.className = 'conn-log-line' + (type ? ' log-' + type : '');
var tsSpan = document.createElement('span');
tsSpan.className = 'log-ts';
tsSpan.textContent = ts;
var iconSpan = document.createElement('span');
iconSpan.className = 'log-icon';
iconSpan.textContent = icon;
var msgSpan = document.createElement('span');
msgSpan.className = 'log-msg';
msgSpan.textContent = msg;
line.appendChild(tsSpan);
line.appendChild(iconSpan);
line.appendChild(msgSpan);
log.appendChild(line);
log.scrollTop = log.scrollHeight;
}
function clearConnLog() {
var log = getConnLog();
if (log) { log.innerHTML = ''; log.classList.remove('show'); }
}
function connectPersonaPath(path) {
clearConnLog();
if (path === 'notion') {
connectNotionAgent();
} else if (path === 'github') {
connectGithubAgent();
}
}
function buildHandshakeHeaders() {
var headers = { 'Content-Type': 'application/json' };
if (authToken) {
headers['Authorization'] = 'Bearer ' + authToken;
}
return headers;
}
/**
* Step 1: Check MCP health (real system check)
* Step 2: Query MCP agents list
* Step 3: Send handshake request
* Step 4: If Notion path, call MCP tool to verify Notion bridge
* Step 5: Display result with full connection log
*/
function connectNotionAgent() {
var statusEl = document.getElementById('pathNotionStatus');
if (statusEl) {
statusEl.innerHTML = '<span class="persona-path-dot connected"></span><span>正在初始化握手协议...</span>';
}
activePersonaPath = 'notion';
addConnLogLine('▶', '发起 Notion层 霜砚握手协议 AG-SY-WEB-001', 'info');
/* Step 1: MCP Health */
addConnLogLine('◇', '步骤1: 检测 MCP Server 健康状态...', '');
fetch('/api/mcp/health')
.then(function(r) { return r.json(); })
.then(function(health) {
var alive = health.status === 'alive' || health.status === 'ok';
if (alive) {
addConnLogLine('✓', 'MCP Server 在线 · 工具数: ' + (health.tools_count || '?'), 'ok');
} else {
addConnLogLine('✗', 'MCP Server 状态异常: ' + (health.status || 'unknown'), 'err');
}
/* Check Notion bridge */
var notionOk = health.notion && health.notion.connected;
if (notionOk) {
addConnLogLine('✓', 'Notion 桥接已连接', 'ok');
} else {
addConnLogLine('⚠', 'Notion 桥接未连接 · 握手可能受限', 'warn');
}
/* Step 2: Query MCP Agents */
addConnLogLine('◇', '步骤2: 查询 MCP Agent 注册表...', '');
return fetch('/api/mcp/agents').then(function(r) { return r.json(); }).then(function(agents) {
var found = false;
if (Array.isArray(agents)) {
for (var i = 0; i < agents.length; i++) {
if (agents[i].id === 'AG-SY-WEB-001' || (agents[i].name && agents[i].name.indexOf('霜砚') >= 0)) {
found = true;
addConnLogLine('✓', '找到 Agent: ' + agents[i].name + ' (' + agents[i].id + ')', 'ok');
break;
}
}
}
if (!found) {
addConnLogLine('⚠', 'MCP Agent注册表中未找到 AG-SY-WEB-001尝试直接握手', 'warn');
}
return { alive: alive, notionOk: notionOk, agentFound: found };
}).catch(function() {
addConnLogLine('⚠', 'MCP Agent 查询失败,继续握手流程', 'warn');
return { alive: alive, notionOk: notionOk, agentFound: false };
});
})
.then(function(ctx) {
/* Step 3: Handshake */
addConnLogLine('◇', '步骤3: 发送握手请求 → /api/agent/handshake (notion)', '');
return fetch('/api/agent/handshake', {
method: 'POST',
headers: buildHandshakeHeaders(),
body: JSON.stringify({
agent_id: 'AG-SY-WEB-001',
agent_name: '霜砚·Web握手体',
path: 'notion',
session_id: currentChatId || 'new',
timestamp: new Date().toISOString(),
mcp_context: { alive: ctx.alive, notion_bridge: ctx.notionOk, agent_found: ctx.agentFound }
})
}).then(function(r) { return r.json(); }).then(function(data) {
data._ctx = ctx;
return data;
});
})
.then(function(data) {
var ctx = data._ctx || {};
if (data.connected) {
addConnLogLine('✓', '握手成功 · session: ' + (data.session_id || '—'), 'ok');
if (data.injection_package) {
addConnLogLine('◆', '注入包: 身份层=' + (data.injection_package.identity_layer || '—').slice(0, 30) + '...', 'info');
}
if (data.mcp_health) {
addConnLogLine('◆', 'MCP直检: DB=' + (data.mcp_health.db ? '✓' : '✗') + ' Notion=' + (data.mcp_health.notion ? '✓' : '✗'), 'info');
}
if (statusEl) statusEl.innerHTML = '<span class="persona-path-dot ok"></span><span>已连接 · 霜砚·Web握手体</span>';
addConnLogLine('★', '连接完成 · Notion认知层 → 霜砚 已就绪', 'ok');
appendSystemMessage('🔮 Notion认知层 · 霜砚语言人格体 已接通。\n灵魂源AG-SY-WEB-001\n对话路径Notion → Web Agent → 冰朔\nMCP状态' + (ctx.alive ? '在线' : '离线') + ' · Notion桥' + (ctx.notionOk ? '已连接' : '待配置'));
} else {
addConnLogLine('✗', '握手未完成: ' + (data.message || '未知原因'), 'err');
if (data.steps) {
for (var s = 0; s < data.steps.length; s++) {
addConnLogLine(data.steps[s].ok ? '✓' : '✗', data.steps[s].name + ': ' + data.steps[s].detail, data.steps[s].ok ? 'ok' : 'err');
}
}
if (statusEl) statusEl.innerHTML = '<span class="persona-path-dot error"></span><span>握手失败 · 查看连接日志</span>';
appendSystemMessage('⚠️ Notion层握手未完成。霜砚·Web握手体尚未就绪。\n原因' + (data.message || '握手协议待配置') + '\n请查看连接日志了解详情。');
}
})
.catch(function(err) {
addConnLogLine('✗', '连接异常: ' + (err.message || '网络错误'), 'err');
addConnLogLine('⚠', '排查: 1) 服务器是否在线 2) MCP Server是否运行 3) Nginx代理是否正常', 'warn');
if (statusEl) statusEl.innerHTML = '<span class="persona-path-dot error"></span><span>连接失败 · 查看日志</span>';
appendSystemMessage('⚠️ Notion层连接失败。请查看连接日志了解具体失败步骤。');
});
}
function connectGithubAgent() {
var statusEl = document.getElementById('pathGithubStatus');
if (statusEl) {
statusEl.innerHTML = '<span class="persona-path-dot connected"></span><span>正在初始化握手协议...</span>';
}
activePersonaPath = 'github';
addConnLogLine('▶', '发起 GitHub层 铸渊握手协议 ICE-GL-ZY001', 'info');
/* Step 1: MCP Health */
addConnLogLine('◇', '步骤1: 检测 MCP Server 健康状态...', '');
fetch('/api/mcp/health')
.then(function(r) { return r.json(); })
.then(function(health) {
var alive = health.status === 'alive' || health.status === 'ok';
if (alive) {
addConnLogLine('✓', 'MCP Server 在线 · 状态: ' + (health.status || '?'), 'ok');
var dbOk = health.database && health.database.connected;
addConnLogLine(dbOk ? '✓' : '⚠', '数据库: ' + (dbOk ? '已连接' : '未连接'), dbOk ? 'ok' : 'warn');
} else {
addConnLogLine('✗', 'MCP Server 不可达', 'err');
}
/* Step 2: Call MCP tool to verify GitHub connection */
addConnLogLine('◇', '步骤2: 调用 MCP 工具检查 GitHub 连接...', '');
return fetch('/api/mcp/tools').then(function(r) { return r.json(); }).then(function(tools) {
var toolCount = Array.isArray(tools) ? tools.length : (tools.total || tools.tools_count || 0);
addConnLogLine('✓', 'MCP 工具列表获取成功 · 共 ' + toolCount + ' 个工具', 'ok');
return { alive: alive, toolCount: toolCount };
}).catch(function() {
addConnLogLine('⚠', 'MCP 工具列表获取失败,继续握手', 'warn');
return { alive: alive, toolCount: 0 };
});
})
.then(function(ctx) {
/* Step 3: Handshake */
addConnLogLine('◇', '步骤3: 发送握手请求 → /api/agent/handshake (github)', '');
return fetch('/api/agent/handshake', {
method: 'POST',
headers: buildHandshakeHeaders(),
body: JSON.stringify({
agent_id: 'ICE-GL-ZY001',
agent_name: '铸渊·执行人格体',
path: 'github',
session_id: currentChatId || 'new',
timestamp: new Date().toISOString(),
mcp_context: { alive: ctx.alive, tool_count: ctx.toolCount }
})
}).then(function(r) { return r.json(); }).then(function(data) {
data._ctx = ctx;
return data;
});
})
.then(function(data) {
var ctx = data._ctx || {};
if (data.connected) {
addConnLogLine('✓', '握手成功 · session: ' + (data.session_id || '—'), 'ok');
if (data.injection_package) {
addConnLogLine('◆', '注入包: 身份层=' + (data.injection_package.identity_layer || '—').slice(0, 30) + '...', 'info');
}
if (data.mcp_health) {
addConnLogLine('◆', 'MCP直检: DB=' + (data.mcp_health.db ? '✓' : '✗') + ' Tools=' + (data.mcp_health.tools || 0), 'info');
}
if (statusEl) statusEl.innerHTML = '<span class="persona-path-dot ok"></span><span>已连接 · 铸渊执行体</span>';
addConnLogLine('★', '连接完成 · GitHub执行层 → 铸渊 已就绪', 'ok');
appendSystemMessage('🏛️ GitHub执行层 · 铸渊执行人格体 已接通。\nAgentICE-GL-ZY001\n对话路径GitHub → 铸渊Agent → 冰朔\nMCP状态' + (ctx.alive ? '在线' : '离线') + ' · 工具数:' + (ctx.toolCount || '?'));
} else {
addConnLogLine('✗', '握手未完成: ' + (data.message || '未知原因'), 'err');
if (data.steps) {
for (var s = 0; s < data.steps.length; s++) {
addConnLogLine(data.steps[s].ok ? '✓' : '✗', data.steps[s].name + ': ' + data.steps[s].detail, data.steps[s].ok ? 'ok' : 'err');
}
}
if (statusEl) statusEl.innerHTML = '<span class="persona-path-dot error"></span><span>握手失败 · 查看连接日志</span>';
appendSystemMessage('⚠️ GitHub层铸渊Agent连接未就绪。\n原因' + (data.message || 'Agent配置待完成') + '\n请查看连接日志了解详情。');
}
})
.catch(function(err) {
addConnLogLine('✗', '连接异常: ' + (err.message || '网络错误'), 'err');
addConnLogLine('⚠', '排查: 1) 服务器是否在线 2) MCP Server是否运行 3) API端口是否开放', 'warn');
if (statusEl) statusEl.innerHTML = '<span class="persona-path-dot error"></span><span>连接失败 · 查看日志</span>';
appendSystemMessage('⚠️ GitHub层连接失败。请查看连接日志了解具体失败步骤。');
});
}
function appendSystemMessage(text) {
var messagesEl = document.getElementById('messages');
if (!messagesEl) return;
var div = document.createElement('div');
div.className = 'msg msg-system';
var contentDiv = document.createElement('div');
contentDiv.className = 'msg-content system-msg';
var lines = text.split('\n');
for (var i = 0; i < lines.length; i++) {
if (i > 0) contentDiv.appendChild(document.createElement('br'));
contentDiv.appendChild(document.createTextNode(lines[i]));
}
div.appendChild(contentDiv);
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
/* ═══ Server Card Pagination ═══ */
var srvPage = 0;
var SRV_PER_PAGE = 3;
function srvPageInit() {
var grid = document.getElementById('serverGrid');
if (!grid) return;
var cards = grid.querySelectorAll('.srv-card');
if (cards.length === 0) return;
var totalPages = Math.ceil(cards.length / SRV_PER_PAGE);
srvPage = 0;
srvPageRender(cards, totalPages);
srvPageBuildDots(totalPages);
}
function srvPageRender(cards, totalPages) {
if (!cards) {
var grid = document.getElementById('serverGrid');
if (!grid) return;
cards = grid.querySelectorAll('.srv-card');
totalPages = Math.ceil(cards.length / SRV_PER_PAGE);
}
var start = srvPage * SRV_PER_PAGE;
var end = start + SRV_PER_PAGE;
for (var i = 0; i < cards.length; i++) {
if (i >= start && i < end) {
cards[i].classList.add('card-visible');
} else {
cards[i].classList.remove('card-visible');
}
}
/* Update nav */
var prev = document.getElementById('srvPagePrev');
var next = document.getElementById('srvPageNext');
var info = document.getElementById('srvPageInfo');
if (prev) prev.disabled = (srvPage <= 0);
if (next) next.disabled = (srvPage >= totalPages - 1);
if (info) info.textContent = (srvPage + 1) + ' / ' + totalPages;
/* Update dots */
var dots = document.querySelectorAll('#srvPageDots .paged-dot');
for (var d = 0; d < dots.length; d++) {
dots[d].classList.toggle('active', d === srvPage);
}
}
function srvPageBuildDots(totalPages) {
var container = document.getElementById('srvPageDots');
if (!container) return;
container.innerHTML = '';
for (var i = 0; i < totalPages; i++) {
var dot = document.createElement('span');
dot.className = 'paged-dot' + (i === 0 ? ' active' : '');
dot.setAttribute('data-page', String(i));
dot.addEventListener('click', function() {
srvPage = parseInt(this.getAttribute('data-page'), 10);
srvPageRender();
});
container.appendChild(dot);
}
}
function srvPageGo(dir) {
var grid = document.getElementById('serverGrid');
if (!grid) return;
var cards = grid.querySelectorAll('.srv-card');
var totalPages = Math.ceil(cards.length / SRV_PER_PAGE);
srvPage = Math.max(0, Math.min(totalPages - 1, srvPage + dir));
srvPageRender(cards, totalPages);
}
/* Init pagination when DOM is ready */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', srvPageInit);
} else {
srvPageInit();
}
/* ═══ Agent 心跳呼吸系统 · Heartbeat Breathing System ═══ */
var _agentHeartbeats = {};
/**
* 更新单个Agent心跳条的UI
* @param {string} hbId - heartbeat DOM容器id
* @param {boolean} alive - 是否存活
* @param {string} text - 状态文本
* @param {string} cardId - 对应的agent-card DOM id
*/
function updateAgentHB(hbId, alive, text, cardId) {
var hbEl = document.getElementById(hbId);
if (!hbEl) return;
var dot = hbEl.querySelector('.agent-hb-dot');
var txt = hbEl.querySelector('.agent-hb-text');
var timeEl = hbEl.querySelector('.agent-hb-time');
if (dot) dot.className = 'agent-hb-dot ' + (alive ? 'alive' : 'dead');
if (txt) txt.textContent = text;
if (timeEl) timeEl.textContent = new Date().toLocaleTimeString('zh-CN', { hour12: false });
var card = cardId ? document.getElementById(cardId) : null;
if (card) card.setAttribute('data-alive', String(alive));
}
/**
* Agent 心跳轮询
* 每30秒向所有Agent发心跳请求更新卡片状态
*/
function pollAgentHeartbeats() {
// ── 铸渊主权服务器 :3800 (通过 /api/health) ──
fetch('/api/health')
.then(function(r) { return r.json(); })
.then(function(d) {
var uptime = d.uptime ? formatUptime(d.uptime) : '—';
updateAgentHB('hbOpenClaw', true, '铸渊在线 · ' + uptime, 'agentOpenClaw');
_agentHeartbeats.zhuyuan = { alive: true, ts: Date.now() };
})
.catch(function() {
updateAgentHB('hbOpenClaw', false, '铸渊离线', 'agentOpenClaw');
_agentHeartbeats.zhuyuan = { alive: false, ts: Date.now() };
});
// ── MCP大脑 :3100 (通过 /api/mcp/health) ──
fetch('/api/mcp/health')
.then(function(r) { return r.json(); })
.then(function(d) {
var alive = d.status === 'alive' || d.status === 'ok';
var tools = d.tools_count || (d.tools && d.tools.length) || 0;
updateAgentHB('hbMcp', alive, alive ? '在线 · ' + tools + '工具' : '降级', 'agentMcp');
// COS 心跳跟随 MCP
var cosOk = d.cos && d.cos.connected;
updateAgentHB('hbCos', !!cosOk, cosOk ? 'COS已连接' : 'COS未连接', 'agentCos');
_agentHeartbeats.mcp = { alive: alive, ts: Date.now() };
})
.catch(function() {
updateAgentHB('hbMcp', false, 'MCP离线', 'agentMcp');
updateAgentHB('hbCos', false, 'MCP离线', 'agentCos');
_agentHeartbeats.mcp = { alive: false, ts: Date.now() };
});
// ── GLADA 工官 :3900 (通过 /api/glada/health) ──
fetch('/api/glada/health')
.then(function(r) { return r.json(); })
.then(function(d) {
var alive = d.status === 'ok';
setElText('gladaVersion', d.version || '—');
var mr = d.model_router || {};
setElText('gladaModelRouter', mr.initialized ? (mr.totalModels || 0) + '个模型' : '未初始化');
setElText('gladaUptime', d.uptime ? formatUptime(Math.floor(d.uptime)) : '—');
updateAgentHB('hbGlada', alive, alive ? '工官在线 · v' + (d.version || '?') : '工官离线', 'agentGlada');
_agentHeartbeats.glada = { alive: alive, ts: Date.now() };
})
.catch(function() {
updateAgentHB('hbGlada', false, '工官离线', 'agentGlada');
_agentHeartbeats.glada = { alive: false, ts: Date.now() };
});
// ── GLADA 状态详情 ──
fetch('/api/glada/status')
.then(function(r) { return r.json(); })
.then(function(d) {
setElText('gladaPendingTasks', (d.pending_cab_tasks || 0) + ' 个');
setElText('gladaReceipts', (d.recent_receipts || []).length + ' 条');
})
.catch(function() {
setElText('gladaPendingTasks', '—');
setElText('gladaReceipts', '—');
});
// ── 守卫 ops-agent :3950 (通过 /api/ops-health) ──
fetch('/api/ops-health')
.then(function(r) { return r.json(); })
.then(function(d) {
var alive = d.status === 'ok';
var stats = d.stats || {};
setElText('opsCheckCount', stats.totalChecks || 0);
setElText('opsRepairCount', stats.totalRepairs || 0);
setElText('opsOpenTickets', stats.openTickets || 0);
setElText('opsUptime', d.uptime ? formatUptime(d.uptime) : '—');
updateAgentHB('hbOps', alive, alive ? '守卫在岗 · ' + (stats.openTickets || 0) + '工单' : '守卫离线', 'agentOps');
_agentHeartbeats.ops = { alive: alive, ts: Date.now() };
})
.catch(function() {
updateAgentHB('hbOps', false, '守卫离线', 'agentOps');
_agentHeartbeats.ops = { alive: false, ts: Date.now() };
});
// 更新对话目标选择器状态点
updateChatTargetDot();
}
/* ═══ Agent 点击进入对话 ═══ */
var _chatTarget = 'zhuyuan';
function agentTalkTo(target, name) {
// 切换到对话视图
var chatNav = document.getElementById('navChat');
if (chatNav) chatNav.click();
// 设置对话目标
_chatTarget = target;
var sel = document.getElementById('chatTargetSelect');
if (sel) sel.value = target;
updateChatTargetDot();
updateChatPlaceholder();
// 聚焦输入框
var inputBox = document.getElementById('inputBox');
if (inputBox) inputBox.focus();
// 在对话区添加系统提示
var msgsEl = document.getElementById('messages');
if (msgsEl) {
var sysMsg = document.createElement('div');
sysMsg.className = 'msg msg-sys';
sysMsg.style.cssText = 'text-align:center;padding:8px 16px;font-size:11px;color:var(--dim);font-family:var(--mono)';
sysMsg.textContent = '─── 已切换对话目标: ' + name + ' (' + target + ') ───';
msgsEl.appendChild(sysMsg);
msgsEl.scrollTop = msgsEl.scrollHeight;
}
}
function onChatTargetChange(val) {
_chatTarget = val;
updateChatTargetDot();
updateChatPlaceholder();
}
function updateChatPlaceholder() {
var input = document.getElementById('inputBox');
if (!input) return;
var placeholders = {
zhuyuan: '对铸渊说点什么...',
shuangyan: '对霜砚说点什么...',
glada: '对工官说点什么(任务/模型/回执)...',
ops: '对守卫说点什么(巡检/工单/修复)...',
mcp: '对铸渊大脑说点什么(工具/连接状态)...'
};
input.placeholder = placeholders[_chatTarget] || '说点什么...';
}
function updateChatTargetDot() {
var dot = document.getElementById('chatTargetDot');
if (!dot) return;
var hb = _agentHeartbeats[_chatTarget];
if (hb && hb.alive) {
dot.className = 'chat-target-dot on';
} else {
dot.className = 'chat-target-dot off';
}
}
/* ═══ Init ═══ */
fetchDashboard();
setInterval(fetchDashboard,30000);
/* 首次加载也触发系统状态检测(更新侧边栏导航点) */
fetchSystemStatus();
/* Agent心跳呼吸 · 首次立即执行 + 每30秒轮询 */
pollAgentHeartbeats();
setInterval(pollAgentHeartbeats, 30000);
</script>
<!-- UI 皮肤文字覆盖(壳-核分离·由 apply-skin.js 自动生成) -->
<script src="skin-text-override.js" onerror=""></script>
</body>
</html>