mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-15 03:08:56 +02:00
gestion du backend llama.cpp depuis l'UI web : statut (commit, retard, binaire, accelerateur detecte), verification des maj (git fetch), install / update / recompilation en job asynchrone avec phases, compteur de fichiers compiles et logs de build streames (poll /api/llamacpp/job) ; le service est arrete pendant le build puis relance, BIN repointe automatiquement
This commit is contained in:
1 parent
29f249be09
commit
7ac571974f
8 files changed
+841
-4
No files matched your search
@@ -17,6 +17,52 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// buildSink est un collecteur de lignes optionnel : quand il est posé (jobs
|
||||
// web, voir web_llamacpp.go), runStep/runBuildStep y dupliquent leur sortie en
|
||||
// plus du terminal / des fichiers de log. nil en usage CLI normal.
|
||||
var (
|
||||
buildSinkMu sync.Mutex
|
||||
buildSink func(string)
|
||||
)
|
||||
|
||||
func setBuildSink(f func(string)) {
|
||||
buildSinkMu.Lock()
|
||||
buildSink = f
|
||||
buildSinkMu.Unlock()
|
||||
}
|
||||
|
||||
func emitBuildLine(line string) {
|
||||
buildSinkMu.Lock()
|
||||
f := buildSink
|
||||
buildSinkMu.Unlock()
|
||||
if f != nil {
|
||||
f(line)
|
||||
}
|
||||
}
|
||||
|
||||
// sinkWriter découpe un flux en lignes et les pousse vers emitBuildLine.
|
||||
// Sert à téer la sortie des commandes de runStep quand un sink est actif.
|
||||
// (mutex : stdout et stderr d'une même commande peuvent écrire en parallèle)
|
||||
type sinkWriter struct {
|
||||
mu sync.Mutex
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (s *sinkWriter) Write(p []byte) (int, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.buf = append(s.buf, p...)
|
||||
for {
|
||||
i := strings.IndexByte(string(s.buf), '\n')
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
emitBuildLine(strings.TrimRight(string(s.buf[:i]), "\r"))
|
||||
s.buf = s.buf[i+1:]
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func detectBuildPlan() buildPlan {
|
||||
p := buildPlan{backend: "cpu", jobs: numJobs()}
|
||||
// Flags communs : Release + tuning natif pour la machine de build.
|
||||
@@ -415,8 +461,19 @@ func runStepEnv(name, dir, extraEnv, bin string, args ...string) error {
|
||||
fmt.Printf("\n%s %s %s\n", cyan("▶"), name, dim(strings.Join(args, " ")))
|
||||
cmd := exec.Command(bin, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
// Tee vers le sink de build (jobs web) en plus du terminal.
|
||||
var out io.Writer = os.Stdout
|
||||
var errw io.Writer = os.Stderr
|
||||
buildSinkMu.Lock()
|
||||
sinkOn := buildSink != nil
|
||||
buildSinkMu.Unlock()
|
||||
if sinkOn {
|
||||
sw := &sinkWriter{}
|
||||
out = io.MultiWriter(os.Stdout, sw)
|
||||
errw = io.MultiWriter(os.Stderr, sw)
|
||||
}
|
||||
cmd.Stdout = out
|
||||
cmd.Stderr = errw
|
||||
cmd.Stdin = os.Stdin
|
||||
if extraEnv != "" {
|
||||
env := os.Environ()
|
||||
@@ -498,6 +555,7 @@ func runBuildStep(name, dir, extraEnv, bin, logPath string, args ...string) erro
|
||||
if logf != nil {
|
||||
fmt.Fprintln(logf, line)
|
||||
}
|
||||
emitBuildLine(line)
|
||||
mu.Lock()
|
||||
if f := compiledFile(line); f != "" {
|
||||
count++
|
||||
|
||||
+162
-1
@@ -290,6 +290,12 @@ 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) --------------- */
|
||||
.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)}
|
||||
#lc-log{background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;font-size:10px;line-height:1.45;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin:0}
|
||||
.lc-spin{display:inline-block;animation:spin 1s linear infinite}
|
||||
</style></head><body>
|
||||
<button id="menubtn" onclick="toggleSide()">☰</button>
|
||||
<div id="backdrop" onclick="toggleSide()"></div>
|
||||
@@ -388,6 +394,21 @@ button:disabled{opacity:.5;cursor:not-allowed}
|
||||
<button onclick="apiKeyAction('clear')">retirer</button>
|
||||
</div>
|
||||
</details>
|
||||
<details id="lc-details"><summary>Backend llama.cpp <span id="lc-badge" style="margin-left:auto;margin-right:12px;font-size:10px"></span></summary>
|
||||
<div id="lc-status" class="muted" style="font-size:12px;line-height:1.6">…</div>
|
||||
<div class="row" id="lc-actions" style="margin-top:8px;flex-wrap:wrap">
|
||||
<button id="lc-install" onclick="lcInstall()" style="display:none;border-color:var(--accent);color:var(--accent)">⬇ installer</button>
|
||||
<button id="lc-check" onclick="lcCheck()">↥ vérifier les maj</button>
|
||||
<button id="lc-update" onclick="lcUpdate(false)" style="display:none;border-color:var(--accent);color:var(--accent)">⬆ mettre à jour</button>
|
||||
<button id="lc-rebuild" onclick="lcUpdate(true)" style="display:none">🔧 recompiler</button>
|
||||
</div>
|
||||
<div id="lc-check-msg" class="muted" style="font-size:11px;margin-top:6px;line-height:1.5"></div>
|
||||
<div id="lc-job" style="display:none;margin-top:8px">
|
||||
<div id="lc-job-phase" style="font-size:12px;display:flex;align-items:center;gap:6px"></div>
|
||||
<div class="lc-logbar" onclick="lcToggleLog()"><span id="lc-log-caret">▸</span> logs du build</div>
|
||||
<pre id="lc-log" hidden></pre>
|
||||
</div>
|
||||
</details>
|
||||
<details><summary>Service</summary>
|
||||
<div class="row">
|
||||
<button onclick="act('start')">start</button>
|
||||
@@ -944,7 +965,7 @@ async function saveCrawlUrl(){
|
||||
const url=document.getElementById('crawl-url').value.trim();
|
||||
renderInternet(await jpost('/api/internet',{url}));
|
||||
}
|
||||
async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadCfg(),loadPresets(),loadAgent(),loadInternet(),loadApiKey(),loadPrefs()]); }
|
||||
async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadCfg(),loadPresets(),loadAgent(),loadInternet(),loadApiKey(),loadPrefs(),loadLlamacpp()]); }
|
||||
async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); }
|
||||
function openBenchModal(){ document.getElementById('bench-modal').style.display = 'flex'; }
|
||||
function closeBenchModal(){ document.getElementById('bench-modal').style.display = 'none'; }
|
||||
@@ -1570,4 +1591,144 @@ 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).
|
||||
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('<span style="color:var(--dim)">llama.cpp n\'est pas encore installé sur cette machine.</span>');
|
||||
parts.push('<span style="color:var(--dim)">L\'installation clone le dépôt, détecte le GPU et compile <code>llama-server</code> automatiquement.</span>');
|
||||
lcBadge('<span class="tag" style="opacity:.6">absent</span>');
|
||||
} else {
|
||||
const esc = t=>String(t||'').replace(/[<>&]/g,c=>({'<':'<','>':'>','&':'&'}[c]));
|
||||
parts.push('commit <b style="color:var(--text)">'+esc(s.commit)+'</b>'+(s.branch?' <span style="opacity:.7">('+esc(s.branch)+')</span>':''));
|
||||
if(s.commit_date) parts.push('<span style="opacity:.8">'+esc(String(s.commit_date).slice(0,16))+'</span> — '+esc(s.commit_msg));
|
||||
if(s.bin){
|
||||
parts.push('binaire : <span style="color:var(--ok)">✓ compilé</span>'+(s.in_use?' · <span style="color:var(--accent)">utilisé par la config</span>':' · <span style="opacity:.7">non utilisé par la config (BIN pointe ailleurs)</span>'));
|
||||
} else {
|
||||
parts.push('binaire : <span style="color:var(--err)">absent (pas encore compilé)</span>');
|
||||
}
|
||||
if(s.behind > 0) lcBadge('<span class="tag" style="color:var(--accent);border-color:var(--accent)">maj dispo</span>');
|
||||
else if(s.bin) lcBadge('<span class="tag ok">ok</span>');
|
||||
else lcBadge('<span class="tag err">à compiler</span>');
|
||||
}
|
||||
if(s.plan){
|
||||
const p = s.plan;
|
||||
parts.push('accélérateur détecté : <b style="color:var(--text)">'+p.backend.toUpperCase()+'</b>'+(p.arch?' <span style="opacity:.7">(arch '+p.arch+')</span>':'')+' · '+p.jobs+' jobs');
|
||||
}
|
||||
st.innerHTML = parts.join('<br>');
|
||||
document.getElementById('lc-install').style.display = s.installed ? 'none' : '';
|
||||
document.getElementById('lc-check').style.display = s.installed ? '' : 'none';
|
||||
document.getElementById('lc-update').style.display = (s.installed && (s.behind>0 || !s.bin)) ? '' : 'none';
|
||||
document.getElementById('lc-rebuild').style.display = s.installed ? '' : 'none';
|
||||
// Job en cours (page rechargée pendant un build) → on raccroche le polling.
|
||||
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…';
|
||||
try{
|
||||
const r = await jpost('/api/llamacpp/check');
|
||||
if(!r.ok){ msg.innerHTML = '<span style="color:var(--err)">'+(r.error||'erreur')+'</span>'; return; }
|
||||
if(r.behind > 0){
|
||||
msg.innerHTML = '<b style="color:var(--accent)">'+r.behind+' commit(s) de retard</b> sur origin/'+r.branch+'<br>'+
|
||||
'dernier distant : <b>'+r.remote+'</b> — '+String(r.remote_msg||'').replace(/[<>&]/g,'');
|
||||
document.getElementById('lc-update').style.display = '';
|
||||
lcBadge('<span class="tag" style="color:var(--accent);border-color:var(--accent)">maj dispo</span>');
|
||||
} else if(!r.has_binary){
|
||||
msg.innerHTML = 'sources à jour mais <b>binaire absent</b> — lance une recompilation.';
|
||||
} else {
|
||||
msg.innerHTML = '<span style="color:var(--ok)">✓ à jour</span> ('+r.local+')';
|
||||
}
|
||||
} finally { btn.disabled = false; btn.textContent = '↥ vérifier les maj'; }
|
||||
}
|
||||
|
||||
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', {});
|
||||
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'].forEach(id=>{
|
||||
const b=document.getElementById(id); if(b) b.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
function lcStartPolling(){
|
||||
const jobBox = document.getElementById('lc-job');
|
||||
jobBox.style.display = '';
|
||||
document.getElementById('lc-log').textContent = '';
|
||||
lcLogNext = 0;
|
||||
lcSetButtons(true);
|
||||
lcBadge('<span class="tag" style="color:var(--accent);border-color:var(--accent)"><span class="lc-spin">⏳</span> build</span>');
|
||||
if(lcPoll) clearInterval(lcPoll);
|
||||
lcPoll = setInterval(lcPollJob, 1000);
|
||||
lcPollJob();
|
||||
}
|
||||
|
||||
async function lcPollJob(){
|
||||
let j;
|
||||
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;
|
||||
pre.textContent += j.lines.join('\n') + '\n';
|
||||
if(stick) pre.scrollTop = pre.scrollHeight;
|
||||
}
|
||||
if(typeof j.next === 'number') lcLogNext = j.next;
|
||||
if(j.running){
|
||||
phaseEl.innerHTML = '<span class="lc-spin">⏳</span> <span>'+String(j.phase||'…').replace(/[<>&]/g,'')+'</span>';
|
||||
return;
|
||||
}
|
||||
// Terminé.
|
||||
clearInterval(lcPoll); lcPoll = null;
|
||||
lcSetButtons(false);
|
||||
if(j.error){
|
||||
phaseEl.innerHTML = '<span style="color:var(--err)">✗ '+String(j.error).replace(/[<>&]/g,'')+'</span>';
|
||||
// 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');
|
||||
} else {
|
||||
phaseEl.innerHTML = '<span style="color:var(--ok)">✓ '+String(j.phase||'terminé').replace(/[<>&]/g,'')+'</span>';
|
||||
toast('llama.cpp : '+(j.phase||'terminé'));
|
||||
}
|
||||
loadAll(); // rafraîchit tout (config, statut service… et ce panneau)
|
||||
}
|
||||
|
||||
function lcToggleLog(){
|
||||
const pre = document.getElementById('lc-log');
|
||||
const bar = document.querySelector('.lc-logbar');
|
||||
if(pre.hasAttribute('hidden')){ pre.removeAttribute('hidden'); bar.classList.add('open'); pre.scrollTop = pre.scrollHeight; }
|
||||
else { pre.setAttribute('hidden',''); bar.classList.remove('open'); }
|
||||
}
|
||||
</script></body></html>
|
||||
@@ -116,6 +116,21 @@
|
||||
<button onclick="apiKeyAction('clear')">retirer</button>
|
||||
</div>
|
||||
</details>
|
||||
<details id="lc-details"><summary>Backend llama.cpp <span id="lc-badge" style="margin-left:auto;margin-right:12px;font-size:10px"></span></summary>
|
||||
<div id="lc-status" class="muted" style="font-size:12px;line-height:1.6">…</div>
|
||||
<div class="row" id="lc-actions" style="margin-top:8px;flex-wrap:wrap">
|
||||
<button id="lc-install" onclick="lcInstall()" style="display:none;border-color:var(--accent);color:var(--accent)">⬇ installer</button>
|
||||
<button id="lc-check" onclick="lcCheck()">↥ vérifier les maj</button>
|
||||
<button id="lc-update" onclick="lcUpdate(false)" style="display:none;border-color:var(--accent);color:var(--accent)">⬆ mettre à jour</button>
|
||||
<button id="lc-rebuild" onclick="lcUpdate(true)" style="display:none">🔧 recompiler</button>
|
||||
</div>
|
||||
<div id="lc-check-msg" class="muted" style="font-size:11px;margin-top:6px;line-height:1.5"></div>
|
||||
<div id="lc-job" style="display:none;margin-top:8px">
|
||||
<div id="lc-job-phase" style="font-size:12px;display:flex;align-items:center;gap:6px"></div>
|
||||
<div class="lc-logbar" onclick="lcToggleLog()"><span id="lc-log-caret">▸</span> logs du build</div>
|
||||
<pre id="lc-log" hidden></pre>
|
||||
</div>
|
||||
</details>
|
||||
<details><summary>Service</summary>
|
||||
<div class="row">
|
||||
<button onclick="act('start')">start</button>
|
||||
|
||||
@@ -216,5 +216,5 @@ async function saveCrawlUrl(){
|
||||
const url=document.getElementById('crawl-url').value.trim();
|
||||
renderInternet(await jpost('/api/internet',{url}));
|
||||
}
|
||||
async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadCfg(),loadPresets(),loadAgent(),loadInternet(),loadApiKey(),loadPrefs()]); }
|
||||
async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadCfg(),loadPresets(),loadAgent(),loadInternet(),loadApiKey(),loadPrefs(),loadLlamacpp()]); }
|
||||
async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); }
|
||||
@@ -0,0 +1,140 @@
|
||||
// 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).
|
||||
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('<span style="color:var(--dim)">llama.cpp n\'est pas encore installé sur cette machine.</span>');
|
||||
parts.push('<span style="color:var(--dim)">L\'installation clone le dépôt, détecte le GPU et compile <code>llama-server</code> automatiquement.</span>');
|
||||
lcBadge('<span class="tag" style="opacity:.6">absent</span>');
|
||||
} else {
|
||||
const esc = t=>String(t||'').replace(/[<>&]/g,c=>({'<':'<','>':'>','&':'&'}[c]));
|
||||
parts.push('commit <b style="color:var(--text)">'+esc(s.commit)+'</b>'+(s.branch?' <span style="opacity:.7">('+esc(s.branch)+')</span>':''));
|
||||
if(s.commit_date) parts.push('<span style="opacity:.8">'+esc(String(s.commit_date).slice(0,16))+'</span> — '+esc(s.commit_msg));
|
||||
if(s.bin){
|
||||
parts.push('binaire : <span style="color:var(--ok)">✓ compilé</span>'+(s.in_use?' · <span style="color:var(--accent)">utilisé par la config</span>':' · <span style="opacity:.7">non utilisé par la config (BIN pointe ailleurs)</span>'));
|
||||
} else {
|
||||
parts.push('binaire : <span style="color:var(--err)">absent (pas encore compilé)</span>');
|
||||
}
|
||||
if(s.behind > 0) lcBadge('<span class="tag" style="color:var(--accent);border-color:var(--accent)">maj dispo</span>');
|
||||
else if(s.bin) lcBadge('<span class="tag ok">ok</span>');
|
||||
else lcBadge('<span class="tag err">à compiler</span>');
|
||||
}
|
||||
if(s.plan){
|
||||
const p = s.plan;
|
||||
parts.push('accélérateur détecté : <b style="color:var(--text)">'+p.backend.toUpperCase()+'</b>'+(p.arch?' <span style="opacity:.7">(arch '+p.arch+')</span>':'')+' · '+p.jobs+' jobs');
|
||||
}
|
||||
st.innerHTML = parts.join('<br>');
|
||||
document.getElementById('lc-install').style.display = s.installed ? 'none' : '';
|
||||
document.getElementById('lc-check').style.display = s.installed ? '' : 'none';
|
||||
document.getElementById('lc-update').style.display = (s.installed && (s.behind>0 || !s.bin)) ? '' : 'none';
|
||||
document.getElementById('lc-rebuild').style.display = s.installed ? '' : 'none';
|
||||
// Job en cours (page rechargée pendant un build) → on raccroche le polling.
|
||||
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…';
|
||||
try{
|
||||
const r = await jpost('/api/llamacpp/check');
|
||||
if(!r.ok){ msg.innerHTML = '<span style="color:var(--err)">'+(r.error||'erreur')+'</span>'; return; }
|
||||
if(r.behind > 0){
|
||||
msg.innerHTML = '<b style="color:var(--accent)">'+r.behind+' commit(s) de retard</b> sur origin/'+r.branch+'<br>'+
|
||||
'dernier distant : <b>'+r.remote+'</b> — '+String(r.remote_msg||'').replace(/[<>&]/g,'');
|
||||
document.getElementById('lc-update').style.display = '';
|
||||
lcBadge('<span class="tag" style="color:var(--accent);border-color:var(--accent)">maj dispo</span>');
|
||||
} else if(!r.has_binary){
|
||||
msg.innerHTML = 'sources à jour mais <b>binaire absent</b> — lance une recompilation.';
|
||||
} else {
|
||||
msg.innerHTML = '<span style="color:var(--ok)">✓ à jour</span> ('+r.local+')';
|
||||
}
|
||||
} finally { btn.disabled = false; btn.textContent = '↥ vérifier les maj'; }
|
||||
}
|
||||
|
||||
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', {});
|
||||
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'].forEach(id=>{
|
||||
const b=document.getElementById(id); if(b) b.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
function lcStartPolling(){
|
||||
const jobBox = document.getElementById('lc-job');
|
||||
jobBox.style.display = '';
|
||||
document.getElementById('lc-log').textContent = '';
|
||||
lcLogNext = 0;
|
||||
lcSetButtons(true);
|
||||
lcBadge('<span class="tag" style="color:var(--accent);border-color:var(--accent)"><span class="lc-spin">⏳</span> build</span>');
|
||||
if(lcPoll) clearInterval(lcPoll);
|
||||
lcPoll = setInterval(lcPollJob, 1000);
|
||||
lcPollJob();
|
||||
}
|
||||
|
||||
async function lcPollJob(){
|
||||
let j;
|
||||
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;
|
||||
pre.textContent += j.lines.join('\n') + '\n';
|
||||
if(stick) pre.scrollTop = pre.scrollHeight;
|
||||
}
|
||||
if(typeof j.next === 'number') lcLogNext = j.next;
|
||||
if(j.running){
|
||||
phaseEl.innerHTML = '<span class="lc-spin">⏳</span> <span>'+String(j.phase||'…').replace(/[<>&]/g,'')+'</span>';
|
||||
return;
|
||||
}
|
||||
// Terminé.
|
||||
clearInterval(lcPoll); lcPoll = null;
|
||||
lcSetButtons(false);
|
||||
if(j.error){
|
||||
phaseEl.innerHTML = '<span style="color:var(--err)">✗ '+String(j.error).replace(/[<>&]/g,'')+'</span>';
|
||||
// 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');
|
||||
} else {
|
||||
phaseEl.innerHTML = '<span style="color:var(--ok)">✓ '+String(j.phase||'terminé').replace(/[<>&]/g,'')+'</span>';
|
||||
toast('llama.cpp : '+(j.phase||'terminé'));
|
||||
}
|
||||
loadAll(); // rafraîchit tout (config, statut service… et ce panneau)
|
||||
}
|
||||
|
||||
function lcToggleLog(){
|
||||
const pre = document.getElementById('lc-log');
|
||||
const bar = document.querySelector('.lc-logbar');
|
||||
if(pre.hasAttribute('hidden')){ pre.removeAttribute('hidden'); bar.classList.add('open'); pre.scrollTop = pre.scrollHeight; }
|
||||
else { pre.setAttribute('hidden',''); bar.classList.remove('open'); }
|
||||
}
|
||||
@@ -271,3 +271,9 @@ 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) --------------- */
|
||||
.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)}
|
||||
#lc-log{background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;font-size:10px;line-height:1.45;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin:0}
|
||||
.lc-spin{display:inline-block;animation:spin 1s linear infinite}
|
||||
@@ -0,0 +1,452 @@
|
||||
// web_llamacpp.go — pilotage du backend llama.cpp depuis l'UI web :
|
||||
// statut (commit, retard, binaire, plan de build), vérification des mises à
|
||||
// jour (git fetch), et jobs asynchrones install / update / rebuild dont la
|
||||
// progression et les logs sont pollés par le client.
|
||||
//
|
||||
// GET /api/llamacpp → statut complet (dépôt, commit, binaire, plan)
|
||||
// POST /api/llamacpp/check → git fetch + retard sur origin (réseau)
|
||||
// POST /api/llamacpp/install → job d'installation {force?}
|
||||
// POST /api/llamacpp/update → job de mise à jour {clean?}
|
||||
// GET /api/llamacpp/job?from= → progression du job (phase, lignes, erreur)
|
||||
package jean
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// lcJob est LE job llama.cpp en cours (un seul à la fois). Les lignes de log
|
||||
// sont indexées de façon absolue (base = index de Lines[0]) pour que le client
|
||||
// puisse poller « à partir de N » même quand on tronque le début.
|
||||
type lcJob struct {
|
||||
Action string // "install" | "update"
|
||||
Phase string
|
||||
Compiled int // fichiers compilés (progression du build)
|
||||
Running bool
|
||||
Err string
|
||||
StartedAt int64
|
||||
EndedAt int64
|
||||
OldCommit string
|
||||
NewCommit string
|
||||
lines []string
|
||||
base int
|
||||
}
|
||||
|
||||
var (
|
||||
lcMu sync.Mutex
|
||||
lcCur *lcJob
|
||||
)
|
||||
|
||||
const lcMaxLines = 4000
|
||||
|
||||
// lcAppend ajoute une ligne au log du job courant et met à jour la phase / le
|
||||
// compteur de fichiers compilés à partir du contenu (mêmes heuristiques que le
|
||||
// spinner du CLI). Appelée par le sink de build (emitBuildLine).
|
||||
func lcAppend(line string) {
|
||||
lcMu.Lock()
|
||||
defer lcMu.Unlock()
|
||||
if lcCur == nil {
|
||||
return
|
||||
}
|
||||
lcCur.lines = append(lcCur.lines, line)
|
||||
if len(lcCur.lines) > lcMaxLines {
|
||||
drop := len(lcCur.lines) - lcMaxLines
|
||||
lcCur.lines = append([]string(nil), lcCur.lines[drop:]...)
|
||||
lcCur.base += drop
|
||||
}
|
||||
if f := compiledFile(line); f != "" {
|
||||
lcCur.Compiled++
|
||||
lcCur.Phase = fmt.Sprintf("compilation… %d fichiers", lcCur.Compiled)
|
||||
} else if p := phaseLabel(line); p != "" {
|
||||
lcCur.Phase = p
|
||||
}
|
||||
}
|
||||
|
||||
// lcPhase pose une phase explicite (étapes hors build : clone, fetch, service…)
|
||||
// et la trace aussi dans le log.
|
||||
func lcPhase(phase string) {
|
||||
lcMu.Lock()
|
||||
if lcCur != nil {
|
||||
lcCur.Phase = phase
|
||||
lcCur.lines = append(lcCur.lines, "▶ "+phase)
|
||||
}
|
||||
lcMu.Unlock()
|
||||
}
|
||||
|
||||
// startLcJob démarre un job (install ou update) si aucun n'est en cours.
|
||||
func startLcJob(action string, run func()) error {
|
||||
lcMu.Lock()
|
||||
defer lcMu.Unlock()
|
||||
if lcCur != nil && lcCur.Running {
|
||||
return fmt.Errorf("un job %s est déjà en cours", lcCur.Action)
|
||||
}
|
||||
lcCur = &lcJob{Action: action, Running: true, Phase: "démarrage…", StartedAt: time.Now().Unix()}
|
||||
setBuildSink(lcAppend)
|
||||
go func() {
|
||||
defer func() {
|
||||
setBuildSink(nil)
|
||||
lcMu.Lock()
|
||||
lcCur.Running = false
|
||||
lcCur.EndedAt = time.Now().Unix()
|
||||
lcMu.Unlock()
|
||||
}()
|
||||
run()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func lcFail(err error) {
|
||||
lcMu.Lock()
|
||||
if lcCur != nil {
|
||||
lcCur.Err = err.Error()
|
||||
lcCur.Phase = "échec"
|
||||
lcCur.lines = append(lcCur.lines, "✗ "+err.Error())
|
||||
}
|
||||
lcMu.Unlock()
|
||||
}
|
||||
|
||||
func lcDone(msg string) {
|
||||
lcMu.Lock()
|
||||
if lcCur != nil {
|
||||
lcCur.Phase = msg
|
||||
lcCur.lines = append(lcCur.lines, "✓ "+msg)
|
||||
}
|
||||
lcMu.Unlock()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// handleLlamacpp renvoie le statut complet du backend llama.cpp : dépôt,
|
||||
// commit courant, retard connu (sans fetch réseau), binaire compilé et son
|
||||
// usage dans config.env, plan de build détecté, et le job éventuel.
|
||||
func handleLlamacpp(w http.ResponseWriter, r *http.Request) {
|
||||
repo := llamacppRepoDir()
|
||||
out := map[string]any{
|
||||
"repo": repo,
|
||||
"installed": isDir(filepath.Join(repo, ".git")),
|
||||
}
|
||||
if out["installed"].(bool) {
|
||||
out["branch"] = gitOutput(repo, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
out["commit"] = gitOutput(repo, "rev-parse", "--short", "HEAD")
|
||||
out["commit_date"] = gitOutput(repo, "log", "-1", "--format=%ci")
|
||||
out["commit_msg"] = gitOutput(repo, "log", "-1", "--format=%s")
|
||||
if br, _ := out["branch"].(string); br != "" && br != "HEAD" {
|
||||
if behind := gitOutput(repo, "rev-list", "--count", "HEAD..origin/"+br); behind != "" {
|
||||
n, _ := strconv.Atoi(behind)
|
||||
out["behind"] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
bin := llamaServerBin(repo)
|
||||
out["bin"] = bin
|
||||
cfgBin := ReadConfig()["BIN"]
|
||||
out["config_bin"] = cfgBin
|
||||
out["in_use"] = bin != "" && samePath(bin, cfgBin)
|
||||
|
||||
plan := detectBuildPlan()
|
||||
out["plan"] = map[string]any{
|
||||
"backend": plan.backend,
|
||||
"arch": plan.cudaArch,
|
||||
"jobs": plan.jobs,
|
||||
}
|
||||
out["job"] = lcJobSnapshot(0, false)
|
||||
sendJSON(w, 200, out)
|
||||
}
|
||||
|
||||
// samePath compare deux chemins en neutralisant séparateurs, symlinks et casse
|
||||
// (Windows).
|
||||
func samePath(a, b string) bool {
|
||||
if a == "" || b == "" {
|
||||
return false
|
||||
}
|
||||
norm := func(p string) string {
|
||||
if real, err := filepath.EvalSymlinks(p); err == nil {
|
||||
p = real
|
||||
}
|
||||
p = filepath.Clean(p)
|
||||
return strings.ToLower(filepath.ToSlash(p))
|
||||
}
|
||||
return norm(a) == norm(b)
|
||||
}
|
||||
|
||||
// handleLlamacppCheck fait un vrai git fetch puis renvoie le retard sur origin
|
||||
// et le dernier commit distant. Synchrone (quelques secondes réseau).
|
||||
func handleLlamacppCheck(w http.ResponseWriter, r *http.Request) {
|
||||
repo := llamacppRepoDir()
|
||||
if !isDir(filepath.Join(repo, ".git")) {
|
||||
sendJSON(w, 200, map[string]any{"ok": false, "error": "llama.cpp n'est pas installé"})
|
||||
return
|
||||
}
|
||||
branch := gitOutput(repo, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if branch == "" || branch == "HEAD" {
|
||||
branch = "master"
|
||||
}
|
||||
if err := runStep("git fetch", repo, "git", "fetch", "origin", "--quiet"); err != nil {
|
||||
sendJSON(w, 200, map[string]any{"ok": false, "error": "git fetch a échoué : " + err.Error()})
|
||||
return
|
||||
}
|
||||
behind := 0
|
||||
if b := gitOutput(repo, "rev-list", "--count", "HEAD..origin/"+branch); b != "" {
|
||||
behind, _ = strconv.Atoi(b)
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{
|
||||
"ok": true,
|
||||
"behind": behind,
|
||||
"local": gitOutput(repo, "rev-parse", "--short", "HEAD"),
|
||||
"remote": gitOutput(repo, "rev-parse", "--short", "origin/"+branch),
|
||||
"remote_date": gitOutput(repo, "log", "-1", "--format=%ci", "origin/"+branch),
|
||||
"remote_msg": gitOutput(repo, "log", "-1", "--format=%s", "origin/"+branch),
|
||||
"branch": branch,
|
||||
"has_binary": llamaServerBin(repo) != "",
|
||||
"needs_rebuild": behind > 0 || llamaServerBin(repo) == "",
|
||||
})
|
||||
}
|
||||
|
||||
// handleLlamacppInstall lance le job d'installation (clone + build + BIN).
|
||||
func handleLlamacppInstall(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err := startLcJob("install", func() { lcRunInstall(req.Force) }); err != nil {
|
||||
sendJSON(w, 409, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// handleLlamacppUpdate lance le job de mise à jour (pull + rebuild + restart).
|
||||
// {clean:true} force une recompilation from scratch même sans nouveau commit.
|
||||
func handleLlamacppUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Clean bool `json:"clean"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err := startLcJob("update", func() { lcRunUpdate(req.Clean) }); err != nil {
|
||||
sendJSON(w, 409, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
from, _ := strconv.Atoi(r.URL.Query().Get("from"))
|
||||
sendJSON(w, 200, lcJobSnapshot(from, true))
|
||||
}
|
||||
|
||||
// lcJobSnapshot construit la vue JSON du job courant. withLines=false renvoie
|
||||
// juste l'entête (imbriquée dans /api/llamacpp).
|
||||
func lcJobSnapshot(from int, withLines bool) map[string]any {
|
||||
lcMu.Lock()
|
||||
defer lcMu.Unlock()
|
||||
if lcCur == nil {
|
||||
return map[string]any{"exists": false}
|
||||
}
|
||||
j := lcCur
|
||||
out := map[string]any{
|
||||
"exists": true,
|
||||
"action": j.Action,
|
||||
"running": j.Running,
|
||||
"phase": j.Phase,
|
||||
"compiled": j.Compiled,
|
||||
"error": j.Err,
|
||||
"started_at": j.StartedAt,
|
||||
"ended_at": j.EndedAt,
|
||||
"old": j.OldCommit,
|
||||
"new": j.NewCommit,
|
||||
}
|
||||
if withLines {
|
||||
start := from - j.base
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > len(j.lines) {
|
||||
start = len(j.lines)
|
||||
}
|
||||
out["lines"] = append([]string(nil), j.lines[start:]...)
|
||||
out["next"] = j.base + len(j.lines)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Corps des jobs (miroir web de llamacppInstall / llamacppUpdate, sans stdout
|
||||
// interactif : tout passe par lcPhase / le sink de build)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func lcRunInstall(force bool) {
|
||||
repo := defaultRepoDir()
|
||||
|
||||
lcPhase("vérification des outils (git, cmake, compilateur)…")
|
||||
if err := requireTools("git", "cmake"); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
if err := ensureCompiler(); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
ensureAccelerator()
|
||||
|
||||
if isDir(filepath.Join(repo, ".git")) {
|
||||
if !force {
|
||||
// Dépôt déjà là : on bascule sur une mise à jour (même intention).
|
||||
lcPhase("dépôt déjà présent — bascule en mise à jour")
|
||||
lcRunUpdate(false)
|
||||
return
|
||||
}
|
||||
lcPhase("suppression du dépôt existant (--force)…")
|
||||
if err := os.RemoveAll(repo); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(repo), 0o755); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
|
||||
lcPhase("clone de llama.cpp…")
|
||||
if err := runStep("git clone", "", "git", "clone", "--depth=1", llamacppRepoURL, repo); err != nil {
|
||||
lcFail(fmt.Errorf("git clone a échoué : %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !lcBuildAndSwitch(repo, true) {
|
||||
return
|
||||
}
|
||||
lcDone("installation terminée")
|
||||
}
|
||||
|
||||
func lcRunUpdate(clean bool) {
|
||||
lcPhase("vérification des outils (git, cmake, compilateur)…")
|
||||
if err := requireTools("git", "cmake"); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
if err := ensureCompiler(); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
ensureAccelerator()
|
||||
|
||||
repo := llamacppRepoDir()
|
||||
if !isDir(filepath.Join(repo, ".git")) {
|
||||
lcFail(fmt.Errorf("aucun dépôt llama.cpp (%s) — lance d'abord l'installation", repo))
|
||||
return
|
||||
}
|
||||
|
||||
oldCommit := gitOutput(repo, "rev-parse", "--short", "HEAD")
|
||||
lcMu.Lock()
|
||||
lcCur.OldCommit = oldCommit
|
||||
lcMu.Unlock()
|
||||
|
||||
branch := gitOutput(repo, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if branch == "" || branch == "HEAD" {
|
||||
branch = "master"
|
||||
}
|
||||
|
||||
lcPhase("git fetch origin…")
|
||||
if err := runStep("git fetch", repo, "git", "fetch", "origin", "--quiet"); err != nil {
|
||||
lcFail(fmt.Errorf("git fetch a échoué : %w", err))
|
||||
return
|
||||
}
|
||||
localRev := gitOutput(repo, "rev-parse", "HEAD")
|
||||
remoteRev := gitOutput(repo, "rev-parse", "origin/"+branch)
|
||||
if localRev != "" && localRev == remoteRev && !clean && llamaServerBin(repo) != "" {
|
||||
lcDone("déjà à jour (" + oldCommit + ") — rien à faire")
|
||||
return
|
||||
}
|
||||
if localRev != remoteRev {
|
||||
lcPhase("git pull origin/" + branch + "…")
|
||||
if err := runStep("git pull --ff-only", repo, "git", "pull", "--ff-only", "origin", branch); err != nil {
|
||||
lcFail(fmt.Errorf("git pull a échoué (modifs locales ?) : %w", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
newCommit := gitOutput(repo, "rev-parse", "--short", "HEAD")
|
||||
lcMu.Lock()
|
||||
lcCur.NewCommit = newCommit
|
||||
lcMu.Unlock()
|
||||
|
||||
// Le binaire en cours d'exécution ne peut pas être réécrit → stop du service
|
||||
// pendant le build, redémarrage après (même en échec).
|
||||
svcWasUp := serviceIsActive()
|
||||
if svcWasUp {
|
||||
lcPhase("arrêt du service le temps du build…")
|
||||
if err := serviceAction("stop"); err != nil {
|
||||
lcAppend("[warn] impossible d'arrêter le service : " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
ok := lcBuildAndSwitch(repo, clean)
|
||||
if svcWasUp {
|
||||
lcPhase("redémarrage du service…")
|
||||
if err := serviceAction("start"); err != nil {
|
||||
lcAppend("[warn] redémarrage du service échoué : " + err.Error())
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if oldCommit == newCommit {
|
||||
lcDone("recompilé (" + newCommit + ")")
|
||||
} else {
|
||||
lcDone("mis à jour : " + oldCommit + " → " + newCommit)
|
||||
}
|
||||
}
|
||||
|
||||
// lcBuildAndSwitch détecte le plan, compile llama-server et pointe BIN dessus.
|
||||
// Renvoie false (job en échec) si une étape casse.
|
||||
func lcBuildAndSwitch(repo string, clean bool) bool {
|
||||
plan := detectBuildPlan()
|
||||
lcAppend(fmt.Sprintf("plan de build : backend=%s arch=%s jobs=%d", plan.backend, plan.cudaArch, plan.jobs))
|
||||
lcPhase("configuration CMake…")
|
||||
if err := buildLlamacpp(repo, plan, clean); err != nil {
|
||||
lcAppendLogTail(filepath.Join(repo, "configure.log"), filepath.Join(repo, "build.log"))
|
||||
lcFail(err)
|
||||
return false
|
||||
}
|
||||
bin := llamaServerBin(repo)
|
||||
if bin == "" {
|
||||
lcFail(fmt.Errorf("build terminé mais binaire introuvable sous %s", filepath.Join(repo, "build")))
|
||||
return false
|
||||
}
|
||||
lcAppend("binaire compilé : " + bin)
|
||||
if err := SetConfigKey("BIN", bin); err != nil {
|
||||
lcFail(fmt.Errorf("build ok mais échec écriture BIN dans config.env : %w", err))
|
||||
return false
|
||||
}
|
||||
lcAppend("BIN mis à jour dans " + confPath())
|
||||
return true
|
||||
}
|
||||
|
||||
// lcAppendLogTail remonte la fin des logs de build dans le job pour que
|
||||
// l'erreur réelle soit visible dans l'UI sans aller chercher les fichiers.
|
||||
func lcAppendLogTail(paths ...string) {
|
||||
for _, p := range paths {
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines := strings.Split(strings.TrimRight(string(b), "\n"), "\n")
|
||||
if len(lines) > 30 {
|
||||
lines = lines[len(lines)-30:]
|
||||
}
|
||||
lcAppend("--- fin de " + filepath.Base(p) + " ---")
|
||||
for _, l := range lines {
|
||||
lcAppend(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,11 @@ func newWebMux() *http.ServeMux {
|
||||
api("/api/models/download", handleModelDownload)
|
||||
api("/api/models/download/status", handleModelDownloadStatus)
|
||||
api("/api/backends", handleBackends)
|
||||
api("/api/llamacpp", handleLlamacpp) // statut du backend llama.cpp
|
||||
api("/api/llamacpp/check", handleLlamacppCheck) // git fetch + retard sur origin
|
||||
api("/api/llamacpp/install", handleLlamacppInstall) // job : clone + build + BIN
|
||||
api("/api/llamacpp/update", handleLlamacppUpdate) // job : pull + rebuild + restart
|
||||
api("/api/llamacpp/job", handleLlamacppJob) // progression + logs du job
|
||||
api("/api/presets", handlePresets)
|
||||
api("/api/preset", handlePreset)
|
||||
api("/api/preset/save", handlePresetSave)
|
||||
|
||||
Reference in new issue
Block a user