import { api } from "../core/api.js"; import { $, esc, icon, toast } from "../core/ui.js"; import { track } from "./jobs.js"; 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 ca. 24GB VRAM.", params_b: 32, quant: "Q4_K_M", ctx: 32768, alias: "coder" }, { id: "qwen-coder-7b", name: "Qwen 2.5 Coder 7B", 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" }, { id: "llama3-vision-11b", name: "Llama 3.2 Vision 11B", 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" }, { id: "qwen-general-7b", name: "Qwen 2.5 7B", 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" } ]; function estimateMemoryGB(params_b, quant, ctx) { const bpp = 0.6; const weights = params_b * bpp; 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); 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 freeRam = (ram_bytes - ram_used) / (1024 ** 3); 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 currentResults = []; function mount() { 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

`; $("#cb-btn-search").addEventListener("click", doSearch); $("#cb-search").addEventListener("keydown", e => { if (e.key === "Enter") doSearch(); }); $("#cb-modal-close").addEventListener("click", () => $("#cb-modal").style.display = "none"); $("#cb-m-download").addEventListener("click", doDownload); $("#cb-m-files").addEventListener("change", updateLiveFit); $("#cb-m-ctx").addEventListener("input", updateLiveFit); renderCurated(); } function extractParamsB(name) { const moe = name.match(/(\d+)x(\d+(?:\.\d+)?)[bB]/i); if (moe) return parseInt(moe[1]) * parseFloat(moe[2]); const m = name.match(/(\d+(?:\.\d+)?)[bB](?![a-zA-Z])/i); if (m) return parseFloat(m[1]); return 7; // Fallback } function extractQuant(filename) { const m = filename.match(/(Q\d_[A-Z0-9_]+|IQ\d_[A-Z0-9_]+|FP16|BF16)/i); return m ? m[1].toUpperCase() : "Q4_K_M"; } function updateLiveFit() { const repo = $("#cb-m-repo").textContent; const file = $("#cb-m-files").value; const ctx = parseInt($("#cb-m-ctx").value) || 8192; if (!repo || !file) { $("#cb-m-fit-container").style.display = "none"; return; } const params_b = extractParamsB(repo); const quant = extractQuant(file); const m = { params_b, quant, ctx }; const fit = getFit(m, lastSys); $("#cb-m-fit-container").style.display = "flex"; $("#cb-m-fit-text").innerHTML = `Geschätzter Bedarf: ~${fit.req.toFixed(1)} GB RAM/VRAM
${params_b}B Params · ${quant}`; $("#cb-m-fit-badge").innerHTML = `${fit.text}`; } async function doSearch() { const q = $("#cb-search").value.trim(); if (!q) return renderCurated(); const btn = $("#cb-btn-search"); btn.disabled = true; btn.textContent = "Lade..."; $("#cb-section-title").textContent = "Suchergebnisse für: " + esc(q); $("#cb-grid").innerHTML = `
Suche auf HuggingFace...
`; try { const url = `https://huggingface.co/api/models?search=${encodeURIComponent(q)}&filter=gguf&sort=downloads&direction=-1&limit=12`; const r = await fetch(url); const data = await r.json(); currentResults = data; renderResults(data); } catch (e) { $("#cb-grid").innerHTML = `
${esc(e.message)}
`; } btn.disabled = false; btn.textContent = "Suchen"; } function renderResults(results) { const grid = $("#cb-grid"); if (!results || results.length === 0) { grid.innerHTML = `
Keine GGUF-Modelle gefunden.
`; return; } grid.innerHTML = results.map((m, i) => `

${esc(m.id.split('/').pop())}

${esc(m.author)}
GGUF ⬇ ${(m.downloads||0).toLocaleString()}
`).join(""); } // Global hook for inline onclick window.openModelModal = async (index) => { const m = currentResults[index]; if (!m) return; $("#cb-modal").style.display = "flex"; $("#cb-m-title").textContent = m.id.split('/').pop(); $("#cb-m-repo").textContent = m.id; $("#cb-m-files").innerHTML = ""; $("#cb-m-alias").value = m.id.split('/').pop().toLowerCase().replace(/[^a-z0-9]/g, "-"); $("#cb-m-files").style.display = "none"; $("#cb-m-loading").style.display = "block"; try { const url = `https://huggingface.co/api/models/${m.id}/tree/main`; const r = await fetch(url); const tree = await r.json(); const files = tree.filter(f => f.path.endsWith('.gguf')).map(f => f.path); $("#cb-m-loading").style.display = "none"; $("#cb-m-files").style.display = "block"; if (files.length === 0) { $("#cb-m-files").innerHTML = ""; $("#cb-m-download").disabled = true; $("#cb-m-fit-container").style.display = "none"; } else { $("#cb-m-files").innerHTML = files.map(f => ``).join(""); $("#cb-m-download").disabled = false; updateLiveFit(); } } catch(e) { $("#cb-m-loading").textContent = "Fehler beim Laden der Dateien."; } }; async function doDownload() { const repo = $("#cb-m-repo").textContent; const file = $("#cb-m-files").value; const alias = $("#cb-m-alias").value.trim(); const ctx = parseInt($("#cb-m-ctx").value) || 8192; if (!repo || !file || !alias) return toast("Bitte alle Felder ausfüllen.", true); $("#cb-m-download").disabled = true; $("#cb-m-download").textContent = "Starte..."; try { const res = await api("/api/download", { method: "POST", body: JSON.stringify({ repo, file }) }); // Register directly, jobengine will download it, MC will pick it up when done await api("/api/register", { method: "POST", body: JSON.stringify({ alias, model_path: res.expected_path, ctx }) }); toast("Download gestartet! Siehe Aktivitäten."); $("#cb-modal").style.display = "none"; track(res.job_id); // open in jobs // Switch to activity tab document.querySelector(".nav-item[data-view='activity']").click(); } catch (e) { toast("Fehler: " + e.message, true); } $("#cb-m-download").disabled = false; $("#cb-m-download").textContent = "Herunterladen & Einpflegen"; } function renderCurated() { $("#cb-section-title").textContent = "Kuratierte Empfehlungen"; const grid = $("#cb-grid"); if (!grid) return; grid.innerHTML = CURATED_MODELS.map((m, i) => { const fit = getFit(m, lastSys); return `

${esc(m.name)}

${fit.text}
${m.desc}
~${fit.req.toFixed(1)} GB RAM ${m.quant}
`; }).join(""); } window.openCuratedModal = (index) => { const m = CURATED_MODELS[index]; if (!m) return; $("#cb-modal").style.display = "flex"; $("#cb-m-title").textContent = m.name; $("#cb-m-repo").textContent = m.repo; $("#cb-m-files").innerHTML = ``; $("#cb-m-files").style.display = "block"; $("#cb-m-loading").style.display = "none"; $("#cb-m-alias").value = m.alias; $("#cb-m-ctx").value = m.ctx; $("#cb-m-download").disabled = false; updateLiveFit(); }; function onSystem(sys) { lastSys = sys; if (!$("#cb-search").value.trim()) renderCurated(); } function unmount() {} export default { id: "cookbook", mount, unmount, onSystem };