mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 14:38:12 +02:00
Modèles : les supprimer depuis l'interface, et ne charger que des presets
Un .gguf ne pouvait s'effacer qu'en cochant « supprimer aussi le fichier »
au moment de supprimer son preset. Tout ce qui avait été téléchargé sans
preset — une quantization essayée puis abandonnée, un projecteur du mauvais
dépôt — restait donc sur le disque pour des dizaines de gigaoctets, sans
aucun moyen de partir depuis Loki.
L'éditeur de preset porte désormais une ligne « Modèles installés » (repliée,
sous le sélecteur de fichier) : chaque .gguf des dossiers déclarés avec sa
taille, ses tranches, qui le réclame, et un bouton « supprimer ».
Rien n'est réversible ici, d'où deux garde-fous côté serveur, partagés par
/api/models/delete ET par la case de l'éditeur de preset :
- le modèle CHARGÉ est intouchable — l'effacer sous le moteur ne rend pas
l'espace tant qu'il tient le descripteur, et le démarrage suivant meurt
sur un fichier introuvable ;
- un modèle réclamé par des presets ne part qu'avec `force`, le refus 409
nommant les presets pour que la seconde confirmation dise ce qu'on casse.
Supprimer un preset ne peut donc plus emporter le modèle de son voisin.
La suppression emporte toute la famille de tranches et annonce la place
réellement libérée. /api/models marque au passage le modèle chargé et les
presets qui référencent chaque fichier.
Le sélecteur de l'en-tête ne liste plus que des presets. Il proposait aussi
les .gguf du disque, chargés en n'écrivant que MODEL= : le contexte, le NGL
et l'échantillonnage du modèle PRÉCÉDENT restaient en place, et un 27B chargé
par-dessus les réglages d'un 4B partait sur 100k de contexte pour mourir en
OOM sans que rien ne dise pourquoi. Charger un modèle, c'est appliquer TOUS
ses réglages : ça s'appelle un preset, et c'est le seul chemin. /api/models/use
disparaît avec le groupe qu'il servait.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoKfYRvVHTf8GyKhgY5WxT
This commit is contained in:
10 files changed
+637
-152
No files matched your search
@@ -48,17 +48,18 @@ function openDetails(el){
|
||||
}
|
||||
}
|
||||
|
||||
// Sélecteur de modèle de l'en-tête. Il liste les presets ; en choisir un revient
|
||||
// à cliquer la ligne correspondante dans les réglages (switchTo demande
|
||||
// Sélecteur de modèle de l'en-tête. Il ne liste QUE des presets ; en choisir un
|
||||
// revient à cliquer la ligne correspondante dans les réglages (switchTo demande
|
||||
// confirmation puis relance le moteur).
|
||||
//
|
||||
// Il a listé un temps les .gguf du disque, qu'on chargeait en n'écrivant que
|
||||
// MODEL= : le contexte, le NGL et l'échantillonnage du modèle PRÉCÉDENT
|
||||
// restaient en place. Un 27B chargé par-dessus les réglages d'un 4B partait sur
|
||||
// 100k de contexte et mourait en OOM, sans que rien ne dise pourquoi. Charger un
|
||||
// modèle, c'est appliquer TOUS ses réglages : ça s'appelle un preset, et c'est
|
||||
// le seul chemin.
|
||||
function onModelSwitch(sel){
|
||||
const v = sel.value;
|
||||
// Entrée du groupe « Modèles » : charger CE .gguf, sans preset — la config
|
||||
// active garde son contexte et son échantillonnage, seul MODEL change.
|
||||
if(v && v.startsWith('m:')){
|
||||
switchModelFile(v.slice(2), sel.options[sel.selectedIndex].textContent);
|
||||
return;
|
||||
}
|
||||
const n = parseInt(v, 10);
|
||||
if(!n) return;
|
||||
const name = sel.options[sel.selectedIndex].textContent;
|
||||
@@ -68,22 +69,6 @@ function onModelSwitch(sel){
|
||||
switchTo(n, name);
|
||||
}
|
||||
|
||||
// Bascule directe vers un fichier de modèle (POST /api/models/use) : confirme,
|
||||
// écrit MODEL, le service redémarre derrière. Annulé ou en échec, la sélection
|
||||
// revient sur le modèle réellement chargé.
|
||||
async function switchModelFile(value, name){
|
||||
if(!await askConfirm('Charger « ' + name + ' » et redémarrer le moteur ?', {title:'Changer de modèle', okText:'Charger'})){
|
||||
paintModelSwitch();
|
||||
return;
|
||||
}
|
||||
toast('changement de modèle…');
|
||||
let r = null;
|
||||
try{ r = await jpost('/api/models/use', {value}); }catch(_){}
|
||||
if(!r || !r.ok){ toast('erreur : ' + ((r && r.error) || 'réseau')); paintModelSwitch(); return; }
|
||||
toast('modèle en cours de chargement…');
|
||||
loadAll();
|
||||
}
|
||||
|
||||
// Le sélecteur affiche LE MODÈLE CHARGÉ, pas seulement le preset actif.
|
||||
//
|
||||
// Les deux ne coïncident pas toujours : un preset n'est « actif » que si son
|
||||
@@ -96,22 +81,12 @@ async function switchModelFile(value, name){
|
||||
// Deux sources, deux moments : loadPresets() donne la liste, loadCfg() donne le
|
||||
// modèle. Chacune dépose ce qu'elle sait et redemande le dessin — elles partent
|
||||
// en parallèle (loadAll), aucun ordre n'est garanti.
|
||||
let MS_PRESETS = [], MS_ACTIVE = null, MS_MODEL = '', MS_FILES = [];
|
||||
let MS_PRESETS = [], MS_ACTIVE = null, MS_MODEL = '';
|
||||
|
||||
function renderModelSwitch(presets, active){
|
||||
MS_PRESETS = presets || []; MS_ACTIVE = active || null;
|
||||
paintModelSwitch();
|
||||
}
|
||||
// Les fichiers .gguf découverts sur le disque : le sélecteur doit permettre de
|
||||
// CHARGER un modèle, pas seulement de rappeler un preset — sans preset créé,
|
||||
// il n'offrait aucun choix. Chargé par loadAll(), rafraîchi avec le reste.
|
||||
async function loadModelSwitchFiles(){
|
||||
try{
|
||||
const r = await jget('/api/models');
|
||||
MS_FILES = Array.isArray(r) ? r : [];
|
||||
}catch(_){ return; }
|
||||
paintModelSwitch();
|
||||
}
|
||||
// Appelé par loadCfg avec la valeur MODEL de la configuration active.
|
||||
function setLoadedModel(name){
|
||||
MS_MODEL = (name || '').trim();
|
||||
@@ -149,20 +124,7 @@ function paintModelSwitch(){
|
||||
});
|
||||
sel.appendChild(g);
|
||||
}
|
||||
// Modèles du disque : en choisir un le CHARGE (voir switchModelFile). Un
|
||||
// preset règle tout (contexte, sampling…) ; un modèle ne change que MODEL.
|
||||
if(MS_FILES.length){
|
||||
const g = document.createElement('optgroup'); g.label = 'Modèles (.gguf)';
|
||||
for(const m of MS_FILES){
|
||||
const o = document.createElement('option');
|
||||
o.value = 'm:' + m.value;
|
||||
o.textContent = modelLabel(m.name);
|
||||
if(m.missing && m.missing.length) o.disabled = true; // tranches manquantes
|
||||
g.appendChild(o);
|
||||
}
|
||||
sel.appendChild(g);
|
||||
}
|
||||
sel.disabled = !MS_PRESETS.length && !MS_FILES.length;
|
||||
sel.disabled = !MS_PRESETS.length;
|
||||
sel.title = MS_MODEL
|
||||
? 'modèle chargé : ' + MS_MODEL + ' — changer de preset relance le moteur'
|
||||
: 'aucun modèle configuré';
|
||||
|
||||
Reference in new issue
Block a user