v5 Phase 2: aktuelle Modelle, GGUF-Klartext, Tools, HF-Token

- recipes.py: Juni-2026-Modelle (Qwen3-Coder-30B-A3B, Qwen3-8B/30B, Qwen2.5-VL-7B, Qwen3-4B);
  nur Repo gespeichert, GGUF-Datei wird beim Installieren dynamisch aufgeloest (_pick_gguf) +
  UPGRADES-Map fuer Phase 3.
- cookbook: 'kein GGUF' neutral statt rot + GGUF-Erklaerung (infoDot); ctx-infoDot.
- connect.js: 'Empfohlene Tools (Juni 2026)' (OpenCode/Cline/Continue) + MCP-Hinweis.
- HF-Token in Einstellungen -> als HF_TOKEN an Downloads/Recipe-Install durchgereicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-21 14:13:30 +02:00
parent 8e7a5425d4
commit 970e04af30
8 changed files with 113 additions and 55 deletions
+5
View File
@@ -100,6 +100,11 @@
<input id="token" class="tokin" placeholder="Nur nötig, wenn der Server geschützt ist…" autocomplete="off">
<div class="hint mt-2">Schützt den Zugriff. Wird für API-Aufrufe und Live-Verbindungen mitgeschickt.</div>
</div>
<div class="mt-4">
<label>HuggingFace-Token (optional)</label>
<input id="hf-token" class="tokin" placeholder="hf_…" autocomplete="off">
<div class="hint mt-2">Nicht nötig für öffentliche Modelle — vermeidet aber Rate-Limits und erlaubt zugangsbeschränkte Modelle. Erstellen: huggingface.co/settings/tokens</div>
</div>
</div>
</div>
+4
View File
@@ -16,6 +16,10 @@ export function hdr() {
return h;
}
// HuggingFace-Token (optional) — wird bei Downloads als HF_TOKEN mitgesendet.
export function getHfToken() { return localStorage.getItem("mc_hf_token") || ""; }
export function setHfToken(t) { localStorage.setItem("mc_hf_token", (t || "").trim()); }
export async function api(path, opts = {}) {
const r = await fetch(path, { headers: hdr(), ...opts });
const data = await r.json().catch(() => ({}));
+4 -1
View File
@@ -1,7 +1,7 @@
// main.js — App-Boot: Panels mounten, Nav starten, Topbar/Alert pflegen, Polling fahren.
// Panel-Vertrag: { id, mount?(), onStatus?(s), onJobs?(jobs) }.
import { api, getToken, setToken } from "./core/api.js";
import { api, getToken, setToken, getHfToken, setHfToken } from "./core/api.js";
import { $ } from "./core/ui.js";
import { initNav } from "./core/nav.js";
@@ -112,6 +112,9 @@ function bootToken() {
i.addEventListener("change", e => { setToken(e.target.value); pollStatus(); });
}
const hf = $("#hf-token");
if (hf) { hf.value = getHfToken(); hf.addEventListener("change", e => setHfToken(e.target.value)); }
const sbtn = $("#nav-settings");
const smod = $("#settings-modal");
const scls = $("#sm-close");
+19 -1
View File
@@ -30,6 +30,15 @@ function field(label, value) {
}
const okChip = txt => `<span class="chip" style="background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a">${icon("check")}${esc(txt)}</span>`;
function toolRow(name, desc, url, badge) {
return `<div class="li" style="align-items:flex-start">
<div class="li-main">
<div class="flex items-center gap-2"><span class="li-id" style="font-family:var(--sans);font-weight:500">${esc(name)}</span>${badge ? `<span class="fit-badge ok">${esc(badge)}</span>` : ""}</div>
<div class="li-sub">${esc(desc)}</div></div>
<a href="${esc(url)}" target="_blank" rel="noopener"><button class="ghost">Öffnen</button></a>
</div>`;
}
function render() {
const c = $(".view[data-view='connect']"); if (!c) return;
const url = baseUrl();
@@ -56,9 +65,18 @@ function render() {
<div id="cn-vision"></div>
</div>
<div class="card">
<div class="card-h"><h3>Empfohlene Tools (Juni 2026)</h3></div>
<div class="card-sub">Diese verbinden sich nahtlos mit deinem Stack. Einrichtungs-Configs findest du darunter.</div>
${toolRow("OpenCode", "Beliebtester Open-Source-Coding-Agent (MIT) — Terminal, Desktop & IDE.", "https://opencode.ai", "Empfohlen")}
${toolRow("Cline", "Bestes Erlebnis in VS Code, Bring-Your-Own-Key, Mensch-im-Loop.", "https://cline.bot")}
${toolRow("Continue.dev", "Feinkörnige Kontrolle + bestes MCP-Ökosystem für Werkzeuge.", "https://continue.dev")}
<div class="hint" style="margin-top:12px">MCP gibt Agenten zusätzliche Werkzeuge (Web durchsuchen, Dateien, Tools). Continue & Cline unterstützen MCP — mehr dazu im Guide.</div>
</div>
<div class="card" style="padding:0;overflow:hidden">
<details class="guide-acc" open>
<summary>OpenCode (Windows)</summary>
<summary>OpenCode (Windows) — Config</summary>
<div class="acc-body">
<p>Lege in OpenCode einen OpenAI-kompatiblen Provider an:</p>
${field("Basis-URL", url)}
+6 -4
View File
@@ -1,10 +1,11 @@
// cookbook.js — Cookbook 2.0 (v4): Use-Case-Setups („Wofür?") + Profi-Suche.
// Backend: /api/cookbook/{recipes,install-recipe,analyze,evaluate} (hw_math).
import { api } from "../core/api.js";
import { api, getHfToken } from "../core/api.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 GGUF_HELP = "GGUF ist das lokal lauffähige Dateiformat. Unsere Engine lädt nur GGUF — Repos ohne GGUF-Datei kann sie nicht nutzen.";
const FILTERS = [
{ id: "", label: "Alle" }, { id: "coder", label: "Coder" }, { id: "scout", label: "Scout" },
@@ -55,6 +56,7 @@ function mount() {
<span><i style="background:var(--warn)"></i>läuft, aber knapp</span>
<span><i style="background:var(--err)"></i>zu groß für deinen Speicher</span>
</div>
<div class="hint" style="margin:-6px 0 14px">Es werden nur Modelle im GGUF-Format ${infoDot(GGUF_HELP)} gezeigt — nur die laufen lokal.</div>
<div class="grid grid-3" id="cb-grid"></div>
</div>
</details>
@@ -163,7 +165,7 @@ function openRecipe(id) {
async function installRecipe(id) {
const btn = $("#cb-r-install"); btn.disabled = true; btn.textContent = "Starte…";
try {
const r = await api("/api/cookbook/install-recipe", { method: "POST", body: JSON.stringify({ recipe_id: id }) });
const r = await api("/api/cookbook/install-recipe", { method: "POST", body: JSON.stringify({ recipe_id: id, hf_token: getHfToken() }) });
toast(`Setup wird installiert (${r.count} Modelle) — siehe Aktivität.`);
$("#cb-recipe-modal").style.display = "none";
document.querySelector(".nav-item[data-view='activity']")?.click();
@@ -226,7 +228,7 @@ async function fetchFitForCard(i, repo_id) {
const res = await api("/api/cookbook/analyze", { method: "POST", body: JSON.stringify({ repo_id, ctx: 8192 }) });
const b = $("#cb-b-" + i), mt = $("#cb-m-" + i);
if (!b || !mt) return;
if (!res.files?.length) { b.className = "fit-badge bad"; b.textContent = "keine GGUFs"; mt.textContent = "—"; return; }
if (!res.files?.length) { b.className = "fit-badge"; b.style.cssText = "background:rgba(139,151,165,.14);color:var(--mut)"; b.title = GGUF_HELP; b.textContent = "kein GGUF"; mt.textContent = "—"; return; }
let best = res.files.find(f => f.quant?.includes("Q4_K_M")) || res.files[0];
b.className = "fit-badge " + fitCls(best.fit.level); b.textContent = best.fit.text;
mt.textContent = metricLine(best.fit) + " · " + (best.quant || "GGUF");
@@ -289,7 +291,7 @@ async function doDownload() {
if (!repo || !file || !alias) return toast("Bitte alle Felder ausfüllen.", true);
const btn = $("#cb-m-download"); btn.disabled = true; btn.textContent = "Starte…";
try {
const res = await api("/api/download", { method: "POST", body: JSON.stringify({ repo, file }) });
const res = await api("/api/download", { method: "POST", body: JSON.stringify({ repo, file, hf_token: getHfToken() }) });
await api("/api/register", { method: "POST", body: JSON.stringify({ alias, model_path: res.expected_path, ctx }) });
toast("Download gestartet — siehe Aktivität.");
$("#cb-modal").style.display = "none";