Find direct Hub repositories and untagged conversational GGUF models
This commit is contained in:
@@ -155,3 +155,10 @@ Die Einstellung bleibt im Browser über Kategorien hinweg erhalten. Fehlende
|
||||
Herausgeber-Metadaten können dazu führen, dass Ableitungen sichtbar bleiben;
|
||||
es handelt sich nicht um eine verifizierte Liste offizieller Herausgeber.
|
||||
API-Parameter: `GET /api/v1/catalog/search?...&base_only=true`.
|
||||
|
||||
Die Suche akzeptiert auch `owner/repository` und direkte Hugging-Face-Repository-
|
||||
URLs. Eine solche Direktauswahl umgeht Kategorie- und Basismodellfilter mit einem
|
||||
sichtbaren Hinweis. Bei GGUF-Dateinamen wird die Endung für die Repositorysuche
|
||||
entfernt. Findet die Chat-Suche nichts, werden zusätzlich als `conversational`
|
||||
gekennzeichnete GGUF-Repositories ohne `text-generation`-Tag geprüft. Das ist
|
||||
keine globale Suche nach beliebigen Dateinamen im Hub.
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ window.CatalogUI = (() => {
|
||||
function html(library, downloads=false) {
|
||||
return `<section id="live-catalog"><div class="section-heading"><div><span class="kicker">HUGGING FACE · LIVE-KATALOG</span><h2>${downloads ? 'Downloads' : library ? 'Deine Dateien auf Athena' : 'Das nächste Modell entdecken'}</h2><p>${downloads ? 'Abgeschlossene Einträge ausblenden. Die Dateien bleiben in deiner Bibliothek.' : library ? 'Heruntergeladene Dateien mit festgehaltener Version und Integritätsprüfung.' : 'Modell suchen, Variante vergleichen und gezielt auf Athena herunterladen.'}</p></div><button class="secondary" id="hub-refresh">Aktualisieren</button></div>
|
||||
<div id="hub-capacity" class="small muted"></div><div id="hub-job"></div><p id="hub-message" role="status" aria-live="polite"></p>
|
||||
${downloads ? '' : library ? '<div id="hub-results" class="model-grid"></div>' : `<form id="hub-search" class="hub-search"><label>Modell oder Anbieter<input name="q" maxlength="120" placeholder="z. B. Qwen oder FLUX" autocomplete="off"></label><button>Suchen</button></form><label class="hub-base-filter"><input id="hub-base-only" type="checkbox"> Nur Basismodelle</label><p class="small muted">Blendet anhand der Hub-Metadaten Finetunes, LoRAs, Merges und separate Quantisierungen aus. Unvollständig gekennzeichnete Ableitungen können sichtbar bleiben.</p><div class="hub-layout"><div><div class="hub-list-heading"><span id="hub-count" class="small muted">Beliebte Modelle</span><label class="sr-only" for="hub-sort">Ergebnisse sortieren</label><select id="hub-sort"><option value="downloads">Meiste Downloads</option><option value="name">Name A–Z</option><option value="newest">Neu hinzugefügt</option></select></div><div id="hub-results" class="hub-results"></div></div><section id="hub-files" class="card hub-detail" aria-label="Modelldetails"><span class="kicker">01 ENTDECKEN → 02 AUSWÄHLEN → 03 LADEN</span><h2>Welches Modell interessiert dich?</h2><p>Wähle ein Ergebnis, um Dateien, Varianten und Lizenz zu prüfen.</p><p class="note">Downloads belegen Festplattenspeicher. Sie starten kein Modell. Für Bild, Audio und Video sind häufig mehrere Dateien und ein passender Worker erforderlich.</p></section></div>`}</section>`;
|
||||
${downloads ? '' : library ? '<div id="hub-results" class="model-grid"></div>' : `<form id="hub-search" class="hub-search"><label>Modell, Anbieter oder Repository-Link<input name="q" maxlength="120" placeholder="Name, GGUF-Dateiname oder Hugging-Face-Link" autocomplete="off"></label><button>Suchen</button></form><label class="hub-base-filter"><input id="hub-base-only" type="checkbox"> Nur Basismodelle</label><p class="small muted">Blendet anhand der Hub-Metadaten Finetunes, LoRAs, Merges und separate Quantisierungen aus. Unvollständig gekennzeichnete Ableitungen können sichtbar bleiben.</p><div class="hub-layout"><div><div class="hub-list-heading"><span id="hub-count" class="small muted">Beliebte Modelle</span><label class="sr-only" for="hub-sort">Ergebnisse sortieren</label><select id="hub-sort"><option value="downloads">Meiste Downloads</option><option value="name">Name A–Z</option><option value="newest">Neu hinzugefügt</option></select></div><div id="hub-results" class="hub-results"></div></div><section id="hub-files" class="card hub-detail" aria-label="Modelldetails"><span class="kicker">01 ENTDECKEN → 02 AUSWÄHLEN → 03 LADEN</span><h2>Welches Modell interessiert dich?</h2><p>Wähle ein Ergebnis, um Dateien, Varianten und Lizenz zu prüfen.</p><p class="note">Downloads belegen Festplattenspeicher. Sie starten kein Modell. Für Bild, Audio und Video sind häufig mehrere Dateien und ein passender Worker erforderlich.</p></section></div>`}</section>`;
|
||||
}
|
||||
function bind(kind, library, downloads=false) {
|
||||
const root = document.querySelector('#live-catalog'); if (!root) return;
|
||||
@@ -45,7 +45,7 @@ window.CatalogUI = (() => {
|
||||
}
|
||||
async function search(q) {
|
||||
const sequence = ++searchSequence; message('Modelle werden gesucht …');
|
||||
try { const result = await api('/search?'+new URLSearchParams({q,kind,sort:el('hub-sort').value,base_only:String(el('hub-base-only').checked)})); if (!root.isConnected || sequence !== searchSequence) return; models = result.models; renderModels(); enrich(sequence); message(result.base_only ? `Basismodellfilter aktiv · ${result.scanned} Hub-Treffer geprüft, höchstens 20 angezeigt. Bei fehlenden Treffern Suche eingrenzen oder Filter ausschalten.` : kind === 'audio' ? 'Audio-Suche: derzeit Text-to-Speech-Modelle.' : el('hub-sort').value==='newest'?'Neueste Hub-Repositories zuerst (Erstellungsdatum, nicht letztes Update).':'Wähle ein Modell für die Dateiauswahl. Name A–Z sortiert die angezeigten Treffer.'); }
|
||||
try { const result = await api('/search?'+new URLSearchParams({q,kind,sort:el('hub-sort').value,base_only:String(el('hub-base-only').checked)})); if (!root.isConnected || sequence !== searchSequence) return; models = result.models; renderModels(); enrich(sequence); message(result.notice || (result.base_only ? `Basismodellfilter aktiv · ${result.scanned} Hub-Treffer geprüft, höchstens 20 angezeigt. Bei fehlenden Treffern Suche eingrenzen oder Filter ausschalten.` : kind === 'audio' ? 'Audio-Suche: derzeit Text-to-Speech-Modelle.' : el('hub-sort').value==='newest'?'Neueste Hub-Repositories zuerst (Erstellungsdatum, nicht letztes Update).':'Wähle ein Modell für die Dateiauswahl. Name A–Z sortiert die angezeigten Treffer.')); }
|
||||
catch(error) { if (sequence === searchSequence) message(error.message); }
|
||||
}
|
||||
async function enrich(sequence){
|
||||
|
||||
+17
-1
@@ -97,12 +97,28 @@ class Catalog:
|
||||
if kind not in KINDS or not isinstance(q,str) or len(q)>120:raise ValueError('Ungültige Suche.')
|
||||
if sort not in ('downloads','name','newest'):raise ValueError('Ungültige Sortierung.')
|
||||
if not isinstance(base_only,bool):raise ValueError('Ungültiger Basismodellfilter.')
|
||||
q=q.strip();direct=None;notice=None
|
||||
if q.startswith('https://huggingface.co/'):
|
||||
url=urllib.parse.urlsplit(q)
|
||||
if url.netloc!='huggingface.co' or url.query or url.fragment:raise ValueError('Bitte einen direkten Hugging-Face-Repository-Link eingeben.')
|
||||
direct=repo_id(url.path.strip('/'))
|
||||
elif '/' in q:direct=repo_id(q)
|
||||
if direct:
|
||||
item=metadata('/api/models/'+direct)
|
||||
return dict(models=[dict(repo=item['id'],downloads=item.get('downloads'),gated=item.get('gated',False),created_at=item.get('createdAt'))],base_only=base_only,scanned=1,notice='Direkt ausgewähltes Repository: Kategorie- und Basismodellfilter werden für diesen Treffer nicht angewendet.')
|
||||
if q.lower().endswith('.gguf'):
|
||||
q=q[:-5];notice='GGUF-Dateiname als Repository-Suchbegriff verwendet. Die Datei wählst du anschließend in den Modelldetails.'
|
||||
query=dict(search=q,filter=KINDS[kind],limit=100 if base_only else 20,sort='createdAt' if sort=='newest' else 'downloads',direction=-1)
|
||||
if base_only:query['expand']=['downloads','createdAt','gated','cardData','tags']
|
||||
rows=metadata('/api/models?'+urllib.parse.urlencode(query,doseq=True))
|
||||
if not rows and q and kind=='chat':
|
||||
fallback=dict(query,filter='gguf',expand=['downloads','createdAt','gated','cardData','tags','pipeline_tag'])
|
||||
candidates=metadata('/api/models?'+urllib.parse.urlencode(fallback,doseq=True))
|
||||
rows=[x for x in candidates if x.get('pipeline_tag')=='text-generation' or 'conversational' in (x.get('tags') or [])]
|
||||
if rows:notice='GGUF-Sprachmodelle ohne reguläres Kategorie-Tag gefunden. Bei aktiver Basismodellauswahl bleiben Quantisierungen ausgeblendet.'
|
||||
scanned=len(rows)
|
||||
if base_only:rows=[x for x in rows if not is_derived_model(x)]
|
||||
return {'models':[dict(repo=x['id'],downloads=x.get('downloads'),gated=x.get('gated',False),created_at=x.get('createdAt')) for x in rows[:20]],'base_only':base_only,'scanned':scanned}
|
||||
return {'models':[dict(repo=x['id'],downloads=x.get('downloads'),gated=x.get('gated',False),created_at=x.get('createdAt')) for x in rows[:20]],'base_only':base_only,'scanned':scanned,'notice':notice}
|
||||
def files(self,repo):
|
||||
repo=repo_id(repo)
|
||||
with self.cache_lock:
|
||||
|
||||
@@ -30,6 +30,17 @@ class CatalogTests(unittest.TestCase):
|
||||
self.assertEqual([x['repo'] for x in value['models']],['maker/base']);self.assertEqual(value['scanned'],21)
|
||||
self.assertIn('limit=100',fetch.call_args.args[0]);self.assertIn('expand=cardData',fetch.call_args.args[0])
|
||||
with self.assertRaises(ValueError):Catalog(d).search('','video','downloads','true')
|
||||
def test_direct_repository_bypasses_discovery_filters_explicitly(self):
|
||||
item={'id':'jpetrina/example','tags':['gguf']}
|
||||
with tempfile.TemporaryDirectory() as d,patch('catalog.metadata',return_value=item):
|
||||
for q in ('jpetrina/example','https://huggingface.co/jpetrina/example'):
|
||||
result=Catalog(d).search(q,'chat','downloads',True)
|
||||
self.assertEqual(result['models'][0]['repo'],item['id']);self.assertIn('nicht angewendet',result['notice'])
|
||||
def test_gguf_filename_fallback_finds_conversational_without_pipeline_tag(self):
|
||||
item={'id':'maker/model-GGUF','tags':['gguf','conversational']}
|
||||
with tempfile.TemporaryDirectory() as d,patch('catalog.metadata',side_effect=[[],[item]]) as fetch:
|
||||
result=Catalog(d).search('model.gguf','chat')
|
||||
self.assertEqual(result['models'][0]['repo'],item['id']);self.assertIn('search=model&',fetch.call_args_list[0].args[0]);self.assertIn('filter=gguf',fetch.call_args.args[0])
|
||||
def test_download_integrity_and_library(self):
|
||||
for valid in (True,False):
|
||||
with self.subTest(valid=valid),tempfile.TemporaryDirectory() as directory:
|
||||
|
||||
Reference in New Issue
Block a user