Fusion de main : la suppression des modèles y a atterri, le sélecteur reste

main a livré entre-temps « un .gguf gardé se réutilise et s'efface »
(9bdaf00), qui couvre la suppression des modèles depuis l'éditeur avec un
index des références plus complet que celui de cette branche :
buildModelRefIndex lit aussi le --mmproj resté dans EXTRA_ARGS et le
construit en une passe, et deleteModelFile porte lui-même le refus « en
service ». Cette branche cède donc tout ce qui doublonnait.

Abandonné (repris par main) :
- modelSizeOnDisk, modelIsLoaded, modelPresetRefs, presetsUsingModel →
  modelFamilySize, modelUsers, buildModelRefIndex ;
- la liste « Modèles installés » (#m-mdl-*) et son CSS → celle de main
  (#m-inst-*), qui sélectionne aussi un modèle déjà téléchargé ;
- deletePresetModel → main supprime le .gguf partagé mais NOMME les autres
  presets concernés, plutôt que de conserver le fichier ;
- backend_models_delete_test.go → backend_models_refs_test.go couvre les
  mêmes cas, y compris les octets libérés d'une famille de tranches.

Conservé, c'est ce qui reste propre à cette branche : le sélecteur de
l'en-tête ne liste QUE des presets. Charger un .gguf n'écrivait que MODEL=,
laissant en place le contexte et l'échantillonnage du modèle précédent — un
27B par-dessus les réglages d'un 4B mourait en OOM sans explication.
/api/models/use disparaît avec le groupe qu'elle servait.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoKfYRvVHTf8GyKhgY5WxT
This commit is contained in:
Claude committed 2026-09-12 22:26:34 +00:00
commit 96ef1ac95b
26 files changed
+2773 -737

No files matched your search

+60 -13
View File
@@ -239,6 +239,54 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
</div>
</div>
</div>
<!-- Preset « externe » : le chat part vers une API OpenAI-compatible distante
(OpenAI, Groq, OpenRouter, un vLLM sur une autre machine…) au lieu du
llama-server local. C'est un preset comme un autre — il apparaît dans la
même liste, se bascule pareil — mais ses réglages n'ont rien à voir avec
ceux d'un modèle local : ni quantification, ni couches GPU, ni moteur.
D'où une fenêtre à part plutôt que des champs grisés dans l'éditeur. -->
<div id="ext-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:30;align-items:center;justify-content:center;padding:20px" onclick="if(event.target===this)closeExternal()">
<div style="background:var(--bg);border:1px solid var(--border);border-radius:4px;width:100%;max-width:520px;display:flex;flex-direction:column;max-height:90vh">
<div style="padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center">
<strong id="ext-title">API externe</strong>
<button onclick="closeExternal()" style="margin:0" title="fermer" aria-label="Fermer"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 6.5l11 11"/><path d="M17.5 6.5l-11 11"/></svg></button>
</div>
<div class="pe-body" style="padding:0">
<div class="pe-group">
<div class="pe-gh">Connexion</div>
<div class="pe-list">
<div class="pe-row stack plain"><input id="ext-name" class="pe-inp" placeholder="Nom du preset (ex. Groq 70B)"></div>
<div class="pe-row stack">
<span class="pe-row-l">URL<span class="pe-sub">base (…/v1) ou URL complète des complétions</span></span>
<input id="ext-url" class="pe-inp" placeholder="https://api.groq.com/openai/v1" spellcheck="false">
</div>
<div class="pe-row stack">
<span class="pe-row-l">Modèle<span class="pe-sub">le nom exact attendu par l'API</span></span>
<input id="ext-model" class="pe-inp" placeholder="llama-3.3-70b-versatile" spellcheck="false">
</div>
<div class="pe-row stack">
<span class="pe-row-l">Clé d'API<span class="pe-sub" id="ext-key-sub">vide = aucune authentification</span></span>
<input id="ext-key" class="pe-inp" type="password" autocomplete="off" spellcheck="false" placeholder="sk-…" oninput="extKeyTouched=true">
</div>
<div class="pe-row">
<span class="pe-row-l">Contexte<span class="pe-sub">pilote la jauge et le seuil de compactage</span></span>
<span class="pe-row-c"><input id="ext-ctx" class="pe-val" type="number" min="0" step="1024" placeholder="32768"><span class="pe-unit">tok</span></span>
</div>
<div class="pe-row stack">
<button id="ext-test" class="pe-link" onclick="testExternal()">Tester la connexion</button>
<div id="ext-test-out" class="pe-note"></div>
</div>
</div>
</div>
</div>
<div style="padding:12px 16px;border-top:1px solid var(--border);display:flex;justify-content:space-between;gap:6px">
<button id="ext-del" onclick="deleteExternal()" class="danger" style="display:none">supprimer</button>
<span style="flex:1"></span>
<button onclick="closeExternal()">annuler</button>
<button id="ext-save" onclick="saveExternal()" style="border-color:var(--accent);color:var(--accent)">enregistrer</button>
</div>
</div>
</div>
<!-- Fenêtre d'export. Un fil relu pour retrouver une réponse n'a que faire des
mille lignes de raisonnement qui l'ont produite : on choisit ce qu'on
emporte plutôt que de tout prendre ou rien. Les options de CONTENU sont les
@@ -306,7 +354,7 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
<div id="cfg" class="slist">…</div>
</section>
<section class="set-pane" data-pane="presets">
<div class="set-pane-h"><span>Presets</span><button class="preset-add" onclick="event.preventDefault();event.stopPropagation();openPreset('')" title="ajouter" aria-label="ajouter"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5.5v13"/><path d="M5.5 12h13"/></svg></button></div>
<div class="set-pane-h"><span>Presets</span><button class="preset-add" onclick="event.preventDefault();event.stopPropagation();openExternal('')" title="ajouter une API externe" aria-label="ajouter une API externe"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5a10 10 0 0 1 14 0"/><path d="M8.5 16a5.5 5.5 0 0 1 7 0"/><circle cx="12" cy="19.5" r="1"/></svg></button><button class="preset-add" onclick="event.preventDefault();event.stopPropagation();openPreset('')" title="ajouter" aria-label="ajouter"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5.5v13"/><path d="M5.5 12h13"/></svg></button></div>
<div class="slist"><div id="presets" class="rowlist"></div></div>
</section>
<section class="set-pane" data-pane="agent">
@@ -981,18 +1029,6 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
<span class="pe-row-l">Fichier</span>
<span class="pe-selc wide"><select id="m-model" onchange="onPickModel()"></select></span>
</div>
<!-- Gestion des .gguf du disque. Un modèle ne se supprimait que « en
même temps que son preset » : les fichiers téléchargés puis
abandonnés (mauvaise quantization, essai) restaient là pour des
dizaines de Go, sans aucun moyen de les effacer depuis Loki.
Repliée par défaut — on n'y va que le jour où le disque sature. -->
<div id="m-mdl-toggle" class="pe-row" style="cursor:pointer" onclick="toggleInstalledModels()">
<span class="pe-row-l">Modèles installés<span class="pe-sub">supprimer un .gguf pour libérer de la place</span></span>
<span class="pe-row-c"><span id="m-mdl-state" class="pe-sub"></span><span id="m-mdl-caret" class="pe-caret"></span></span>
</div>
<div id="m-mdl-body" class="pe-row stack" style="display:none">
<div id="m-mdl-list" class="pe-note"></div>
</div>
<div class="pe-row">
<span class="pe-row-l">Quantization<span class="pe-sub">auto si vide</span></span>
<span class="pe-row-c"><input id="m-quant" class="pe-val" oninput="applyQuant()" placeholder="Auto"></span>
@@ -1040,6 +1076,17 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
</div>
<div class="pe-note">Ces dossiers servent aussi de destination de téléchargement.</div>
</div>
<!-- Modèles installés. Un .gguf survit à la suppression de son preset
(c'est voulu : on le réutilise dans un autre). Encore faut-il
pouvoir le REPRENDRE et, un jour, l'effacer — sans ça les
fichiers gardés devenaient invisibles et indéboulonnables. -->
<div id="m-inst-toggle" class="pe-row" style="cursor:pointer" onclick="toggleInstalled()">
<span class="pe-row-l">Modèles installés<span class="pe-sub">les .gguf présents sur le disque — et de quoi en effacer un</span></span>
<span class="pe-row-c"><span id="m-inst-state" class="pe-sub"></span><span id="m-inst-caret" class="pe-caret"></span></span>
</div>
<div id="m-inst-body" class="pe-row stack" style="display:none">
<div id="m-inst-list"></div>
</div>
<!-- Jeton Hugging Face. Replié comme « Dossiers de modèles » : on n'y
touche qu'une fois, le jour où un dépôt à accès restreint refuse
le téléchargement. Il vaut aussi pour la recherche, d'où sa place