feat: OCG router proxy — quota-aware 6-key routing

This commit is contained in:
hmo
2026-08-01 12:39:35 +08:00
parent 3e2a74d4d9
commit 79a9264cbd
3 changed files with 636 additions and 0 deletions
+44
View File
@@ -462,6 +462,50 @@ document.getElementById('rdp-status-txt').textContent=fOn?'Connected':e4.rdp_ena
if(cards)cards.innerHTML=html;
}
}catch(e5){}
/* --- OCG Router Proxy section --- */
var pr=document.getElementById('proxy-router-section');
if(!pr){
pr=document.createElement('div');pr.id='proxy-router-section';pr.className='ps';pr.style.marginTop='16px';
pr.innerHTML='<h2>OCG 路由代理<span class="help-btn" onclick="showModuleHelp(\'ocg_router\',\'human\')" title="使用说明">?</span><span class="help-btn ai" onclick="showModuleHelp(\'ocg_router\',\'ai\')" title="AI Spec">\u00a7</span></h2>'
+'<div id="proxy-status-line" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:10px;font-size:18px"></div>'
+'<div id="proxy-pool" style="display:flex;flex-wrap:wrap;gap:6px"></div>';
ci.appendChild(pr);
}
try{
var prr=await fetch('/api/proxy/status');var prd=await prr.json();
var pl=document.getElementById('proxy-status-line');
var pp=document.getElementById('proxy-pool');
if(!prd.ok||prd.proxy_running===false){
if(pl)pl.innerHTML='<span style="color:var(--red)">\u2717 代理未运行</span>';
if(pp)pp.innerHTML='<span style="color:var(--dim);font-size:18px">'+esc(prd.error||'无法连接 :19878')+'</span>';
}else{
if(pl)pl.innerHTML='<span style="color:var(--green);font-weight:600">当前路由: '+esc(prd.current_key||'—')+'</span>'
+'<span style="color:var(--dim)">'+prd.total_requests+' 请求</span>'
+'<span style="color:var(--dim)">'+prd.pool_healthy+'/'+prd.pool_size+' healthy</span>';
if(pp){
var ph='';
for(var i=0;i<prd.pool.length;i++){
var k=prd.pool[i];
var mp=k.monthly_pct!=null?k.monthly_pct:0;
var wp=k.weekly_pct!=null?k.weekly_pct:0;
var ok=k.healthy&&!k.in_cooldown;
var edge=ok?'var(--green)':'var(--red)';
var bg=ok?'var(--card)':(k.in_cooldown?'rgba(251,140,0,.1)':'rgba(229,57,53,.06)');
ph+='<div style="background:'+bg+';border:1px solid '+edge+';border-radius:6px;padding:6px 10px;min-width:120px;font-size:17px">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px">'
+'<strong style="font-size:16px">'+esc(k.key_id||'?')+'</strong>'
+(k.hits>0?'<span style="font-size:14px;color:var(--accent)">'+k.hits+' hits</span>':'')
+'</div>'
+'<div style="font-size:14px;color:var(--dim)">M '+mp+'% \u00b7 W '+wp+'%</div>'
+(k.in_cooldown?'<div style="font-size:13px;color:var(--yellow);margin-top:1px">冷却中</div>':'')
+(k.error?'<div style="font-size:13px;color:var(--red);margin-top:1px">\u2717 '+esc(k.error)+'</div>':'')
+'</div>';
}
pp.innerHTML=ph;
}
}
}catch(prE){}
}catch(e){}
}
function _fmtCountdown(sec){