226 lines
7.9 KiB
JavaScript
226 lines
7.9 KiB
JavaScript
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 = `
|
|
<div class="grid grid-3" id="cb-grid"></div>
|
|
|
|
<div class="card" style="margin-top: 24px;">
|
|
<div class="card-h"><h3>Custom Download</h3></div>
|
|
<div style="display:flex; gap:12px; margin-bottom:12px;">
|
|
<input id="cb-repo" class="tokin" placeholder="HuggingFace Repo (z.B. unsloth/Qwen2.5-Coder-7B-Instruct-GGUF)" style="flex:2">
|
|
<input id="cb-file" class="tokin" placeholder="Dateipfad (z.B. Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf)" style="flex:1">
|
|
<button class="btn" id="cb-btn-dl">Herunterladen</button>
|
|
</div>
|
|
<div id="cb-dl-prog" style="display:none; color:var(--text-dim); font-size:12px; margin-bottom:12px;"></div>
|
|
<div id="cb-register-box" style="display:none; padding-top:12px; border-top:1px solid var(--border);">
|
|
<div style="margin-bottom:8px;">Modell heruntergeladen! Jetzt einpflegen:</div>
|
|
<div style="display:flex; gap:12px;">
|
|
<input id="cb-reg-alias" class="tokin" placeholder="Alias (z.B. coder)" style="flex:1">
|
|
<input id="cb-reg-path" class="tokin" placeholder="Modell-Pfad" style="flex:2" readonly>
|
|
<input id="cb-reg-ctx" class="tokin" placeholder="Ctx (z.B. 8192)" type="number" style="width:100px;">
|
|
<button class="btn" id="cb-btn-reg">Einpflegen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 `
|
|
<div class="card" style="display:flex; flex-direction:column;">
|
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
|
<h3 style="margin:0">${m.name}</h3>
|
|
<span style="font-size:11px; padding:2px 6px; border-radius:4px; background:${fit.class === 'green' ? 'rgba(0,255,0,0.1)' : fit.class === 'yellow' ? 'rgba(255,255,0,0.1)' : 'rgba(255,0,0,0.1)'}; color:${fit.class === 'green' ? '#4ade80' : fit.class === 'yellow' ? '#fde047' : '#f87171'};">
|
|
${fit.text}
|
|
</span>
|
|
</div>
|
|
<div style="font-size:12px; color:var(--text-dim); margin-top:8px; flex:1;">
|
|
${m.desc}<br><br>
|
|
<b>Größe:</b> ~${fit.req.toFixed(1)} GB (inkl. Context)<br>
|
|
<b>Quant:</b> ${m.quant}
|
|
</div>
|
|
<button class="btn cb-btn-preset" data-id="${m.id}" style="margin-top:16px; width:100%;">Herunterladen</button>
|
|
</div>
|
|
`;
|
|
}).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) {}
|
|
}
|