v6 Phase B: News-Magazin-Layout + Guide-Neubau (Tutorials)
- news.js: Magazin-Karten-Layout (Quell-Chip, Featured-Beitrag, 'fuer dich'-Marker) statt Liste. - guides.js: praktische Tutorials/Workflows (erster Workflow, Kontext leeren, Agent noetig?, Auto-Swap, Tools optimal nutzen) + kompakte Konzept-Karten statt klobiger Akkordeons. - components.css: .news-grid/.news-card + .concept-grid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+61
-48
@@ -1,49 +1,57 @@
|
||||
// guides.js — Anfänger-Guide (v5): erklärt die Local-LLM-Welt idiotensicher.
|
||||
// Reine Erklär-Seite. Tool-Einrichtung/Configs liegen im Tab „Verbinden".
|
||||
// guides.js — Anfänger-Guide (v6): praktische Tutorials/Workflows + kompakte Konzepte.
|
||||
|
||||
import { $ } from "../core/ui.js";
|
||||
|
||||
const SECTIONS = [
|
||||
["Erste Schritte — in 3 Schritten loslegen", `
|
||||
<ol style="padding-left:18px;line-height:1.9">
|
||||
<li><b>Modell holen:</b> Im <b>Cookbook</b> auf „Wofür möchtest du es nutzen?" deinen Fall wählen
|
||||
(z.B. „Coden") → „Komplettes Setup installieren". Der Download läuft im Tab <b>Aktivität</b>.</li>
|
||||
<li><b>Verbinden:</b> Im Tab <b>Verbinden</b> die Basis-URL kopieren und in deinem Tool (z.B. OpenCode)
|
||||
als „OpenAI-kompatibel" eintragen. Mit „Verbindung testen" prüfen.</li>
|
||||
<li><b>Loslegen:</b> Im Tool das Modell (z.B. <code>coder</code>) auswählen und arbeiten. Die Engine
|
||||
lädt das passende Modell automatisch.</li>
|
||||
const TUTORIALS = [
|
||||
["Dein erster Workflow — in 3 Schritten", `
|
||||
<ol class="tut-steps">
|
||||
<li><b>Modell holen:</b> Tab <b>Cookbook</b> → wähle deinen Anwendungsfall (z.B. „Coden") →
|
||||
<b>Komplettes Setup installieren</b>. Der Download läuft im Tab <b>Aktivität</b>.</li>
|
||||
<li><b>Verbinden:</b> Tab <b>Verbinden</b> → Basis-URL kopieren → in deinem Tool (z.B. OpenCode)
|
||||
als „OpenAI-kompatibel" einfügen. <b>Verbindung testen</b> bestätigt, dass es läuft.</li>
|
||||
<li><b>Arbeiten:</b> Im Tool das Modell (z.B. <code>coder</code>) wählen und loslegen. Das passende
|
||||
Modell wird <b>automatisch geladen</b> — du musst nichts starten.</li>
|
||||
</ol>`],
|
||||
["Was ist ein LLM bzw. ein Modell?", `
|
||||
<p>Ein <b>LLM</b> (Large Language Model) ist ein KI-Sprachmodell — die „Intelligenz", die Text versteht
|
||||
und schreibt (wie ChatGPT, nur <b>lokal auf deinem Mini-PC</b>, ohne Cloud). Ein <b>Modell</b> ist eine
|
||||
konkrete Datei davon. Verschiedene Modelle sind unterschiedlich gut/schnell und auf Dinge spezialisiert
|
||||
(Coden, Bilder, Chat).</p>`],
|
||||
["Was ist GGUF?", `
|
||||
<p><b>GGUF</b> ist das Datei<b>format</b>, in dem lokale Modelle gespeichert werden — gemacht, damit sie
|
||||
effizient auf normaler Hardware laufen. Unsere Engine (llama-swap/llama.cpp) lädt <b>nur GGUF</b>.
|
||||
Steht im Cookbook „keine GGUF", hat dieses HuggingFace-Repo schlicht keine passende Datei — dann ein
|
||||
anderes Repo nehmen (oft gibt's „…-GGUF"-Varianten).</p>`],
|
||||
["Quantisierung (z.B. Q4_K_M)?", `
|
||||
<p>Modelle sind riesig. <b>Quantisierung</b> 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. <b>Q4_K_M</b> ist der beliebte Sweetspot aus Größe und Qualität. Höhere Zahl (Q6, Q8)
|
||||
= etwas besser, aber größer/langsamer.</p>`],
|
||||
["Kontextfenster — das Gedächtnis", `
|
||||
<p>Das <b>Kontextfenster</b> ist das Kurzzeitgedächtnis: wie viel Text (Tokens) sich das Modell
|
||||
<i>gleichzeitig</i> merken kann. <b>Größer</b> = es überblickt längere Dateien/Gespräche, braucht aber
|
||||
<b>mehr Speicher</b> und wird etwas langsamer. Fürs Coden (viele Dateien) hilft groß; für kurzen Chat
|
||||
reicht klein. Mission Control rechnet dir das <b>Optimum für deine Hardware</b> aus.</p>`],
|
||||
["Swapping — warum nur ein Modell läuft", `
|
||||
<p>llama-swap lädt <b>immer nur ein Modell gleichzeitig</b> 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 <b>größte</b> Einzelmodell, <b>nicht</b> die Summe.</p>`],
|
||||
["MCP, Skills & Agenten", `
|
||||
<p><b>Agent:</b> Ein Programm, das das Modell <i>selbstständig</i> Aufgaben erledigen lässt — Dateien
|
||||
lesen/schreiben, Befehle ausführen, mehrere Schritte planen (z.B. OpenCode, Cline).</p>
|
||||
<p><b>MCP (Model Context Protocol):</b> Ein Standard, über den ein Agent dem Modell <b>zusätzliche
|
||||
Werkzeuge</b> 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.</p>
|
||||
<p><b>Skills:</b> Vorgefertigte Fähigkeiten/Anleitungen, die ein Agent für wiederkehrende Aufgaben nutzt.</p>`],
|
||||
["Wie leere ich den Kontext? Machen die Tools das selbst?", `
|
||||
<p><b>Kurz: meistens ja, automatisch.</b> Der Kontext (das „Gedächtnis") gilt pro Unterhaltung.
|
||||
Startest du im Tool einen <b>neuen Chat / eine neue Session</b>, ist der Kontext wieder leer —
|
||||
das Tool verwaltet das Fenster für dich.</p>
|
||||
<p>Du musst nur selten eingreifen: wird ein Gespräch sehr lang, fang einen neuen an. <b>Wichtig:</b>
|
||||
„Grafikspeicher leeren" (Tab Server) ist etwas anderes — das wirft das <i>Modell</i> aus dem Speicher,
|
||||
nicht den Gesprächsverlauf.</p>`],
|
||||
["Brauche ich überhaupt einen Agenten?", `
|
||||
<p>Kommt drauf an, was du willst:</p>
|
||||
<ul class="tut-steps" style="list-style:disc">
|
||||
<li><b>Nur chatten / Fragen stellen:</b> Nein — eine Chat-Oberfläche wie <b>OpenWebUI</b> reicht.</li>
|
||||
<li><b>Coden, wo die KI selbst Dateien liest/ändert und Befehle ausführt:</b> Ja — dafür gibt es
|
||||
<b>Agenten</b> wie OpenCode oder Cline.</li>
|
||||
</ul>
|
||||
<p>Das Gute: Diese Agenten <b>laufen lokal auf deinem PC</b> und reden mit deinem lokalen Modell.
|
||||
Du brauchst <b>keinen</b> Cloud-Agenten — die „empfohlenen Tools" SIND die Agenten.</p>`],
|
||||
["Auto-Swap — wie das Laden funktioniert", `
|
||||
<p>llama-swap lädt automatisch das Modell, das gerade angefragt wird, und entlädt es nach einer
|
||||
Leerlaufzeit wieder (oder wenn du ein anderes anfragst). <b>Es läuft immer nur eines gleichzeitig.</b></p>
|
||||
<p>Heißt für dich: Du richtest beliebig viele Modelle ein und wählst im Tool einfach das passende —
|
||||
das Laden/Entladen passiert im Hintergrund, ohne dass du etwas tun musst.</p>`],
|
||||
["Die empfohlenen Tools optimal nutzen", `
|
||||
<ul class="tut-steps" style="list-style:disc">
|
||||
<li><b>OpenCode:</b> Autonomes Coden im Terminal/Editor — ideal für „bau mir Feature X".</li>
|
||||
<li><b>Cline:</b> Dasselbe als VS-Code-Erweiterung, mit Bestätigung vor jeder Aktion.</li>
|
||||
<li><b>OpenWebUI:</b> Klassische Chat-Oberfläche für Fragen/Texte.</li>
|
||||
</ul>
|
||||
<p><b>Tipp:</b> starkes Modell (<code>coder</code>) für schwere Aufgaben, schnelles
|
||||
(<code>coder-fast</code>) für simple — du wählst es einfach im Tool aus.</p>`],
|
||||
];
|
||||
|
||||
const CONCEPTS = [
|
||||
["LLM / Modell", "Die KI selbst — versteht und schreibt Text (wie ChatGPT, nur lokal auf deinem PC). Ein Modell ist eine konkrete Datei davon, oft auf etwas spezialisiert (Code, Bilder, Chat)."],
|
||||
["GGUF", "Das lokal lauffähige Dateiformat. Unsere Engine lädt nur GGUF — Repos ohne GGUF kann sie nicht nutzen."],
|
||||
["Quantisierung (Q4_K_M)", "Verkleinert ein Modell (16-Bit → 4-Bit), damit es in den Speicher passt — bei kaum Qualitätsverlust. Q4_K_M ist der beliebte Sweetspot."],
|
||||
["Kontextfenster", "Das Kurzzeitgedächtnis: wie viel Text sich das Modell gleichzeitig merkt. Größer = mehr Überblick, mehr Speicher, etwas langsamer."],
|
||||
["Auto-Swap", "llama-swap lädt/entlädt Modelle automatisch — immer nur eins gleichzeitig. Du managst nichts."],
|
||||
["Agent", "Ein Programm (OpenCode, Cline), das die KI selbstständig Aufgaben erledigen lässt: Dateien lesen/schreiben, Befehle ausführen, planen."],
|
||||
["MCP", "Model Context Protocol — gibt einem Agenten zusätzliche Werkzeuge (Web durchsuchen, Dateien, Tools). Macht aus dem Textmodell einen echten Helfer."],
|
||||
["Skills", "Vorgefertigte Fähigkeiten/Anleitungen, die ein Agent für wiederkehrende Aufgaben nutzt."],
|
||||
];
|
||||
|
||||
function mount() {
|
||||
@@ -51,13 +59,18 @@ function mount() {
|
||||
c.innerHTML = `
|
||||
<div class="pagehead"><div>
|
||||
<h1>Guide für Einsteiger</h1>
|
||||
<div class="sub">Die Local-LLM-Welt in Klartext — ohne Vorwissen verständlich.</div></div></div>
|
||||
<div class="sub">Praktische Workflows + die Local-LLM-Welt in Klartext — ohne Vorwissen.</div></div></div>
|
||||
|
||||
<div class="card-h"><h3>Tutorials & Workflows</h3></div>
|
||||
<div class="card" style="padding:0;overflow:hidden">
|
||||
${SECTIONS.map(([title, body], i) => `
|
||||
<details class="guide-acc"${i === 0 ? " open" : ""}>
|
||||
<summary>${title}</summary>
|
||||
<div class="acc-body">${body}</div>
|
||||
</details>`).join("")}
|
||||
${TUTORIALS.map(([t, b], i) => `
|
||||
<details class="guide-acc"${i === 0 ? " open" : ""}><summary>${t}</summary>
|
||||
<div class="acc-body">${b}</div></details>`).join("")}
|
||||
</div>
|
||||
|
||||
<div class="card-h" style="margin-top:8px"><h3>Konzepte kurz erklärt</h3></div>
|
||||
<div class="concept-grid">
|
||||
${CONCEPTS.map(([term, text]) => `<div class="concept"><h4>${term}</h4><p>${text}</p></div>`).join("")}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
+15
-27
@@ -1,4 +1,4 @@
|
||||
// news.js — News-Board (v5): kuratierte News + „Für dich: Upgrades" (Modell-Tausch-Vorschläge).
|
||||
// news.js — News-Board (v6): Magazin-Layout + „Für dich: Upgrades".
|
||||
|
||||
import { api, getHfToken } from "../core/api.js";
|
||||
import { $, esc, ago, toast } from "../core/ui.js";
|
||||
@@ -10,13 +10,11 @@ function mount() {
|
||||
$(".view[data-view='news']").innerHTML = `
|
||||
<div class="pagehead">
|
||||
<div><h1>News</h1>
|
||||
<div class="sub">Neuigkeiten rund um lokale LLMs — aus kuratierten Quellen (HuggingFace, r/LocalLLaMA, Latent Space, Ahead of AI …).</div></div>
|
||||
<div class="sub">Aktuelles aus der Local-LLM-Welt — aus kuratierten Quellen (HuggingFace, r/LocalLLaMA, Latent Space, Ahead of AI …).</div></div>
|
||||
<button class="ghost" id="news-refresh">Aktualisieren</button>
|
||||
</div>
|
||||
<div id="upgrades"></div>
|
||||
<div class="card"><div id="news-list">
|
||||
<div class="empty" style="text-align:center;padding:40px">Lade News…</div>
|
||||
</div></div>`;
|
||||
<div id="news-list"><div class="empty" style="text-align:center;padding:40px">Lade News…</div></div>`;
|
||||
$("#news-refresh").addEventListener("click", () => { load(); loadUpgrades(); });
|
||||
load();
|
||||
loadUpgrades();
|
||||
@@ -34,21 +32,18 @@ function upRow(u, i) {
|
||||
<button class="primary" data-up="${i}">Installieren</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function loadUpgrades() {
|
||||
const el = $("#upgrades"); if (!el) return;
|
||||
try {
|
||||
const d = await api("/api/cookbook/upgrades");
|
||||
UPS = d.upgrades || [];
|
||||
UPS = (await api("/api/cookbook/upgrades")).upgrades || [];
|
||||
if (!UPS.length) { el.innerHTML = ""; return; }
|
||||
el.innerHTML = `<div class="card" style="margin-bottom:16px">
|
||||
el.innerHTML = `<div class="card" style="margin-bottom:18px;border-color:rgba(45,212,191,.25)">
|
||||
<div class="card-h"><h3>Für dich: Upgrades</h3><span class="meta">${UPS.length}</span></div>
|
||||
<div class="card-sub">Neuere Modelle, die ein installiertes ersetzen können — Begründung inklusive.</div>
|
||||
<div class="card-sub">Neuere Modelle, die ein installiertes ersetzen können — mit Begründung.</div>
|
||||
${UPS.map(upRow).join("")}</div>`;
|
||||
el.querySelectorAll("[data-up]").forEach(b => b.addEventListener("click", () => installUpgrade(+b.getAttribute("data-up"), b)));
|
||||
} catch { el.innerHTML = ""; }
|
||||
}
|
||||
|
||||
async function installUpgrade(i, btn) {
|
||||
const u = UPS[i]; if (!u) return;
|
||||
btn.disabled = true; btn.textContent = "Starte…";
|
||||
@@ -60,32 +55,25 @@ async function installUpgrade(i, btn) {
|
||||
} catch (e) { toast("Fehler: " + e.message, true); btn.disabled = false; btn.textContent = "Installieren"; }
|
||||
}
|
||||
|
||||
// ---- News ----
|
||||
function card(n) {
|
||||
// ---- News (Magazin-Layout) ----
|
||||
function newsCard(n, featured) {
|
||||
const date = n.ts ? ago(n.ts) : "";
|
||||
const rel = n.relevant ? ' · <span class="text-accent">für deine Hardware</span>' : "";
|
||||
return `<a class="li" href="${esc(n.link)}" target="_blank" rel="noopener" style="text-decoration:none;color:inherit">
|
||||
<div class="li-main">
|
||||
<div style="font-size:14px;line-height:1.4">${esc(n.title)}</div>
|
||||
<div class="li-sub">${esc(n.source)}${rel}</div>
|
||||
</div>
|
||||
<div class="li-right"><div class="li-time">${esc(date)}</div></div>
|
||||
const rel = n.relevant ? `<span class="news-rel">für dich</span>` : "";
|
||||
return `<a href="${esc(n.link)}" target="_blank" rel="noopener" class="news-card${featured ? " featured" : ""}">
|
||||
<div class="news-meta"><span class="news-src">${esc(n.source)}</span>${rel}<span class="news-date">${esc(date)}</span></div>
|
||||
<div class="news-title">${esc(n.title)}</div>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const el = $("#news-list"); if (!el) return;
|
||||
el.innerHTML = `<div class="empty" style="text-align:center;padding:40px">Lade News… (erstes Laden kann kurz dauern)</div>`;
|
||||
try {
|
||||
const d = await api("/api/news");
|
||||
const items = d.items || [];
|
||||
const items = (await api("/api/news")).items || [];
|
||||
el.innerHTML = items.length
|
||||
? `<div class="list">${items.map(card).join("")}</div>`
|
||||
? `<div class="news-grid">${items.map((n, i) => newsCard(n, i === 0)).join("")}</div>`
|
||||
: `<div class="empty-c"><div class="e-t">Keine News geladen</div>
|
||||
<div class="e-s">Die Quellen sind gerade nicht erreichbar — später nochmal probieren.</div></div>`;
|
||||
} catch (e) {
|
||||
el.innerHTML = `<div class="alert err" style="margin:0">${esc(e.message)}</div>`;
|
||||
}
|
||||
} catch (e) { el.innerHTML = `<div class="alert err" style="margin:0">${esc(e.message)}</div>`; }
|
||||
}
|
||||
|
||||
export default { id: "news", mount };
|
||||
|
||||
Reference in New Issue
Block a user