diff --git a/routers/models.py b/routers/models.py index 11fb5d6..364d908 100644 --- a/routers/models.py +++ b/routers/models.py @@ -16,7 +16,7 @@ from auth import auth from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL, MODELS_DIR, TOKEN from jobengine import JOBS, start_job from llamaswap import _swap_get, read_config, write_config -from hw_math import extract_params_b, max_ctx_for +from hw_math import extract_params_b, max_ctx_for, estimate_memory_gb import re import os import psutil @@ -101,8 +101,10 @@ def status(): "quant": quant, "caps": caps, "filename": filename, - "params_b": extract_params_b(filename or name), - "optimal_ctx": (max_ctx_for(extract_params_b(filename or name), quant or "Q4_K_M", ram_gb) if ram_gb else None), + "params_b": (_pb := extract_params_b(filename or name)), + "optimal_ctx": (_oc := (max_ctx_for(_pb, quant or "Q4_K_M", ram_gb) if ram_gb else None)), + "peak_ram_gb": round(estimate_memory_gb(_pb, quant or "Q4_K_M", ctx), 1), + "peak_ram_optimal_gb": (round(estimate_memory_gb(_pb, quant or "Q4_K_M", _oc), 1) if _oc else None), } } swap_ok = True diff --git a/static/css/components.css b/static/css/components.css index 2ee3737..4cb8189 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -145,6 +145,9 @@ input:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)} textarea{resize:vertical;min-height:64px;font-family:var(--sans)} .row{display:flex;gap:10px}.row>div{flex:1} .hint{font-size:12px;color:var(--mut);margin:-4px 0 12px} +.info-dot{display:inline-flex;vertical-align:-3px;color:var(--dim);cursor:help;margin-left:5px} +.info-dot svg{width:14px;height:14px} +.info-dot:hover{color:var(--accent)} .mono-sm{font-family:var(--mono);font-size:11.5px;color:var(--mut)} /* ---- Buttons ---- */ diff --git a/static/js/core/ui.js b/static/js/core/ui.js index f673c47..e1d30bd 100644 --- a/static/js/core/ui.js +++ b/static/js/core/ui.js @@ -74,6 +74,11 @@ export const ICON = { export function icon(name) { return ICON[name] || ""; } +// kleines Info-ⓘ mit Klartext-Tooltip (native title) — für Erklärungen direkt am Feld. +export function infoDot(text) { + return `${ICON.info}`; +} + // --------------------------------------------------------------------------- // Format-Helfer (Klartext-Zahlen) // --------------------------------------------------------------------------- diff --git a/static/js/panels/cookbook.js b/static/js/panels/cookbook.js index 7ad8e7c..2fc828c 100644 --- a/static/js/panels/cookbook.js +++ b/static/js/panels/cookbook.js @@ -2,7 +2,9 @@ // Backend: /api/cookbook/{recipes,install-recipe,analyze,evaluate} (hw_math). import { api } from "../core/api.js"; -import { $, esc, icon, toast } from "../core/ui.js"; +import { $, esc, icon, toast, infoDot } from "../core/ui.js"; + +const CTX_HELP = "Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr, braucht aber mehr Speicher und wird etwas langsamer."; const FILTERS = [ { id: "", label: "Alle" }, { id: "coder", label: "Coder" }, { id: "scout", label: "Scout" }, @@ -63,7 +65,8 @@ function mount() {

Setup

- +
+
Lädt alle Modelle herunter und pflegt sie mit optimalem Kontext ein. Fortschritt in der Aktivität.
@@ -78,7 +81,7 @@ function mount() {
-
+
@@ -147,6 +150,9 @@ function openRecipe(id) {
${esc(m.fit.text)} `).join("")}`; + const maxRam = Math.max(...r.models.map(m => m.fit.req_gb)); + $("#cb-r-note").innerHTML = `Größter Spitzenbedarf: ~${maxRam.toFixed(1)} GB. ` + + `Es läuft immer nur ein Modell gleichzeitig — das größte bestimmt, ob das Setup passt (kein Summieren).`; const btn = $("#cb-r-install"); btn.className = r.fit_level === "too_tight" ? "primary warn" : "primary"; btn.textContent = r.fit_level === "too_tight" ? "Trotzdem installieren (zu groß)" : "Komplettes Setup installieren"; diff --git a/static/js/panels/guides.js b/static/js/panels/guides.js index a09b350..378042b 100644 --- a/static/js/panels/guides.js +++ b/static/js/panels/guides.js @@ -1,106 +1,64 @@ -// guides.js — Integrations-Anleitungen (v3): Copy-Paste-Configs für externe Tools. +// guides.js — Anfänger-Guide (v5): erklärt die Local-LLM-Welt idiotensicher. +// Reine Erklär-Seite. Tool-Einrichtung/Configs liegen im Tab „Verbinden". -import { $, esc } from "../core/ui.js"; +import { $ } from "../core/ui.js"; -// In die Zwischenablage kopieren (kopiert das vorhergehende Eingabefeld / Codeblock). -window.mcCopy = (btn, sel) => { - const src = sel ? btn.parentElement.querySelector(sel) : btn.previousElementSibling; - const text = src.value !== undefined ? src.value : src.textContent; - navigator.clipboard?.writeText(text); - const old = btn.textContent; btn.textContent = "Kopiert!"; - setTimeout(() => (btn.textContent = old), 1200); -}; - -// Basis-URL aus dem Browser ableiten (zeigt die echte Bosgame-Adresse statt localhost). -function apiBase() { - const host = location.hostname || "localhost"; - return `${location.protocol}//${host}:8080/v1`; -} - -// Beschriftetes Feld mit Kopier-Button. -function field(label, value) { - return ` -
- - -
`; -} - -function render() { - const c = $(".view[data-view='guides']"); - if (!c) return; - const url = apiBase(); - const py = `from openai import OpenAI - -client = OpenAI( - base_url="${url}", - api_key="dein_token" # optional -) - -resp = client.chat.completions.create( - model="coder", # Alias aus dem Cookbook - messages=[{"role": "user", "content": "Hallo Modell!"}] -) -print(resp.choices[0].message.content)`; +const SECTIONS = [ + ["Erste Schritte — in 3 Schritten loslegen", ` +
    +
  1. Modell holen: Im Cookbook auf „Wofür möchtest du es nutzen?" deinen Fall wählen + (z.B. „Coden") → „Komplettes Setup installieren". Der Download läuft im Tab Aktivität.
  2. +
  3. Verbinden: Im Tab Verbinden die Basis-URL kopieren und in deinem Tool (z.B. OpenCode) + als „OpenAI-kompatibel" eintragen. Mit „Verbindung testen" prüfen.
  4. +
  5. Loslegen: Im Tool das Modell (z.B. coder) auswählen und arbeiten. Die Engine + lädt das passende Modell automatisch.
  6. +
`], + ["Was ist ein LLM bzw. ein Modell?", ` +

Ein LLM (Large Language Model) ist ein KI-Sprachmodell — die „Intelligenz", die Text versteht + und schreibt (wie ChatGPT, nur lokal auf deinem Mini-PC, ohne Cloud). Ein Modell ist eine + konkrete Datei davon. Verschiedene Modelle sind unterschiedlich gut/schnell und auf Dinge spezialisiert + (Coden, Bilder, Chat).

`], + ["Was ist GGUF?", ` +

GGUF ist das Dateiformat, in dem lokale Modelle gespeichert werden — gemacht, damit sie + effizient auf normaler Hardware laufen. Unsere Engine (llama-swap/llama.cpp) lädt nur GGUF. + Steht im Cookbook „keine GGUF", hat dieses HuggingFace-Repo schlicht keine passende Datei — dann ein + anderes Repo nehmen (oft gibt's „…-GGUF"-Varianten).

`], + ["Quantisierung (z.B. Q4_K_M)?", ` +

Modelle sind riesig. Quantisierung verkleinert sie, indem die Zahlen gröber gespeichert werden + (16-Bit → 4-Bit) — sie passen dann in den Speicher und laufen schneller, bei kaum spürbarem + Qualitätsverlust. Q4_K_M ist der beliebte Sweetspot aus Größe und Qualität. Höhere Zahl (Q6, Q8) + = etwas besser, aber größer/langsamer.

`], + ["Kontextfenster — das Gedächtnis", ` +

Das Kontextfenster ist das Kurzzeitgedächtnis: wie viel Text (Tokens) sich das Modell + gleichzeitig merken kann. Größer = es überblickt längere Dateien/Gespräche, braucht aber + mehr Speicher und wird etwas langsamer. Fürs Coden (viele Dateien) hilft groß; für kurzen Chat + reicht klein. Mission Control rechnet dir das Optimum für deine Hardware aus.

`], + ["Swapping — warum nur ein Modell läuft", ` +

llama-swap lädt immer nur ein Modell gleichzeitig und tauscht es bei Bedarf automatisch aus. + Vorteil: Du kannst viele Modelle einrichten, ohne dass sie sich den Speicher teilen müssen. + Heißt auch: Der Speicherbedarf deines Setups = das größte Einzelmodell, nicht die Summe.

`], + ["MCP, Skills & Agenten", ` +

Agent: Ein Programm, das das Modell selbstständig Aufgaben erledigen lässt — Dateien + lesen/schreiben, Befehle ausführen, mehrere Schritte planen (z.B. OpenCode, Cline).

+

MCP (Model Context Protocol): Ein Standard, über den ein Agent dem Modell zusätzliche + Werkzeuge gibt — z.B. „durchsuche das Web", „lies meine Datenbank", „steuere Tool X". So wird aus + einem reinen Textmodell ein nützlicher Helfer mit Zugriff auf echte Tools.

+

Skills: Vorgefertigte Fähigkeiten/Anleitungen, die ein Agent für wiederkehrende Aufgaben nutzt.

`], +]; +function mount() { + const c = $(".view[data-view='guides']"); if (!c) return; c.innerHTML = `
-

Guides & Integrationen

-
Binde deine lokalen Modelle in andere Tools ein — fertige Configs zum Kopieren.
- +

Guide für Einsteiger

+
Die Local-LLM-Welt in Klartext — ohne Vorwissen verständlich.
-
- Cline / Cursor -
-

Nutze deine lokalen Modelle kostenlos in Cursor oder Cline (Provider: „OpenAI Compatible").

- ${field("Modell-ID", "coder")} - ${field("Basis-URL (OpenAI-kompatibel)", url)} - ${field("API-Key", "(dein Token oder leer lassen)")} -
-
- -
- OpenWebUI -
-

Settings → Admin → Connections → neue OpenAI-Verbindung:

- ${field("Basis-URL", url)} - ${field("API-Key", "dummy-key")} -

OpenWebUI erkennt automatisch, wenn ein Modell getauscht wird.

-
-
- -
- Python / LangChain (openai-SDK) -
-

Mit dem offiziellen openai-Paket:

-
- -
${esc(py)}
-
-
-
- -
- n8n (AI-Agent-Nodes) -
-

„OpenAI Chat Model"-Node → Credentials → Custom URL:

- ${field("Basis-URL", url)} -

Modell-ID „coder" eintragen und loslegen.

-
-
- -
- Begriffe einfach erklärt (Glossar) -
-

LLM-Engine (llama-swap): Der Dienst im Hintergrund, der die Sprachmodelle lädt und eine OpenAI-kompatible Schnittstelle bereitstellt.

-

VRAM / Grafikspeicher: Der schnelle Speicher, in dem ein Modell laufen muss. Große Modelle brauchen viel davon.

-

Quantisierung (z.B. Q4_K_M): Verkleinert ein Modell (16-Bit → 4-Bit), damit es in den Speicher passt — bei kaum Qualitätsverlust.

-

Kontext-Größe: Wie viel Text sich das Modell gleichzeitig „merken" kann. Mehr Kontext = deutlich mehr Speicherbedarf.

-
-
+ ${SECTIONS.map(([title, body], i) => ` +
+ ${title} +
${body}
+
`).join("")}
`; } -function mount() { render(); } - export default { id: "guides", mount }; diff --git a/static/js/panels/models.js b/static/js/panels/models.js index 747a0fc..e5f63dd 100644 --- a/static/js/panels/models.js +++ b/static/js/panels/models.js @@ -1,7 +1,9 @@ // models.js — "Modelle"-Ansicht (v3): Schnelltest-Chat, Modell-Tabelle, Kontext-Konfiguration. import { api } from "../core/api.js"; -import { $, badge, esc, toast, confirmModal } from "../core/ui.js"; +import { $, badge, esc, toast, confirmModal, infoDot } from "../core/ui.js"; + +const CTX_HELP = "Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr (längere Dateien/Chats), braucht aber mehr Speicher und wird etwas langsamer."; let ALL = []; function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); } @@ -53,14 +55,14 @@ function mount() {

Modell konfigurieren

- + -
Höhere Werte erlauben längere Texte, brauchen aber mehr Grafikspeicher.
- `; @@ -104,9 +106,10 @@ function openConfig(alias) { $("#cfg-ctx").value = m.meta?.ctx || 8192; const opt = m.meta?.optimal_ctx, rec = $("#cfg-rec"); if (opt) { - const cur = m.meta?.ctx || 0; - $("#cfg-rec-note").textContent = - `Bis ~${Math.round(opt / 1024)}k passt komfortabel. Aktuell: ~${Math.round(cur / 1024)}k.`; + const cur = m.meta?.ctx || 0, peak = m.meta?.peak_ram_gb, peakOpt = m.meta?.peak_ram_optimal_gb; + $("#cfg-rec-note").innerHTML = + `Optimal ~${Math.round(opt / 1024)}k → ~${peakOpt} GB Spitzenbedarf.
` + + `Aktuell ~${Math.round(cur / 1024)}k → ~${peak} GB.`; $("#cfg-rec-apply").onclick = () => { $("#cfg-ctx").value = opt; }; rec.style.display = "flex"; } else { rec.style.display = "none"; }