4740 lines
229 KiB
HTML
4740 lines
229 KiB
HTML
<!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">↓</div>
|
||
<div class="arch-step"><span class="arch-dot web"></span>网站交互层(频道)</div>
|
||
<div class="arch-arrow">↓</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∞ · <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(/^>\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执行层 · 铸渊执行人格体 已接通。\nAgent:ICE-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> |