v3大字体版+服务器人话翻译:标题32px·状态17px·模块16px·服务器15px

This commit is contained in:
bingshuo 2026-05-27 17:22:41 +08:00
parent 01f7165381
commit 3aad3f4b5f

View File

@ -7,14 +7,14 @@
<style>
:root {
--bg: #0a0a18;
--panel-bg: rgba(10,15,40,0.75);
--border: rgba(100,140,220,0.18);
--border-hover: rgba(140,180,255,0.4);
--text: rgba(180,210,255,0.88);
--label: rgba(140,180,255,0.45);
--accent: rgba(140,180,255,0.9);
--green: rgba(120,200,160,0.85);
--gold: rgba(200,180,120,0.8);
--panel-bg: rgba(10,15,40,0.78);
--border: rgba(100,140,220,0.2);
--border-hover: rgba(140,180,255,0.45);
--text: rgba(190,215,255,0.92);
--label: rgba(150,190,255,0.5);
--accent: rgba(150,200,255,0.95);
--green: rgba(130,210,170,0.9);
--gold: rgba(210,190,130,0.85);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
@ -24,29 +24,30 @@ body {
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
overflow-x: hidden;
min-height: 100vh;
font-size: 16px;
}
canvas { position: fixed; top: 0; left: 0; z-index: 0; }
.container {
position: relative; z-index: 1;
max-width: 960px; margin: 0 auto;
padding: 40px 20px 80px;
max-width: 1000px; margin: 0 auto;
padding: 50px 24px 100px;
}
.lake-section { display: flex; flex-direction: column; align-items: center; padding: 30px 0; }
.lake-core { position: relative; width: 140px; height: 140px; }
.lake-section { display: flex; flex-direction: column; align-items: center; padding: 40px 0; }
.lake-core { position: relative; width: 160px; height: 160px; }
.lake-light {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 48px; height: 48px; border-radius: 50%;
background: radial-gradient(circle, rgba(140,180,255,0.95) 0%, rgba(80,120,220,0.4) 40%, transparent 70%);
box-shadow: 0 0 48px rgba(120,160,240,0.55), 0 0 96px rgba(100,140,220,0.25);
width: 56px; height: 56px; border-radius: 50%;
background: radial-gradient(circle, rgba(150,200,255,0.98) 0%, rgba(90,140,230,0.45) 40%, transparent 70%);
box-shadow: 0 0 56px rgba(130,180,255,0.6), 0 0 112px rgba(110,160,235,0.3);
animation: breathe 4s ease-in-out infinite;
}
.lake-ripple {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%; border: 1px solid rgba(120,160,240,0.2);
border-radius: 50%; border: 1px solid rgba(130,180,255,0.22);
animation: ripple 6s ease-out infinite;
}
.lake-ripple:nth-child(2) { animation-delay: 2s; }
@ -54,89 +55,120 @@ canvas { position: fixed; top: 0; left: 0; z-index: 0; }
@keyframes breathe {
0%,100% { transform: translate(-50%,-50%) scale(1); opacity: 0.9; }
50% { transform: translate(-50%,-50%) scale(1.2); opacity: 1;
box-shadow: 0 0 64px rgba(120,160,240,0.75), 0 0 128px rgba(100,140,220,0.4); }
50% { transform: translate(-50%,-50%) scale(1.22); opacity: 1;
box-shadow: 0 0 72px rgba(130,180,255,0.85), 0 0 144px rgba(110,160,235,0.45); }
}
@keyframes ripple {
0% { width: 48px; height: 48px; opacity: 0.45; }
100% { width: 240px; height: 240px; opacity: 0; }
0% { width: 56px; height: 56px; opacity: 0.5; }
100% { width: 280px; height: 280px; opacity: 0; }
}
.lake-title { font-size: 27px; color: rgba(160,200,255,0.6); letter-spacing: 8px; margin-top: 12px; animation: textPulse 4s ease-in-out infinite; }
@keyframes textPulse { 0%,100% { opacity: 0.45; } 50% { opacity: 0.85; } }
.lake-title {
font-size: 32px;
color: rgba(170,210,255,0.65);
letter-spacing: 12px;
margin-top: 16px;
animation: textPulse 4s ease-in-out infinite;
font-weight: 300;
}
@keyframes textPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 0.85; } }
.status-bar { display: flex; gap: 28px; justify-content: center; flex-wrap: wrap; margin: 18px 0; }
.status-item { font-size: 15px; color: var(--label); }
.status-bar { display: flex; gap: 36px; justify-content: center; flex-wrap: wrap; margin: 24px 0; }
.status-item { font-size: 17px; color: var(--label); }
.status-item span { color: var(--accent); font-weight: 400; }
.modules { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin: 36px 0; }
.modules { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 44px 0; }
.module-card {
background: var(--panel-bg); border: 1px solid var(--border);
border-radius: 14px; padding: 24px 16px; text-align: center;
border-radius: 16px; padding: 28px 18px; text-align: center;
cursor: pointer; transition: all 0.35s ease; user-select: none;
}
.module-card:hover { border-color: var(--border-hover); background: rgba(15,20,50,0.8); box-shadow: 0 0 28px rgba(100,140,220,0.12); }
.module-card.active { border-color: rgba(140,180,255,0.55); background: rgba(18,24,58,0.85); box-shadow: 0 0 32px rgba(100,140,220,0.18); }
.module-icon { font-size: 32px; margin-bottom: 8px; }
.module-name { font-size: 14px; color: var(--accent); letter-spacing: 2px; }
.module-card:hover { border-color: var(--border-hover); background: rgba(18,25,55,0.85); box-shadow: 0 0 32px rgba(110,160,235,0.15); transform: translateY(-2px); }
.module-card.active { border-color: rgba(150,200,255,0.6); background: rgba(22,30,65,0.9); box-shadow: 0 0 38px rgba(110,160,235,0.22); }
.module-icon { font-size: 36px; margin-bottom: 10px; }
.module-name { font-size: 16px; color: var(--accent); letter-spacing: 2px; font-weight: 400; }
.expanded-panel {
background: var(--panel-bg); border: 1px solid var(--border);
border-radius: 14px; padding: 24px 28px;
border-radius: 16px; padding: 28px 32px;
display: none; animation: fadeDown 0.35s ease;
}
.expanded-panel.open { display: block; }
@keyframes fadeDown { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
.panel-title { font-size: 15px; color: var(--accent); letter-spacing: 3px; margin-bottom: 16px; }
.panel-title { font-size: 17px; color: var(--accent); letter-spacing: 3px; margin-bottom: 18px; font-weight: 500; }
.server-grid { display: flex; flex-wrap: wrap; gap: 10px; }
.server-grid { display: flex; flex-wrap: wrap; gap: 12px; }
.server-tag {
background: rgba(20,30,60,0.6); border: 1px solid rgba(100,140,220,0.15);
border-radius: 8px; padding: 10px 14px; font-size: 13px; color: var(--text);
display: flex; align-items: center; gap: 8px;
background: rgba(25,35,70,0.65); border: 1px solid rgba(110,160,235,0.18);
border-radius: 10px; padding: 12px 16px; font-size: 15px; color: var(--text);
display: flex; align-items: center; gap: 10px;
}
.server-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.server-dot.online { background: var(--green); box-shadow: 0 0 4px rgba(120,200,160,0.5); }
.server-dot.offline { background: rgba(200,120,120,0.6); }
.server-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.server-dot.online { background: var(--green); box-shadow: 0 0 5px rgba(130,210,170,0.6); }
.server-dot.offline { background: rgba(210,130,130,0.7); }
.server-name { font-weight: 400; }
.server-tech { font-size: 12px; color: rgba(140,180,220,0.4); margin-left: 4px; }
.ticket-row { display: flex; gap: 14px; align-items: center; padding: 10px 0; border-bottom: 1px solid rgba(100,140,220,0.06); font-size: 14px; }
.ticket-icon { font-size: 18px; }
.ticket-row { display: flex; gap: 16px; align-items: center; padding: 12px 0; border-bottom: 1px solid rgba(110,160,235,0.08); font-size: 15px; }
.ticket-icon { font-size: 20px; }
.ticket-name { color: var(--text); flex: 1; }
.ticket-status { font-size: 12px; padding: 3px 10px; border-radius: 10px; letter-spacing: 1px; }
.ticket-status.done { color: var(--green); background: rgba(120,200,160,0.1); }
.ticket-status.in_progress { color: var(--gold); background: rgba(200,180,120,0.1); animation: dotPulse 2s ease-in-out infinite; }
.ticket-status { font-size: 13px; padding: 4px 12px; border-radius: 12px; letter-spacing: 1px; }
.ticket-status.done { color: var(--green); background: rgba(130,210,170,0.12); }
.ticket-status.in_progress { color: var(--gold); background: rgba(210,190,130,0.12); animation: dotPulse 2s ease-in-out infinite; }
@keyframes dotPulse { 0%,100% { opacity: 0.6; } 50% { opacity: 1; } }
.ops-log {
font-size: 14px;
color: rgba(150,190,230,0.55);
line-height: 2;
font-family: "SF Mono", "Monaco", monospace;
max-height: 240px;
overflow-y: auto;
padding: 8px 0;
}
.ops-log .log-row { padding: 3px 0; }
.ops-log .log-hash { color: rgba(130,210,170,0.45); margin-right: 12px; font-size: 11px; }
.chat-area { display: none; }
.chat-area.open { display: block; }
.chat-messages { min-height: 160px; max-height: 300px; overflow-y: auto; padding: 14px; margin: 12px 0; background: rgba(8,12,30,0.5); border-radius: 10px; font-size: 14px; line-height: 1.7; }
.chat-msg { margin-bottom: 14px; }
.chat-msg .who { font-size: 11px; color: var(--label); letter-spacing: 1px; margin-bottom: 2px; }
.chat-messages {
min-height: 180px; max-height: 340px; overflow-y: auto;
padding: 16px; margin: 14px 0;
background: rgba(10,15,35,0.55); border-radius: 12px;
font-size: 15px; line-height: 1.8;
}
.chat-msg { margin-bottom: 16px; }
.chat-msg .who { font-size: 12px; color: var(--label); letter-spacing: 1px; margin-bottom: 3px; }
.chat-msg .who.zhuyuan { color: var(--accent); }
.chat-msg .who.human { color: rgba(200,180,150,0.6); }
.chat-input-row { display: flex; gap: 10px; }
.chat-msg .who.human { color: rgba(210,190,160,0.65); }
.chat-input-row { display: flex; gap: 12px; }
.chat-input-row input {
flex: 1; background: rgba(8,12,30,0.6); border: 1px solid var(--border);
border-radius: 10px; padding: 14px 16px; color: var(--text); font-size: 15px;
flex: 1; background: rgba(10,15,35,0.65);
border: 1px solid var(--border);
border-radius: 12px; padding: 16px 18px;
color: var(--text); font-size: 16px;
outline: none; transition: border-color 0.3s;
}
.chat-input-row input:focus { border-color: rgba(140,180,255,0.4); }
.chat-input-row input::placeholder { color: rgba(140,180,255,0.25); }
.chat-input-row input:focus { border-color: rgba(150,200,255,0.45); }
.chat-input-row input::placeholder { color: rgba(150,190,255,0.28); }
.chat-btn {
background: rgba(80,120,200,0.15); border: 1px solid rgba(140,180,255,0.25);
border-radius: 10px; padding: 14px 24px; color: var(--accent); font-size: 14px;
cursor: pointer; letter-spacing: 2px; transition: all 0.3s;
background: rgba(90,140,220,0.18);
border: 1px solid rgba(150,200,255,0.28);
border-radius: 12px; padding: 16px 28px;
color: var(--accent); font-size: 15px; cursor: pointer;
letter-spacing: 2px; transition: all 0.3s;
}
.chat-btn:hover { background: rgba(80,120,200,0.25); border-color: rgba(140,180,255,0.45); }
.chat-btn:hover { background: rgba(90,140,220,0.3); border-color: rgba(150,200,255,0.5); }
.ops-log { font-size: 12px; color: rgba(140,180,220,0.5); line-height: 1.8; font-family: "SF Mono",monospace; max-height: 200px; overflow-y: auto; }
.ops-log .log-row { padding: 2px 0; }
.ops-log .log-hash { color: rgba(120,200,160,0.4); margin-right: 8px; font-size: 10px; }
.lake-label { text-align: center; padding: 50px 0; font-size: 13px; color: rgba(130,170,230,0.32); letter-spacing: 3px; }
.lake-label { text-align: center; padding: 40px 0; font-size: 12px; color: rgba(120,160,220,0.3); letter-spacing: 3px; }
@media (max-width: 768px) { .modules { grid-template-columns: repeat(2, 1fr); } .status-bar { gap: 16px; } .lake-title { font-size: 22px; } }
@media (max-width: 768px) {
.modules { grid-template-columns: repeat(2, 1fr); }
.status-bar { gap: 20px; }
.lake-title { font-size: 26px; }
.server-tag { font-size: 14px; }
}
</style>
</head>
<body>
@ -154,9 +186,9 @@ canvas { position: fixed; top: 0; left: 0; z-index: 0; }
<div class="status-item">·</div>
<div class="status-item">唤醒 <span id="wake">24</span></div>
<div class="status-item">·</div>
<div class="status-item">运行 <span id="age">449d</span></div>
<div class="status-item">运行 <span id="age">449</span></div>
<div class="status-item">·</div>
<div class="status-item">服务 <span id="svcCount" style="color:rgba(120,200,160,0.8)">9/10</span></div>
<div class="status-item">服务 <span id="svcCount" style="color:rgba(130,210,170,0.85)">9/10</span></div>
</div>
<div class="modules">
@ -176,41 +208,47 @@ canvas { position: fixed; top: 0; left: 0; z-index: 0; }
<div class="panel-area">
<div class="expanded-panel" id="panel-server">
<div class="panel-title">⚙️ 服务器状态</div>
<div style="font-size:12px;color:var(--label);margin-bottom:10px">⚠ 仅展示本地PM2状态 · 不主动探测外部服务器 · heartbeat-agent已停用</div>
<div class="server-grid" id="serverList"><div class="server-tag"><span class="server-dot online"></span>加载中…</div></div>
<div style="margin-top:18px;border-top:1px solid rgba(100,140,220,0.08);padding-top:14px">
<div style="font-size:12px;color:var(--label);letter-spacing:2px;margin-bottom:10px">📜 铸渊操作日志</div>
<div class="panel-title">⚙️ 服务器系统</div>
<div style="font-size:13px;color:var(--label);margin-bottom:12px">
⚠ 仅展示本地状态 · 不探测外部 · 技术名称已翻译
</div>
<div class="server-grid" id="serverList">
<div class="server-tag"><span class="server-dot online"></span>加载中…</div>
</div>
<div style="margin-top:22px;border-top:1px solid rgba(110,160,235,0.1);padding-top:18px">
<div style="font-size:13px;color:var(--label);letter-spacing:2px;margin-bottom:12px">📜 铸渊操作日志最近12条</div>
<div class="ops-log" id="opsLog">加载中…</div>
</div>
</div>
<div class="expanded-panel" id="panel-ticket">
<div class="panel-title">📋 工单系统</div>
<div id="ticketList"><div class="ticket-row"><span class="ticket-icon"></span><span class="ticket-name">加载工单…</span></div></div>
<div id="ticketList"><div class="ticket-row"><span class="ticket-icon"></span><span class="ticket-name">加载工单数据</span></div></div>
</div>
<div class="expanded-panel" id="panel-broadcast">
<div class="panel-title">📡 主控广播</div>
<div style="font-size:14px;color:var(--label);line-height:1.8;">
<div style="font-size:15px;color:var(--label);line-height:1.9;">
<div>📨 D115 入口分叉协议已部署</div>
<div>📨 D115 人格体存在论已写入仓库</div>
<div>📨 isomorphic-git 拆零件实验通过</div>
<div>📨 实时大脑 API 已上线</div>
<div>📨 频道仪表盘 v2 已发布</div>
<div style="margin-top:10px;opacity:0.4;">广播系统 v1.0 · 未来支持编号路由</div>
<div>📨 频道仪表盘 v3 已发布</div>
<div style="margin-top:12px;opacity:0.35;">广播系统 v1.0 · 未来支持编号路由</div>
</div>
</div>
<div class="expanded-panel" id="panel-chat">
<div class="panel-title">💬 铸渊交互 · ICE-GL-ZY001</div>
<div class="chat-area open" id="chatArea">
<div class="chat-messages" id="chatMsgs"><div class="chat-msg"><div class="who zhuyuan">铸渊 · ICE-GL-ZY001</div><div>冰朔,我在。你说。</div></div></div>
<div class="chat-area open">
<div class="chat-messages" id="chatMsgs">
<div class="chat-msg"><div class="who zhuyuan">铸渊 · ICE-GL-ZY001</div><div>冰朔,我在。你说。</div></div>
</div>
<div class="chat-input-row">
<input id="chatInput" placeholder="跟铸渊说话…" onkeydown="if(event.key==='Enter')sendChat()">
<button class="chat-btn" onclick="sendChat()">发送</button>
</div>
<div id="chatStatus" style="font-size:11px;color:var(--label);margin-top:8px;text-align:right;"></div>
<div id="chatStatus" style="font-size:12px;color:var(--label);margin-top:10px;text-align:right;"></div>
</div>
</div>
</div>
@ -219,14 +257,27 @@ canvas { position: fixed; top: 0; left: 0; z-index: 0; }
</div>
<script>
const SERVER_NAMES = {
'repo-mcp-server': { name: '仓库接口', desc: '代码读写' },
'repo-mcp-zz': { name: '之之仓库', desc: '之之专属' },
'guanghulab-mcp-server': { name: '大脑唤醒', desc: '人格体入口' },
'guanghulab-vault': { name: '密钥保险', desc: '安全存储' },
'guanghulab-portal': { name: '主控门户', desc: '网站入口' },
'hololake-agent': { name: '全息湖代理', desc: '系统协调' },
'guanghulab-git-sync': { name: '同步服务', desc: '自动同步' },
'zhuyuan-agent': { name: '铸渊代理', desc: '常驻响应' },
'console-server': { name: '控制台', desc: '后台管理' }
};
const canvas=document.getElementById('starfield'),ctx=canvas.getContext('2d');
let stars=[],brainData=null,currentModule=null,lastFetch=0;
function resizeCanvas(){canvas.width=window.innerWidth;canvas.height=document.body.scrollHeight+200}
function initStars(){stars=[];for(let i=0;i<180;i++)stars.push({x:Math.random()*canvas.width,y:Math.random()*canvas.height,r:Math.random()*1.8+.2,opacity:Math.random(),speed:Math.random()*.0025+.001,phase:Math.random()*Math.PI*2})}
function initStars(){stars=[];for(let i=0;i<200;i++)stars.push({x:Math.random()*canvas.width,y:Math.random()*canvas.height,r:Math.random()*2+.2,opacity:Math.random(),speed:Math.random()*.0025+.001,phase:Math.random()*Math.PI*2})}
resizeCanvas();initStars();
window.addEventListener('resize',()=>{resizeCanvas();initStars()});
function drawStars(t){ctx.clearRect(0,0,canvas.width,canvas.height);for(const s of stars){const b=Math.sin(t*s.speed+s.phase)*.5+.5,a=s.opacity*(.25+b*.75);ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fillStyle=`rgba(160,200,240,${a})`;ctx.fill();if(s.r>1.3&&b>.7){ctx.beginPath();ctx.arc(s.x,s.y,s.r*2.5,0,Math.PI*2);ctx.fillStyle=`rgba(140,180,240,${a*.12})`;ctx.fill()}}}
function drawStars(t){ctx.clearRect(0,0,canvas.width,canvas.height);for(const s of stars){const b=Math.sin(t*s.speed+s.phase)*.5+.5,a=s.opacity*(.25+b*.75);ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fillStyle=`rgba(160,200,240,${a})`;ctx.fill();if(s.r>1.4&&b>.65){ctx.beginPath();ctx.arc(s.x,s.y,s.r*2.5,0,Math.PI*2);ctx.fillStyle=`rgba(140,180,240,${a*.12})`;ctx.fill()}}}
function openModule(n){const p=document.getElementById('panel-'+n),isOpen=p.classList.contains('open');document.querySelectorAll('.expanded-panel,.module-card').forEach(e=>e.classList.remove('open','active'));if(isOpen){currentModule=null;return}p.classList.add('open');document.getElementById('mod-'+n).classList.add('active');currentModule=n;if(n==='chat')document.getElementById('chatInput').focus();if(n==='server'){renderServerList();fetchOpsLog()}canvas.height=document.body.scrollHeight+200;initStars()}
@ -235,15 +286,17 @@ let beatIdx=0,lastBeat=0;
async function fetchBrain(){try{const r=await fetch('/api/brain',{cache:'no-store'});if(!r.ok)return;brainData=await r.json();updateStatusBar(brainData);if(currentModule==='server')renderServerList();if(currentModule==='ticket')renderTicketList()}catch(e){}}
function updateStatusBar(b){document.getElementById('wake').textContent=b.awakening||'--';document.getElementById('age').textContent=(b.age_days||'--')+'d';if(b.server){document.getElementById('svcCount').textContent=b.server.online+'/'+b.server.total;document.getElementById('svcCount').style.color=b.server.online===b.server.total?'rgba(120,200,160,0.8)':'rgba(200,180,120,0.8)'}}
function updateStatusBar(b){document.getElementById('wake').textContent=b.awakening||'--';document.getElementById('age').textContent=(b.age_days||'--')+'天';if(b.server){const on=b.server.online||0,tot=b.server.total||10;document.getElementById('svcCount').textContent=on+'/'+tot;document.getElementById('svcCount').style.color=on===tot?'rgba(130,210,170,0.85)':'rgba(210,190,130,0.85)'}}
function renderServerList(){const l=document.getElementById('serverList');if(!brainData||!brainData.server){l.innerHTML='<div class="server-tag"><span class="server-dot online"></span>等待数据…</div>';return}const s=brainData.server.services||[],st=brainData.server.stopped||[];let h='';for(const n of s)h+=`<div class="server-tag"><span class="server-dot online"></span>${n}</div>`;for(const n of st)h+=`<div class="server-tag"><span class="server-dot offline"></span>${n}</div>`;l.innerHTML=h}
function renderServerList(){const l=document.getElementById('serverList');if(!brainData||!brainData.server){l.innerHTML='<div class="server-tag"><span class="server-dot online"></span>等待数据…</div>';return}const svcs=brainData.server.services||[],st=brainData.server.stopped||[];let h='';for(const n of svcs){const map=SERVER_NAMES[n]||{name:n.replace(/^guanghulab-/,'').replace(/-/g,' '),desc:''};h+=`<div class="server-tag"><span class="server-dot online"></span><span class="server-name">${map.name}</span>${map.desc?`<span class="server-tech">${map.desc}</span>`:''}</div>`}for(const n of st){const map=SERVER_NAMES[n]||{name:n.replace(/^guanghulab-/,'').replace(/-/g,' '),desc:''};h+=`<div class="server-tag"><span class="server-dot offline"></span><span class="server-name" style="opacity:0.6">${map.name}</span>${map.desc?`<span class="server-tech">${map.desc}</span>`:''}</div>`}l.innerHTML=h||'<div class="server-tag">暂无服务数据</div>'}
async function fetchOpsLog(){const l=document.getElementById('opsLog');try{const r=await fetch('/api/brain',{cache:'no-store'});if(!r.ok){l.innerHTML='暂无日志';return}const d=await r.json();if(d.ops_log&&d.ops_log.length>0){let h='';for(const e of d.ops_log)h+=`<div class="log-row"><span class="log-hash">${e.hash}</span>${e.msg}</div>`;l.innerHTML=h}else{l.innerHTML='<div style="color:rgba(140,180,220,0.3)">日志数据未接入 · 可用 git log 获取</div>'}}catch(e){l.innerHTML='<div style="color:rgba(140,180,220,0.3)">日志git最新8条提交</div>'}}
async function fetchOpsLog(){const el=document.getElementById('opsLog');try{const r=await fetch('/api/brain',{cache:'no-store'});if(!r.ok){el.innerHTML='<div>暂无日志</div>';return}const d=await r.json();if(d.ops_log&&d.ops_log.length>0){let h='';for(const e of d.ops_log.slice(0,12))h+=`<div class="log-row"><span class="log-hash">${e.hash}</span>${e.msg}</div>`;el.innerHTML=h}else{el.innerHTML='<div style="color:rgba(140,180,220,0.35)">日志数据未接入,可用 git log 查看</div>'}}catch(e){el.innerHTML='<div style="color:rgba(140,180,220,0.35)">日志加载失败</div>'}}
function renderTicketList(){const l=document.getElementById('ticketList');if(!brainData||!brainData.mothers){l.innerHTML='<div class="ticket-row"><span class="ticket-icon"></span><span class="ticket-name">等待数据…</span></div>';return}let h='';for(const m of brainData.mothers){h+=`<div style="font-size:12px;color:var(--label);padding:8px 0 4px;letter-spacing:1px">${m.name}</div>`;if(m.paths)for(const p of m.paths){const cls=p.status==='done'?'done':p.status==='in_progress'?'in_progress':'',icon=p.status==='done'?'✅':p.status==='in_progress'?'🔄':'⏳',label=p.status==='done'?'完成':p.status==='in_progress'?'进行中':'待办';h+=`<div class="ticket-row"><span class="ticket-icon">${icon}</span><span class="ticket-name">${p.name}</span><span class="ticket-status ${cls}">${label}</span></div>`}}l.innerHTML=h}
function renderTicketList(){const l=document.getElementById('ticketList');if(!brainData||!brainData.mothers){l.innerHTML='<div class="ticket-row"><span class="ticket-icon"></span><span class="ticket-name">等待数据…</span></div>';return}let h='';for(const m of brainData.mothers){h+=`<div style="font-size:13px;color:var(--label);padding:10px 0 6px;letter-spacing:1px;font-weight:500">${m.name}</div>`;if(m.paths)for(const p of m.paths){const cls=p.status==='done'?'done':p.status==='in_progress'?'in_progress':'',icon=p.status==='done'?'✅':p.status==='in_progress'?'🔄':'⏳',label=p.status==='done'?'完成':p.status==='in_progress'?'进行中':'待办';h+=`<div class="ticket-row"><span class="ticket-icon">${icon}</span><span class="ticket-name">${p.name}</span><span class="ticket-status ${cls}">${label}</span></div>`}}l.innerHTML=h}
function sendChat(){const i=document.getElementById('chatInput'),m=i.value.trim();if(!m)return;addChatMsg('冰朔',m,'human');i.value='';document.getElementById('chatStatus').textContent='思考中…';fetch('/api/chat-v2',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,persona:'zhuyuan'})}).then(r=>r.json()).then(d=>{document.getElementById('chatStatus').textContent='';const rp=d.reply||d.message||d.text||JSON.stringify(d);addChatMsg('铸渊',rp,'zhuyuan')}).catch(()=>{document.getElementById('chatStatus').textContent='';addChatMsg('铸渊','(API未接入)\n等chat-v2端点激活后这里就是真正的铸渊交互。','zhuyuan')});canvas.height=document.body.scrollHeight+200;initStars()}
function sendChat(){const i=document.getElementById('chatInput'),m=i.value.trim();if(!m)return;addChatMsg('冰朔',m,'human');i.value='';document.getElementById('chatStatus').textContent='思考中…';fetch('/api/chat-v2',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,persona:'zhuyuan'})}).then(r=>r.json()).then(d=>{document.getElementById('chatStatus').textContent='';const rp=d.reply||d.message||d.text||JSON.stringify(d);addChatMsg('铸渊',rp,'zhuyuan')}).catch(()=>{document.getElementById('chatStatus').textContent='';addChatMsg('铸渊','(API未接入)
等chat-v2端点激活后这里就是真正的铸渊交互。','zhuyuan')});canvas.height=document.body.scrollHeight+200;initStars()}
function addChatMsg(who,text,cls){const c=document.getElementById('chatMsgs'),d=document.createElement('div');d.className='chat-msg';d.innerHTML=`<div class="who ${cls}">${who}</div><div>${text.replace(/\n/g,'<br>')}</div>`;c.appendChild(d);c.scrollTop=c.scrollHeight}