Feinschliff Phase 2: Dashboard Redesign, RAM Check, Accordions

This commit is contained in:
Hitonabi
2026-06-20 22:30:40 +02:00
parent e3be7fbfb5
commit 8b76adc96e
10 changed files with 358 additions and 119 deletions
+67 -19
View File
@@ -104,6 +104,14 @@ function mount() {
</div>
</div>
<div id="cb-m-fit-container" style="margin-top:24px; padding:16px; background:var(--bg); border:1px solid var(--line); border-radius:8px; display:flex; justify-content:space-between; align-items:center;">
<div>
<div style="font-weight:600; font-size:14px; margin-bottom:4px;">Ressourcen-Check</div>
<div class="meta" id="cb-m-fit-text">Berechne...</div>
</div>
<div id="cb-m-fit-badge"></div>
</div>
<button class="primary" id="cb-m-download" style="width:100%; margin-top:24px; padding:12px">Herunterladen & Einpflegen</button>
</div>
</div>
@@ -113,10 +121,47 @@ function mount() {
$("#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: <b>~${fit.req.toFixed(1)} GB RAM/VRAM</b> <br><small class="meta">${params_b}B Params · ${quant}</small>`;
$("#cb-m-fit-badge").innerHTML = `<span class="badge ${fit.class}">${fit.text}</span>`;
}
async function doSearch() {
const q = $("#cb-search").value.trim();
if (!q) return renderCurated();
@@ -127,13 +172,13 @@ async function doSearch() {
$("#cb-grid").innerHTML = `<div class="meta" style="grid-column:1/-1; text-align:center; padding:40px">Suche auf HuggingFace...</div>`;
try {
const url = \`https://huggingface.co/api/models?search=\${encodeURIComponent(q)}&filter=gguf&sort=downloads&direction=-1&limit=12\`;
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 = \`<div class="alert err" style="grid-column:1/-1">\${esc(e.message)}</div>\`;
$("#cb-grid").innerHTML = `<div class="alert err" style="grid-column:1/-1">${esc(e.message)}</div>`;
}
btn.disabled = false; btn.textContent = "Suchen";
}
@@ -145,17 +190,17 @@ function renderResults(results) {
return;
}
grid.innerHTML = results.map((m, i) => \`
<div class="card" style="display:flex; flex-direction:column; cursor:pointer" onclick="window.openModelModal(\${i})">
<h3 style="margin:0; font-size:16px; word-break:break-all">\${esc(m.id.split('/').pop())}</h3>
<div class="meta" style="font-size:12px; margin-top:4px">\${esc(m.author)}</div>
grid.innerHTML = results.map((m, i) => `
<div class="card" style="display:flex; flex-direction:column; cursor:pointer" onclick="window.openModelModal(${i})">
<h3 style="margin:0; font-size:16px; word-break:break-all">${esc(m.id.split('/').pop())}</h3>
<div class="meta" style="font-size:12px; margin-top:4px">${esc(m.author)}</div>
<div style="flex:1; margin-top:16px;"></div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:16px">
<span class="badge b-idle" style="font-size:11px">GGUF</span>
<span class="meta" style="font-size:12px">⬇ \${(m.downloads||0).toLocaleString()}</span>
<span class="meta" style="font-size:12px">⬇ ${(m.downloads||0).toLocaleString()}</span>
</div>
</div>
\`).join("");
`).join("");
}
// Global hook for inline onclick
@@ -172,7 +217,7 @@ window.openModelModal = async (index) => {
$("#cb-m-loading").style.display = "block";
try {
const url = \`https://huggingface.co/api/models/\${m.id}/tree/main\`;
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);
@@ -183,9 +228,11 @@ window.openModelModal = async (index) => {
if (files.length === 0) {
$("#cb-m-files").innerHTML = "<option value=''>Keine GGUF-Dateien im Hauptverzeichnis gefunden.</option>";
$("#cb-m-download").disabled = true;
$("#cb-m-fit-container").style.display = "none";
} else {
$("#cb-m-files").innerHTML = files.map(f => \`<option value="\${esc(f)}">\${esc(f)}</option>\`).join("");
$("#cb-m-files").innerHTML = files.map(f => `<option value="${esc(f)}">${esc(f)}</option>`).join("");
$("#cb-m-download").disabled = false;
updateLiveFit();
}
} catch(e) {
$("#cb-m-loading").textContent = "Fehler beim Laden der Dateien.";
@@ -235,21 +282,21 @@ function renderCurated() {
if (!grid) return;
grid.innerHTML = CURATED_MODELS.map((m, i) => {
const fit = getFit(m, lastSys);
return \`
<div class="card" style="display:flex; flex-direction:column; cursor:pointer" onclick="window.openCuratedModal(\${i})">
return `
<div class="card" style="display:flex; flex-direction:column; cursor:pointer" onclick="window.openCuratedModal(${i})">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0; font-size:16px">\${esc(m.name)}</h3>
<span class="badge \${fit.class}">\${fit.text}</span>
<h3 style="margin:0; font-size:16px">${esc(m.name)}</h3>
<span class="badge ${fit.class}">${fit.text}</span>
</div>
<div style="font-size:13px; color:var(--mut); margin-top:12px; flex:1; line-height:1.5;">
\${m.desc}
${m.desc}
</div>
<div style="display:flex; justify-content:space-between; margin-top:16px; font-size:12px" class="meta">
<span>~\${fit.req.toFixed(1)} GB RAM</span>
<span>\${m.quant}</span>
<span>~${fit.req.toFixed(1)} GB RAM</span>
<span>${m.quant}</span>
</div>
</div>
\`;
`;
}).join("");
}
@@ -259,12 +306,13 @@ window.openCuratedModal = (index) => {
$("#cb-modal").style.display = "flex";
$("#cb-m-title").textContent = m.name;
$("#cb-m-repo").textContent = m.repo;
$("#cb-m-files").innerHTML = \`<option value="\${esc(m.file)}">\${esc(m.file)}</option>\`;
$("#cb-m-files").innerHTML = `<option value="${esc(m.file)}">${esc(m.file)}</option>`;
$("#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) {