mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 06:28:11 +02:00
Le lot 1 savait dire qu'un moteur officiel antérieur à b10864 évince trop tôt les points de reprise des hybrides, mais laissait l'utilisateur chercher seul quelle version prendre. Le panneau Moteur propose maintenant la version recommandée, sur clic seulement, et garde un chemin de retour sans réseau. La mise à jour elle-même n'est pas neutre pour le prompt : depuis b10763, llama-server active preserve_reasoning par défaut, et un gabarit qui retirait la réflexion des tours passés (Qwen3.6, clear_thinking) la rend alors — vide, puisque Loki ne la renvoie pas sans REASONING_ECHO. Avant la bascule, Loki lit le gabarit chargé et le verdict de la sonde ; si le rendu va changer, la confirmation le dit et propose REASONING_PRESERVE=off (case cochée seulement quand c'est établi : sur Qwen3.8, off changerait à son tour le rendu). L'utilisateur choisit. Après la bascule, le rendu de conversations synthétiques est comparé via /apply-template entre l'ancien et le nouveau moteur, et le premier écart est signalé. - encart sans réseau (build de confiance < b10864) : gains cités (points de reprise, MTP rapide b11009, MTP qwen4exp b11331), SPEC reste off - POST /api/engine/plan, sur clic : dernier build publié, vérifié ≥ b10864 et tag figé existant ; sans réseau, une erreur et rien d'installé - POST /api/engine/rollback + fichier engine/previous-bin : la version quittée (gardée par le ménage du lot 1) ; le retour la rend « précédente » à son tour - REASONING_PRESERVE=off posée dans le preset actif, seulement si le nouveau moteur connaît --no-reasoning-preserve et que la clé est vide - comparaison du rendu en tâche de fond, après chargement et changement de build_info (un ancien moteur survivant ne fausse pas le verdict) - aucune clé nouvelle ; rien au démarrage ; aucune mise à jour sans clic Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
5.1 KiB
JavaScript
87 lines
5.1 KiB
JavaScript
// Pastille d'état d'une section du menu (mode agent, internet, MCP, distant).
|
|
// `state` : true = actif (pilule pleine), false = inactif (contour), 'warn'/'err'
|
|
// = état signalé, null = pastille masquée. Un seul endroit décide de leur allure.
|
|
function setBadge(id, state, text){
|
|
const el = typeof id==='string' ? document.getElementById(id) : id;
|
|
if(!el) return;
|
|
el.className = 'sbadge' + (state===true ? ' on' : (state==='warn' || state==='err') ? ' '+state : '');
|
|
el.textContent = state===null || state===undefined ? '' : (text||'');
|
|
}
|
|
// Ouverture/fermeture des modales. Le display seul apparaissait d'un bloc : on
|
|
// pose la classe .show une frame APRÈS l'affichage pour que la transition CSS
|
|
// parte de son état initial (sans ce reflow forcé, le navigateur applique tout
|
|
// d'un coup et l'animation ne joue pas). À la fermeture on attend la fin de la
|
|
// transition avant de repasser en display:none.
|
|
function showModal(id){
|
|
const el = typeof id==='string' ? document.getElementById(id) : id;
|
|
if(!el) return;
|
|
el.style.display='flex';
|
|
void el.offsetHeight;
|
|
el.classList.add('show');
|
|
}
|
|
function hideModal(id){
|
|
const el = typeof id==='string' ? document.getElementById(id) : id;
|
|
if(!el) return;
|
|
el.classList.remove('show');
|
|
// Rouverte entre-temps ? On ne la cache surtout pas.
|
|
setTimeout(()=>{ if(!el.classList.contains('show')) el.style.display='none'; }, 180);
|
|
}
|
|
function toast(m){ const t=document.getElementById('toast'); t.textContent=m; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),1800); }
|
|
// Modales natives (askConfirm/askPrompt/askAlert) — remplacent confirm()/prompt()/
|
|
// alert() par de vraies boîtes stylées. Chacune renvoie une Promise : askConfirm →
|
|
// bool, askPrompt → string|null (null si annulé), askAlert → void. Échap/clic dehors
|
|
// = annuler ; Entrée = valider (sur prompt aussi).
|
|
let _askResolver=null, _askKind='confirm', _askCheck=false;
|
|
// État de la case optionnelle de la DERNIÈRE confirmation (opts.check). Lu par
|
|
// l'appelant juste après le await — la Promise, elle, ne renvoie que oui/non.
|
|
function askChecked(){ return _askCheck; }
|
|
function askResolve(ok){
|
|
if(!_askResolver) return;
|
|
const r=_askResolver; _askResolver=null;
|
|
_askCheck = ok && document.getElementById('ask-check-input').checked;
|
|
hideModal('ask-modal');
|
|
document.removeEventListener('keydown', _askKey, true);
|
|
if(_askKind==='prompt') r(ok ? document.getElementById('ask-input').value : null);
|
|
else r(ok);
|
|
}
|
|
function _askKey(e){
|
|
if(e.key==='Escape'){ e.preventDefault(); e.stopPropagation(); askResolve(false); }
|
|
else if(e.key==='Enter'){ e.preventDefault(); e.stopPropagation(); askResolve(true); }
|
|
}
|
|
function _openAsk(kind, message, opts){
|
|
// Compat 2 conventions : (message, opts) [UI loki] ET l'objet unique
|
|
// {title,msg,yes,no,placeholder} passé par e2e.js sur ajean.link (server.html,
|
|
// où cette fonction remplace le window.askConfirm du bootstrap boîte noire).
|
|
if(message && typeof message==='object'){ const o=message; opts={title:o.title, okText:o.yes, cancelText:o.no, placeholder:o.placeholder, default:o.default, danger:o.danger}; message=o.msg||''; }
|
|
opts=opts||{};
|
|
// Une modale déjà ouverte ne doit pas être écrasée en silence : son appelant
|
|
// attend une réponse, et l'écraser laissait sa promesse pendante POUR
|
|
// TOUJOURS (requête suspendue, bouton figé). On l'annule proprement d'abord.
|
|
if(_askResolver){ const prev=_askResolver; _askResolver=null; prev(_askKind==='prompt' ? null : false); }
|
|
_askKind=kind;
|
|
document.getElementById('ask-title').textContent = opts.title || (kind==='alert'?'Info':kind==='prompt'?'Saisie':'Confirmation');
|
|
document.getElementById('ask-msg').textContent = message||'';
|
|
const inp=document.getElementById('ask-input');
|
|
if(kind==='prompt'){ inp.style.display=''; inp.value=opts.default||''; inp.placeholder=opts.placeholder||''; }
|
|
else inp.style.display='none';
|
|
// Case facultative (ex. « supprimer aussi le fichier .gguf »).
|
|
const chk=document.getElementById('ask-check');
|
|
chk.style.display = opts.check ? 'inline-flex' : 'none';
|
|
document.getElementById('ask-check-label').textContent = opts.check || '';
|
|
// opts.checked : case cochée d'office (le choix sûr), toujours décochable.
|
|
document.getElementById('ask-check-input').checked = !!opts.checked;
|
|
_askCheck=false;
|
|
const cancel=document.getElementById('ask-cancel'), ok=document.getElementById('ask-ok');
|
|
cancel.style.display = kind==='alert' ? 'none' : '';
|
|
cancel.textContent = opts.cancelText || 'Annuler';
|
|
ok.textContent = opts.okText || (kind==='alert'?'OK':kind==='prompt'?'Valider':'Confirmer');
|
|
ok.classList.toggle('danger', !!opts.danger);
|
|
showModal('ask-modal');
|
|
document.addEventListener('keydown', _askKey, true);
|
|
setTimeout(()=>{ const f = kind==='prompt'?inp:ok; f.focus(); if(kind==='prompt') inp.select(); }, 30);
|
|
return new Promise(res=>{ _askResolver=res; });
|
|
}
|
|
function askConfirm(message,opts){ return _openAsk('confirm',message,opts); }
|
|
function askPrompt(message,opts){ return _openAsk('prompt',message,opts); }
|
|
function askAlert(message,opts){ return _openAsk('alert',message,opts); }
|