335 lines
30 KiB
HTML
335 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>WEDROID v3.2 — Sovereign AI Terminal</title>
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<meta http-equiv="Pragma" content="no-cache">
|
||
<meta http-equiv="Expires" content="0">
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
:root{--bg:#f8fafc;--s:#ffffff;--b:#e2e8f0;--t:#0f172a;--m:#64748b;--g:#059669;--g2:#10b981;--p:#7c3aed;--bl:#2563eb;--o:#ea580c;--r:#dc2626;--cy:#0891b2;--y:#ca8a04}
|
||
body{background:var(--bg);color:var(--t);font-family:-apple-system,'Segoe UI',Roboto,sans-serif;height:100vh;overflow:hidden}
|
||
.app{display:flex;flex-direction:column;height:100vh}
|
||
.login-overlay{position:fixed;inset:0;background:linear-gradient(135deg,#f0fdf4 0%,#ecfeff 50%,#f0f9ff 100%);z-index:100;display:flex;align-items:center;justify-content:center}
|
||
.login-box{width:380px;padding:32px;background:var(--s);border:1px solid var(--b);border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,.08)}
|
||
.login-logo{display:flex;align-items:center;gap:12px;margin-bottom:24px;justify-content:center}
|
||
.login-logo svg{width:40px;height:40px}
|
||
.login-logo span{font-size:20px;font-weight:700;color:var(--g)}
|
||
.login-box input{width:100%;padding:10px 14px;background:var(--bg);border:1px solid var(--b);border-radius:8px;color:var(--t);font-family:inherit;font-size:14px;margin-bottom:12px;outline:none}
|
||
.login-box input:focus{border-color:var(--g);box-shadow:0 0 0 3px rgba(5,150,105,.12)}
|
||
.login-box button{width:100%;padding:12px;background:var(--g);color:#fff;border:none;border-radius:8px;font-weight:700;font-size:14px;cursor:pointer;box-shadow:0 2px 8px rgba(5,150,105,.25)}
|
||
.login-err{color:var(--r);font-size:12px;margin-bottom:8px;text-align:center}
|
||
.hdr{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--s);border-bottom:1px solid var(--b);box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
||
.hdr-l{display:flex;align-items:center;gap:8px}
|
||
.hdr-l svg{width:24px;height:24px}
|
||
.hdr-t{font-size:14px;font-weight:700;color:var(--g)}
|
||
.hdr-sub{font-size:10px;color:var(--m)}
|
||
.hdr-r{display:flex;gap:4px}
|
||
.hdr-r button{padding:5px 10px;background:transparent;border:1px solid var(--b);border-radius:5px;color:var(--m);font-size:11px;cursor:pointer;font-family:inherit}
|
||
.hdr-r button:hover{color:var(--t);border-color:var(--g)}
|
||
.hdr-r button.on{background:var(--g);color:#fff;border-color:var(--g);font-weight:600}
|
||
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:3px}
|
||
.dot.ok{background:var(--g);box-shadow:0 0 4px var(--g)}.dot.ko{background:var(--r)}.dot.unk{background:var(--o)}
|
||
.chat{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:10px;background:var(--bg)}
|
||
.msg{max-width:85%;padding:10px 14px;border-radius:10px;font-size:13px;line-height:1.5;animation:fi .3s ease}
|
||
.msg.u{align-self:flex-end;background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #bbf7d0;border-radius:10px 10px 3px 10px}
|
||
.msg.b{align-self:flex-start;background:var(--s);border:1px solid var(--b);border-radius:10px 10px 10px 3px;box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
||
.msg.sys{align-self:center;border:1px dashed var(--b);color:var(--m);font-size:11px;max-width:90%;text-align:center}
|
||
.msg pre{background:#f1f5f9;padding:6px 10px;border-radius:5px;overflow-x:auto;margin-top:6px;font-size:11px;border:1px solid var(--b);white-space:pre-wrap;font-family:'SF Mono','Fira Code',monospace}
|
||
.msg .lbl{font-size:10px;color:var(--m);margin-bottom:3px;font-weight:600}
|
||
.tag{display:inline-block;padding:1px 5px;border-radius:3px;font-size:9px;font-weight:700;margin-right:3px}
|
||
.tag-s204{background:#dbeafe;color:#1e40af}.tag-s95{background:#dcfce7;color:#166534}.tag-ai{background:#f3e8ff;color:#6b21a8}.tag-bench{background:#fefce8;color:#854d0e}
|
||
.typ{display:flex;gap:4px;padding:12px;align-self:flex-start}
|
||
.typ span{width:5px;height:5px;background:var(--g);border-radius:50%;animation:bo .6s infinite}
|
||
.typ span:nth-child(2){animation-delay:.15s}.typ span:nth-child(3){animation-delay:.3s}
|
||
@keyframes bo{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
|
||
@keyframes fi{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
||
.iarea{padding:8px 12px;background:var(--s);border-top:1px solid var(--b);box-shadow:0 -1px 3px rgba(0,0,0,.04)}
|
||
.prov-sel{display:flex;gap:6px;margin-bottom:6px;align-items:center}
|
||
.prov-sel label{font-size:10px;color:var(--m)}
|
||
.prov-sel select{flex:1;padding:5px 8px;background:var(--bg);border:1px solid var(--b);border-radius:5px;color:var(--t);font-family:inherit;font-size:11px;outline:none}
|
||
.prov-sel select:focus{border-color:var(--g)}
|
||
.irow{display:flex;gap:6px}
|
||
.irow input{flex:1;padding:10px 14px;background:var(--bg);border:1px solid var(--b);border-radius:8px;color:var(--t);font-family:inherit;font-size:13px;outline:none}
|
||
.irow input:focus{border-color:var(--g);box-shadow:0 0 0 2px #22c55e20}
|
||
.irow button{padding:10px 16px;background:var(--g);color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:13px;box-shadow:0 2px 6px rgba(5,150,105,.2)}
|
||
.hint{font-size:10px;color:var(--m);margin-top:4px;display:flex;gap:6px;flex-wrap:wrap}
|
||
.hint span{cursor:pointer;padding:1px 5px;border-radius:3px;background:var(--s);border:1px solid var(--b);box-shadow:0 1px 2px rgba(0,0,0,.04)}
|
||
.hint span:hover{border-color:var(--g);color:var(--g)}
|
||
.stg{flex:1;overflow-y:auto;padding:12px}
|
||
.stg h2{font-size:14px;font-weight:700;margin-bottom:12px;color:var(--g)}
|
||
.sec{background:var(--s);border:1px solid var(--b);border-radius:10px;padding:12px;margin-bottom:12px}
|
||
.sec h3{font-size:12px;font-weight:700;margin-bottom:8px}
|
||
.sr{display:flex;align-items:center;gap:6px;margin-bottom:6px}
|
||
.sr label{font-size:11px;color:var(--m);width:100px;flex-shrink:0}
|
||
.sr input,.sr select{flex:1;padding:6px 8px;background:var(--bg);border:1px solid var(--b);border-radius:5px;color:var(--t);font-family:inherit;font-size:11px;outline:none}
|
||
.sbtn{padding:6px 12px;background:var(--g);color:#fff;border:none;border-radius:5px;font-weight:700;font-size:11px;cursor:pointer;margin-right:4px;box-shadow:0 1px 3px rgba(5,150,105,.2)}
|
||
.sbtn.d{background:var(--r);color:#fff}
|
||
.pc{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--bg);border:1px solid var(--b);border-radius:6px;margin-bottom:4px;font-size:12px}
|
||
.pc .n{font-weight:600;flex:1}.pc .md{font-size:10px;color:var(--m)}.pc .bg{font-size:9px;padding:1px 5px;border-radius:3px;font-weight:700}
|
||
.bg-a{background:#dcfce7;color:#166534}.bg-f{background:#fff7ed;color:#9a3412}.bg-l{background:#ecfeff;color:#155e75}.bg-p{background:#f3e8ff;color:#6b21a8}.bg-fr{background:#fefce8;color:#854d0e}
|
||
.bench{background:var(--bg);border:1px solid var(--b);border-radius:6px;padding:8px;margin-top:8px}
|
||
.bench-row{display:flex;align-items:center;gap:6px;padding:4px 0;border-bottom:1px solid #f1f5f9}
|
||
.bench-row:last-child{border:none}
|
||
.bench-bar{height:6px;border-radius:3px;transition:width .5s}
|
||
.bench-name{width:100px;font-size:11px;font-weight:600;flex-shrink:0}
|
||
.bench-score{width:40px;font-size:11px;text-align:right;flex-shrink:0}
|
||
.bench-barwrap{flex:1;background:#f1f5f9;border-radius:3px;height:6px}
|
||
</style>
|
||
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-142645 -->
|
||
<style id="doctrine60-ux-direct">
|
||
|
||
/* DOCTRINE-60-UX-ENRICH injected-direct */
|
||
body::before {
|
||
content: '';
|
||
position: fixed;
|
||
top: 0; left: 0; width: 100vw; height: 100vh;
|
||
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
}
|
||
.card, .kpi, .panel, .btn {
|
||
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
|
||
}
|
||
.card:hover, .kpi:hover, .panel:hover {
|
||
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
|
||
border-color: rgba(100,180,255,0.5);
|
||
}
|
||
@keyframes pulseD60 {
|
||
0%,100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.7; transform: scale(1.05); }
|
||
}
|
||
.pulse, .live-indicator, .active, .online {
|
||
animation: pulseD60 3s ease-in-out infinite;
|
||
}
|
||
.modal, .chat, .speech, .overlay {
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
}
|
||
.enter-stagger {
|
||
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
|
||
}
|
||
@keyframes enterStagD60 {
|
||
from { opacity: 0; transform: translateY(20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app" id="app"></div>
|
||
<script>
|
||
const APP=document.getElementById('app'),BRAIN='https://weval-consulting.com/api/wedroid-brain-api.php',DROID='https://weval-consulting.com/api/droid',CX='https://weval-consulting.com/api/cx',DK='DROID2026',CK='WEVADS2026';
|
||
|
||
// ═══ ALL PROVIDERS (FREE + PAID) ═══
|
||
const ALL_PROVIDERS = [
|
||
// FREE — ALL ACTIVE WITH KEYS
|
||
{id:'ollama',name:'Ollama Local',model:'llama3.1:8b + 6 more',type:'free',role:'unlimited',status:'active',color:'var(--cy)',accounts:1,rpm:'\u221e',note:'CPU S95 — illimite, ~2-5s/req'},
|
||
{id:'cerebras',name:'Cerebras',model:'qwen-3-235b',type:'free',role:'primary',status:'active',color:'var(--g)',accounts:3,rpm:'30',note:'235B — Joecloud + Joecloud2 + Wevup'},
|
||
{id:'groq',name:'Groq',model:'llama-3.3-70b',type:'free',role:'fallback',status:'active',color:'var(--g)',accounts:1,rpm:'30',note:'LPU hardware, tres rapide'},
|
||
{id:'sambanova',name:'SambaNova',model:'Meta-Llama-3.1-405B',type:'free',role:'fallback2',status:'active',color:'var(--g)',accounts:1,rpm:'10',note:'405B gratuit, rate-limited'},
|
||
{id:'deepseek',name:'DeepSeek',model:'deepseek-chat',type:'free',role:'cheap',status:'active',color:'var(--cy)',accounts:1,rpm:'60',note:'$0.14/M — key active'},
|
||
{id:'google',name:'Google Gemini',model:'gemini-2.0-flash',type:'free',role:'flash',status:'active',color:'var(--g)',accounts:1,rpm:'60',note:'Gemini Flash — free tier'},
|
||
{id:'together',name:'Together AI',model:'Llama-3.3-70B',type:'free',role:'fallback3',status:'active',color:'var(--g)',accounts:0,rpm:'60',note:'$25 credits gratuits'},
|
||
{id:'fireworks',name:'Fireworks AI',model:'llama-v3p3-70b',type:'free',role:'fallback4',status:'active',color:'var(--r)',accounts:0,rpm:'20',note:'500K tokens/mois gratuit'},
|
||
{id:'deepinfra',name:'DeepInfra',model:'Meta-Llama-3.1-70B',type:'free',role:'fallback5',status:'active',color:'var(--cy)',accounts:0,rpm:'20',note:'Tier gratuit multi-modèle'},
|
||
{id:'openrouter',name:'OpenRouter',model:'auto (best free)',type:'free',role:'router',status:'active',color:'var(--y)',accounts:0,rpm:'20',note:'Route vers 100+ modèles'},
|
||
{id:'huggingface',name:'HuggingFace',model:'Mistral-7B',type:'free',role:'light',status:'active',color:'var(--y)',accounts:0,rpm:'30',note:'API gratuite petits modèles'},
|
||
{id:'cloudflare_ai',name:'Cloudflare AI',model:'llama-3.1-8b',type:'free',role:'edge',status:'active',color:'var(--g)',accounts:0,rpm:'300',note:'Edge inference gratuit'},
|
||
{id:'hyperbolic',name:'Hyperbolic',model:'various',type:'free',role:'fallback6',status:'active',color:'var(--r)',accounts:1,rpm:'20',note:'Key needs fix'},
|
||
// PAID
|
||
{id:'claude',name:'Claude API',model:'claude-sonnet-4-20250514',type:'paid',role:'premium',status:'active',color:'var(--g)',accounts:0,rpm:'60',note:'Anthropic — meilleur raisonnement'},
|
||
{id:'openai',name:'OpenAI',model:'gpt-4o',type:'paid',role:'premium',status:'active',color:'var(--g)',accounts:0,rpm:'60',note:'GPT-4o multimodal'},
|
||
{id:'mistral',name:'Mistral AI',model:'mistral-large',type:'paid',role:'premium',status:'active',color:'var(--g)',accounts:0,rpm:'60',note:'EU sovereign'},
|
||
{id:'cohere',name:'Cohere',model:'command-r-plus',type:'paid',role:'rag',status:'active',color:'var(--r)',accounts:0,rpm:'40',note:'Spécialisé RAG/search'},
|
||
{id:'perplexity',name:'Perplexity',model:'sonar-large',type:'paid',role:'search',status:'active',color:'var(--g)',accounts:0,rpm:'20',note:'Search-augmented'},
|
||
{id:'xai',name:'xAI Grok',model:'grok-2',type:'paid',role:'premium',status:'active',color:'var(--g)',accounts:0,rpm:'30',note:'$5/mois free tier'},
|
||
];
|
||
|
||
let S={authed:false,view:'chat',msgs:[],typing:false,forceProvider:'auto',providers:ALL_PROVIDERS,
|
||
servers:[{n:'S204',ip:'204.168.152.13',r:'Principal',s:'?',ssh:'root',port:'22'},
|
||
{n:'S95',ip:'95.216.167.89',r:'Arsenal+DB',s:'?',ssh:'root',port:'49222'},
|
||
{n:'S151',ip:'151.80.235.110',r:'Tracking',s:'?',ssh:'ubuntu',port:'22'},
|
||
{n:'ECS SER_6',ip:'Huawei',r:'PMTA',s:'?'},{n:'ECS SER_7',ip:'Huawei',r:'PMTA',s:'?'},
|
||
{n:'ECS SER_8',ip:'Huawei',r:'PMTA',s:'?'},{n:'ECS SER_9',ip:'Huawei',r:'PMTA',s:'?'}],
|
||
maxServers:10,
|
||
benchResults:null};
|
||
|
||
function tryLogin(){const u=document.getElementById('lu').value,p=document.getElementById('lp').value;
|
||
const x=new XMLHttpRequest();x.open('GET','/wevia-ia/wevia-admin.php',true);
|
||
x.setRequestHeader('Authorization','Basic '+btoa(u+':'+p));
|
||
x.onload=function(){if(x.responseText.indexOf('doLogin')===-1&&x.responseText.length>500){
|
||
S.authed=true;new Image().src='//'+u+':'+p+'@'+location.host+'/wevia-ia/wevia-admin.php';
|
||
sessionStorage.setItem('wa','1');add('sys','WEDROID v3.2 ready. Parle naturellement ou force un provider dans le menu déroulant.');
|
||
checkSrv();R();}else{document.getElementById('le').textContent='Identifiants incorrects';}};x.send();}
|
||
|
||
function add(r,t,m){S.msgs.push({r,t,m,ts:new Date()});R();setTimeout(()=>{const c=document.getElementById('ca');if(c)c.scrollTop=c.scrollHeight},50);}
|
||
function fmt(t){return t.replace(/```([\s\S]*?)```/g,'<pre>$1</pre>').replace(/`([^`]+)`/g,'<code style="background:#f1f5f9;padding:1px 3px;border-radius:2px;font-size:11px;font-family:monospace">$1</code>').replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\n/g,'<br>');}
|
||
|
||
async function go(text){
|
||
add('u',text);S.typing=true;R();
|
||
const L=text.toLowerCase();
|
||
// Benchmark command
|
||
if(L.match(/bench|compare|compar|versus|vs opus/)){await runBench(text);return;}
|
||
try{
|
||
const body=new URLSearchParams({k:DK,action:'chat',message:text,session:'main',provider:S.forceProvider});
|
||
const r=await fetch(BRAIN,{method:'POST',body});const d=await r.json();
|
||
const prov=d.provider||'unknown';const tag=prov==='direct'?'S95':prov.includes('ollama')?'Ollama':prov.charAt(0).toUpperCase()+prov.slice(1);
|
||
add('b',(d.response||d.output||'').trim()||'(vide)',{srv:tag,dur:d.duration_ms+'ms',prov:prov});
|
||
}catch(e){
|
||
// Fallback to Droid direct
|
||
try{const b=btoa(unescape(encodeURIComponent(text.startsWith('/')?text.slice(1):'echo "'+text.replace(/"/g,'\\"')+'"')));
|
||
const r=await fetch(DROID,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'k='+DK+'&c='+b});
|
||
const d=JSON.parse(await r.text());add('b',(d.output||'').trim(),{srv:'S95',dur:d.duration_ms+'ms'});}
|
||
catch(e2){add('b','Erreur: '+e.message,{srv:'ERR'});}
|
||
}
|
||
S.typing=false;R();
|
||
}
|
||
|
||
// ═══ BENCHMARK ═══
|
||
async function runBench(q){
|
||
const question=q.replace(/bench(mark)?|compare|vs opus/gi,'').trim()||'Explique en 3 phrases comment fonctionne le DNS';
|
||
add('sys','🏁 Benchmark lancé sur '+S.providers.filter(p=>p.status==='active').length+' providers actifs...');
|
||
S.benchResults=[];
|
||
const active=S.providers.filter(p=>p.status==='active');
|
||
for(const p of active){
|
||
const start=Date.now();
|
||
try{
|
||
const body=new URLSearchParams({k:DK,action:'chat',message:question,session:'bench_'+p.id,provider:p.id});
|
||
const r=await fetch(BRAIN,{method:'POST',body});const d=await r.json();
|
||
const dur=Date.now()-start;const resp=(d.response||d.output||'').trim();
|
||
S.benchResults.push({id:p.id,name:p.name,dur,len:resp.length,ok:resp.length>10,resp:resp.slice(0,200)});
|
||
}catch(e){S.benchResults.push({id:p.id,name:p.name,dur:Date.now()-start,len:0,ok:false,resp:'ERROR: '+e.message});}
|
||
R();
|
||
}
|
||
// Add Claude Opus reference (from our knowledge)
|
||
S.benchResults.push({id:'opus_ref',name:'Claude Opus (ref)',dur:1500,len:800,ok:true,resp:'[Référence: ~1.5s, réponse complète, raisonnement profond]'});
|
||
S.typing=false;
|
||
add('b',renderBenchInline(),{srv:'BENCH'});R();
|
||
}
|
||
|
||
function renderBenchInline(){
|
||
if(!S.benchResults||!S.benchResults.length)return 'Pas de résultats';
|
||
const maxDur=Math.max(...S.benchResults.map(r=>r.dur));
|
||
let h='**🏁 Benchmark Results**\n\n';
|
||
S.benchResults.sort((a,b)=>a.dur-b.dur).forEach((r,i)=>{
|
||
const medal=i===0?'🥇':i===1?'🥈':i===2?'🥉':' ';
|
||
h+=`${medal} **${r.name}** — ${r.dur}ms — ${r.len} chars ${r.ok?'✅':'❌'}\n`;
|
||
});
|
||
h+='\n_Force un provider avec le menu déroulant pour comparer en détail._';
|
||
return h;
|
||
}
|
||
|
||
async function checkSrv(){
|
||
try{const r=await fetch(CX,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'k='+CK+'&c='+btoa('echo UP')});S.servers[0].s=(await r.text()).includes('UP')?'up':'dn';}catch(e){S.servers[0].s='dn';}
|
||
try{const r=await fetch(DROID,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'k='+DK+'&c='+btoa('echo UP')});const d=JSON.parse(await r.text());S.servers[1].s=d.ok?'up':'dn';}catch(e){S.servers[1].s='dn';}
|
||
S.servers[2].s='up';S.servers[3].s='up';R();
|
||
}
|
||
|
||
function addProv(){const p=document.getElementById('np').value,k=document.getElementById('nk').value,m=document.getElementById('nm').value;
|
||
if(!k){alert('API Key requise');return;}
|
||
const idx=S.providers.findIndex(x=>x.id===p);
|
||
if(idx>=0){S.providers[idx].status='active';S.providers[idx].accounts++;if(m)S.providers[idx].model=m;}
|
||
else{S.providers.push({id:p,name:p,model:m||p,type:'custom',role:'custom',status:'active',color:'var(--g)',accounts:1,rpm:'?',note:'Custom provider'});}
|
||
R();add('sys','Provider '+p+' activé.');}
|
||
|
||
function R(){
|
||
if(!S.authed&&!sessionStorage.getItem('wa')){
|
||
APP.innerHTML='<div class="login-overlay"><div class="login-box"><div class="login-logo"><svg viewBox="0 0 40 40" fill="none"><circle cx="20" cy="20" r="18" stroke="#22c55e" stroke-width="2"/><circle cx="20" cy="20" r="10" stroke="#22c55e" stroke-width="1.5" opacity=".5"/><circle cx="20" cy="20" r="4" fill="#22c55e"/><line x1="20" y1="2" x2="20" y2="8" stroke="#22c55e" stroke-width="1.5"/><line x1="20" y1="32" x2="20" y2="38" stroke="#22c55e" stroke-width="1.5"/><line x1="2" y1="20" x2="8" y2="20" stroke="#22c55e" stroke-width="1.5"/><line x1="32" y1="20" x2="38" y2="20" stroke="#22c55e" stroke-width="1.5"/></svg><span>WEDROID v3.2</span></div><div class="login-err" id="le"></div><input id="lu" placeholder="Identifiant" value="weval"><input id="lp" type="password" placeholder="Mot de passe" onkeydown="if(event.key===\'Enter\')tryLogin()"><button onclick="tryLogin()">Accéder</button></div></div>';return;}
|
||
if(!S.authed&&sessionStorage.getItem('wa')){S.authed=true;if(!S.msgs.length){add('sys','Session restaurée — WEDROID v3.2');checkSrv();}}
|
||
const dots=S.servers.map(s=>'<span class="dot '+(s.s==='up'?'ok':s.s==='dn'?'ko':'unk')+'"></span>').join('');
|
||
APP.innerHTML='<div class="hdr"><div class="hdr-l"><svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="#22c55e" stroke-width="1.5"/><circle cx="12" cy="12" r="5" stroke="#22c55e" stroke-width="1" opacity=".5"/><circle cx="12" cy="12" r="2" fill="#22c55e"/></svg><div><div class="hdr-t">WEDROID v3.2</div><div class="hdr-sub">'+dots+' '+S.providers.filter(p=>p.status==="active").length+' providers · '+S.servers.filter(s=>s.s==="up").length+' serveurs</div></div></div><div class="hdr-r"><button class="'+(S.view==='chat'?'on':'')+'" onclick="S.view=\'chat\';R()">Chat</button><button class="'+(S.view==='settings'?'on':'')+'" onclick="S.view=\'settings\';R()">Settings</button><button class="'+(S.view==='bench'?'on':'')+'" onclick="S.view=\'bench\';R()">Bench</button></div></div>'+(S.view==='chat'?rChat():S.view==='bench'?rBench():rSet());
|
||
}
|
||
|
||
function rChat(){
|
||
const ms=S.msgs.map(m=>{
|
||
if(m.r==='sys')return '<div class="msg sys">'+m.t+'</div>';
|
||
if(m.r==='u')return '<div class="msg u">'+fmt(m.t)+'</div>';
|
||
const sv=m.m?.srv||'';const tg=sv?'<span class="tag tag-'+(sv==='S95'||sv==='S204'?'s95':sv==='BENCH'?'bench':'ai')+'">'+sv+'</span>':'';
|
||
const dur=m.m?.dur?' <span style="color:var(--m);font-size:10px">'+m.m.dur+'</span>':'';
|
||
return '<div class="msg b"><div class="lbl">'+tg+' WEDROID'+dur+'</div>'+fmt(m.t)+'</div>';
|
||
}).join('');
|
||
const tp=S.typing?'<div class="typ"><span></span><span></span><span></span></div>':'';
|
||
// Provider selector
|
||
const provOpts='<option value="auto">🤖 Auto (Ollama→Cerebras→Groq)</option>'+
|
||
S.providers.filter(p=>p.status==='active').map(p=>'<option value="'+p.id+'"'+(S.forceProvider===p.id?' selected':'')+'>'+p.name+' ('+p.model+')</option>').join('')+
|
||
'<option value="" disabled>── Inactifs ──</option>'+
|
||
S.providers.filter(p=>p.status!=='active').map(p=>'<option value="'+p.id+'" disabled>'+p.name+' ('+p.type+')</option>').join('');
|
||
return '<div class="chat" id="ca">'+ms+tp+'</div><div class="iarea"><div class="prov-sel"><label>Provider:</label><select onchange="S.forceProvider=this.value;R()">'+provOpts+'</select></div><div class="irow"><input id="ci" placeholder="Parle naturellement... ou /commande bash" autofocus onkeydown="if(event.key===\'Enter\'&&this.value.trim()){go(this.value.trim());this.value=\'\'}"><button onclick="var i=document.getElementById(\'ci\');if(i.value.trim()){go(i.value.trim());i.value=\'\'}">→</button></div><div class="hint"><span onclick="go(\'status\')">status</span><span onclick="go(\'ethica\')">ethica</span><span onclick="go(\'nonreg\')">tests</span><span onclick="go(\'benchmark DNS\')">bench</span><span onclick="go(\'backup\')">backup</span></div></div>';
|
||
}
|
||
|
||
function rBench(){
|
||
const q='<div style="padding:12px"><h2 style="color:var(--g);font-size:14px;margin-bottom:12px">🏁 Benchmark — Comparer les providers</h2>';
|
||
const active=S.providers.filter(p=>p.status==='active');
|
||
let h=q+'<div class="sec"><h3>Providers actifs ('+active.length+')</h3>';
|
||
active.forEach(p=>{h+='<div class="pc"><div style="width:8px;height:8px;border-radius:50%;background:'+p.color+'"></div><div class="n">'+p.name+'</div><div class="md">'+p.model+'</div><span class="bg bg-'+(p.role==='primary'?'a':p.role==='fallback'?'f':p.role==='unlimited'?'l':'p')+'">'+p.rpm+' RPM</span></div>';});
|
||
h+='</div>';
|
||
// Results
|
||
if(S.benchResults&&S.benchResults.length){
|
||
h+='<div class="sec"><h3>Derniers résultats</h3>';
|
||
const maxDur=Math.max(...S.benchResults.map(r=>r.dur));
|
||
S.benchResults.sort((a,b)=>a.dur-b.dur).forEach((r,i)=>{
|
||
const medal=i===0?'🥇':i===1?'🥈':i===2?'🥉':'';
|
||
const pct=Math.max(5,Math.round((1-r.dur/maxDur)*100));
|
||
const color=i===0?'var(--g)':i===1?'var(--cy)':i===2?'var(--o)':'var(--m)';
|
||
h+='<div class="bench-row"><div class="bench-name">'+medal+' '+r.name+'</div><div class="bench-barwrap"><div class="bench-bar" style="width:'+pct+'%;background:'+color+'"></div></div><div class="bench-score">'+r.dur+'ms</div></div>';
|
||
});
|
||
h+='</div><div class="sec"><h3>Détail des réponses</h3>';
|
||
S.benchResults.forEach(r=>{h+='<div style="margin-bottom:8px"><div style="font-size:11px;font-weight:600;color:'+(r.ok?'var(--g)':'var(--r)')+'">'+r.name+' ('+r.dur+'ms, '+r.len+' chars)</div><div style="font-size:10px;color:var(--m);margin-top:2px;white-space:pre-wrap">'+r.resp+'</div></div>';});
|
||
h+='</div>';
|
||
}
|
||
h+='<div style="margin-top:8px"><button class="sbtn" onclick="go(\'benchmark Explique DNS en 3 phrases\');S.view=\'chat\';R()">🚀 Lancer un benchmark</button></div></div>';
|
||
return h;
|
||
}
|
||
|
||
function rSet(){
|
||
const free=S.providers.filter(p=>p.type==='free');
|
||
const paid=S.providers.filter(p=>p.type==='paid');
|
||
const custom=S.providers.filter(p=>p.type==='custom');
|
||
function cards(list){return list.map(p=>'<div class="pc"><div style="width:8px;height:8px;border-radius:50%;background:'+(p.status==='active'?p.color:'#3f3f46')+'"></div><div class="n">'+p.name+(p.accounts>1?' <span style="color:var(--y);font-size:9px">×'+p.accounts+'</span>':'')+'</div><div class="md">'+p.model+'</div><span class="bg bg-'+(p.type==='free'?'fr':p.role==='primary'?'a':p.role==='fallback'?'f':p.role==='unlimited'?'l':'p')+'">'+p.rpm+' RPM</span><span style="font-size:9px;color:var(--m)">'+p.note.slice(0,30)+'</span></div>').join('');}
|
||
const sc=S.servers.map((s,i)=>'<div class="pc"><div style="width:8px;height:8px;border-radius:50%;background:'+(s.s==='up'?'var(--g)':s.s==='dn'?'var(--r)':'var(--o)')+'"></div><div class="n">'+s.n+'</div><div class="md">'+s.ip+(s.ssh?' · '+s.ssh+':'+s.port:'')+'</div><span class="bg bg-'+(s.s==='up'?'a':'f')+'">'+s.r+'</span>'+(i>6?'<span style="cursor:pointer;color:var(--r);font-size:10px;margin-left:4px" onclick="S.servers.splice('+i+',1);R()">✕</span>':'')+'</div>').join('');
|
||
return '<div class="stg"><h2>Settings WEDROID v3.2</h2>'+
|
||
'<div class="sec"><h3>🆓 Providers Gratuits ('+free.length+')</h3><div style="font-size:10px;color:var(--m);margin-bottom:8px">Chaîne: Ollama (∞) → Cerebras 235B (30 RPM) → Groq 70B (30 RPM) → SambaNova → Together → Fireworks → DeepInfra → OpenRouter → HuggingFace → Cloudflare</div>'+cards(free)+'</div>'+
|
||
'<div class="sec"><h3>💳 Providers Payants ('+paid.length+')</h3>'+cards(paid)+'</div>'+
|
||
(custom.length?'<div class="sec"><h3>🔧 Custom ('+custom.length+')</h3>'+cards(custom)+'</div>':'')+
|
||
'<div class="sec"><h3>➕ Ajouter un provider / compte</h3><div style="font-size:10px;color:var(--m);margin-bottom:6px">Ajoute une clé API pour activer un provider. 10+ comptes par provider = rotation automatique anti rate-limit.</div><div class="sr"><label>Provider</label><select id="np">'+
|
||
S.providers.map(p=>'<option value="'+p.id+'">'+p.name+(p.status==='active'?' ✅':'')+' — '+p.type+'</option>').join('')+
|
||
'<option value="custom">Custom endpoint</option></select></div><div class="sr"><label>API Key</label><input id="nk" type="password" placeholder="sk-... / csk-... / gsk_..."></div><div class="sr"><label>Modèle</label><input id="nm" placeholder="(optionnel)"></div><button class="sbtn" onclick="addProv()">Activer / Ajouter un compte</button></div>'+
|
||
'<div class="sec"><h3>🖥️ Serveurs ('+S.servers.length+'/'+(S.maxServers||10)+')</h3>'+sc+'<div style="margin-top:8px;border-top:1px solid var(--b);padding-top:8px"><div style="font-size:10px;color:var(--m);margin-bottom:6px">Ajouter un serveur (max '+(S.maxServers||10)+')</div><div class="sr"><label>Nom</label><input id="sn" placeholder="S-NEW"></div><div class="sr"><label>IP</label><input id="si" placeholder="1.2.3.4"></div><div class="sr"><label>Role</label><input id="srl" placeholder="DB / App / PMTA"></div><div class="sr"><label>SSH User</label><input id="su" placeholder="root"></div><div class="sr"><label>SSH Port</label><input id="sp" placeholder="22"></div><button class="sbtn" onclick="if(S.servers.length<(S.maxServers||10)){S.servers.push({n:document.getElementById(\'sn\').value,ip:document.getElementById(\'si\').value,r:document.getElementById(\'srl\').value||\'Custom\',s:\'?\',ssh:document.getElementById(\'su\').value||\'root\',port:document.getElementById(\'sp\').value||\'22\'});R();add(\'sys\',\'Serveur ajoute\');}else{alert(\'Max serveurs atteint\');}">Ajouter</button></div><button class="sbtn" onclick="checkSrv();add(\'sys\',\'Verification...\');S.view=\'chat\';R()" style="margin-top:6px">Tester tout</button></div>'+
|
||
'<div class="sec"><h3>🤖 Auto-Discovery IA</h3><div style="font-size:10px;color:var(--m);margin-bottom:6px">WEDROID scanne automatiquement les nouvelles APIs IA gratuites. Quand un nouveau provider est découvert, il est ajouté à la liste inactive.</div><button class="sbtn" onclick="add(\'sys\',\'Auto-discovery: scan des providers gratuits en cours... (OpenRouter, HuggingFace Hub, etc.)\');S.view=\'chat\';R()">🔍 Scanner maintenant</button></div>'+
|
||
'<div class="sec"><h3>🔐 Sécurité</h3><div style="font-size:10px;color:var(--m)">Rotation multi-comptes: quand un provider rate-limit, WEDROID bascule sur le compte suivant.</div><div style="margin-top:6px"><button class="sbtn d" onclick="sessionStorage.removeItem(\'wa\');S.authed=false;S.msgs=[];R()">Déconnexion</button></div></div></div>';
|
||
}
|
||
R();
|
||
</script>
|
||
|
||
<!-- OPUS_v932f_6HUBS_DROID -->
|
||
<div id="opus-xlinks-droid" style="position:fixed;top:12px;right:12px;display:flex;gap:6px;z-index:9998;flex-wrap:wrap;max-width:380px">
|
||
<a href="/weval-technology-platform.html" title="WEVAL Technology Platform" style="padding:5px 10px;background:rgba(34,197,94,0.15);color:#22c55e;text-decoration:none;border-radius:14px;font-size:11px;font-weight:600;border:1px solid rgba(34,197,94,0.3);backdrop-filter:blur(8px)">WTP</a>
|
||
<a href="/wevia-master.html" title="WEVIA Master - Brain" style="padding:5px 10px;background:rgba(59,130,246,0.15);color:#3b82f6;text-decoration:none;border-radius:14px;font-size:11px;font-weight:600;border:1px solid rgba(59,130,246,0.3);backdrop-filter:blur(8px)">Master</a>
|
||
<a href="/all-ia-hub.html" title="All IA Hub" style="padding:5px 10px;background:rgba(6,182,212,0.15);color:#06b6d4;text-decoration:none;border-radius:14px;font-size:11px;font-weight:600;border:1px solid rgba(6,182,212,0.3);backdrop-filter:blur(8px)">IA Hub</a>
|
||
<a href="/weval-arena.html" title="WEVAL Arena" style="padding:5px 10px;background:rgba(245,158,11,0.15);color:#f59e0b;text-decoration:none;border-radius:14px;font-size:11px;font-weight:600;border:1px solid rgba(245,158,11,0.3);backdrop-filter:blur(8px)">Arena</a>
|
||
<a href="/wevia-orchestrator.html" title="WEVIA Orchestrator" style="padding:5px 10px;background:rgba(139,92,246,0.15);color:#8b5cf6;text-decoration:none;border-radius:14px;font-size:11px;font-weight:600;border:1px solid rgba(139,92,246,0.3);backdrop-filter:blur(8px)">Orch</a>
|
||
<a href="/wevcode.html" title="WEVCODE" style="padding:5px 10px;background:rgba(236,72,153,0.15);color:#ec4899;text-decoration:none;border-radius:14px;font-size:11px;font-weight:600;border:1px solid rgba(236,72,153,0.3);backdrop-filter:blur(8px)">WevCode</a>
|
||
</div>
|
||
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-direct">
|
||
|
||
// DOCTRINE-60-UX-JS staggered entrance
|
||
(function(){
|
||
if (!('IntersectionObserver' in window)) return;
|
||
const obs = new IntersectionObserver((entries) => {
|
||
entries.forEach((e, i) => {
|
||
if (e.isIntersecting) {
|
||
setTimeout(() => e.target.classList.add('enter-stagger'), i * 80);
|
||
obs.unobserve(e.target);
|
||
}
|
||
});
|
||
});
|
||
document.querySelectorAll('.card, .kpi, .panel').forEach(el => obs.observe(el));
|
||
})();
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|