+
-
▸ logs du build
+
▸ détails
diff --git a/internal/jean/ui/src/js/10-llamacpp.js b/internal/jean/ui/src/js/10-llamacpp.js
index 02f30e0..af18b5f 100644
--- a/internal/jean/ui/src/js/10-llamacpp.js
+++ b/internal/jean/ui/src/js/10-llamacpp.js
@@ -1,132 +1,108 @@
-// Backend llama.cpp : statut, vérification des maj, install / update / rebuild
-// en job asynchrone côté serveur (logs + progression pollés sur /api/llamacpp/job).
+// Backend llama.cpp — vue simple : deux modes au choix.
+// ⚡ Version rapide = binaires officiels précompilés (aucune compilation)
+// 🔧 Version optimisée = compilée pour la machine
+// Un clic sur une carte l'installe (ou l'active si déjà installée). La carte
+// active propose « mettre à jour ». Aucun détail technique affiché.
let lcState = null, lcPoll = null, lcLogNext = 0;
-function lcBadge(html){ document.getElementById('lc-badge').innerHTML = html; }
-
async function loadLlamacpp(){
let s;
try{ s = await jget('/api/llamacpp'); }catch(_){ return; }
lcState = s;
- const st = document.getElementById('lc-status');
- const parts = [];
- if(!s.installed){
- parts.push('
llama.cpp n\'est pas encore installé sur cette machine.');
- parts.push('
L\'installation clone le dépôt, détecte le GPU et compile llama-server automatiquement.');
- lcBadge('
absent');
- } else {
- const esc = t=>String(t||'').replace(/[<>&]/g,c=>({'<':'<','>':'>','&':'&'}[c]));
- parts.push('commit
'+esc(s.commit)+''+(s.branch?'
('+esc(s.branch)+')':''));
- if(s.commit_date) parts.push('
'+esc(String(s.commit_date).slice(0,16))+' — '+esc(s.commit_msg));
- if(s.bin){
- parts.push('binaire :
✓ compilé'+(s.in_use?' ·
utilisé par la config':' ·
non utilisé par la config (BIN pointe ailleurs)'));
- } else {
- parts.push('binaire :
absent (pas encore compilé)');
- }
- if(s.behind > 0) lcBadge('
maj dispo');
- else if(s.bin) lcBadge('
ok');
- else lcBadge('
à compiler');
- }
- if(s.plan){
- const p = s.plan;
- parts.push('accélérateur détecté :
'+p.backend.toUpperCase()+''+(p.arch?'
(arch '+p.arch+')':'')+' · '+p.jobs+' jobs');
- }
- // Mode actif : binaires officiels précompilés vs build local.
const pb = s.prebuilt || {};
- if(pb.in_use){
- parts.push('mode :
⚡ binaires précompilés officiels'+(pb.tag?'
('+pb.tag+')':''));
- lcBadge('
précompilé');
- } else if(s.installed && s.in_use){
- parts.push('mode :
🔧 compilé depuis les sources'+(pb.tag?'
(précompilé '+pb.tag+' aussi installé)':''));
+ const fastActive = !!pb.in_use;
+ const optActive = s.installed && s.in_use && !pb.in_use;
+
+ const head = document.getElementById('lc-status');
+ if(!fastActive && !optActive){
+ head.innerHTML = 'Le moteur d\'IA n\'est pas encore installé. Choisis une version ci-dessous — la
rapide convient à presque tout le monde.';
+ } else if(fastActive){
+ head.innerHTML = 'Moteur actif :
version rapide.';
+ } else {
+ head.innerHTML = 'Moteur actif :
version optimisée.';
}
- st.innerHTML = parts.join('
');
- document.getElementById('lc-install').style.display = s.installed ? 'none' : '';
- document.getElementById('lc-check').style.display = (s.installed || pb.in_use) ? '' : 'none';
- document.getElementById('lc-update').style.display = (s.installed && !pb.in_use && (s.behind>0 || !s.bin)) ? '' : 'none';
- document.getElementById('lc-rebuild').style.display = (s.installed && !pb.in_use) ? '' : 'none';
- document.getElementById('lc-prebuilt').textContent = pb.in_use ? '⚡ maj binaires précompilés' : '⚡ binaires précompilés';
- // Job en cours (page rechargée pendant un build) → on raccroche le polling.
+ lcRenderMode('fast', fastActive, !!pb.bin);
+ lcRenderMode('opt', optActive, !!s.bin);
+
+ // Job en cours (page rechargée pendant une install) → on raccroche l'affichage.
if(s.job && s.job.exists && s.job.running && !lcPoll){
document.getElementById('lc-details').open = true;
lcStartPolling();
}
}
-async function lcCheck(){
- const btn = document.getElementById('lc-check');
- const msg = document.getElementById('lc-check-msg');
- btn.disabled = true; btn.textContent = '⏳ vérification…';
- // Mode précompilé : on compare à la dernière release officielle, pas au git.
- if(lcState && lcState.prebuilt && lcState.prebuilt.in_use){
- try{
- const r = await jpost('/api/llamacpp/prebuilt/check');
- if(!r.ok){ msg.innerHTML = '
'+(r.error||'erreur')+''; return; }
- if(r.update){
- msg.innerHTML = 'nouvelle release officielle :
'+r.latest+' (installée : '+(r.current||'aucune')+')
'+String(r.variant||'').replace(/[<>&]/g,'')+' · ~'+r.size_mb+' Mo';
- lcBadge('
maj dispo');
- } else {
- msg.innerHTML = '
✓ à jour ('+r.current+')';
- }
- } finally { btn.disabled = false; btn.textContent = '↥ vérifier les maj'; }
- return;
+function lcRenderMode(mode, active, installed){
+ const card = document.getElementById(mode==='fast' ? 'lc-mode-fast' : 'lc-mode-opt');
+ const state = document.getElementById(mode==='fast' ? 'lc-fast-state' : 'lc-opt-state');
+ card.classList.toggle('active', active);
+ if(active){
+ state.innerHTML = '
✓ Actif'
+ + '
↻ mettre à jour';
+ } else if(installed){
+ state.innerHTML = '
→ cliquer pour l\'activer';
+ } else {
+ state.innerHTML = '
→ cliquer pour installer';
}
- try{
- const r = await jpost('/api/llamacpp/check');
- if(!r.ok){ msg.innerHTML = '
'+(r.error||'erreur')+''; return; }
- if(r.behind > 0){
- msg.innerHTML = '
'+r.behind+' commit(s) de retard sur origin/'+r.branch+'
'+
- 'dernier distant :
'+r.remote+' — '+String(r.remote_msg||'').replace(/[<>&]/g,'');
- document.getElementById('lc-update').style.display = '';
- lcBadge('
maj dispo');
- } else if(!r.has_binary){
- msg.innerHTML = 'sources à jour mais
binaire absent — lance une recompilation.';
- } else {
- msg.innerHTML = '
✓ à jour ('+r.local+')';
+}
+
+// Clic sur une carte : activer / installer le mode correspondant.
+async function lcPick(mode){
+ const s = lcState || {}, pb = s.prebuilt || {};
+ if(mode === 'fast'){
+ if(pb.in_use) return; // déjà actif
+ if(pb.bin){ // déjà téléchargée → simple bascule
+ if(!await askConfirm('Basculer sur la version rapide (déjà installée) ? Le moteur va redémarrer.', {title:'Version rapide', okText:'Basculer'})) return;
+ return lcUse('fast');
}
- } finally { btn.disabled = false; btn.textContent = '↥ vérifier les maj'; }
+ if(!await askConfirm('Installer la version rapide de llama.cpp : téléchargement prêt à l\'emploi (~2 min, aucune compilation). Le moteur va redémarrer à la fin.', {title:'Version rapide', okText:'Installer'})) return;
+ const r = await jpost('/api/llamacpp/prebuilt', {});
+ if(!r.ok){ toast('erreur : '+(r.error||'')); return; }
+ lcStartPolling();
+ } else {
+ if(s.installed && s.in_use && !pb.in_use) return; // déjà actif
+ if(s.bin){ // déjà compilée → simple bascule
+ if(!await askConfirm('Basculer sur la version optimisée (déjà compilée) ? Le moteur va redémarrer.', {title:'Version optimisée', okText:'Basculer'})) return;
+ return lcUse('opt');
+ }
+ if(!await askConfirm('Compiler la version optimisée pour ta machine. Ça peut prendre de longues minutes (surtout avec une carte NVIDIA). Le moteur va redémarrer à la fin.', {title:'Version optimisée', okText:'Compiler'})) return;
+ const r = await jpost('/api/llamacpp/install', {});
+ if(!r.ok){ toast('erreur : '+(r.error||'')); return; }
+ lcStartPolling();
+ }
}
-async function lcInstall(){
- if(!await askConfirm('Cloner llama.cpp puis compiler llama-server pour cette machine (détection GPU automatique). La compilation peut prendre de longues minutes (surtout en CUDA).', {title:'Installer llama.cpp ?', okText:'Installer'})) return;
- const r = await jpost('/api/llamacpp/install', {});
+// Bascule instantanée entre deux versions déjà installées (pas de recompilation).
+async function lcUse(mode){
+ toast('bascule…');
+ const r = await jpost('/api/llamacpp/use', {mode});
if(!r.ok){ toast('erreur : '+(r.error||'')); return; }
+ toast(mode==='fast' ? 'version rapide activée' : 'version optimisée activée');
+ setTimeout(loadAll, 1500);
+}
+
+// « Mettre à jour » sur la carte active.
+async function lcUpdateActive(){
+ const s = lcState || {}, pb = s.prebuilt || {};
+ if(pb.in_use){
+ if(!await askConfirm('Vérifier et installer la dernière version rapide. Le moteur va redémarrer.', {title:'Mettre à jour', okText:'Mettre à jour'})) return;
+ const r = await jpost('/api/llamacpp/prebuilt', {});
+ if(!r.ok){ toast('erreur : '+(r.error||'')); return; }
+ } else {
+ if(!await askConfirm('Vérifier et installer la dernière version optimisée (recompilation si besoin). Le moteur va redémarrer.', {title:'Mettre à jour', okText:'Mettre à jour'})) return;
+ const r = await jpost('/api/llamacpp/update', {clean:false});
+ if(!r.ok){ toast('erreur : '+(r.error||'')); return; }
+ }
lcStartPolling();
}
-async function lcPrebuilt(){
- const inUse = lcState && lcState.prebuilt && lcState.prebuilt.in_use;
- const msg = inUse
- ? 'Télécharger la dernière release officielle précompilée de llama.cpp (~2 min, aucune compilation). Le service sera arrêté pendant le remplacement puis redémarré.'
- : 'Télécharger les binaires OFFICIELS précompilés de llama.cpp au lieu de compiler (~2 min). Le variant est choisi automatiquement (CUDA / Vulkan / Metal / CPU) et BIN pointera dessus. Le build local, s\'il existe, reste sur le disque — tu peux y revenir via l\'éditeur de preset. Note : builds génériques (pas de tuning natif) ; sous Linux il n\'existe pas de build CUDA officiel (variant Vulkan).';
- if(!await askConfirm(msg, {title: inUse ? 'Mettre à jour les binaires précompilés ?' : 'Passer aux binaires précompilés ?', okText: inUse ? 'Mettre à jour' : 'Télécharger'})) return;
- const r = await jpost('/api/llamacpp/prebuilt', {});
- if(!r.ok){ toast('erreur : '+(r.error||'')); return; }
- lcStartPolling();
-}
-
-async function lcUpdate(clean){
- const msg = clean
- ? 'Recompiler llama.cpp from scratch (build/ supprimé). Le service sera arrêté pendant le build puis redémarré.'
- : 'Mettre à jour llama.cpp (git pull) et recompiler. Le service sera arrêté pendant le build puis redémarré.';
- if(!await askConfirm(msg, {title: clean ? 'Recompiler llama.cpp ?' : 'Mettre à jour llama.cpp ?', okText: clean ? 'Recompiler' : 'Mettre à jour'})) return;
- const r = await jpost('/api/llamacpp/update', {clean: !!clean});
- if(!r.ok){ toast('erreur : '+(r.error||'')); return; }
- lcStartPolling();
-}
-
-function lcSetButtons(disabled){
- ['lc-install','lc-check','lc-update','lc-rebuild','lc-prebuilt'].forEach(id=>{
- const b=document.getElementById(id); if(b) b.disabled = disabled;
- });
-}
+// --- Progression de l'installation (téléchargement / compilation) ----------
+function lcBusy(on){ document.querySelector('.lc-modes').classList.toggle('busy', on); }
function lcStartPolling(){
- const jobBox = document.getElementById('lc-job');
- jobBox.style.display = '';
+ document.getElementById('lc-job').style.display = '';
document.getElementById('lc-log').textContent = '';
lcLogNext = 0;
- lcSetButtons(true);
- lcBadge('
⏳ build');
+ lcBusy(true);
if(lcPoll) clearInterval(lcPoll);
lcPoll = setInterval(lcPollJob, 1000);
lcPollJob();
@@ -137,7 +113,6 @@ async function lcPollJob(){
try{ j = await jget('/api/llamacpp/job?from='+lcLogNext); }catch(_){ return; }
if(!j.exists) return;
const phaseEl = document.getElementById('lc-job-phase');
- // Logs incrémentaux (offset absolu — le serveur tronque le début si besoin).
if(j.lines && j.lines.length){
const pre = document.getElementById('lc-log');
const stick = pre.scrollTop + pre.clientHeight >= pre.scrollHeight - 20;
@@ -149,21 +124,19 @@ async function lcPollJob(){
phaseEl.innerHTML = '
⏳ '+String(j.phase||'…').replace(/[<>&]/g,'')+'';
return;
}
- // Terminé.
clearInterval(lcPoll); lcPoll = null;
- lcSetButtons(false);
+ lcBusy(false);
if(j.error){
phaseEl.innerHTML = '
✗ '+String(j.error).replace(/[<>&]/g,'')+'';
- // Ouvre les logs pour que l'erreur réelle soit visible tout de suite.
const pre = document.getElementById('lc-log');
if(pre.hasAttribute('hidden')) lcToggleLog();
pre.scrollTop = pre.scrollHeight;
- toast('build llama.cpp en échec');
+ toast('échec — voir les détails');
} else {
phaseEl.innerHTML = '
✓ '+String(j.phase||'terminé').replace(/[<>&]/g,'')+'';
- toast('llama.cpp : '+(j.phase||'terminé'));
+ toast('c\'est prêt ✓');
}
- loadAll(); // rafraîchit tout (config, statut service… et ce panneau)
+ loadAll();
}
function lcToggleLog(){
diff --git a/internal/jean/ui/src/styles.css b/internal/jean/ui/src/styles.css
index 8fb570d..793da28 100644
--- a/internal/jean/ui/src/styles.css
+++ b/internal/jean/ui/src/styles.css
@@ -271,7 +271,21 @@ button:disabled{opacity:.5;cursor:not-allowed}
h2,details>summary{font-size:10px}
#menubtn{width:32px;height:32px;font-size:15px;top:calc(6px + env(safe-area-inset-top))}
}
-/* --- Backend llama.cpp (gestion install/update depuis l'UI) --------------- */
+/* --- Backend llama.cpp (2 modes : rapide / optimisée) --------------------- */
+.lc-head{font-size:12px;line-height:1.5;margin-bottom:10px}
+.lc-modes{display:flex;flex-direction:column;gap:8px}
+.lc-modes.busy{opacity:.5;pointer-events:none}
+.lc-mode{border:1px solid var(--border);border-radius:8px;padding:10px 12px;cursor:pointer;transition:border-color .12s,background .12s}
+.lc-mode:hover{border-color:var(--accent)}
+.lc-mode.active{border-color:var(--accent);background:var(--panel);cursor:default}
+.lc-mode-t{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px}
+.lc-reco{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--accent);border:1px solid var(--accent);border-radius:8px;padding:1px 6px}
+.lc-mode-d{font-size:11px;color:var(--dim);margin-top:3px}
+.lc-mode-state{font-size:11px;margin-top:7px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
+.lc-mode-go{color:var(--accent)}
+.lc-mode-active-tag{color:var(--accent);font-weight:600}
+.lc-update-link{color:var(--dim);text-decoration:underline;cursor:pointer}
+.lc-update-link:hover{color:var(--accent)}
.lc-logbar{display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.08em;font-weight:600;padding:6px 0;margin-top:4px}
.lc-logbar #lc-log-caret{font-size:9px;transition:transform .12s;display:inline-block}
.lc-logbar.open #lc-log-caret{transform:rotate(90deg)}
diff --git a/internal/jean/web_llamacpp.go b/internal/jean/web_llamacpp.go
index 270d8f0..34661b3 100644
--- a/internal/jean/web_llamacpp.go
+++ b/internal/jean/web_llamacpp.go
@@ -313,6 +313,39 @@ func lcRunPrebuilt() {
lcDone("binaires précompilés installés (" + tag + ")")
}
+// handleLlamacppUse bascule BIN entre deux versions DÉJÀ installées, sans
+// rien recompiler : "fast" = binaires précompilés, "opt" = build local. Le
+// service redémarre pour prendre le nouveau binaire. L'UI n'appelle ceci que
+// quand la version cible existe déjà (sinon elle lance un job d'installation).
+func handleLlamacppUse(w http.ResponseWriter, r *http.Request) {
+ var req struct {
+ Mode string `json:"mode"`
+ }
+ _ = json.NewDecoder(r.Body).Decode(&req)
+ var bin string
+ switch req.Mode {
+ case "fast":
+ bin = prebuiltServerBin()
+ case "opt":
+ bin = llamaServerBin(llamacppRepoDir())
+ default:
+ sendJSON(w, 400, map[string]any{"ok": false, "error": "mode inconnu"})
+ return
+ }
+ if bin == "" {
+ sendJSON(w, 400, map[string]any{"ok": false, "error": "cette version n'est pas installée"})
+ return
+ }
+ if err := SetConfigKey("BIN", bin); err != nil {
+ sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
+ return
+ }
+ if serviceIsActive() {
+ _ = serviceAction("restart")
+ }
+ sendJSON(w, 200, map[string]any{"ok": true, "bin": bin})
+}
+
// handleLlamacppJob renvoie l'état du job courant + les lignes de log depuis
// l'offset absolu ?from=N (le client mémorise `next` et enchaîne).
func handleLlamacppJob(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/jean/web_server.go b/internal/jean/web_server.go
index be5125d..2094c9b 100644
--- a/internal/jean/web_server.go
+++ b/internal/jean/web_server.go
@@ -95,6 +95,7 @@ func newWebMux() *http.ServeMux {
api("/api/llamacpp/job", handleLlamacppJob) // progression + logs du job
api("/api/llamacpp/prebuilt", handleLlamacppPrebuilt) // job : binaires officiels précompilés
api("/api/llamacpp/prebuilt/check", handleLlamacppPrebuiltCheck) // dernière release officielle vs installée
+ api("/api/llamacpp/use", handleLlamacppUse) // bascule BIN entre versions déjà installées
api("/api/presets", handlePresets)
api("/api/preset", handlePreset)
api("/api/preset/save", handlePresetSave)