Group runtime settings and add profile component downloads and assignments
This commit is contained in:
+45
-2
@@ -12,6 +12,12 @@ SCHEMAS={
|
||||
'audio':{'speed':(.25,4,1)},
|
||||
'video':{'width':(256,1920,768),'height':(256,1088,512),'frames':(1,241,33),'fps':(1,60,24),'steps':(1,100,20),'seed':(-1,2147483647,-1)}
|
||||
}
|
||||
# Explicit model-card recipe. This is a source recommendation, not a runtime test.
|
||||
QWEN_REPO='abenzerps/Qwen-Image-2.1-Uncensored-GGUF'
|
||||
QWEN_COMPONENTS={
|
||||
'text_encoder':{'label':'Textencoder','files':['text_encoders/qwen3vl_8b_int8_convrot.safetensors','text_encoders/qwen3vl_8b_bf16.safetensors']},
|
||||
'vae':{'label':'VAE','files':['vae/qwen_image_2.1_vae_bf16.safetensors']}
|
||||
}
|
||||
class Profiles:
|
||||
def __init__(self,path,catalog):
|
||||
self.path=Path(path);self.catalog=catalog;self.lock=threading.RLock()
|
||||
@@ -24,7 +30,10 @@ class Profiles:
|
||||
p['model']=self.catalog.entry(p['model_id'])
|
||||
p['blockers']=['Für dieses Profil ist noch kein ausführbarer Worker angebunden.']
|
||||
if p['kind']=='image' and 'qwen-image-2.1' in p['model']['repo'].lower():
|
||||
p['blockers']=['Qwen-Image-2.1 benötigt zusätzlich einen kompatiblen Textencoder und VAE.','Eine eigene Bildlaufzeit muss eingerichtet werden; llama.cpp führt keine Bildmodelle aus.']
|
||||
p['blockers']=['Eine eigene Bildlaufzeit muss eingerichtet werden; llama.cpp führt keine Bildmodelle aus.']
|
||||
for role,info in reversed(list(QWEN_COMPONENTS.items())):
|
||||
try:self._component(p['model'],role,p.get('components',{}).get(role))
|
||||
except ValueError:p['blockers'].insert(0,info['label']+' fehlt oder ist noch nicht zugeordnet.')
|
||||
except ValueError as exc:p['model']=None;p['blockers']=[str(exc)]
|
||||
p['state']='configured';p['runnable']=False
|
||||
return {'profiles':rows,'schemas':SCHEMAS}
|
||||
@@ -33,6 +42,7 @@ class Profiles:
|
||||
kind=data['kind'];name=data['name'];params=data['parameters']
|
||||
if not isinstance(kind,str) or kind not in SCHEMAS or not isinstance(name,str) or not re.fullmatch('[a-zA-Z0-9][a-zA-Z0-9_-]{1,63}',name):raise ValueError('API-Name: 2–64 Buchstaben, Ziffern, Bindestrich oder Unterstrich.')
|
||||
model=self.catalog.entry(data['model_id'])
|
||||
if model['repo']==QWEN_REPO and any(model['file'] in r['files'] for r in QWEN_COMPONENTS.values()):raise ValueError('Textencoder und VAE werden über Komponenten zugeordnet, nicht als Hauptmodell.')
|
||||
if model['kind']!=kind or not model['file'].lower().endswith(('.gguf','.safetensors')):raise ValueError('Eine Gewichtsdatei dieses Bereichs auswählen, keine Konfiguration.')
|
||||
if not isinstance(params,dict) or set(params)!=set(SCHEMAS[kind]):raise ValueError('Unvollständige oder unbekannte Profilparameter.')
|
||||
for key,(lo,hi,_) in SCHEMAS[kind].items():
|
||||
@@ -46,8 +56,41 @@ class Profiles:
|
||||
if data['id'] is not None and not existing:raise ValueError('Profil nicht gefunden.')
|
||||
if data['revision']!=(existing['revision'] if existing else 0):raise ValueError('Profil wurde zwischenzeitlich geändert. Ansicht neu laden.')
|
||||
if any(p['name']==name and p is not existing for p in self.rows):raise ValueError('API-Profilname bereits vergeben.')
|
||||
row=dict(id=existing['id'] if existing else uuid.uuid4().hex,revision=data['revision']+1,name=name,kind=kind,model_id=model['id'],parameters=params,updated_at=time.time())
|
||||
components=existing.get('components',{}) if existing and existing['model_id']==model['id'] else {}
|
||||
row=dict(components=components,id=existing['id'] if existing else uuid.uuid4().hex,revision=data['revision']+1,name=name,kind=kind,model_id=model['id'],parameters=params,updated_at=time.time())
|
||||
rows=[row if p is existing else p for p in self.rows] if existing else self.rows+[row]
|
||||
self.path.parent.mkdir(parents=True,exist_ok=True,mode=0o700)
|
||||
temp=self.path.with_suffix('.tmp');temp.write_text(json.dumps(rows));temp.replace(self.path);self.rows=rows
|
||||
return row
|
||||
|
||||
def _component(self,model,role,ident):
|
||||
if model['repo']!=QWEN_REPO or role not in QWEN_COMPONENTS:raise ValueError('Für dieses Modell ist kein geprüftes Komponentenrezept hinterlegt.')
|
||||
item=self.catalog.entry(ident)
|
||||
if item['repo']!=QWEN_REPO or item['kind']!='image' or item['file'] not in QWEN_COMPONENTS[role]['files']:raise ValueError('Die Datei gehört nicht zu dieser Komponente des Modells.')
|
||||
return item
|
||||
def components(self,model_id):
|
||||
model=self.catalog.entry(model_id)
|
||||
if model['repo']!=QWEN_REPO or model['kind']!='image':return dict(supported=False,requirements=[],message='Für dieses Modell ist noch kein Komponentenrezept hinterlegt. Keine automatische Zuordnung.')
|
||||
source=self.catalog.files(QWEN_REPO);entries=self.catalog.status()['entries'];requirements=[]
|
||||
for role,info in QWEN_COMPONENTS.items():
|
||||
available=[]
|
||||
for item in entries:
|
||||
try:
|
||||
self._component(model,role,item['id']);available.append(item)
|
||||
except ValueError:pass
|
||||
candidates=[dict(f,repo=QWEN_REPO,revision=source['revision'],gated=source['gated']) for name in info['files'] for f in source['files'] if f['name']==name]
|
||||
requirements.append(dict(role=role,label=info['label'],available=available,candidates=candidates))
|
||||
return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime='ComfyUI + ComfyUI-GGUF',
|
||||
message='Zusatzdateien laut Modellkarte für ComfyUI. Kompatibilität und Speicherbedarf müssen mit der eingerichteten Laufzeit noch praktisch geprüft werden.')
|
||||
def assign(self,data):
|
||||
if set(data)!={'id','revision','components'} or not isinstance(data['components'],dict) or set(data['components'])-set(QWEN_COMPONENTS):raise ValueError('Ungültige Komponentenzuordnung.')
|
||||
with self.lock:
|
||||
old=next((p for p in self.rows if p['id']==data['id']),None)
|
||||
if not old or old['kind']!='image' or old['revision']!=data['revision']:raise ValueError('Profil geändert oder nicht gefunden. Ansicht neu laden.')
|
||||
model=self.catalog.entry(old['model_id'])
|
||||
components={k:v for k,v in data['components'].items() if v}
|
||||
for role,ident in components.items():self._component(model,role,ident)
|
||||
updated=dict(old,components=components,revision=old['revision']+1,updated_at=time.time())
|
||||
rows=[updated if p is old else p for p in self.rows]
|
||||
temp=self.path.with_suffix('.tmp');temp.write_text(json.dumps(rows));temp.replace(self.path);self.rows=rows
|
||||
return updated
|
||||
|
||||
Reference in New Issue
Block a user