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 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 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()}
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 renderServerList(){const l=document.getElementById('serverList');if(!brainData||!brainData.server){l.innerHTML='<divclass="server-tag"><spanclass="server-dot online"></span>等待数据…</div>';return}const s=brainData.server.services||[],st=brainData.server.stopped||[];let h='';for(const n of s)h+=`<divclass="server-tag"><spanclass="server-dot online"></span>${n}</div>`;for(const n of st)h+=`<divclass="server-tag"><spanclass="server-dot offline"></span>${n}</div>`;l.innerHTML=h}
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+=`<divclass="log-row"><spanclass="log-hash">${e.hash}</span>${e.msg}</div>`;l.innerHTML=h}else{l.innerHTML='<divstyle="color:rgba(140,180,220,0.3)">日志数据未接入 · 可用 git log 获取</div>'}}catch(e){l.innerHTML='<divstyle="color:rgba(140,180,220,0.3)">日志:git最新8条提交</div>'}}
function renderTicketList(){const l=document.getElementById('ticketList');if(!brainData||!brainData.mothers){l.innerHTML='<divclass="ticket-row"><spanclass="ticket-icon">⏳</span><spanclass="ticket-name">等待数据…</span></div>';return}let h='';for(const m of brainData.mothers){h+=`<divstyle="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+=`<divclass="ticket-row"><spanclass="ticket-icon">${icon}</span><spanclass="ticket-name">${p.name}</span><spanclass="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 addChatMsg(who,text,cls){const c=document.getElementById('chatMsgs'),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){drawStars(t);if(t-lastBeat>800){hbEl.textContent=beats[beatIdx];beatIdx=(beatIdx+1)%beats.length;lastBeat=t}if(t-lastFetch>3000){fetchBrain();lastFetch=t}requestAnimationFrame(loop)}