feat(cookbook): autonome Modell-Entdeckung aus vertrauenswuerdigen Quellen
"Aktuell beste Modelle fuer dein System": fragt vertrauenswuerdige HF-Orgs (unsloth/bartowski/ggml-org/lmstudio-community) LIVE ab, kategorisiert die Treffer (vision/coder/reasoning/agent/scout), filtert per hw_math auf das, was auf die Hardware passt, und cached das Ergebnis (TTL 12 h, lazy + Knopf "Aktualisieren"). Damit bleibt das Cookbook von selbst aktuell, ohne dass Modelle hartkodiert werden. - sources.py: TRUSTED_AUTHORS + CATEGORIES + SKIP_TOKENS (reine Daten). - config.py: DISCOVER_CACHE_PATH (persistent neben den Modellen, uebersteht Deploys) + DISCOVER_TTL. - cookbook.py: /api/cookbook/discover (force-Param), refresh_discover, Bestandsabgleich (_model_installed -> "schon installiert als X") und ehrliche Voraussetzungen je Modell (Vision->mmproj/jinja, unquantisiert, zu gross/knapp). - cookbook.js: Sektion mit Kategorien, Fit-Ampel, Downloads, Hinweisen, 1-Klick-Installieren bzw. "installiert"-Markierung; "Aktualisieren"-Knopf. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,13 @@ function mount() {
|
||||
<div class="empty" style="grid-column:1/-1;text-align:center">Lade Setups…</div>
|
||||
</div>
|
||||
|
||||
<details class="guide-acc card" style="padding:0;overflow:hidden;margin-top:8px">
|
||||
<div class="card-h" style="align-items:center;margin-top:20px"><h3>Aktuell beste Modelle für dein System</h3>
|
||||
<span class="chip" id="cb-disc-when" style="margin-left:auto"></span>
|
||||
<button class="ghost" id="cb-disc-refresh" style="margin-left:10px">Aktualisieren</button></div>
|
||||
<div class="card-sub" style="margin:-6px 0 10px">Automatisch aus vertrauenswürdigen Quellen (HuggingFace) — laufend aktuell, gefiltert auf das, was auf deine Hardware passt.</div>
|
||||
<div id="cb-discover"><div class="empty" style="text-align:center">Lade aktuelle Empfehlungen…</div></div>
|
||||
|
||||
<details class="guide-acc card" style="padding:0;overflow:hidden;margin-top:18px">
|
||||
<summary>Profi-Modus: HuggingFace direkt durchsuchen</summary>
|
||||
<div class="acc-body">
|
||||
<div class="flex gap-3" style="margin-bottom:8px">
|
||||
@@ -135,8 +141,79 @@ function mount() {
|
||||
$("#cb-new-models").addEventListener("click", e => { const d = e.target.closest(".cb-nm-del"); if (d) d.closest(".cb-nm-row").remove(); });
|
||||
$("#cb-new-save").addEventListener("click", saveNewRecipe);
|
||||
|
||||
$("#cb-disc-refresh").addEventListener("click", () => loadDiscover(true));
|
||||
$("#cb-discover").addEventListener("click", e => {
|
||||
const b = e.target.closest("[data-inst]"); if (!b) return;
|
||||
installDiscovered(b.getAttribute("data-inst"), b.getAttribute("data-role"), b.getAttribute("data-pb"), b);
|
||||
});
|
||||
|
||||
renderHwChip();
|
||||
loadRecipes();
|
||||
loadDiscover();
|
||||
}
|
||||
|
||||
// ---- Automatische Modell-Entdeckung („aktuell beste Modelle") ----
|
||||
function fmtAgo(ts) {
|
||||
const s = Date.now() / 1000 - ts;
|
||||
if (s < 90) return "gerade eben";
|
||||
if (s < 3600) return `vor ${Math.round(s / 60)} min`;
|
||||
if (s < 86400) return `vor ${Math.round(s / 3600)} h`;
|
||||
return `vor ${Math.round(s / 86400)} Tg.`;
|
||||
}
|
||||
|
||||
async function loadDiscover(force = false) {
|
||||
const box = $("#cb-discover"), when = $("#cb-disc-when"), btn = $("#cb-disc-refresh");
|
||||
if (force) { btn.disabled = true; btn.textContent = "Suche…"; box.innerHTML = `<div class="empty" style="text-align:center">Frage Quellen ab…</div>`; }
|
||||
try {
|
||||
const d = await api("/api/cookbook/discover" + (force ? "?force=true" : ""));
|
||||
if (when) when.textContent = d.updated ? `aktualisiert ${fmtAgo(d.updated)}` : "";
|
||||
renderDiscover(d);
|
||||
} catch (e) {
|
||||
box.innerHTML = `<div class="alert warn" style="margin:0"><span class="a-dot"></span><span>Empfehlungen nicht ladbar: ${esc(e.message)}</span></div>`;
|
||||
}
|
||||
btn.disabled = false; btn.textContent = "Aktualisieren";
|
||||
}
|
||||
|
||||
function renderDiscover(d) {
|
||||
const box = $("#cb-discover");
|
||||
const cats = d.categories || [];
|
||||
if (!cats.length) { box.innerHTML = `<div class="empty" style="text-align:center">Keine passenden Modelle gefunden.</div>`; return; }
|
||||
box.innerHTML = cats.map(c => `
|
||||
<div style="margin-bottom:8px">
|
||||
<div class="flex items-center gap-2" style="margin:6px 0 8px"><span class="text-accent">${icon(c.icon)}</span>
|
||||
<h4 style="margin:0;font-size:13.5px;font-weight:600">${esc(c.title)}</h4></div>
|
||||
<div class="grid grid-3">${c.models.map(discCard).join("")}</div>
|
||||
</div>`).join("");
|
||||
}
|
||||
|
||||
function discCard(m) {
|
||||
const reqs = (m.requirements || []).map(r =>
|
||||
`<div class="li-sub" style="color:var(--warn);margin-top:3px">⚠ ${esc(r)}</div>`).join("");
|
||||
const action = m.installed
|
||||
? `<span class="fit-badge ok" title="bereits eingerichtet als ${esc(m.installed)}">✓ installiert</span>`
|
||||
: `<button class="primary" data-inst="${esc(m.repo)}" data-role="${esc(m.role)}" data-pb="${m.params_b}"
|
||||
style="padding:6px 12px;font-size:12.5px">Installieren</button>`;
|
||||
return `<div class="card" style="display:flex;flex-direction:column">
|
||||
<div class="flex justify-between" style="align-items:flex-start;gap:8px">
|
||||
<div style="min-width:0"><h3 style="margin:0;font-size:14px;font-weight:500;word-break:break-word">${esc(m.name)}</h3>
|
||||
<div class="text-xs text-mut" style="margin-top:3px">${esc(m.author)} · ${m.params_b}B</div></div>
|
||||
<span class="fit-badge ${fitCls(m.fit.level)}">${esc(m.fit.text)}</span>
|
||||
</div>
|
||||
<div class="mono-sm text-mut" style="margin-top:8px;font-size:11.5px">${metricLine(m.fit)} · ⬇ ${(m.downloads || 0).toLocaleString()}</div>
|
||||
${reqs}
|
||||
<div style="flex:1;min-height:8px"></div>
|
||||
<div class="flex justify-end" style="margin-top:10px">${action}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function installDiscovered(repo, role, params_b, btn) {
|
||||
btn.disabled = true; btn.textContent = "Starte…";
|
||||
try {
|
||||
await api("/api/cookbook/install-model", { method: "POST",
|
||||
body: JSON.stringify({ repo, role, params_b: parseFloat(params_b) || 7, quant: "Q4_K_M", hf_token: getHfToken() }) });
|
||||
toast("Download gestartet — siehe Aktivität.");
|
||||
document.querySelector(".nav-item[data-view='activity']")?.click();
|
||||
} catch (e) { toast("Fehler: " + e.message, true); btn.disabled = false; btn.textContent = "Installieren"; }
|
||||
}
|
||||
|
||||
// ---- Use-Case-Setups ----
|
||||
|
||||
Reference in New Issue
Block a user