Files
wevads-platform/public/api-key-pool.html
2026-02-26 04:53:11 +01:00

297 lines
17 KiB
HTML
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.
<?php include_once("/opt/wevads-arsenal/public/api/wevads-metrics.php"); ?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVADS - 🔑 WEVADS Key Manager — All Providers × Personas</title>
<style>
:root{--bg:#0f172a;--c:#1e293b;--b:#334155;--t:#e2e8f0;--gd:#22c55e;--rd:#ef4444;--yl:#eab308;--bl:#3b82f6;--pr:#a855f7;--m:'DM Sans',sans-serif}
*{margin:0;padding:0;box-sizing:border-box}body{font-family:var(--m);background:#060a14;color:var(--t);padding:16px}
h1{font-size:20px;margin-bottom:4px}h2{font-size:13px;color:#64748b;margin-bottom:16px}
.stats{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px}
.stat{background:var(--c);border-radius:10px;padding:10px 16px;border:1px solid var(--b)}
.stat .n{font-size:22px;font-weight:700}.stat .l{font-size:10px;color:#64748b}
.filters{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
.fbtn{padding:6px 12px;border-radius:8px;border:1px solid var(--b);background:var(--c);color:var(--t);cursor:pointer;font-size:11px;font-family:var(--m)}
.fbtn.active{border-color:var(--bl);background:#3b82f620;color:var(--bl)}
.provider{background:var(--c);border-radius:10px;margin-bottom:8px;border:1px solid var(--b);overflow:hidden}
.provider.has-keys{border-color:var(--gd)}
.phdr{display:flex;align-items:center;padding:12px 16px;cursor:pointer;gap:10px;user-select:none}
.phdr:hover{background:#0c122008}
.phdr .arrow{transition:.2s;font-size:10px}.phdr.open .arrow{transform:rotate(90deg)}
.phdr .pname{font-weight:600;font-size:14px;flex:1}
.badge{padding:2px 7px;border-radius:5px;font-size:10px;font-weight:600}
.badge.ok{background:#16a34a20;color:var(--gd)}.badge.err{background:#dc262620;color:var(--rd)}
.badge.pend{background:#ca8a0420;color:var(--yl)}.badge.free{background:#3b82f620;color:var(--bl)}
.badge.paid{background:#a855f720;color:var(--pr)}.badge.keys{background:#22c55e20;color:var(--gd)}
.pbody{display:none;padding:0 16px 12px;border-top:1px solid var(--b)}
.pbody.open{display:block}
.signup-row{display:flex;gap:8px;align-items:center;margin:10px 0;flex-wrap:wrap}
.signup-row a{color:var(--pr);font-size:12px;text-decoration:none}
.signup-row a:hover{text-decoration:underline}
.hmkey{display:flex;gap:6px;margin:8px 0;align-items:center}
.hmkey label{font-size:11px;color:#64748b;min-width:100px}
.hmkey input{flex:1;background:var(--bg);border:1px solid var(--b);border-radius:6px;padding:6px 10px;color:var(--t);font-size:11px;font-family:monospace}
.hmkey input:focus{outline:none;border-color:var(--bl)}
.btn{padding:5px 10px;border:none;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer}
.btn-t{background:var(--bl);color:#fff}.btn-s{background:var(--gd);color:#fff}.btn-x{background:var(--pr);color:#fff}
.ptable{width:100%;border-collapse:collapse;margin-top:8px;font-size:11px}
.ptable th{text-align:left;padding:6px 8px;background:var(--bg);color:#64748b;font-size:10px;font-weight:600}
.ptable td{padding:5px 8px;border-top:1px solid #1e293b50}
.ptable tr:hover{background:#0c122006}
.ptable input{width:100%;background:var(--bg);border:1px solid var(--b);border-radius:4px;padding:4px 6px;color:var(--t);font-size:10px;font-family:monospace}
.ptable input:focus{outline:none;border-color:var(--bl)}
.ptable .btn{padding:3px 6px;font-size:9px}
.res{padding:4px 8px;border-radius:4px;font-size:10px;margin-top:4px}
.res.ok{background:#16a34a15;color:var(--gd)}.res.fail{background:#dc262615;color:var(--rd)}
.bulk-row{display:flex;gap:6px;margin-top:8px}
.cnt{font-size:10px;padding:1px 5px;border-radius:4px;background:var(--bg)}
.wv-status{position:fixed;top:12px;right:140px;z-index:9998;background:rgba(52,211,153,.15);border:1px solid #34d399;border-radius:12px;padding:3px 10px;color:#34d399;font-size:10px;font-weight:700;font-family:'JetBrains Mono',monospace}
.sc,.card,[class*="stat-card"]{transition:all .25s ease;position:relative;overflow:hidden}
.sc:hover,.card:hover,[class*="stat-card"]:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.25)}
.sc::after,.card::after{content:'';position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--cy,#22d3ee);opacity:0;transition:opacity .25s}
.sc:hover::after,.card:hover::after{opacity:.7}
.btn,.button,[class*="btn-"]{transition:all .2s ease}
.btn:hover,.button:hover{transform:translateY(-1px)}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.sc,.card{animation:fadeIn .4s ease both}
.sc:nth-child(2),.card:nth-child(2){animation-delay:.05s}
.sc:nth-child(3),.card:nth-child(3){animation-delay:.1s}
.sc:nth-child(4),.card:nth-child(4){animation-delay:.15s}
.sc:nth-child(5),.card:nth-child(5){animation-delay:.2s}
.sc:nth-child(6),.card:nth-child(6){animation-delay:.25s}
</style>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=DM+Sans:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="wevads-global.css?v1770777318">
</head>
<body>
<h1>🔑 Key Manager — All Providers × 178 Personas</h1>
<p style="font-size:12px;color:#64748b;margin:6px 0 16px;max-width:600px;line-height:1.6">Key Manager — 45 providers × 178 personas, rotation automatique des clés API.</p>
<h2>Chaque persona = 1 compte = 1 quota de tokens gratuit. Multiplie les clés pour maximiser la capacité.</h2>
<div class="stats" id="stats">Chargement...</div>
<div class="filters" id="filters"></div>
<div id="providers">Chargement des providers...</div>
<script>
const FACTORY='/api/ia-provider-factory.php';
const ROUTER='/api/provider-router.php';
let allData={};
let currentFilter='all';
async function loadAll(){
const [dash,router]=await Promise.all([
fetch(FACTORY+'?action=dashboard').then(r=>r.json()).catch(()=>({})),
fetch(ROUTER+'?action=providers').then(r=>r.json()).catch(()=>({}))
]);
allData.dashboard=dash;
allData.router=router;
allData.providers=dash.providers||{};
// Stats
let totalAccounts=0,totalKeys=0,totalActive=0,provCount=Object.keys(allData.providers).length;
for(const[k,v] of Object.entries(allData.providers)){
totalAccounts+=v.accounts||0;
totalKeys+=v.keys||0;
totalActive+=v.active||0;
}
document.getElementById('stats').innerHTML=`
<div class="stat"><div class="n" style="color:var(--bl)">${provCount}</div><div class="l">Providers</div></div>
<div class="stat"><div class="n">${totalAccounts.toLocaleString()}</div><div class="l">Total Accounts</div></div>
<div class="stat"><div class="n" style="color:var(--gd)">${totalKeys}</div><div class="l">Real Keys</div></div>
<div class="stat"><div class="n" style="color:var(--yl)">${totalAccounts-totalKeys}</div><div class="l">Need Keys</div></div>
<div class="stat"><div class="n" style="color:var(--gd)">${totalActive}</div><div class="l">Active</div></div>
`;
// Filters
document.getElementById('filters').innerHTML=`
<button class="fbtn active" onclick="setFilter('all',this)">All (${provCount})</button>
<button class="fbtn" onclick="setFilter('haskeys',this)">Has Keys (${Object.entries(allData.providers).filter(([k,v])=>v.keys>0).length})</button>
<button class="fbtn" onclick="setFilter('nokeys',this)">Needs Keys (${Object.entries(allData.providers).filter(([k,v])=>v.keys===0).length})</button>
<button class="fbtn" onclick="setFilter('free',this)">Free Tier</button>
<button class="fbtn" onclick="setFilter('paid',this)">Paid</button>
`;
renderProviders();
}
function setFilter(f,el){
currentFilter=f;
document.querySelectorAll('.fbtn').forEach(b=>b.classList.remove('active'));
el.classList.add('active');
renderProviders();
}
const SIGNUP={
'Cerebras':'https://cloud.cerebras.ai/','Groq':'https://console.groq.com/',
'DeepSeek':'https://platform.deepseek.com/','Gemini':'https://aistudio.google.com/',
'Claude':'https://console.anthropic.com/','SambaNova':'https://cloud.sambanova.ai/',
'Hyperbolic':'https://app.hyperbolic.xyz/','Mistral':'https://console.mistral.ai/',
'Cohere':'https://dashboard.cohere.com/','OpenRouter':'https://openrouter.ai/',
'Together':'https://api.together.xyz/','Fireworks':'https://fireworks.ai/',
'NVIDIA_NIM':'https://build.nvidia.com/','HuggingFace':'https://huggingface.co/settings/tokens',
'DeepInfra':'https://deepinfra.com/dash/api_keys','AI21':'https://studio.ai21.com/',
'Cloudflare':'https://dash.cloudflare.com/','Lepton':'https://www.lepton.ai/',
'Novita':'https://novita.ai/','Nebius':'https://studio.nebius.ai/',
'Perplexity':'https://perplexity.ai/settings/api','Lambda':'https://cloud.lambdalabs.com/',
'Replicate':'https://replicate.com/account/api-tokens','GLHF':'https://glhf.chat/users/settings/api',
'Chutes':'https://chutes.ai/app/api','OctoAI':'https://octoai.cloud/',
'xAI':'https://console.x.ai/','Anyscale':'https://app.endpoints.anyscale.com/'
};
const PAID_PROVIDERS=['DeepSeek_Paid','DeepSeek_R1','Claude_Pro','Claude_API','Gemini_Advanced','Gemini_API','OpenAI_Plus','OpenAI_Pro','OpenAI_API','Mistral_Paid','Cohere_Pro'];
function renderProviders(){
let entries=Object.entries(allData.providers).sort((a,b)=>{
if(b[1].keys!==a[1].keys) return b[1].keys-a[1].keys;
return b[1].accounts-a[1].accounts;
});
if(currentFilter==='haskeys') entries=entries.filter(([k,v])=>v.keys>0);
if(currentFilter==='nokeys') entries=entries.filter(([k,v])=>v.keys===0);
if(currentFilter==='free') entries=entries.filter(([k,v])=>!PAID_PROVIDERS.includes(k));
if(currentFilter==='paid') entries=entries.filter(([k,v])=>PAID_PROVIDERS.includes(k));
let h='';
for(const[name,info] of entries){
const isPaid=PAID_PROVIDERS.includes(name);
const hasKeys=info.keys>0;
const signup=SIGNUP[name.split('_')[0]]||'';
h+=`<div class="provider ${hasKeys?'has-keys':''}">
<div class="phdr" onclick="toggleProvider(this,'${name}')">
<span class="arrow">▶</span>
<span class="pname">${name}</span>
<span class="cnt">${info.accounts} comptes</span>
${hasKeys?`<span class="badge keys">${info.keys} 🔑</span>`:''}
${info.active>0?`<span class="badge ok">${info.active} ✅</span>`:''}
<span class="badge ${isPaid?'paid':'free'}">${isPaid?'💰 Paid':'🆓 Free'}</span>
</div>
<div class="pbody" id="body-${name}">
${signup?`<div class="signup-row">🔗 Signup: <a href="${signup}" target="_blank">${signup}</a></div>`:''}
<div class="hmkey">
<label>🔑 Main Key:</label>
<input id="mainkey-${name}" placeholder="Coller la clé API principale...">
<button class="btn btn-t" onclick="testMainKey('${name}')">🧪 Test</button>
<button class="btn btn-s" onclick="saveMainKey('${name}')">💾 Hamid</button>
</div>
<div id="mainres-${name}"></div>
<div class="bulk-row">
<button class="btn btn-x" onclick="loadPersonas('${name}')">👥 Charger ${info.accounts} comptes persona</button>
<button class="btn btn-t" onclick="testAllKeys('${name}')">🧪 Tester toutes les clés</button>
</div>
<div id="personas-${name}"><p style="font-size:11px;color:#64748b;margin-top:6px">Clique "Charger" pour voir les comptes persona</p></div>
</div>
</div>`;
}
document.getElementById('providers').innerHTML=h;
}
function toggleProvider(el,name){
el.classList.toggle('open');
document.getElementById('body-'+name).classList.toggle('open');
}
async function testMainKey(name){
const key=document.getElementById('mainkey-'+name).value.trim();
const div=document.getElementById('mainres-'+name);
if(!key){div.innerHTML='<div class="res fail">Colle une clé</div>';return;}
div.innerHTML='<div class="res" style="color:var(--bl)">⏳ Test...</div>';
try{
const r=await fetch(ROUTER,{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({action:'chat',provider:name.split('_')[0],message:'say OK',test_key:key})});
const d=await r.json();
div.innerHTML=d.response?
`<div class="res ok">✅ ${d.provider} OK — ${d.latency_ms}ms</div>`:
`<div class="res fail">❌ ${JSON.stringify(d).substring(0,100)}</div>`;
}catch(e){div.innerHTML=`<div class="res fail">❌ ${e.message}</div>`;}
}
async function saveMainKey(name){
const key=document.getElementById('mainkey-'+name).value.trim();
if(!key){alert('Colle une clé');return;}
try{
// Save to hamid_providers
const fd=new FormData();fd.append('action','update_provider');fd.append('provider_name',name);fd.append('api_key',key);
await fetch('/api/hamid-chef.php',{method:'POST',body:fd});
// Also to ia_provider_accounts
await fetch(FACTORY,{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({action:'create_account',provider:name,api_key:key})});
document.getElementById('mainres-'+name).innerHTML='<div class="res ok">💾 Saved to Hamid + Factory</div>';
}catch(e){document.getElementById('mainres-'+name).innerHTML=`<div class="res fail">❌ ${e.message}</div>`;}
}
async function loadPersonas(name){
const div=document.getElementById('personas-'+name);
div.innerHTML='<p style="color:var(--bl);font-size:11px">⏳ Chargement...</p>';
try{
const r=await fetch(FACTORY+'?action=scan');
const d=await r.json();
// Filter for this provider from scan results
const accounts=(d.results||[]).filter(a=>a.provider_name===name);
if(!accounts.length){
// Fallback: query DB directly through router
div.innerHTML=`<p style="font-size:11px;color:#64748b">Utilise le scan DB pour charger...</p>`;
const r2=await fetch(ROUTER+'?action=providers');
const d2=await r2.json();
div.innerHTML=`<p style="font-size:11px;color:#64748b">${(d2.multi_accounts||[]).find(m=>m.provider_name===name)?.total_accounts||0} comptes trouvés. Utilise l'API factory pour les gérer.</p>`;
return;
}
let h=`<table class="ptable"><thead><tr>
<th>#</th><th>Persona</th><th>Email</th><th>Status</th><th>API Key</th><th>Actions</th>
</tr></thead><tbody>`;
accounts.forEach(a=>{
const hasKey=a.has_key||false;
const keyVal=hasKey?'••••••••••':'';
h+=`<tr>
<td>${a.id||'-'}</td>
<td>${a.persona_name||a.account_email||'-'}</td>
<td style="font-size:10px">${a.account_email||'-'}</td>
<td><span class="badge ${hasKey?'ok':'pend'}">${a.status||'pending'}</span></td>
<td><input id="pk-${a.id}" placeholder="API key..." value="${keyVal}"></td>
<td><button class="btn btn-s" onclick="savePersonaKey(${a.id},'${name}')">💾</button></td>
</tr>`;
});
h+='</tbody></table>';
div.innerHTML=h;
}catch(e){div.innerHTML=`<p style="color:var(--rd);font-size:11px">❌ ${e.message}</p>`;}
}
async function savePersonaKey(id,provider){
const key=document.getElementById('pk-'+id)?.value?.trim();
if(!key||key==='••••••••••'){alert('Colle une vraie clé API');return;}
try{
await fetch(FACTORY,{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({action:'update_key',account_id:id,api_key:key})});
alert('✅ Key saved #'+id);
loadPersonas(provider);
}catch(e){alert('❌ '+e.message);}
}
async function testAllKeys(name){
const div=document.getElementById('mainres-'+name);
div.innerHTML='<div class="res" style="color:var(--bl)">⏳ Testing all keys for '+name+'...</div>';
try{
const r=await fetch(ROUTER+'?action=rotate_test&provider='+encodeURIComponent(name));
const d=await r.json();
const results=d.results||[];
let h=results.map(r=>`<span class="badge ${r.ok?'ok':'err'}">${r.provider}${r.account_id?'#'+r.account_id:''}: ${r.ok?r.latency+'ms':'❌'}</span>`).join(' ');
div.innerHTML=`<div class="res ${results.some(r=>r.ok)?'ok':'fail'}">${h||'No results'}</div>`;
}catch(e){div.innerHTML=`<div class="res fail">❌ ${e.message}</div>`;}
}
loadAll();
</script>
<script src="arsenal-common.js?v1770778169">
<?php include("/opt/wevads-arsenal/public/universal-drill.html"); ?>
</body>
</html>
</script>