Recognize model family hints and reusable runtimes
This commit is contained in:
@@ -23,6 +23,12 @@ Der Bildtest und `/v1/images/edits` akzeptieren beim geprüften Qwen-Image-2.1-R
|
|||||||
bis zu vier Referenzbilder. Die Zahl wird pro aktivem Modellrezept angezeigt;
|
bis zu vier Referenzbilder. Die Zahl wird pro aktivem Modellrezept angezeigt;
|
||||||
andere Bildmodelle erhalten keine ungeprüfte Edit-Fähigkeit.
|
andere Bildmodelle erhalten keine ungeprüfte Edit-Fähigkeit.
|
||||||
|
|
||||||
|
Entdecken und Bibliothek zeigen die aus Hugging-Face-Metadaten erkennbare
|
||||||
|
Modellfamilie und den Status der vorhandenen Laufzeit. Bei unbekannten
|
||||||
|
Bildvarianten kann Deck eine vorhandene ComfyUI-Installation als mögliche Basis
|
||||||
|
anzeigen. Ohne geprüftes Komponentenrezept und Workflow bleibt das Modell
|
||||||
|
gesperrt; ein weiterer Runtime-Build wird daraus nicht automatisch abgeleitet.
|
||||||
|
|
||||||
## Zugang und API-Token
|
## Zugang und API-Token
|
||||||
|
|
||||||
Beim ersten Öffnen werden Oberflächenkennwort und separater API-Token eingerichtet.
|
Beim ersten Öffnen werden Oberflächenkennwort und separater API-Token eingerichtet.
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ window.CatalogUI = (() => {
|
|||||||
}
|
}
|
||||||
function executionHTML(x) {
|
function executionHTML(x) {
|
||||||
if(!x)return '<p>Ausführbarkeit noch nicht geprüft.</p>';
|
if(!x)return '<p>Ausführbarkeit noch nicht geprüft.</p>';
|
||||||
return `<section class="note execution-setup"><strong>${escape(x.label)}</strong>${x.runtime?`<p>${escape(x.runtime)} · ${x.installed?'bereits installiert – wird wiederverwendet':'noch nicht eingerichtet'}</p>`:''}<p>${escape(x.message)}</p>${x.components?.length?`<p>Benötigt: ${x.components.map(escape).join(' · ')}. Bei Profilen unter „Komponenten“ zuordnen; TTS enthält das komplette Paket.</p>`:''}${x.route?`<a class="link" href="${escape(x.route)}">${x.installed?'Einrichtung ansehen':'Ausführung einrichten'} →</a>`:''}${x.alternative?`<p><a class="link" href="${escape(x.alternative.route)}">${escape(x.alternative.label)} →</a></p>`:''}<p class="small">Installierte Laufzeit bedeutet nicht, dass Modell und Kontext in den Speicher passen. Ein Test startet nur auf deinen Auftrag.</p></section>`;
|
return `<section class="note execution-setup"><strong>${escape(x.label)}</strong><p>Modellfamilie: ${escape(x.family||'Unbekannt')}${x.evidence?' · '+escape(x.evidence):''}</p>${x.runtime?`<p>${escape(x.runtime)} · ${x.installed?'bereits installiert – wird wiederverwendet':'noch nicht eingerichtet'}</p>`:''}<p>${escape(x.message)}</p>${x.components?.length?`<p>Benötigt: ${x.components.map(escape).join(' · ')}. Bei Profilen unter „Komponenten“ zuordnen; TTS enthält das komplette Paket.</p>`:''}${x.route?`<a class="link" href="${escape(x.route)}">${x.installed?'Einrichtung ansehen':'Ausführung einrichten'} →</a>`:''}${x.alternative?`<p><a class="link" href="${escape(x.alternative.route)}">${escape(x.alternative.label)} →</a></p>`:''}<p class="small">Installierte Laufzeit bedeutet nicht, dass Modell und Kontext in den Speicher passen. Ein Test startet nur auf deinen Auftrag.</p></section>`;
|
||||||
}
|
}
|
||||||
function html(library, downloads=false) {
|
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>
|
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>
|
||||||
|
|||||||
+16
-5
@@ -82,6 +82,17 @@ def capabilities(model):
|
|||||||
tools=bool(tags & {'tool-use','tool-calling','function-calling','function_calling'})
|
tools=bool(tags & {'tool-use','tool-calling','function-calling','function_calling'})
|
||||||
return dict(vision='Hinweis laut Hub' if vision else 'Projektordatei vorhanden' if files else 'Unbekannt',tools='Hinweis laut Hub' if tools else 'Unbekannt')
|
return dict(vision='Hinweis laut Hub' if vision else 'Projektordatei vorhanden' if files else 'Unbekannt',tools='Hinweis laut Hub' if tools else 'Unbekannt')
|
||||||
|
|
||||||
|
def runtime_metadata(model):
|
||||||
|
"""Persist only bounded, non-secret Hub hints; never treat them as a runnable recipe."""
|
||||||
|
config=model.get('config') if isinstance(model.get('config'),dict) else {}
|
||||||
|
architectures=config.get('architectures') if isinstance(config.get('architectures'),list) else []
|
||||||
|
architecture=next((x for x in architectures if isinstance(x,str) and 0<len(x)<=100),None)
|
||||||
|
architecture=architecture or next((config.get(k) for k in ('_class_name','model_type') if isinstance(config.get(k),str) and 0<len(config[k])<=100),None)
|
||||||
|
tags=[x for x in model.get('tags',[]) if isinstance(x,str) and len(x)<=80][:32] if isinstance(model.get('tags'),list) else []
|
||||||
|
return dict(pipeline_tag=model.get('pipeline_tag') if isinstance(model.get('pipeline_tag'),str) and len(model['pipeline_tag'])<=80 else None,
|
||||||
|
library_name=model.get('library_name') if isinstance(model.get('library_name'),str) and len(model['library_name'])<=80 else None,
|
||||||
|
architecture=architecture,tags=tags)
|
||||||
|
|
||||||
class Catalog:
|
class Catalog:
|
||||||
def __init__(self,root):
|
def __init__(self,root):
|
||||||
from hub_auth import HubAuth
|
from hub_auth import HubAuth
|
||||||
@@ -142,11 +153,11 @@ class Catalog:
|
|||||||
elif '/' in q:direct=repo_id(q)
|
elif '/' in q:direct=repo_id(q)
|
||||||
if direct:
|
if direct:
|
||||||
item=self._metadata('/api/models/'+direct)
|
item=self._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'),capabilities=capabilities(item))],base_only=base_only,scanned=1,notice='Direkt ausgewähltes Repository: Kategorie- und Basismodellfilter werden für diesen Treffer nicht angewendet.')
|
return dict(models=[dict(repo=item['id'],downloads=item.get('downloads'),gated=item.get('gated',False),created_at=item.get('createdAt'),capabilities=capabilities(item),runtime_metadata=runtime_metadata(item))],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'):
|
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.'
|
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='gguf' if purpose=='projector' else KINDS[kind],limit=100 if base_only else 20,sort='createdAt' if sort=='newest' else 'downloads',direction=-1)
|
query=dict(search=q,filter='gguf' if purpose=='projector' else KINDS[kind],limit=100 if base_only else 20,sort='createdAt' if sort=='newest' else 'downloads',direction=-1)
|
||||||
query['expand']=['downloads','createdAt','gated','cardData','tags','pipeline_tag']
|
query['expand']=['downloads','createdAt','gated','cardData','tags','pipeline_tag','library_name','config']
|
||||||
rows=self._metadata('/api/models?'+urllib.parse.urlencode(query,doseq=True))
|
rows=self._metadata('/api/models?'+urllib.parse.urlencode(query,doseq=True))
|
||||||
if not rows and q and kind=='chat' and purpose=='model':
|
if not rows and q and kind=='chat' and purpose=='model':
|
||||||
fallback=dict(query,filter='gguf',expand=['downloads','createdAt','gated','cardData','tags','pipeline_tag'])
|
fallback=dict(query,filter='gguf',expand=['downloads','createdAt','gated','cardData','tags','pipeline_tag'])
|
||||||
@@ -155,7 +166,7 @@ class Catalog:
|
|||||||
if rows:notice='GGUF-Sprachmodelle ohne reguläres Kategorie-Tag gefunden. Bei aktiver Basismodellauswahl bleiben Quantisierungen ausgeblendet.'
|
if rows:notice='GGUF-Sprachmodelle ohne reguläres Kategorie-Tag gefunden. Bei aktiver Basismodellauswahl bleiben Quantisierungen ausgeblendet.'
|
||||||
scanned=len(rows)
|
scanned=len(rows)
|
||||||
if base_only:rows=[x for x in rows if not is_derived_model(x)]
|
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'),capabilities=capabilities(x)) for x in rows[:20]],'base_only':base_only,'scanned':scanned,'notice':notice}
|
return {'models':[dict(repo=x['id'],downloads=x.get('downloads'),gated=x.get('gated',False),created_at=x.get('createdAt'),capabilities=capabilities(x),runtime_metadata=runtime_metadata(x)) for x in rows[:20]],'base_only':base_only,'scanned':scanned,'notice':notice}
|
||||||
def files(self,repo,revision=None):
|
def files(self,repo,revision=None):
|
||||||
repo=repo_id(repo)
|
repo=repo_id(repo)
|
||||||
if revision is not None and (not isinstance(revision,str) or not re.fullmatch("[a-f0-9]{40}",revision)):raise ValueError("Ungültige Quellversion.")
|
if revision is not None and (not isinstance(revision,str) or not re.fullmatch("[a-f0-9]{40}",revision)):raise ValueError("Ungültige Quellversion.")
|
||||||
@@ -173,7 +184,7 @@ class Catalog:
|
|||||||
size=f.get('size',f.get('lfs',{}).get('size'))
|
size=f.get('size',f.get('lfs',{}).get('size'))
|
||||||
if not isinstance(size,int) or size<1:continue
|
if not isinstance(size,int) or size<1:continue
|
||||||
files.append(dict(name=name,size=size,sha256=f.get('lfs',{}).get('sha256')))
|
files.append(dict(name=name,size=size,sha256=f.get('lfs',{}).get('sha256')))
|
||||||
result=dict(auth_configured=self.hub_auth.status()['configured'],capabilities=capabilities(data),repo=repo,revision=revision,files=files,gated=data.get('gated',False),license=(data.get('cardData') or {}).get('license'),url='https://huggingface.co/'+repo)
|
result=dict(auth_configured=self.hub_auth.status()['configured'],capabilities=capabilities(data),runtime_metadata=runtime_metadata(data),repo=repo,revision=revision,files=files,gated=data.get('gated',False),license=(data.get('cardData') or {}).get('license'),url='https://huggingface.co/'+repo)
|
||||||
with self.cache_lock:
|
with self.cache_lock:
|
||||||
if len(self.cache)>=64:self.cache.pop(next(iter(self.cache)))
|
if len(self.cache)>=64:self.cache.pop(next(iter(self.cache)))
|
||||||
self.cache[cache_key]=(time.monotonic(),result)
|
self.cache[cache_key]=(time.monotonic(),result)
|
||||||
@@ -240,7 +251,7 @@ class Catalog:
|
|||||||
if received!=item['size']:raise ValueError('Unvollständiger Download.')
|
if received!=item['size']:raise ValueError('Unvollständiger Download.')
|
||||||
if item['sha256'] and digest.hexdigest()!=item['sha256']:raise ValueError('SHA-256-Prüfung fehlgeschlagen.')
|
if item['sha256'] and digest.hexdigest()!=item['sha256']:raise ValueError('SHA-256-Prüfung fehlgeschlagen.')
|
||||||
partial.replace(dest)
|
partial.replace(dest)
|
||||||
entry=dict(repo=data['repo'],revision=data['revision'],file=item['name'],size=received,sha256=digest.hexdigest(),upstream_hash_verified=bool(item['sha256']),kind=kind,downloaded_at=time.time(),state='downloaded',runtime_ready=False)
|
entry=dict(repo=data['repo'],revision=data['revision'],file=item['name'],size=received,sha256=digest.hexdigest(),upstream_hash_verified=bool(item['sha256']),kind=kind,downloaded_at=time.time(),state='downloaded',runtime_ready=False,runtime_metadata=data.get('runtime_metadata',{}))
|
||||||
temp=target/'entry.tmp';temp.write_text(json.dumps(entry));temp.replace(target/'entry.json')
|
temp=target/'entry.tmp';temp.write_text(json.dumps(entry));temp.replace(target/'entry.json')
|
||||||
terminal='complete';error=None
|
terminal='complete';error=None
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|||||||
+12
-3
@@ -6,29 +6,38 @@ from tts_runtime import REPO, REVISION
|
|||||||
|
|
||||||
def assess(model, installed, profile=None):
|
def assess(model, installed, profile=None):
|
||||||
kind=model.get('kind'); filename=model.get('file',''); repo=model.get('repo','')
|
kind=model.get('kind'); filename=model.get('file',''); repo=model.get('repo','')
|
||||||
result=dict(state='unsupported',label='Noch nicht unterstützt',runtime=None,installed=False,route=None,components=[],message='Für dieses Modell ist in Deck noch kein geprüfter Ausführungsweg hinterlegt. Herunterladen und Aufbewahren ist möglich; eine Installation allein genügt nicht.')
|
meta=model.get('runtime_metadata') if isinstance(model.get('runtime_metadata'),dict) else {}
|
||||||
|
architecture=meta.get('architecture') if isinstance(meta.get('architecture'),str) else None
|
||||||
|
result=dict(state='unsupported',label='Noch nicht unterstützt',runtime=None,installed=False,route=None,components=[],family=architecture or 'Unbekannt',evidence='Hub-Metadaten' if architecture else 'Keine ausreichenden Metadaten',message='Für dieses Modell ist in Deck noch kein geprüfter Ausführungsweg hinterlegt. Herunterladen und Aufbewahren ist möglich; eine Installation allein genügt nicht.')
|
||||||
if not file_role(filename)['profile_eligible']:
|
if not file_role(filename)['profile_eligible']:
|
||||||
result.update(state='component',label='Zusatzdatei',message='Diese Datei wird einem kompatiblen Modellprofil zugeordnet und benötigt keine eigene Laufzeit.')
|
result.update(state='component',label='Zusatzdatei',message='Diese Datei wird einem kompatiblen Modellprofil zugeordnet und benötigt keine eigene Laufzeit.')
|
||||||
return result
|
return result
|
||||||
if kind=='stt' and stt_supported(model):
|
if kind=='stt' and stt_supported(model):
|
||||||
|
result['family']='Qwen3-ASR 0.6B'
|
||||||
result.update(runtime='llama.cpp · Qwen3-ASR auf CPU',route='#stt-runtime',components=['Passender Audio-Projektor (unter Einrichten herunterladen)'])
|
result.update(runtime='llama.cpp · Qwen3-ASR auf CPU',route='#stt-runtime',components=['Passender Audio-Projektor (unter Einrichten herunterladen)'])
|
||||||
elif kind=='image' and image_recipe(model):
|
elif kind=='image' and image_recipe(model):
|
||||||
|
result['family']='FLUX.2 Klein 9B' if 'FLUX' in filename.upper() else 'Qwen Image 2.1'
|
||||||
result.update(runtime='ComfyUI + ComfyUI-GGUF',route='#image-runtime',components=[x['label'] for x in image_recipe(model).values()])
|
result.update(runtime='ComfyUI + ComfyUI-GGUF',route='#image-runtime',components=[x['label'] for x in image_recipe(model).values()])
|
||||||
elif kind=='audio' and repo==REPO and filename=='model.safetensors' and model.get('revision')==REVISION:
|
elif kind=='audio' and repo==REPO and filename=='model.safetensors' and model.get('revision')==REVISION:
|
||||||
|
result['family']='Qwen3-TTS CustomVoice'
|
||||||
result.update(runtime='Qwen3-TTS CustomVoice',route='#tts-runtime',components=['Vollständiges Modellpaket und Sprach-Tokenizer (im Einrichtungsassistenten enthalten)'])
|
result.update(runtime='Qwen3-TTS CustomVoice',route='#tts-runtime',components=['Vollständiges Modellpaket und Sprach-Tokenizer (im Einrichtungsassistenten enthalten)'])
|
||||||
elif kind=='chat' and filename.lower().endswith('.gguf'):
|
elif kind=='chat' and filename.lower().endswith('.gguf'):
|
||||||
result.update(runtime='llama.cpp',route='#runtime',state='check',label='Kompatibilität beim Laden prüfen',message='llama.cpp ist der mögliche Ausführungsweg für dieses Chat-GGUF. Architektur und gewählte Optionen müssen vom installierten Build unterstützt werden. Der Testchat prüft die tatsächliche Ausführung.')
|
result.update(runtime='llama.cpp',route='#runtime',state='check',label='Kompatibilität beim Laden prüfen',message='llama.cpp ist der mögliche Ausführungsweg für dieses Chat-GGUF. Architektur und gewählte Optionen müssen vom installierten Build unterstützt werden. Der Testchat prüft die tatsächliche Ausführung.')
|
||||||
|
elif kind=='image' and filename.lower().endswith(('.safetensors','.gguf')) and (meta.get('pipeline_tag') in ('text-to-image','image-to-image') or meta.get('library_name') in ('diffusers','comfyui')):
|
||||||
|
result.update(state='workflow_missing',label='Bildfamilie erkannt · Workflow fehlt',runtime='ComfyUI (vorhandene Bildlaufzeit prüfen)',installed=bool(installed.get('image')),route='#image-runtime',message='Hub-Metadaten ordnen die Datei einer Bildpipeline zu. Deck hat bereits eine gemeinsame Bildlaufzeit; ein neuer Build ist nicht automatisch nötig. Für diese Modellvariante fehlen noch ein geprüfter ComfyUI-Workflow und die genaue Komponentenliste. Bis dahin kann sie nicht gestartet werden.')
|
||||||
|
if not architecture:result['family']='Bildpipeline laut Hugging Face'
|
||||||
elif kind=='stt':
|
elif kind=='stt':
|
||||||
result.update(message='Für diese STT-Datei fehlt die Anbindung. Die geprüfte llama.cpp-Variante von Qwen3-ASR kann unter Einrichten zusätzlich geladen und ausdrücklich einem Profil zugeordnet werden.',alternative=dict(route='#stt-runtime',label='Unterstützte STT-Alternative einrichten (Modellwechsel)'))
|
result.update(message='Für diese STT-Datei fehlt die Anbindung. Die geprüfte llama.cpp-Variante von Qwen3-ASR kann unter Einrichten zusätzlich geladen und ausdrücklich einem Profil zugeordnet werden.',alternative=dict(route='#stt-runtime',label='Unterstützte STT-Alternative einrichten (Modellwechsel)'))
|
||||||
elif kind=='audio':
|
elif kind=='audio':
|
||||||
result.update(message='Dieses TTS-Modell wird noch nicht ausgeführt. Deck unterstützt derzeit die festgelegte Qwen3-TTS-CustomVoice-Variante mit eingebauten Stimmen. MLX, Base mit Referenzstimme und andere TTS-Architekturen benötigen andere Anbindungen.',alternative=dict(route='#tts-runtime',label='Unterstützte TTS-Alternative ansehen (Modellwechsel)'))
|
result.update(message='Dieses TTS-Modell wird noch nicht ausgeführt. Deck unterstützt derzeit die festgelegte Qwen3-TTS-CustomVoice-Variante mit eingebauten Stimmen. MLX, Base mit Referenzstimme und andere TTS-Architekturen benötigen andere Anbindungen.',alternative=dict(route='#tts-runtime',label='Unterstützte TTS-Alternative ansehen (Modellwechsel)'))
|
||||||
if video_recipe(model):
|
if video_recipe(model):
|
||||||
|
result['family']='LTX 2.5'
|
||||||
result.update(runtime='LTX-Pipelines · Distilled BF16',state='runtime_missing',label='Komponenten und Video-Laufzeit einrichten',route='#video-runtime',components=[x['label'] for x in video_recipe(model).values()],message='Videoprofil anlegen und dort Komponenten öffnen: passende Dateien aus der Version des Hauptmodells nachladen und zuordnen. LTX-Laufzeit unter Einstellungen → Video installieren; danach Videoprofil in der Übersicht auswählen.')
|
result.update(runtime='LTX-Pipelines · Distilled BF16',state='runtime_missing',label='Komponenten und Video-Laufzeit einrichten',route='#video-runtime',components=[x['label'] for x in video_recipe(model).values()],message='Videoprofil anlegen und dort Komponenten öffnen: passende Dateien aus der Version des Hauptmodells nachladen und zuordnen. LTX-Laufzeit unter Einstellungen → Video installieren; danach Videoprofil in der Übersicht auswählen.')
|
||||||
if not result['runtime']:return result
|
if not result['runtime']:return result
|
||||||
result['installed']=bool(installed.get(kind))
|
result['installed']=bool(installed.get(kind))
|
||||||
if kind!='chat':
|
if kind!='chat' and result['state']!='workflow_missing':
|
||||||
result.update(state='setup',label='Unterstützt – Einrichtung prüfen',message='Die Laufzeit wird für kompatible Modelle gemeinsam genutzt. Modellabhängige Komponenten werden dem jeweiligen Profil zugeordnet.')
|
result.update(state='setup',label='Unterstützt – Einrichtung prüfen',message='Die Laufzeit wird für kompatible Modelle gemeinsam genutzt. Modellabhängige Komponenten werden dem jeweiligen Profil zugeordnet.')
|
||||||
if not result['installed']:
|
if not result['installed'] and result['state']!='workflow_missing':
|
||||||
result.update(state='runtime_missing',label='Passende Laufzeit fehlt')
|
result.update(state='runtime_missing',label='Passende Laufzeit fehlt')
|
||||||
if profile:
|
if profile:
|
||||||
result['blockers']=profile.get('blockers',[])
|
result['blockers']=profile.get('blockers',[])
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
return self.respond(overview(data['files'],self.server.hardware.snapshot()))
|
return self.respond(overview(data['files'],self.server.hardware.snapshot()))
|
||||||
if path.path=='/api/v1/catalog/files':
|
if path.path=='/api/v1/catalog/files':
|
||||||
data=dict(self.server.catalog.files(q.get('repo',[''])[0]));hardware=self.server.hardware.snapshot()
|
data=dict(self.server.catalog.files(q.get('repo',[''])[0]));hardware=self.server.hardware.snapshot()
|
||||||
data['files']=[dict(f,assessment=assess(f['size'],hardware,f['name']),execution=self.execution_setup(dict(repo=data['repo'],revision=data['revision'],file=f['name'],kind=q.get('kind',['chat'])[0]))) for f in data['files']]
|
data['files']=[dict(f,assessment=assess(f['size'],hardware,f['name']),execution=self.execution_setup(dict(repo=data['repo'],revision=data['revision'],file=f['name'],kind=q.get('kind',['chat'])[0],runtime_metadata=data.get('runtime_metadata',{})))) for f in data['files']]
|
||||||
return self.respond(data)
|
return self.respond(data)
|
||||||
if path.path=='/api/v1/catalog':
|
if path.path=='/api/v1/catalog':
|
||||||
data=self.server.catalog.status()
|
data=self.server.catalog.status()
|
||||||
|
|||||||
+6
-1
@@ -4,9 +4,14 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from catalog import Catalog, safe_url, repo_id, is_derived_model
|
from catalog import Catalog, safe_url, repo_id, is_derived_model, runtime_metadata
|
||||||
|
|
||||||
class CatalogTests(unittest.TestCase):
|
class CatalogTests(unittest.TestCase):
|
||||||
|
def test_bounded_hub_runtime_metadata(self):
|
||||||
|
hint=runtime_metadata(dict(pipeline_tag='text-to-image',library_name='diffusers',config={'architectures':['NewImagePipeline']},tags=['image-to-image']))
|
||||||
|
self.assertEqual(hint['architecture'],'NewImagePipeline')
|
||||||
|
self.assertEqual(hint['library_name'],'diffusers')
|
||||||
|
self.assertEqual(runtime_metadata(dict(config={'architectures':['x'*200]}))['architecture'],None)
|
||||||
def test_reject_untrusted_urls_and_repos(self):
|
def test_reject_untrusted_urls_and_repos(self):
|
||||||
for value in ['http://huggingface.co/a','https://127.0.0.1/x','https://huggingface.co.evil.org/a','https://user@huggingface.co/a']:
|
for value in ['http://huggingface.co/a','https://127.0.0.1/x','https://huggingface.co.evil.org/a','https://user@huggingface.co/a']:
|
||||||
with self.assertRaises(ValueError):safe_url(value)
|
with self.assertRaises(ValueError):safe_url(value)
|
||||||
|
|||||||
@@ -25,3 +25,11 @@ class ExecutionSetupTests(unittest.TestCase):
|
|||||||
model=dict(kind='image',repo=QWEN_REPO,file='model.gguf')
|
model=dict(kind='image',repo=QWEN_REPO,file='model.gguf')
|
||||||
self.assertEqual(assess(model,{'image':True},dict(runnable=False,blockers=['VAE fehlt']))['state'],'profile_incomplete')
|
self.assertEqual(assess(model,{'image':True},dict(runnable=False,blockers=['VAE fehlt']))['state'],'profile_incomplete')
|
||||||
self.assertEqual(assess(model,{'image':True},dict(runnable=True))['state'],'ready')
|
self.assertEqual(assess(model,{'image':True},dict(runnable=True))['state'],'ready')
|
||||||
|
def test_hub_image_family_reuses_installed_runtime_without_claiming_execution(self):
|
||||||
|
model=dict(kind='image',repo='other/new-image',file='weights.safetensors',runtime_metadata=dict(pipeline_tag='text-to-image',library_name='diffusers',architecture='NewImagePipeline'))
|
||||||
|
result=assess(model,{'image':True})
|
||||||
|
self.assertEqual(result['state'],'workflow_missing')
|
||||||
|
self.assertTrue(result['installed'])
|
||||||
|
self.assertEqual(result['family'],'NewImagePipeline')
|
||||||
|
self.assertIn('Workflow',result['message'])
|
||||||
|
self.assertEqual(assess(dict(model,kind='audio'),{'image':True})['state'],'unsupported')
|
||||||
|
|||||||
Reference in New Issue
Block a user