var names={'repo-mcp-server':'仓库接口','repo-mcp-zz':'之之仓库','guanghulab-mcp-server':'大脑唤醒','guanghulab-vault':'密钥保险','guanghulab-portal':'主控门户','hololake-agent':'全息湖代理','guanghulab-git-sync':'同步服务','zhuyuan-agent':'铸渊代理','console-server':'控制台'};
var cvs=document.getElementById('starfield'),ctx=cvs.getContext('2d'),stars=[],data=null,cur=null,lastF=0;
function resize(){cvs.width=window.innerWidth;cvs.height=document.body.scrollHeight+200}
function mkStars(){stars=[];for(var i=0;i<200;i++)stars.push({x:Math.random()*cvs.width,y:Math.random()*cvs.height,r:Math.random()*2+.2,o:Math.random(),s:Math.random()*.0025+.001,p:Math.random()*Math.PI*2})}
function draw(t){ctx.clearRect(0,0,cvs.width,cvs.height);for(var i=0;i<stars.length;i++){vars=stars[i],b=Math.sin(t*s.s+s.p)*.5+.5,a=s.o*(.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()}}
function showPanel(id){var p=document.getElementById('p-'+id),c=document.getElementById('m-'+id),isOpen=p.classList.contains('show');var ap=document.getElementsByClassName('panel');for(var i=0;i<ap.length;i++)ap[i].classList.remove('show');varac=document.getElementsByClassName('mod');for(varj=0;j<ac.length;j++)ac[j].classList.remove('active');if(isOpen){cur=null;return}p.classList.add('show');c.classList.add('active');cur=id;if(id==='chat')document.getElementById('inp').focus();if(id==='server'){renderSrv();fetchLog()}resize();mkStars()}
function handleLogin(){var u=document.getElementById('login-user').value.trim(),p=document.getElementById('login-pass').value;if(!u||!p){var e=document.getElementById('login-error');e.textContent='请输入账号和密码';e.style.display='block';setTimeout(function(){e.style.display='none'},3000);return}fetch('/api/verify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({user:u,password:p})}).then(function(r){return r.json()}).then(function(d){if(d.ok){localStorage.setItem('current_user',d.username);localStorage.setItem('is_admin',d.is_admin);localStorage.setItem('logged_in','true');loggedIn(d.username);document.getElementById('login-pass').value=''}else{var e=document.getElementById('login-error');e.textContent=d.error||'登录失败';e.style.display='block';setTimeout(function(){e.style.display='none'},3000)}}).catch(function(){var e=document.getElementById('login-error');e.textContent='连接失败';e.style.display='block';setTimeout(function(){e.style.display='none'},3000)})}
function loggedIn(u){document.getElementById('login-bar').style.display='none';document.getElementById('logged-in').style.display='flex';document.getElementById('uname').textContent=u;document.getElementById('avatar').textContent=u[0].toUpperCase()}
function goRepo(){var u=localStorage.getItem('current_user');window.location.href='/code/'+u}
function logout(){localStorage.removeItem('current_user');localStorage.removeItem('is_admin');localStorage.removeItem('logged_in');document.getElementById('logged-in').style.display='none';document.getElementById('login-bar').style.display='flex';document.getElementById('login-user').value=''}
var hb=document.getElementById('hb'),beats=['▮▮▮','▮ ▮ ▮','▮▮▮','▮ ▮ ▮'],idxB=0,lastB=0;
function fetchData(){fetch('/api/brain',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){data=d;document.getElementById('wake').textContent=d.awakening||'--';document.getElementById('age').textContent=(d.age_days||'--')+'天';if(d.server){var on=d.server.online||0,tot=d.server.total||10;document.getElementById('svc').textContent=on+'/'+tot;document.getElementById('svc').style.color=on===tot?'rgba(140,220,180,0.9)':'rgba(220,200,140,0.9)'}if(cur==='server')renderSrv();if(cur==='ticket')renderTk()}).catch(function(){})}
function renderSrv(){var b=document.getElementById('srv-list');if(!data||!data.server){b.innerHTML='<divclass="server-item"><spanclass="server-dot on"></span>等待数据…</div>';return}var on=data.server.services||[],off=data.server.stopped||[],h='';for(var i=0;i<on.length;i++){varn=names[on[i]]||on[i].replace(/^guanghulab-/,'').replace(/-/g,'');h+='<divclass="server-item"><spanclass="server-dot on"></span>'+n+'</div>'}for(var j=0;j<off.length;j++){varm=names[off[j]]||off[j].replace(/^guanghulab-/,'').replace(/-/g,'');h+='<divclass="server-item"><spanclass="server-dot off"></span><spanstyle="opacity:.6">'+m+'</span></div>'}b.innerHTML=h||'<divclass="server-item">暂无服务</div>'}
function fetchLog(){fetch('/api/brain',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){var b=document.getElementById('log-box');if(d.ops_log&&d.ops_log.length){var h='';for(var i=0;i<Math.min(d.ops_log.length,12);i++)h+='<divclass="row"><spanclass="hash">'+d.ops_log[i].hash+'</span>'+d.ops_log[i].msg+'</div>';b.innerHTML=h}else{b.innerHTML='<divstyle="color:var(--faint)">日志未接入</div>'}}).catch(function(){document.getElementById('log-box').innerHTML='<divstyle="color:var(--faint)">加载失败</div>'})}
function renderTk(){var b=document.getElementById('tk-list');if(!data||!data.mothers){b.innerHTML='<divclass="ticket"><spanclass="ticon">⏳</span><spanclass="name">等待数据…</span></div>';return}var h='';for(var i=0;i<data.mothers.length;i++){varm=data.mothers[i];h+='<divstyle="font-size:15px;color:var(--soft);padding:12px 0 8px;letter-spacing:1px;font-weight:500">'+m.name+'</div>';if(m.paths)for(var j=0;j<m.paths.length;j++){varp=m.paths[j],done=p.status==='done',prog=p.status==='in_progress',icon=done?'✅':prog?'🔄':'⏳',tag=done?'done':prog?'progress':'',label=done?'完成':prog?'进行中':'待办';h+='<divclass="ticket"><spanclass="ticon">'+icon+'</span><spanclass="name">'+p.name+'</span><spanclass="tag '+tag+'">'+label+'</span></div>'}}b.innerHTML=h}
function send(){var i=document.getElementById('inp'),m=i.value.trim();if(!m)return;addMsg('冰朔',m,'ren');i.value='';fetch('/api/chat-v2',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,persona:'zhuyuan'})}).then(function(r){return r.json()}).then(function(d){addMsg('铸渊',d.reply||d.message||JSON.stringify(d),'zhu');resize();mkStars()}).catch(function(){addMsg('铸渊','(API未接入)\n\n等chat-v2端点激活后,这里就是真正的铸渊交互。','zhu')})}
function addMsg(who,text,cls){var c=document.getElementById('msgs'),d=document.createElement('div');d.className='chat-msg';d.innerHTML='<divclass="who '+cls+'">'+who+'</div><div>'+text.replace(/\n/g,'<br>')+'</div>';c.appendChild(d);c.scrollTop=c.scrollHeight}
function loop(t){draw(t);if(t-lastB>800){hb.textContent=beats[idxB];idxB=(idxB+1)%beats.length;lastB=t}if(t-lastF>3000){fetchData();lastF=t}requestAnimationFrame(loop)}