From 216b95d15c4b935f8996dbba966ddf029564aef1 Mon Sep 17 00:00:00 2001 From: jaakko Date: Tue, 7 Apr 2026 07:08:34 +0300 Subject: [PATCH] =?UTF-8?q?kpn=20load:=20laitteiston=20VRAM/RAM=20tarkistu?= =?UTF-8?q?s,=20liian=20isot=20mallit=20merkit=C3=A4=C3=A4n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hub: uusi GET /api/v1/hardware palauttaa natiivisolmun GPU/RAM-tiedot. Frontend: kpn load hakee laitteistotiedon ja näyttää mallit joihin laite riittää. Liian isot mallit näkyvät yliviivattuina + varoitus. Co-Authored-By: Claude Opus 4.6 (1M context) --- network-poc/hub/src/main.rs | 28 ++++++++++++++++++++++ network-poc/static/index.html | 44 ++++++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/network-poc/hub/src/main.rs b/network-poc/hub/src/main.rs index 0a2cf9b..61132fa 100644 --- a/network-poc/hub/src/main.rs +++ b/network-poc/hub/src/main.rs @@ -385,6 +385,7 @@ async fn main() { .route("/api/stats", get(api_stats)) .route("/api/v1/chat/completions", axum::routing::post(api_chat_completions)) .route("/api/v1/model", axum::routing::post(api_change_model)) + .route("/api/v1/hardware", get(api_hardware)) .route("/admin", get(admin_page)) .nest_service("/", { let static_dir = std::env::var("STATIC_DIR").unwrap_or_else(|_| "../static".to_string()); @@ -959,6 +960,33 @@ struct ChatCompletionResponse { tokens_generated: u64, } +async fn api_hardware( + axum::extract::State(state): axum::extract::State>, +) -> axum::response::Response { + // Etsitään natiivisolmun GPU-tiedot sessiosta + let sessions = state.db.get_sessions(50); + let native = sessions.iter().find(|s| { + s.get("node_type").and_then(|v| v.as_str()) == Some("native") + }); + + let (vram_mb, gpu_name, ram_mb) = if let Some(s) = native { + let gpus = s.get("gpus").and_then(|v| v.as_array()); + let gpu = gpus.and_then(|g| g.first()); + let vram = gpu.and_then(|g| g.get("vram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0); + let name = gpu.and_then(|g| g.get("name")).and_then(|v| v.as_str()).unwrap_or("?"); + let ram = s.get("system").and_then(|v| v.get("ram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0); + (vram, name.to_string(), ram) + } else { + (0, "ei natiivisolmua".to_string(), 0) + }; + + axum::Json(serde_json::json!({ + "gpu_name": gpu_name, + "vram_mb": vram_mb, + "ram_mb": ram_mb, + })).into_response() +} + async fn api_change_model( axum::extract::State(state): axum::extract::State>, axum::Json(payload): axum::Json, diff --git a/network-poc/static/index.html b/network-poc/static/index.html index e612526..dba2e1b 100644 --- a/network-poc/static/index.html +++ b/network-poc/static/index.html @@ -2373,19 +2373,41 @@ Files: ${Object.keys(generatedFiles).join(', ')}`; if (sub === 'load') { const arg = parts[2]; const ollamaModels = [ - { id: '1', name: 'qwen2.5-coder:0.5b', size: '~400 MB', type: 'selain + Ollama' }, - { id: '2', name: 'qwen2.5-coder:1.5b', size: '~1 GB', type: 'Ollama GPU' }, - { id: '3', name: 'qwen2.5-coder:7b', size: '~4.7 GB', type: 'Ollama GPU', default: true }, - { id: '4', name: 'qwen2.5-coder:14b', size: '~9 GB', type: 'Ollama GPU' }, - { id: '5', name: 'qwen2.5-coder:32b', size: '~20 GB', type: 'Ollama GPU' }, + { id: '1', name: 'qwen2.5-coder:0.5b', size: '~400 MB', vram_mb: 0, type: 'selain + Ollama' }, + { id: '2', name: 'qwen2.5-coder:1.5b', size: '~1 GB', vram_mb: 1500, type: 'Ollama GPU' }, + { id: '3', name: 'qwen2.5-coder:7b', size: '~4.7 GB', vram_mb: 5500, type: 'Ollama GPU', default: true }, + { id: '4', name: 'qwen2.5-coder:14b', size: '~9 GB', vram_mb: 10000, type: 'Ollama GPU' }, + { id: '5', name: 'qwen2.5-coder:32b', size: '~20 GB', vram_mb: 21000, type: 'Ollama GPU' }, ]; if (!arg) { - termLog(' Mallit:', '#c9d1d9'); - for (const m of ollamaModels) { - const active = m.default ? ' ← aktiivinen' : ''; - termLog(` ${m.id} ${m.name} ${m.size} | ${m.type}${active}`); - } - termLog(' Käyttö: kpn load <numero>', '#8b949e'); + // Haetaan laitteistotiedot ja näytetään sopivat mallit + fetch('/api/v1/hardware').then(r => r.json()).then(hw => { + const vram = hw.vram_mb || 0; + const ram = hw.ram_mb || 0; + const gpu = hw.gpu_name || '?'; + const available = vram || ram; // CPU-fallback käyttää RAM:ia + if (vram > 0) { + termLog(` GPU: ${gpu} | VRAM: ${Math.round(vram/1024)} GB | RAM: ${Math.round(ram/1024)} GB`); + } else if (ram > 0) { + termLog(` Ei GPU:ta | RAM: ${Math.round(ram/1024)} GB (CPU-moodi)`); + } + termLog(' Mallit:', '#c9d1d9'); + for (const m of ollamaModels) { + const fits = m.vram_mb === 0 || m.vram_mb < available; + const active = m.default ? ' ← aktiivinen' : ''; + const icon = fits ? `${m.id}` : `${m.id}`; + const warn = !fits ? ' ⚠ ei mahdu' : ''; + termLog(` ${icon} ${fits ? '' : ''}${m.name} ${m.size} | ${m.type}${fits ? '' : ''}${active}${warn}`); + } + termLog(' Käyttö: kpn load <numero>', '#8b949e'); + }).catch(() => { + termLog(' Mallit:', '#c9d1d9'); + for (const m of ollamaModels) { + const active = m.default ? ' ← aktiivinen' : ''; + termLog(` ${m.id} ${m.name} ${m.size} | ${m.type}${active}`); + } + termLog(' Käyttö: kpn load <numero>', '#8b949e'); + }); return; } const selected = ollamaModels.find(m => m.id === arg || m.name === arg);