import { api } from "../core/api.js";
import { $ } from "../core/ui.js";
const COOKBOOK_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 viel Speicher.",
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"
}
];
// Odysseus Hardware Fit Logic
function estimateMemoryGB(params_b, quant, ctx) {
// Q4_K_M is roughly 0.6 bytes per param
const bpp = 0.6;
const weights = params_b * bpp;
// Context roughly: 1GB per 8K for 7B
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);
// Falls keine sys-Daten da sind (Backend mock), nimm Standardwerte an
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);
// Wenn gar keine echten Metriken kommen (Windows Dummy Backend), immer "Fits" für Demo
if (vram === 0 && ram === 0) return { level: "perfect", class: "green", text: "Fits (Mock)", req };
if (vram > 0 && req <= vram) return { level: "perfect", class: "green", text: "Fits VRAM", req };
if (req <= (vram + freeRam)) return { level: "good", class: "yellow", text: "RAM Offload", req };
return { level: "too_tight", class: "red", text: "OOM (Zu groß)", req };
}
let lastSys = null;
let dlPoll = null;
function mount() {
render();
}
function unmount() {
if (dlPoll) clearInterval(dlPoll);
}
function onSystem(sys) {
lastSys = sys;
renderGrid();
}
export default { mount, unmount, onSystem };
function render() {
const c = document.querySelector(".view[data-view='cookbook']");
c.innerHTML = `
`;
document.getElementById("cb-btn-dl").onclick = () => {
startDownload(
document.getElementById("cb-repo").value,
document.getElementById("cb-file").value,
"", 8192
);
};
document.getElementById("cb-btn-reg").onclick = async () => {
const alias = document.getElementById("cb-reg-alias").value;
const path = document.getElementById("cb-reg-path").value;
const ctx = document.getElementById("cb-reg-ctx").value;
if (!alias || !path) return;
try {
await api("/api/models/register", {
method: "POST",
body: JSON.stringify({ alias, model_path: path, ctx_len: parseInt(ctx)||4096 })
});
alert("Erfolgreich eingepflegt!");
document.getElementById("cb-register-box").style.display = "none";
} catch(e) {
alert("Fehler: " + e);
}
};
renderGrid();
}
function renderGrid() {
const grid = document.getElementById("cb-grid");
if (!grid) return;
grid.innerHTML = COOKBOOK_MODELS.map(m => {
const fit = getFit(m, lastSys);
return `
${m.name}
${fit.text}
${m.desc}
Größe: ~${fit.req.toFixed(1)} GB (inkl. Context)
Quant: ${m.quant}
`;
}).join("");
document.querySelectorAll(".cb-btn-preset").forEach(b => {
b.onclick = () => {
const m = COOKBOOK_MODELS.find(x => x.id === b.dataset.id);
if(m) {
document.getElementById("cb-repo").value = m.repo;
document.getElementById("cb-file").value = m.file;
startDownload(m.repo, m.file, m.alias, m.ctx);
}
};
});
}
async function startDownload(repo, file, autoAlias, autoCtx) {
if(!repo || !file) return;
document.getElementById("cb-dl-prog").style.display = "block";
document.getElementById("cb-dl-prog").innerText = "Starte Download...";
document.getElementById("cb-register-box").style.display = "none";
try {
const res = await api("/api/models/download", {
method: "POST",
body: JSON.stringify({ repo_id: repo, filename: file })
});
if (res.job_id) {
if(dlPoll) clearInterval(dlPoll);
dlPoll = setInterval(() => checkProg(res.job_id, autoAlias, autoCtx), 2000);
}
} catch(e) {
document.getElementById("cb-dl-prog").innerText = "Fehler: " + e;
}
}
async function checkProg(jobId, autoAlias, autoCtx) {
try {
const res = await api("/api/jobs");
const j = res.find(x => x.id === jobId);
if (!j) {
clearInterval(dlPoll);
document.getElementById("cb-dl-prog").innerText = "Job verschwunden.";
return;
}
document.getElementById("cb-dl-prog").innerText = j.status + (j.error ? (" - " + j.error) : "");
if (j.status === "done") {
clearInterval(dlPoll);
document.getElementById("cb-register-box").style.display = "block";
document.getElementById("cb-reg-path").value = j.result || "";
if(autoAlias) document.getElementById("cb-reg-alias").value = autoAlias;
if(autoCtx) document.getElementById("cb-reg-ctx").value = autoCtx;
} else if (j.status === "failed") {
clearInterval(dlPoll);
}
} catch(e) {}
}