From e3be7fbfb5504ecfa66f7b0c3c3ee046b8f4ff8f Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Sat, 20 Jun 2026 22:06:04 +0200 Subject: [PATCH] refactor: massive UX and beginner refactoring (Cookbook, Dashboard, Layout, Wording) --- routers/models.py | 5 +- static/css/base.css | 2 +- static/index.html | 11 +- static/js/core/ui.js | 3 + static/js/main.js | 11 +- static/js/panels/cookbook.js | 360 ++++++++++++++++++++--------------- static/js/panels/guides.js | 49 +++-- static/js/panels/jobs.js | 103 ++++++++-- static/js/panels/models.js | 17 +- static/js/panels/overview.js | 70 +------ static/js/panels/server.js | 4 +- 11 files changed, 359 insertions(+), 276 deletions(-) diff --git a/routers/models.py b/routers/models.py index d198ebf..990485d 100644 --- a/routers/models.py +++ b/routers/models.py @@ -61,11 +61,13 @@ def status(): size_bytes = None quant = "" + filename = "" m_path = re.search(r'-(?:m|-model)\s+([^\s]+)', cmd) if m_path: path = m_path.group(1).replace("'", "").replace('"', '') if os.path.exists(path): size_bytes = os.path.getsize(path) + filename = os.path.basename(path) q_match = re.search(r'(Q\d_[A-Z0-9_]+|IQ\d_[A-Z0-9_]+|fp16|bf16)\.gguf', path, flags=re.IGNORECASE) if q_match: quant = q_match.group(1).upper() @@ -86,7 +88,8 @@ def status(): "ctx": ctx, "size_bytes": size_bytes, "quant": quant, - "caps": caps + "caps": caps, + "filename": filename } } swap_ok = True diff --git a/static/css/base.css b/static/css/base.css index 01fdaac..2e82699 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -90,7 +90,7 @@ a{color:var(--act);text-decoration:none} .tokin:focus{outline:none;border-color:var(--act)} /* ---- Content-Bereich ---- */ -.content{padding:22px 26px 64px;max-width:1500px;width:100%} +.content{padding:22px 26px 64px;max-width:1300px;margin:0 auto;width:100%} .view[hidden]{display:none} .view{display:flex;flex-direction:column;gap:18px} diff --git a/static/index.html b/static/index.html index 6f727f0..c453ea4 100644 --- a/static/index.html +++ b/static/index.html @@ -31,7 +31,8 @@
verbinde… - Modelle + Aktiv: + Modelle Jobs0 --:-- @@ -45,14 +46,12 @@
-
-
-
-
-
+
diff --git a/static/js/core/ui.js b/static/js/core/ui.js index 3e3f1f2..e6f41a8 100644 --- a/static/js/core/ui.js +++ b/static/js/core/ui.js @@ -54,6 +54,9 @@ export const ICON = { layers: _svg(''), alert: _svg(''), gauge: _svg(''), + search: _svg(''), + code: _svg(''), + eye: _svg(''), }; export function icon(name) { return ICON[name] || ""; } diff --git a/static/js/main.js b/static/js/main.js index 1cfa7a6..126bfc1 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -25,14 +25,19 @@ function applyStatus(s) { dot.className = "dot off"; label.textContent = "Backend nicht erreichbar"; $("#top-models").textContent = "–"; - showAlert("Backend nicht erreichbar — läuft uvicorn?", false); + $("#top-active").textContent = "–"; + showAlert("Backend nicht erreichbar – läuft uvicorn?", false); } else { const host = s.swap_url.replace(/^https?:\/\//, ""); dot.className = "dot " + (s.swap_ok ? "on" : "off"); - label.textContent = (s.swap_ok ? "llama-swap online · " : "llama-swap offline · ") + host; + label.textContent = (s.swap_ok ? "LLM-Engine: Online – " : "LLM-Engine: Offline – ") + host; $("#top-models").textContent = (s.models || []).length; + + const active = (s.models || []).find(m => m.state === "running"); + $("#top-active").textContent = active ? active.name : "–"; + if (s.swap_ok) hideAlert(); - else showAlert(`llama-swap nicht erreichbar unter ${host} — läuft der Dienst?`, true); + else showAlert(`LLM-Engine nicht erreichbar unter ${host} – läuft der llama-swap Dienst?`, true); } for (const p of panels) p.onStatus?.(s); } diff --git a/static/js/panels/cookbook.js b/static/js/panels/cookbook.js index d4844b9..6ce2568 100644 --- a/static/js/panels/cookbook.js +++ b/static/js/panels/cookbook.js @@ -1,17 +1,15 @@ import { api } from "../core/api.js"; -import { $ } from "../core/ui.js"; +import { $, esc, icon, toast } from "../core/ui.js"; +import { track } from "./jobs.js"; -const COOKBOOK_MODELS = [ +const CURATED_MODELS = [ { id: "qwen-coder-32b", name: "Qwen 2.5 Coder 32B", repo: "unsloth/Qwen2.5-Coder-32B-Instruct-GGUF", file: "Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf", - desc: "Top-Tier lokales Coder-Modell. Braucht viel Speicher.", - params_b: 32, - quant: "Q4_K_M", - ctx: 32768, - alias: "coder" + desc: "Top-Tier lokales Coder-Modell. Braucht ca. 24GB VRAM.", + params_b: 32, quant: "Q4_K_M", ctx: 32768, alias: "coder" }, { id: "qwen-coder-7b", @@ -19,10 +17,7 @@ const COOKBOOK_MODELS = [ repo: "unsloth/Qwen2.5-Coder-7B-Instruct-GGUF", file: "Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf", desc: "Schneller Coder. Perfekte Balance aus Speed und Qualität.", - params_b: 7, - quant: "Q4_K_M", - ctx: 32768, - alias: "coder-fast" + params_b: 7, quant: "Q4_K_M", ctx: 32768, alias: "coder-fast" }, { id: "llama3-vision-11b", @@ -30,10 +25,7 @@ const COOKBOOK_MODELS = [ repo: "unsloth/Llama-3.2-11B-Vision-Instruct-GGUF", file: "Llama-3.2-11B-Vision-Instruct-Q4_K_M.gguf", desc: "Modell für Bilderkennung und multimodale Tasks.", - params_b: 11, - quant: "Q4_K_M", - ctx: 8192, - alias: "vision" + params_b: 11, quant: "Q4_K_M", ctx: 8192, alias: "vision" }, { id: "qwen-general-7b", @@ -41,185 +33,245 @@ const COOKBOOK_MODELS = [ repo: "unsloth/Qwen2.5-7B-Instruct-GGUF", file: "Qwen2.5-7B-Instruct-Q4_K_M.gguf", desc: "Hervorragendes Generalist/Scout Modell.", - params_b: 7, - quant: "Q4_K_M", - ctx: 8192, - alias: "scout" + params_b: 7, quant: "Q4_K_M", ctx: 8192, alias: "scout" } ]; -// Odysseus Hardware Fit Logic function estimateMemoryGB(params_b, quant, ctx) { - // Q4_K_M is roughly 0.6 bytes per param const bpp = 0.6; const weights = params_b * bpp; - // Context roughly: 1GB per 8K for 7B const context = (ctx / 8192) * (params_b / 7) * 0.8; return weights + context; } function getFit(m, sys) { const req = estimateMemoryGB(m.params_b, m.quant, m.ctx); - // Falls keine sys-Daten da sind (Backend mock), nimm Standardwerte an const vram_bytes = sys?.gpu?.vram?.total || 0; const vram = vram_bytes / (1024 ** 3); const ram_bytes = sys?.ram?.total || 0; const ram_used = sys?.ram?.used || 0; - const ram = (ram_bytes) / (1024 ** 3); + const ram = ram_bytes / (1024 ** 3); const freeRam = (ram_bytes - ram_used) / (1024 ** 3); - // Wenn gar keine echten Metriken kommen (Windows Dummy Backend), immer "Fits" für Demo - if (vram === 0 && ram === 0) return { level: "perfect", class: "green", text: "Fits (Mock)", req }; - - if (vram > 0 && req <= vram) return { level: "perfect", class: "green", text: "Fits VRAM", req }; - if (req <= (vram + freeRam)) return { level: "good", class: "yellow", text: "RAM Offload", req }; - return { level: "too_tight", class: "red", text: "OOM (Zu groß)", req }; + if (vram === 0 && ram === 0) return { level: "perfect", class: "b-run", text: "Fits (Mock)", req }; + if (vram > 0 && req <= vram) return { level: "perfect", class: "b-run", text: "Fits VRAM", req }; + if (req <= (vram + freeRam)) return { level: "good", class: "b-load", text: "RAM Offload", req }; + return { level: "too_tight", class: "b-err", text: "OOM (Zu groß)", req }; } let lastSys = null; -let dlPoll = null; +let currentResults = []; function mount() { - render(); -} - -function unmount() { - if (dlPoll) clearInterval(dlPoll); -} - -function onSystem(sys) { - lastSys = sys; - renderGrid(); -} - -export default { mount, unmount, onSystem }; - -function render() { - const c = document.querySelector(".view[data-view='cookbook']"); + const c = $(".view[data-view='cookbook']"); c.innerHTML = ` +
+
+

HuggingFace Suche

+

Finde GGUF-Modelle direkt auf HuggingFace und lade sie in Mission Control herunter.

+ +
+ + +
+
+
+ +

Kuratierte Empfehlungen

-
-

Custom Download

-
- - - -
- -