zhuyuan-ice-gl-zy001/STELLAR-GAUGE-20260807/planet.js
2026-08-20 00:01:53 +08:00

301 lines
11 KiB
JavaScript
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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<SATS.length;i++){
p=satPos[i]; if(!p) continue;
dx=e.offsetX-p.x; dy=e.offsetY-p.y;
if (dx*dx+dy*dy < 170){
var m=SATS[i];
var tok=(data.usage.tokens||{})[m.key]||0;
var req=(data.usage.requests||{})[m.key]||0;
showTip(p.x,p.y,m.name+" · "+fmt(tok)+" · "+req+" 次");
return;
}
}
var cx=e.offsetX-C, cy=e.offsetY-C, dist=Math.sqrt(cx*cx+cy*cy);
for (i=0;i<sysHit.length;i++){
var h=sysHit[i];
if (Math.abs(dist-h.r) < 4.5){
var a = Math.atan2(cy,cx);
showTip(C+h.r*Math.cos(a), C+h.r*Math.sin(a), h.detail());
return;
}
}
hideTip();
}
function showTip(x,y,txt){
var tip=$("#tip");
tip.textContent=txt; tip.style.left=x+"px"; tip.style.top=y+"px";
tip.style.display="block";
}
function hideTip(){ $("#tip").style.display="none"; }
/* ---------- 数据与校准 ---------- */
function refresh(){
window.pywebview.api.get_data().then(function(d){ compute(d); },
function(){});
}
function openPanel(){
$("#panel").classList.add("open");
window.pywebview.api.get_data().then(function(d){
var p=d.plan||{};
$("#p_cap").value=p.capacity||"";
$("#p_rem").value=(p.remain==null ? "" : p.remain);
}, function(){});
}
function closePanel(){ $("#panel").classList.remove("open"); }
function savePlan(){
var at=new Date();
function pad(n){ return String(n).padStart(2,"0"); }
var stamp=at.getFullYear()+"-"+pad(at.getMonth()+1)+"-"+pad(at.getDate())
+" "+pad(at.getHours())+":"+pad(at.getMinutes());
window.pywebview.api.get_data().then(function(d){
var plan={ capacity:+($("#p_cap").value)||0,
remain: $("#p_rem").value===""?null:+($("#p_rem").value),
set_at:stamp, set_day:stamp.slice(0,10),
base_tokens:d.usage.tokens||{} };
return window.pywebview.api.set_plan(JSON.stringify(plan));
}).then(function(){ closePanel(); refresh(); }, function(){});
}
refresh(); setInterval(refresh, 2000);