mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-19 05:07:20 +02:00
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki) - LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ; units loki-engine / loki-ui ; binaire et aide CLI - updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les binaires AJEAN amont) Conservé à l'identique : le domaine ajean.link (service de tunnel amont), les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et LICENSE (historique et licence de l'amont). go build/vet/test : verts.
86 lines
5.0 KiB
JavaScript
86 lines
5.0 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 || '';
|
|
document.getElementById('ask-check-input').checked = false;
|
|
_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); }
|