/* planet.js · 湖面映星 · 星球绘制与交互(独立文件,避免内联脚本被宿主吞掉) */ window.__boot = "yes"; window.onerror = function(msg, src, ln, col){ try { if (window.pywebview && window.pywebview.api) window.pywebview.api.log("ERR " + msg + " @" + ln); } catch(e){} }; var SATS = [ {key:"Qwen3.8-Max", name:"Qwen 3.8 Max"}, {key:"Qwen3.7-Max", name:"Qwen 3.7 Max"}, {key:"DeepSeek-v4-Pro", name:"DeepSeek v4 Pro"}, {key:"custom", name:"自定义合计"} ]; var $ = function(s){ return document.querySelector(s); }; function fmt(n){ return n>=1e6 ? (n/1e6).toFixed(2)+"M" : n>=1e3 ? (n/1e3).toFixed(1)+"k" : String(Math.round(n)); } var S = 500, C = S/2, PR = 106; var TILT = -0.42, RX = 190, RY = 54; var RR = PR+12; var SYSR = [PR+30, PR+42, PR+54]; var cv = $("#cv"), ctx = cv.getContext("2d"); var dpr = window.devicePixelRatio || 1; cv.width = S*dpr; cv.height = S*dpr; cv.style.width = cv.style.height = S+"px"; ctx.scale(dpr,dpr); var data = {usage:{}, plan:{}, sys:{}}; var core = {cap:"今 日 投 影", big:"--", sub:"token"}; var ringFrac = null, ringShown = 0; var sysShown = [0,0,0]; var satPos = [], sysHit = []; function tokSince(plan, usage){ var now = usage.tokens||{}, base = plan.base_tokens||{}; var s = 0, k; for (k in now) s += Math.max(0, now[k]-(base[k]||0)); return s; } function compute(d){ data = d; var plan = d.plan||{}, usage = d.usage||{}; if (plan.capacity && plan.remain != null){ var spent = Math.max(0, plan.capacity-plan.remain) + tokSince(plan, usage); var left = Math.max(0, plan.capacity-spent); ringFrac = left/plan.capacity; core = {cap:"余 量", big:fmt(left), sub:"共 "+fmt(plan.capacity)}; } else { ringFrac = null; var tot = 0, t = usage.tokens||{}, k2; for (k2 in t) tot += t[k2]; core = {cap:"今 日 投 影", big:fmt(tot), sub:"token · 本地估算"}; } } var bgStars = []; for (var bi=0; bi<26; bi++){ bgStars.push({x:Math.random()*S, y:Math.random()*S, r:Math.random()*1.1+.3, p:Math.random()*7, s:.005+Math.random()*.011, warm:Math.random()<.6}); } function orbitXY(a){ return [C + RX*Math.cos(a)*Math.cos(TILT) - RY*Math.sin(a)*Math.sin(TILT), C + RY*Math.sin(a)*Math.cos(TILT) + RX*Math.cos(a)*Math.sin(TILT)]; } function drawOrbit(front){ ctx.beginPath(); ctx.ellipse(C,C,RX,RY,TILT, front?0:Math.PI, front?Math.PI:Math.PI*2); ctx.strokeStyle = front ? "rgba(255,233,189,.26)" : "rgba(143,154,196,.14)"; ctx.lineWidth = 1; ctx.stroke(); } function drawSats(t, front){ SATS.forEach(function(m,i){ var a = t*.00022*(1+i*.13) + i*Math.PI/2; if ((Math.sin(a)>0) !== front) return; var xy = orbitXY(a), x=xy[0], y=xy[1]; if (front) satPos[i] = {x:x, y:y}; var req = (data.usage.requests||{})[m.key]||0; var glow = req>0 ? 1 : .45; ctx.beginPath(); ctx.arc(x,y,3.4,0,7); ctx.fillStyle = "rgba(255,233,189,"+(.95*glow)+")"; ctx.shadowColor = "rgba(255,233,189,.95)"; ctx.shadowBlur = 12*glow; ctx.fill(); ctx.shadowBlur = 0; }); } function sysVals(){ var s = data.sys||{}; return [ {label:"内存", pct:(s.mem||{}).used_pct||0, detail:function(){var m=data.sys.mem||{}; return "内存 "+(m.used_gb||0)+" / "+(m.total_gb||0)+" GB";}}, {label:"存储", pct:(s.disk||{}).used_pct||0, detail:function(){var d=data.sys.disk||{}; return "存储 余 "+(d.free_gb||0)+" / "+(d.total_gb||0)+" GB";}}, {label:"处理", pct:(s.cpu||{}).pct||0, detail:function(){return "处理器 "+((data.sys.cpu||{}).pct||0)+"%";}} ]; } function drawSysRings(t, breathe){ var vs = sysVals(); sysHit = []; vs.forEach(function(v,i){ var r = SYSR[i]; sysShown[i] += ((v.pct/100) - sysShown[i])*.08; var a0 = -Math.PI/2; ctx.beginPath(); ctx.arc(C,C,r,0,7); ctx.strokeStyle="rgba(143,154,196,.10)"; ctx.lineWidth=1.4; ctx.stroke(); var frac = Math.max(.005, sysShown[i]); ctx.beginPath(); ctx.arc(C,C,r,a0,a0+Math.PI*2*frac); var hot = v.pct>85; ctx.strokeStyle = hot ? "rgba(255,180,150,"+(.85*breathe)+")" : "rgba(196,210,255,"+(.55*breathe)+")"; ctx.lineWidth=1.4; ctx.stroke(); var hx=C+r*Math.cos(a0+Math.PI*2*frac), hy=C+r*Math.sin(a0+Math.PI*2*frac); ctx.beginPath(); ctx.arc(hx,hy,2,0,7); ctx.fillStyle = hot ? "rgba(255,180,150,.95)" : "rgba(220,228,255,.9)"; ctx.fill(); ctx.textAlign="right"; ctx.fillStyle="rgba(122,133,176,"+(.9*breathe)+")"; ctx.font="300 9px 'PingFang SC'"; ctx.fillText(v.label+" "+Math.round(v.pct), C-r-9, C+3); sysHit.push({r:r, pct:v.pct, label:v.label, detail:v.detail}); }); ctx.textAlign="center"; } function draw(){ try { drawInner(performance.now()); } catch(e){ try { window.pywebview && window.pywebview.api && window.pywebview.api.log("DRAW ERR "+e); } catch(e2){} } } // 不依赖 requestAnimationFrame:失焦窗口里 rAF 会被宿主暂停,改用定时器保活 setInterval(draw, 66); function drawInner(t){ ctx.clearRect(0,0,S,S); bgStars.forEach(function(st){ st.p += st.s; var a = .15 + .6*Math.abs(Math.sin(st.p)); ctx.beginPath(); ctx.arc(st.x,st.y,st.r,0,7); ctx.fillStyle = st.warm ? "rgba(255,233,189,"+a+")" : "rgba(196,210,255,"+(a*.7)+")"; ctx.fill(); }); var breathe = .82 + .18*Math.sin(t/1400); var g = ctx.createRadialGradient(C,C,PR*.6,C,C,PR*2); g.addColorStop(0, "rgba(96,110,200,"+(.18*breathe)+")"); g.addColorStop(.6, "rgba(70,80,160,"+(.08*breathe)+")"); g.addColorStop(1, "rgba(0,0,0,0)"); ctx.fillStyle=g; ctx.fillRect(0,0,S,S); drawOrbit(false); drawSats(t,false); g = ctx.createRadialGradient(C-PR*.38, C-PR*.42, PR*.1, C, C, PR*1.05); g.addColorStop(0, "#33437f"); g.addColorStop(.45, "#18224e"); g.addColorStop(.8, "#0a1028"); g.addColorStop(1, "#05070f"); ctx.beginPath(); ctx.arc(C,C,PR,0,7); ctx.fillStyle=g; ctx.fill(); ctx.save(); ctx.beginPath(); ctx.arc(C,C,PR,0,7); ctx.clip(); for (var i=0;i<2;i++){ var off = ((t/9000 + i*.5)%1)*PR*2 - PR; ctx.beginPath(); ctx.ellipse(C, C+off*.7, PR*1.1, PR*.16, -.2+i*.35, 0, 7); ctx.fillStyle = "rgba(120,140,220,"+(.05+.03*Math.sin(t/2000+i))+")"; ctx.fill(); } g = ctx.createRadialGradient(C+PR*.55, C+PR*.55, PR*.1, C+PR*.55, C+PR*.55, PR*1.15); g.addColorStop(0,"rgba(255,233,189,0)"); g.addColorStop(.72,"rgba(255,233,189,0)"); g.addColorStop(.92,"rgba(255,233,189,"+(.30*breathe)+")"); g.addColorStop(1,"rgba(255,233,189,0)"); ctx.fillStyle=g; ctx.fillRect(0,0,S,S); ctx.restore(); ctx.beginPath(); ctx.arc(C,C,PR,0,7); ctx.strokeStyle="rgba(196,210,255,"+(.16*breathe)+")"; ctx.lineWidth=1; ctx.stroke(); ctx.textAlign="center"; ctx.fillStyle="rgba(90,100,140,.95)"; ctx.font="400 10px 'PingFang SC'"; ctx.fillText(core.cap, C, C-30); ctx.fillStyle="#ffe9bd"; ctx.shadowColor="rgba(255,233,189,.55)"; ctx.shadowBlur=18; ctx.font="200 38px 'PingFang SC'"; ctx.fillText(core.big, C, C+10); ctx.shadowBlur=0; ctx.fillStyle="rgba(143,154,196,.85)"; ctx.font="300 10px 'PingFang SC'"; ctx.fillText(core.sub, C, C+32); var target = ringFrac==null ? 1 : ringFrac; ringShown += (target-ringShown)*.06; var a0=-Math.PI/2; ctx.beginPath(); ctx.arc(C,C,RR,0,7); ctx.strokeStyle="rgba(143,154,196,.13)"; ctx.lineWidth=2; ctx.stroke(); ctx.beginPath(); ctx.arc(C,C,RR,a0,a0+Math.PI*2*ringShown); ctx.strokeStyle="rgba(255,233,189,"+(.8*breathe)+")"; ctx.lineWidth=2; ctx.shadowColor="rgba(255,233,189,.8)"; ctx.shadowBlur=8; ctx.stroke(); ctx.shadowBlur=0; var hx=C+RR*Math.cos(a0+Math.PI*2*ringShown), hy=C+RR*Math.sin(a0+Math.PI*2*ringShown); ctx.beginPath(); ctx.arc(hx,hy,3*(.7+.3*Math.sin(t/420)),0,7); ctx.fillStyle="#ffe9bd"; ctx.shadowColor="rgba(255,233,189,1)"; ctx.shadowBlur=10; ctx.fill(); ctx.shadowBlur=0; drawSysRings(t, breathe); drawOrbit(true); drawSats(t,true); ctx.fillStyle="rgba(90,100,140,.5)"; ctx.font="300 9px 'PingFang SC'"; ctx.fillText("拖 动 移 星 · 轻 点 校 准", C, S-12); } draw(); /* ---------- 交互 ---------- */ var down=false, moved=false, lx=0, ly=0; cv.addEventListener("mousedown", function(e){ down=true; moved=false; lx=e.screenX; ly=e.screenY; }); window.addEventListener("mousemove", function(e){ if (down){ var dx=e.screenX-lx, dy=e.screenY-ly; if (Math.abs(dx)+Math.abs(dy)>3){ moved=true; cv.classList.add("dragging"); } if (moved) window.pywebview.api.drag_by(dx,dy); lx=e.screenX; ly=e.screenY; hideTip(); } else { hover(e); } }); window.addEventListener("mouseup", function(e){ if (down && !moved){ var dx=e.offsetX-C, dy=e.offsetY-C; if (dx*dx+dy*dy <= PR*PR) openPanel(); } down=false; cv.classList.remove("dragging"); }); function hover(e){ var i, p, dx, dy; for (i=0;i