diff --git a/internal/jean/ui/index.html b/internal/jean/ui/index.html index e0ac5e5..822f22e 100644 --- a/internal/jean/ui/index.html +++ b/internal/jean/ui/index.html @@ -326,6 +326,9 @@ button:disabled{opacity:.5;cursor:not-allowed}
GPU
…
+
RAM +
…
+
Config active
…
@@ -772,6 +775,16 @@ async function loadVram(){ '
'+(g.used/1024).toFixed(1)+' / '+(g.total/1024).toFixed(1)+' GiB · GPU '+g.util+'% · '+g.temp+'°C
'; }).join('') || '(pas de GPU)'; } +async function loadRam(){ + const m=await jget('/api/ram'); + const box=document.getElementById('ram-details'); + if(!m || !m.total){ if(box) box.style.display='none'; return; } + if(box) box.style.display=''; + const pct=Math.round(m.used*100/m.total); + document.getElementById('ram').innerHTML = + '
'+ + '
'+(m.used/1024).toFixed(1)+' / '+(m.total/1024).toFixed(1)+' GiB · '+pct+'%
'; +} async function loadCfg(){ const c=await jget('/api/config'); const keys=['BIN','MODEL','CTX','BATCH','UBATCH','NGL','PORT']; @@ -998,7 +1011,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(),loadLlamacpp()]); } +async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadRam(),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'; } @@ -1661,6 +1674,7 @@ async function send(){ loadAll(); setInterval(loadStatus, 5000); setInterval(loadVram, 3000); +setInterval(loadRam, 3000); // Backend llama.cpp — la barre latérale sert UNIQUEMENT à installer les moteurs. // ⚡ Version rapide = binaires officiels précompilés (aucune compilation) // 🔧 Version optimisée = compilée pour la machine diff --git a/internal/jean/ui/src/index.tmpl.html b/internal/jean/ui/src/index.tmpl.html index ea8a685..34c15a9 100644 --- a/internal/jean/ui/src/index.tmpl.html +++ b/internal/jean/ui/src/index.tmpl.html @@ -29,6 +29,9 @@
GPU
…
+
RAM +
…
+
Config active
…
diff --git a/internal/jean/ui/src/js/05-status.js b/internal/jean/ui/src/js/05-status.js index 52c3f9d..8dff798 100644 --- a/internal/jean/ui/src/js/05-status.js +++ b/internal/jean/ui/src/js/05-status.js @@ -64,6 +64,16 @@ async function loadVram(){ '
'+(g.used/1024).toFixed(1)+' / '+(g.total/1024).toFixed(1)+' GiB · GPU '+g.util+'% · '+g.temp+'°C
'; }).join('') || '(pas de GPU)'; } +async function loadRam(){ + const m=await jget('/api/ram'); + const box=document.getElementById('ram-details'); + if(!m || !m.total){ if(box) box.style.display='none'; return; } + if(box) box.style.display=''; + const pct=Math.round(m.used*100/m.total); + document.getElementById('ram').innerHTML = + '
'+ + '
'+(m.used/1024).toFixed(1)+' / '+(m.total/1024).toFixed(1)+' GiB · '+pct+'%
'; +} async function loadCfg(){ const c=await jget('/api/config'); const keys=['BIN','MODEL','CTX','BATCH','UBATCH','NGL','PORT']; diff --git a/internal/jean/ui/src/js/06-settings.js b/internal/jean/ui/src/js/06-settings.js index 4fd803e..5bd1dcf 100644 --- a/internal/jean/ui/src/js/06-settings.js +++ b/internal/jean/ui/src/js/06-settings.js @@ -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(),loadLlamacpp()]); } +async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadAgent(),loadInternet(),loadApiKey(),loadPrefs(),loadLlamacpp()]); } async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); } diff --git a/internal/jean/ui/src/js/09-stream.js b/internal/jean/ui/src/js/09-stream.js index 33be052..787df04 100644 --- a/internal/jean/ui/src/js/09-stream.js +++ b/internal/jean/ui/src/js/09-stream.js @@ -144,3 +144,4 @@ async function send(){ loadAll(); setInterval(loadStatus, 5000); setInterval(loadVram, 3000); +setInterval(loadRam, 3000); diff --git a/internal/jean/web_api.go b/internal/jean/web_api.go index 6eb3b25..c2d494e 100644 --- a/internal/jean/web_api.go +++ b/internal/jean/web_api.go @@ -70,6 +70,35 @@ func handleVram(w http.ResponseWriter, r *http.Request) { sendJSON(w, 200, gpus) } +// handleRam renvoie la RAM système {used, total} en Mo (mêmes unités que +// /api/vram → l'UI divise par 1024 pour des Gio). Lit /proc/meminfo (Linux, la +// box de prod). Sur un OS sans /proc, renvoie total=0 → l'UI masque le bloc. +func handleRam(w http.ResponseWriter, r *http.Request) { + out := map[string]any{"used": 0, "total": 0} + b, err := os.ReadFile("/proc/meminfo") + if err == nil { + var totalKB, availKB int + for _, line := range strings.Split(string(b), "\n") { + f := strings.Fields(line) + if len(f) < 2 { + continue + } + v, _ := strconv.Atoi(f[1]) // valeur en kB + switch f[0] { + case "MemTotal:": + totalKB = v + case "MemAvailable:": + availKB = v + } + } + if totalKB > 0 { + out["total"] = totalKB / 1024 + out["used"] = (totalKB - availKB) / 1024 + } + } + sendJSON(w, 200, out) +} + func handleConfigEnv(w http.ResponseWriter, r *http.Request) { sendJSON(w, 200, ReadConfig()) } diff --git a/internal/jean/web_server.go b/internal/jean/web_server.go index 2094c9b..badeb4f 100644 --- a/internal/jean/web_server.go +++ b/internal/jean/web_server.go @@ -79,6 +79,7 @@ func newWebMux() *http.ServeMux { api("/api/ping", handlePing) api("/api/status", handleStatus) api("/api/vram", handleVram) + api("/api/ram", handleRam) api("/api/config", handleConfigEnv) api("/api/catalog", handleCatalog) api("/api/update", handleUpdateCheck)