diff --git a/internal/jean/ui/index.html b/internal/jean/ui/index.html index efbf4c4..a79283a 100644 --- a/internal/jean/ui/index.html +++ b/internal/jean/ui/index.html @@ -290,7 +290,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)} @@ -394,19 +408,23 @@ button:disabled{opacity:.5;cursor:not-allowed} -
Backend llama.cpp -
…
-
- - - - - +
Backend llama.cpp +
…
+
+
+
⚡ Version rapide conseillé
+
prête à l'emploi, installée en ~2 min
+
+
+
+
🔧 Version optimisée
+
compilée pour ta machine, plus longue
+
+
-
-
@@ -1592,135 +1610,111 @@ async function send(){ loadAll(); setInterval(loadStatus, 5000); setInterval(loadVram, 3000); -// 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(); @@ -1731,7 +1725,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; @@ -1743,21 +1736,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/index.tmpl.html b/internal/jean/ui/src/index.tmpl.html index 0b12bd2..2e1c628 100644 --- a/internal/jean/ui/src/index.tmpl.html +++ b/internal/jean/ui/src/index.tmpl.html @@ -116,19 +116,23 @@
-
Backend llama.cpp -
…
-
- - - - - +
Backend llama.cpp +
…
+
+
+
⚡ Version rapide conseillé
+
prête à l'emploi, installée en ~2 min
+
+
+
+
🔧 Version optimisée
+
compilée pour ta machine, plus longue
+
+
-
-
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)