diff --git a/diagnostic/index.html b/diagnostic/index.html index 198622e..3f557a5 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -9836,6 +9836,10 @@ + (m.tools ? ' 🔧' : '') + (m.uncensored ? ' unzensiert' : '') + (m.added_at && (Date.now() - new Date(m.added_at).getTime()) < 7 * 864e5 ? ' neu' : '') + + (m.popularity ? '
↓ ' + + Number(m.popularity.downloads).toLocaleString('de') + '/Monat' + + (m.popularity.downloadsAll ? ' (' + Number(m.popularity.downloadsAll).toLocaleString('de') + ' ges.)' : '') + + ' · ♥ ' + Number(m.popularity.likes).toLocaleString('de') + '
' : '') + (m.hfRepo ? ' ' : '') + '' + '' + escapeHtml(m.description || m.hfRepo || '') @@ -10288,7 +10292,7 @@ ${row('Grundmodell', j.base ? escapeHtml(j.base) : '')} ${row('Von', escapeHtml(j.author))} ${row('Lizenz', escapeHtml(j.license))} - ${row('Beliebtheit', '↓ ' + Number(j.downloads).toLocaleString('de') + ' · ♥ ' + j.likes)} + ${row('Beliebtheit', '↓ ' + Number(j.downloads).toLocaleString('de') + ' Downloads (30 Tage) · ♥ ' + j.likes + ' Likes — sagt „viel benutzt“, nicht „gut“; HuggingFace hat keine Sterne')} ${row('Stand', date(j.updated))} ${row('Schlagworte', (j.tags || []).map(t => '' + escapeHtml(t) + '').join(', '))} diff --git a/diagnostic/server.js b/diagnostic/server.js index b021709..5287c24 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -573,12 +573,16 @@ async function refreshLlmCatalogFromHF() { e.quant = q.quant; e.sizeGB = q.sizeGB; e.quants = q.quants; delete e.unusable; })); } - // Werkzeug-Faehigkeit + Sicherheits-Scan nachtragen (wo noch keine Angabe) - const noTools = [...byId.values()].filter(e => e.hfRepo && (e.tools === undefined || e.safety === undefined)); - for (let i = 0; i < noTools.length; i += 6) { - await Promise.all(noTools.slice(i, i + 6).map(async e => { + // Werkzeug-Faehigkeit + Sicherheits-Scan nachtragen (wo noch keine Angabe), + // Beliebtheit bei jedem Abgleich frisch (aendert sich taeglich) + const today = new Date().toISOString().slice(0, 10); + const toEnrich = [...byId.values()].filter(e => e.hfRepo && (e.tools === undefined || e.safety === undefined + || !e.popularity || e.popularity.at !== today)); + for (let i = 0; i < toEnrich.length; i += 6) { + await Promise.all(toEnrich.slice(i, i + 6).map(async e => { if (e.tools === undefined) e.tools = await hfToolSupport(e.hfRepo); if (e.safety === undefined) e.safety = await hfRepoSafety(e.hfRepo); + if (!e.popularity || e.popularity.at !== today) e.popularity = await hfPopularity(e.hfRepo) || e.popularity; })); } let added = 0; @@ -862,6 +866,14 @@ function knownGgufAuthor(a) { // Funktion: TRUSTED_GGUF_AUTHORS steht we return TRUSTED_GGUF_AUTHORS.has(a) || EXTRA_KNOWN_GGUF_AUTHORS.has(a); } const RISKY_FILE_RE = /\.(py|sh|bash|bat|cmd|ps1|exe|dll|so|dylib|pkl|pickle|pt|pth|ckpt|joblib|npy|zip|tar|gz|7z|jar|js|vbs)$|(^|\/)pytorch_model[^/]*\.bin$/i; +// Beliebtheit: HF hat keine Sterne — nur Downloads (30 Tage / gesamt) und Likes. +async function hfPopularity(repo) { + const d = await hfJson(`https://huggingface.co/api/models/${repo}?expand[]=downloads&expand[]=likes&expand[]=downloadsAllTime`); + if (!d) return null; + return { downloads: d.downloads || 0, downloadsAll: d.downloadsAllTime || 0, likes: d.likes || 0, + at: new Date().toISOString().slice(0, 10) }; +} + async function hfRepoSafety(repo) { const author = String(repo).split("/")[0].toLowerCase(); const out = { known: knownGgufAuthor(author), flagged: [], risky: [], scanned: 0 }; @@ -896,7 +908,7 @@ async function addHfRepoToCatalog(repo, description = "") { cat.push({ id, hfRepo: repo, quant: q.quant, ctx: 8192, sizeGB: q.sizeGB, quants: q.quants, description: description || "Aus den HuggingFace-Trends übernommen", source: "hf", tools: await hfToolSupport(repo), uncensored: UNCENSORED_RE.test(repo), - safety: await hfRepoSafety(repo), + safety: await hfRepoSafety(repo), popularity: await hfPopularity(repo), added_at: new Date().toISOString().slice(0, 10) }); saveLlmCatalog(cat); return { models: cat, id, existed: false };