// Moteur Strata : installation « en un clic » du moteur spécialisé Qwen3.8 Flash
// Next (repris d'AJEAN, « AJEAN MoE »). L'utilisateur choisit la version (Swift
// ou Classique) et la qualité (quant) ; tout le reste (cartes, RAM, mmap,
// réglages) est déduit côté serveur (backend_strata.go). L'installation est une
// tâche comme celles de llama.cpp : même suivi (#lc-job), même pastille.
let strataState = null, strataFam = '', strataQuant = '', strataTarget = '';
async function loadStrata(){
let s;
try{ s = await jget('/api/strata'); }catch(_){ return; }
strataState = s;
const row = document.getElementById('lc-strata');
if(!row) return;
// Hors Linux + NVIDIA, la ligne reste visible mais dit pourquoi : choisir son
// moteur, c'est aussi savoir pourquoi l'un des deux n'est pas proposé.
const env = s.env || {};
row.classList.toggle('moe-off', !env.supported);
const st = document.getElementById('lc-strata-state');
if(st){
const n = (s.installed||[]).length;
st.textContent = !env.supported ? (env.reason || 'indisponible sur cette machine')
: s.active ? 'moteur actif' + (n > 1 ? ' · '+n+' modèles installés' : '')
: n ? n+' modèle(s) installé(s)' : '';
}
const eng = document.getElementById('lc-active-engine');
if(eng) eng.textContent = s.active ? 'Strata' : 'llama.cpp';
}
// presetId (optionnel) : ouverte depuis l'engrenage d'un modèle installé, la
// fenêtre se place directement sur lui.
async function openStrata(presetId){
// état relu à chaque ouverture : juste après une installation, l'ancien
// proposait encore « Installer »
await loadStrata();
if(!strataState) return;
const s = strataState, env = s.env || {};
if(!env.supported){ toast('Strata : '+(env.reason||'indisponible sur cette machine')); return; }
strataTarget = typeof presetId === 'string' ? presetId : '';
const cards = (env.gpus||[]).filter(g=>g.index===env.main || g.index===env.helper)
.sort((a,b)=>(a.index===env.main?-1:1))
.map(g=>g.name.replace(/^NVIDIA GeForce /,'')+' '+Math.round(g.vram_gb)+' Go');
document.getElementById('strata-machine').textContent =
'Machine : '+cards.join(' + ')+', '+Math.round(env.ram_gb)+' Go de RAM, '
+Math.round(env.disk_free_gb)+' Go libres. Réglages automatiques.';
if(!strataFam) strataFam = (s.families[0]||{}).id || '';
if(strataTarget){
for(const f of s.families){
const hit = f.fits.find(x=>x.preset===strataTarget);
if(hit){ strataFam = f.id; strataQuant = hit.quant.id; }
}
}
strataRenderFams();
strataRenderQuants(!strataTarget);
showModal('strata-modal');
}
function closeStrata(){ hideModal('strata-modal'); }
function strataRenderFams(){
const box = document.getElementById('strata-fams');
box.innerHTML = strataState.families.map(f=>
'').join('');
}
function strataRenderQuants(pickReco){
const f = strataState.families.find(x=>x.id===strataFam);
if(!f) return;
// à l'ouverture : le modèle actif s'il est de cette version, sinon un installé, sinon le conseillé
if(pickReco || !f.fits.some(x=>x.quant.id===strataQuant && (x.ok || x.preset))){
const cur = f.fits.find(x=>x.active) || f.fits.find(x=>x.preset);
strataQuant = cur ? cur.quant.id : (f.recommend || '');
}
const box = document.getElementById('strata-quants');
box.innerHTML = f.fits.map(x=>{
const q = x.quant, sel = q.id===strataQuant;
const tag = x.active ? 'actif' : x.preset ? 'installé' : q.id===f.recommend ? 'conseillé' : '';
const reco = tag ? ' '+tag+'' : '';
const usable = x.ok || !!x.preset; // installé : activable même si l'espace disque manque
const desc = x.preset ? escHtml(q.about)
: x.ok ? escHtml(q.about)+' · ~'+Math.round(x.disk_gb)+' Go sur le disque'
: escHtml(x.why);
return '
'
+'
'+q.id+reco+'
'+desc+'
';
}).join('');
const fit = f.fits.find(x=>x.quant.id===strataQuant);
const go = document.getElementById('strata-go');
go.disabled = !fit || !!fit.active;
go.textContent = fit && fit.active ? 'Déjà actif' : fit && fit.preset ? 'Activer' : 'Installer';
strataRenderDisk(fit);
strataRenderSettings(fit);
strataRenderDetails(fit);
document.getElementById('strata-note').textContent = !fit ? 'Aucune version ne tient sur cette machine.'
: fit.active ? 'Modèle actif.' : fit.preset ? 'Modèle installé, prêt à être activé.'
: fit.mmap ? 'RAM insuffisante pour tout charger : experts lus depuis le disque (mmap, ~45 Go de plus). Installation et activation automatiques.'
: 'Installation et activation automatiques. Premier chargement : quelques minutes.';
}
// Place disque : libre, ce que ce choix occupe, ce qui restera.
function strataRenderDisk(fit){
const el = document.getElementById('strata-disk');
const free = Math.round((strataState.env||{}).disk_free_gb||0);
if(!fit){ el.textContent = ''; return; }
if(fit.preset){ el.textContent = 'Disque : '+free+' Go libres. Modèle déjà présent.'; return; }
const need = Math.round(fit.disk_gb), left = free - need;
el.textContent = left >= 5
? 'Disque : '+free+' Go libres, ~'+need+' Go requis, ~'+left+' Go restants.'
: 'Disque : '+free+' Go libres, ~'+need+' Go requis. Espace insuffisant.';
}
// Réglages modifiables d'un modèle installé.
function strataRenderSettings(fit){
const fld = document.getElementById('strata-settings-fld'), save = document.getElementById('strata-save');
const d = fit && fit.details;
fld.hidden = save.hidden = !d;
if(!d) return;
const sel = (id, opts, cur) => '';
const sw = (id, on) => '';
const rows = [
['Contexte', ''],
['Cache KV', sel('strata-s-kv', [['fp16', 'fp16 (meilleure qualité)'],['int8', '8 bits (plus léger)']], d.kv)],
['Prédiction (MTP)', sel('strata-s-spec', [[2,'2'],[3,'3'],[4,'4']], d.spec)],
['Lire les images', sw('strata-s-vision', d.vision)],
];
if(d.has_helper) rows.push(["Carte d'appoint", sw('strata-s-helper', d.helper_on)]);
document.getElementById('strata-settings').innerHTML = rows.map(([a,b])=>
''+escHtml(a)+''+b+'
').join('');
strataCtxLbl();
}
// valeur exacte en jetons (131 072 et non « 128K », source de confusion)
function strataCtxLbl(){
const r = document.getElementById('strata-s-ctx'), o = document.getElementById('strata-s-ctx-v');
if(!r || !o) return;
o.textContent = (+r.value).toLocaleString('fr-FR');
// partie remplie à gauche de la pastille (la piste est dessinée en CSS)
r.style.setProperty('--p', ((r.value - r.min) / (r.max - r.min) * 100) + '%');
}
async function strataSaveSettings(){
const f = strataState && strataState.families.find(x=>x.id===strataFam);
const fit = f && f.fits.find(x=>x.quant.id===strataQuant);
if(!fit || !fit.preset) return;
const v = id => document.getElementById(id);
const body = {preset: fit.preset, ctx: +v('strata-s-ctx').value, kv: v('strata-s-kv').value, spec: +v('strata-s-spec').value,
vision: v('strata-s-vision').checked, helper: v('strata-s-helper') ? v('strata-s-helper').checked : false};
if(fit.active && !await askConfirm('Le modèle va redémarrer avec ces réglages (quelques minutes).', {title:'Strata — Qwen3.8 Flash Next', okText:'Enregistrer'})) return;
const r = await jpost('/api/strata/settings', body);
if(!r.ok){ toast('Erreur : '+(r.error||'')); return; }
toast(r.restarted ? 'Réglages enregistrés, redémarrage en cours' : 'Réglages enregistrés');
strataTarget = fit.preset;
await openStrata(fit.preset);
if(typeof loadPresets === 'function') loadPresets();
}
// La config qui tourne (ou tournera) pour un modèle installé.
function strataRenderDetails(fit){
const fld = document.getElementById('strata-details-fld');
const d = fit && fit.details;
fld.hidden = !d;
if(!d) return;
const k = n => (+n >= 1024 ? Math.round(+n/1024)+'K' : n);
const rows = [
['Carte principale', d.main_gpu],
["Carte d'appoint", d.helper_gpu || 'aucune'],
['Lecture des images', d.vision_gpu || 'aucune'],
['Contexte', (+d.ctx).toLocaleString('fr-FR')+' jetons'],
['Cache KV', d.kv + (d.kv_resident ? ' · '+k(d.kv_resident)+' en VRAM, le reste en RAM' : '')],
['Prédiction (MTP)', d.spec+' jetons'],
['Experts hors GPU', d.drop ? "verrouillés en RAM, hors ceux de la carte d'appoint" : d.mmap ? 'lus depuis le disque (mmap)' : 'chargés en RAM'],
['Lecture des prompts', 'blocs de '+k(d.prefill)+', lecture directe sous '+d.short_read+' jetons'],
["Démarrage d'une conversation", 'reprise après le prompt système (dès '+d.cache_root+' jetons)'],
['Version du moteur', 'paquet AJEAN MoE '+d.version],
];
document.getElementById('strata-details').innerHTML = rows.map(([a,b])=>
''+escHtml(a)+''+escHtml(b)+'
').join('');
}
async function strataInstall(){
const f = strataState && strataState.families.find(x=>x.id===strataFam);
const fit = f && f.fits.find(x=>x.quant.id===strataQuant);
if(!fit || fit.active) return;
// déjà installé : simple bascule, la même que dans la liste des modèles
if(fit.preset){
closeStrata();
await switchTo(-1, f.label+' '+strataQuant, fit.preset);
loadStrata();
return;
}
const msg = 'Installer Qwen3.8 Flash Next '+f.label+' '+strataQuant+' sur Strata ? Téléchargement : ~'
+Math.round(fit.disk_gb)+' Go. Le modèle sera activé à la fin.';
if(!await askConfirm(msg, {title:'Strata — Qwen3.8 Flash Next', okText:'Installer'})) return;
const r = await jpost('/api/strata/install', {family:strataFam, quant:strataQuant});
if(!r.ok){ toast('Erreur : '+(r.error||'')); return; }
closeStrata();
const det = document.getElementById('lc-details');
if(det && 'open' in det) det.open = true;
lcStartPolling();
}