From 541101091d73aa5698005b065e7e45f1adfa6a7a Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:59:31 +0200 Subject: [PATCH] Guide model setup consistently across categories --- README.md | 11 +++++++++++ catalog-ui.js | 2 +- execution_setup.py | 16 +++++++++++++++- profiles-ui.js | 4 ++-- profiles.py | 6 +++--- test_execution_setup.py | 11 +++++++++++ 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0b6175e..6e3953a 100644 --- a/README.md +++ b/README.md @@ -259,3 +259,14 @@ Der Ergebnisplayer bleibt beim Status-Polling bestehen, damit die Wiedergabe nic alle 1,5 Sekunden zurückgesetzt wird. Native WireGuard-Anbindung: [Systemdienst, Zugriffsmodi und Migration](network/NATIVE.md). Der Tunnel läuft außerhalb von Docker; die isolierte Deck-App kann vorerst im Container bleiben. + +## Einheitliche Modelle Einrichtung + +Bibliothek und Profile zeigen für alle Modellkategorien dieselben vier Schritte: +Erkennung, Laufzeit, Zusatzdateien und Ausführungsprüfung. Vorhandene Laufzeiten +werden wiederverwendet; fehlende führen direkt zur passenden Einrichtungsseite. +Unbekannte Familien und fehlende Generierungsanbindungen bleiben ausdrücklich +gesperrt. Der Komponentenbereich bietet vorhandene passende Dateien vorausgewählt +an und einen Sammeldownload fehlender Pflichtdateien. Bei mehreren verfügbaren +Varianten ist eine bewusste Einzelwahl nötig; ungeprüfte Quellen werden nicht +automatisch installiert. Speichern und Downloads starten keine Inferenz. diff --git a/catalog-ui.js b/catalog-ui.js index 413fc47..a60ac24 100644 --- a/catalog-ui.js +++ b/catalog-ui.js @@ -10,7 +10,7 @@ window.CatalogUI = (() => { } function executionHTML(x) { if(!x)return '

Ausführbarkeit noch nicht geprüft.

'; - return `
${escape(x.label)}

Modellfamilie: ${escape(x.family||'Unbekannt')}${x.evidence?' · '+escape(x.evidence):''}

${x.runtime?`

${escape(x.runtime)} · ${x.installed?'bereits installiert – wird wiederverwendet':'noch nicht eingerichtet'}

`:''}

${escape(x.message)}

${x.components?.length?`

Benötigt: ${x.components.map(escape).join(' · ')}. Modellabhängig zuordnen; benötigte Zusatzdateien gehören zum vollständigen Paket.

`:''}${x.suggested_components?.length?`

Zusatzdateien in dieser Quelle (Kompatibilität ungeprüft): ${x.suggested_components.map(escape).join(' · ')}. Nach dem Download unter Profil → Einrichtung & Komponenten prüfen.

`:''}${x.route?`${x.installed?'Einrichtung ansehen':'Ausführung einrichten'} →`:''}${x.candidates?.length?`

Mögliche Laufzeiten

Diese Liste unterscheidet bekannte Zuordnungen von ungeprüften Kategorieoptionen. Eine Installation ersetzt keinen fehlenden Worker.

${x.candidates.map(c=>`

${escape(c.label)} · ${c.installed?'bereits installiert – wiederverwenden':'nicht installiert'}
${escape(c.evidence)}${c.route?`
${c.installed?'Laufzeit / Einrichtung ansehen':'Laufzeit herunterladen / bereitmachen'} →`:'
Kein gepflegter Deck-Installer für diese Option verfügbar.'}

`).join('')}`:`

${escape(x.candidate_message||'')}

`}${x.alternative?`

${escape(x.alternative.label)} →

`:''}

Installierte Laufzeit bedeutet nicht, dass Modell und Kontext in den Speicher passen. Ein Test startet nur auf deinen Auftrag.

`; + return `
${escape(x.label)}${x.setup_steps?.length?`
    ${x.setup_steps.map(s=>`
  1. ${escape(s.label)} · ${s.state==='complete'?'Erledigt':s.state==='blocked'?'Noch nicht möglich':'Nächster Schritt'}
    ${escape(s.message)}${s.route&&s.state==='action'?` · Jetzt einrichten →`:''}
  2. `).join('')}
`:''}

Modellfamilie: ${escape(x.family||'Unbekannt')}${x.evidence?' · '+escape(x.evidence):''}

${x.runtime?`

${escape(x.runtime)} · ${x.installed?'bereits installiert – wird wiederverwendet':'noch nicht eingerichtet'}

`:''}

${escape(x.message)}

${x.components?.length?`

Benötigt: ${x.components.map(escape).join(' · ')}. Modellabhängig zuordnen; benötigte Zusatzdateien gehören zum vollständigen Paket.

`:''}${x.suggested_components?.length?`

Zusatzdateien in dieser Quelle (Kompatibilität ungeprüft): ${x.suggested_components.map(escape).join(' · ')}. Nach dem Download unter Profil → Einrichtung & Komponenten prüfen.

`:''}${x.route?`${x.installed?'Einrichtung ansehen':'Ausführung einrichten'} →`:''}${x.candidates?.length?`
Technische Details und Laufzeitoptionen

Diese Liste unterscheidet bekannte Zuordnungen von ungeprüften Kategorieoptionen. Eine Installation ersetzt keinen fehlenden Worker.

${x.candidates.map(c=>`

${escape(c.label)} · ${c.installed?'bereits installiert – wiederverwenden':'nicht installiert'}
${escape(c.evidence)}${c.route?`
${c.installed?'Laufzeit / Einrichtung ansehen':'Laufzeit herunterladen / bereitmachen'} →`:'
Kein gepflegter Deck-Installer für diese Option verfügbar.'}

`).join('')}
`:`

${escape(x.candidate_message||'')}

`}${x.alternative?`

${escape(x.alternative.label)} →

`:''}

Installierte Laufzeit bedeutet nicht, dass Modell und Kontext in den Speicher passen. Ein Test startet nur auf deinen Auftrag.

`; } function html(library, downloads=false) { return `
HUGGING FACE · LIVE-KATALOG

${downloads ? 'Downloads' : library ? 'Deine Dateien auf Athena' : 'Das nächste Modell entdecken'}

${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.'}

diff --git a/execution_setup.py b/execution_setup.py index 41aa6ee..0d3181e 100644 --- a/execution_setup.py +++ b/execution_setup.py @@ -50,7 +50,7 @@ def _assess(model, installed, profile=None): return result -def assess(model, installed, profile=None): +def _assess_candidates(model, installed, profile=None): result=_assess(model,installed,profile) result['candidates']=[] if result['state']=='component':return result @@ -91,3 +91,17 @@ def assess(model, installed, profile=None): add('category',label,key,route,'Option für diese Kategorie; Kompatibilität mit dieser unbekannten Modellfamilie NICHT bestätigt') result['candidate_message']='Keine passende Laufzeit zuverlässig ermittelt. Modellkarte und Architektur müssen geprüft werden; keine automatische Installation auf Verdacht.' return result + + +def assess(model, installed, profile=None): + result=_assess_candidates(model,installed,profile) + if result['state']=='component':return result + verified=result['state'] not in ('unsupported','workflow_missing') + runtime=(next((c for c in result['candidates'] if c['id'] in ('voxcpm','audio_cpp')),None) or next((c for c in result['candidates'] if c['id']!='category'),None)) if verified else None + worker_missing=result['state'] in ('adapter_missing','workflow_missing','unsupported') + result['setup_steps']=[ + dict(label='Modell erkennen',state='complete' if verified else 'blocked',message='Modellfamilie erkannt' if verified else 'Noch kein geprüfter Einrichtungsweg'), + dict(label='Laufzeit bereitstellen',state=('complete' if runtime['installed'] else 'action') if runtime else 'blocked',message=('Vorhandene Laufzeit wird wiederverwendet' if runtime['installed'] else 'Passende Laufzeit einrichten') if runtime else 'Keine automatische Installation auf Verdacht',route=runtime['route'] if runtime else None), + dict(label='Zusatzdateien zuordnen',state='complete' if result['state']=='ready' or not result['components'] and verified else 'action' if verified else 'blocked',message='Unter Einrichtung & Komponenten: vorhandene Dateien verwenden, fehlende herunterladen' if result['components'] else 'Keine Pflichtdateien im geprüften Rezept' if verified else 'Komponentenbedarf noch nicht zuverlässig bekannt'), + dict(label='Ausführung prüfen',state='blocked' if worker_missing else 'complete' if result['state']=='ready' else 'action',message='Generierungsanbindung fehlt noch; Installation allein reicht nicht' if worker_missing else 'Profil bereit' if result['state']=='ready' else 'Nach Einrichtung im Testbereich prüfen')] + return result diff --git a/profiles-ui.js b/profiles-ui.js index 3a9f773..79c312c 100644 --- a/profiles-ui.js +++ b/profiles-ui.js @@ -37,11 +37,11 @@ window.ProfilesUI=(()=>{ el('profile-editor').innerHTML='

Komponenten werden geprüft …

'; try{ const data=await api('profiles/components?'+new URLSearchParams({model_id:profile.model_id,...(extraRepo?{repo:extraRepo}:{})}));if(!root.isConnected||sequence!==panelSequence)return; - el('profile-editor').innerHTML=`

Komponenten · ${e(profile.name)}

${e(data.message)}

Andere Komponentenquelle auswählen (ungeprüft)
${data.supported?`

Quelle: Modellkarte und Lizenz ↗ · ${e(data.license||'Lizenz nicht angegeben')}

${data.runtime_route?`

${data.runtime_installed?'Laufzeit ansehen':'Passende Laufzeit installieren'} →

`:''}

Laufzeit: ${e(data.runtime)} · ${data.manual?'Zuordnung ungeprüft':data.runtime_installed?'installiert':'noch nicht eingerichtet'}

${!data.manual?'':'

Nur benötigte Dateien einzeln auswählen; diese Liste ist kein automatischer Downloadplan.

'}
${data.requirements.map(r=>`

${e(r.label)}

${r.candidates.map((f,i)=>`

${e(f.name)} · ${(f.size/1024**3).toFixed(2)} GiB · Version ${e(f.revision.slice(0,12))}

${f.gated&&!f.auth_configured?'

Hugging-Face-Zugang unter Einstellungen → Zugang hinterlegen und Modellfreigabe prüfen.

':''}
`).join('')||'

Keine passende Quelldatei verfügbar.

'}
`).join('')}

Lade je eine Datei pro benötigter Komponente. Downloads erscheinen im Reiter Downloads. Danach Bibliothek aktualisieren, Dateien auswählen und die Zuordnung speichern. Es startet kein Modell.

`:''}

`; + el('profile-editor').innerHTML=`

Komponenten · ${e(profile.name)}

${CatalogUI.executionHTML(profile.execution)}

${e(data.message)}

Andere Komponentenquelle auswählen (ungeprüft)
${data.supported?`

Quelle: Modellkarte und Lizenz ↗ · ${e(data.license||'Lizenz nicht angegeben')}

${data.runtime_route?`

${data.runtime_installed?'Laufzeit ansehen':'Passende Laufzeit installieren'} →

`:''}

Laufzeit: ${e(data.runtime)} · ${data.manual?'Zuordnung ungeprüft':data.runtime_installed?'installiert':'noch nicht eingerichtet'}

${!data.manual?'':'

Nur benötigte Dateien einzeln auswählen; diese Liste ist kein automatischer Downloadplan.

'}
${data.requirements.map(r=>`

${e(r.label)}

${r.candidates.map((f,i)=>`

${e(f.name)} · ${(f.size/1024**3).toFixed(2)} GiB · Version ${e(f.revision.slice(0,12))}

${f.gated&&!f.auth_configured?'

Hugging-Face-Zugang unter Einstellungen → Zugang hinterlegen und Modellfreigabe prüfen.

':''}
`).join('')||'

Keine passende Quelldatei verfügbar.

'}
`).join('')}

Lade je eine Datei pro benötigter Komponente. Downloads erscheinen im Reiter Downloads. Danach Bibliothek aktualisieren, Dateien auswählen und die Zuordnung speichern. Es startet kein Modell.

`:''}

`; const missing=data.requirements?.filter(r=>!r.available.length&&!r.optional)||[]; if(el('components-download-all')){el('components-download-all').disabled=!missing.length;el('components-download-all').onclick=async()=>{ const b=el('components-download-all');b.disabled=true;let count=0; - try{const plan=missing.map(r=>{const f=r.candidates.find(f=>!f.gated||f.auth_configured);if(!f)throw Error('Downloadplan unvollständig: '+r.label+'. Quelle oder HF-Zugang prüfen. Es wurde noch kein Download gestartet.');return f;});for(const f of plan){const catalog=await api('catalog');if((catalog.downloads||[]).some(j=>['queued','downloading'].includes(j.state)&&j.repo===f.repo&&j.file===f.name&&j.revision===f.revision))continue;await api('catalog/download',{repo:f.repo,filename:f.name,revision:f.revision,kind:profile.kind});count++;}if(el('components-message'))el('components-message').textContent='Dateien sind im Downloadplan. Nach Abschluss Bibliothek aktualisieren und Zuordnung speichern; passende Dateien werden vorausgewählt.';} + try{const plan=missing.map(r=>{const choices=r.candidates.filter(f=>!f.gated||f.auth_configured);if(choices.length>1)throw Error('Bitte Variante für '+r.label+' einzeln auswählen. Es wurde noch kein Download gestartet.');const f=choices[0];if(!f)throw Error('Downloadplan unvollständig: '+r.label+'. Quelle oder HF-Zugang prüfen. Es wurde noch kein Download gestartet.');return f;});for(const f of plan){const catalog=await api('catalog');if((catalog.downloads||[]).some(j=>['queued','downloading'].includes(j.state)&&j.repo===f.repo&&j.file===f.name&&j.revision===f.revision))continue;await api('catalog/download',{repo:f.repo,filename:f.name,revision:f.revision,kind:profile.kind});count++;}if(el('components-message'))el('components-message').textContent='Dateien sind im Downloadplan. Nach Abschluss Bibliothek aktualisieren und Zuordnung speichern; passende Dateien werden vorausgewählt.';} catch(error){if(el('components-message'))el('components-message').textContent=(count?count+' Datei(en) bereits im Downloadplan. ':'')+error.message;b.disabled=false;} };} el('component-source-open').onclick=()=>{const value=el('component-source').value.trim();if(value)components(profile,value);}; diff --git a/profiles.py b/profiles.py index c330104..41ead60 100644 --- a/profiles.py +++ b/profiles.py @@ -278,10 +278,10 @@ class Profiles: candidates=[dict(f,repo=source.get('repo',info.get('repo',model['repo'])),revision=source['revision'],gated=source['gated'],auth_configured=source.get('auth_configured',False)) 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)) if voice_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='VoxCPM · eigene Python/PyTorch-Laufzeit',runtime_installed=bool(getattr(self,'voxcpm_runtime_ready',lambda:False)()),runtime_route='#voxcpm-runtime',worker_available=False,message='VoxCPM2 benötigt genau diese fünf Zusatzdateien derselben Quellversion. Ein separater Textencoder und ein Vision-Projektor werden nicht benötigt. tokenizer.json und tokenizer_config.json gehören zusammen, sie sind keine doppelten Modelle. Vorhandene Dateien werden wiederverwendet. Die passende Laufzeit kann unter Einstellungen → Laufzeiten → VoxCPM installiert werden. Der Deck-Worker fehlt noch; ein vollständiges Dateipaket allein kann noch nicht gestartet werden.') - if music_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='audio.cpp',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Passendes YuE2-GGUF-Paket: je eine VAE-Datei und die vier Zusatzdateien derselben Quellversion. F16-VAE ist die erste Downloadempfehlung. Vorhandene Dateien wiederverwenden, fehlende herunterladen und danach zuordnen. Die YuE2-Musikanbindung verwendet diese Dateien über audio.cpp. Speichern startet kein Modell; anschließend Musik → Testen verwenden.') - if video:return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='ComfyUI · gemeinsame Bild- und Videolaufzeit',runtime_installed=bool(getattr(self,'video_runtime_ready',lambda:False)()),message='Geprüftes Dateirezept nach Herstellerdokumentation für die zweistufige Distilled-Pipeline mit fester Bildanzahl. Alle Komponenten stammen aus derselben Version wie dein Transformer. Kein beliebiger Gemma-Encoder; keine Comfy-INT8-Dateien. Optionaler Duration-Head, Temporal-Upsampling und DFR sind nicht Teil dieses Rezepts. Vorhandene ComfyUI-Laufzeit wiederverwenden; Videomodell im Bereich Video aktivieren. Vollständige Dateien sind keine Speicherzusage.') + if music_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='audio.cpp',runtime_route='#audio-cpp-runtime',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Passendes YuE2-GGUF-Paket: je eine VAE-Datei und die vier Zusatzdateien derselben Quellversion. F16-VAE ist die erste Downloadempfehlung. Vorhandene Dateien wiederverwenden, fehlende herunterladen und danach zuordnen. Die YuE2-Musikanbindung verwendet diese Dateien über audio.cpp. Speichern startet kein Modell; anschließend Musik → Testen verwenden.') + if video:return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='ComfyUI · gemeinsame Bild- und Videolaufzeit',runtime_route='#image-runtime',runtime_installed=bool(getattr(self,'video_runtime_ready',lambda:False)()),message='Geprüftes Dateirezept nach Herstellerdokumentation für die zweistufige Distilled-Pipeline mit fester Bildanzahl. Alle Komponenten stammen aus derselben Version wie dein Transformer. Kein beliebiger Gemma-Encoder; keine Comfy-INT8-Dateien. Optionaler Duration-Head, Temporal-Upsampling und DFR sind nicht Teil dieses Rezepts. Vorhandene ComfyUI-Laufzeit wiederverwenden; Videomodell im Bereich Video aktivieren. Vollständige Dateien sind keine Speicherzusage.') if not image_recipe(model):return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime='audio.cpp · Paketdefinition',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Zusatzdateien aus der installierten Laufzeit-Paketdefinition. Weitere modellabhängige Pakete können erforderlich sein; dies ist keine vollständige Worker-Zusage.') - return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime=image_adapter(model)['runtime'],runtime_installed=self.image_runtime_ready(), + return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime=image_adapter(model)['runtime'],runtime_route='#image-runtime',runtime_installed=self.image_runtime_ready(), message='Fehlende Dateien herunterladen, danach Bibliothek aktualisieren und zuordnen. Für FLUX.2 Klein empfehlen sich zum Einstieg 4 Schritte und Guidance 1. Die Bildgewichte werden bei Bedarf zwischen RAM und RTX 5080 verschoben.' if image_adapter(model)['workflow']=='flux' else 'Zusatzdateien laut Modellkarte für ComfyUI. Kompatibilität und Speicherbedarf müssen mit der eingerichteten Laufzeit noch praktisch geprüft werden.') def manual_components(self,model,repo=None): source=self.catalog.files(repo or model['repo'],None if repo else model['revision']) diff --git a/test_execution_setup.py b/test_execution_setup.py index 7c93bba..d43f5f2 100644 --- a/test_execution_setup.py +++ b/test_execution_setup.py @@ -51,3 +51,14 @@ class ExecutionSetupTests(unittest.TestCase): result=self.check('music','audio-cpp/Yue2-3B-GGUF','yue2-vae-f16.gguf') self.assertEqual(result['state'],'component') self.assertEqual(result['candidates'],[]) + + def test_shared_setup_steps_reuse_runtime_and_never_claim_missing_worker(self): + for model,installed in [(dict(kind='image',repo=QWEN_REPO,file='model.gguf'),{'image':True}),(dict(kind='voice',repo='openbmb/VoxCPM2',file='model.safetensors'),{'voxcpm':True})]: + result=assess(model,installed) + self.assertEqual(result['setup_steps'][1]['state'],'complete') + self.assertIn('wiederverwendet',result['setup_steps'][1]['message']) + self.assertEqual(result['setup_steps'][-1]['state'],'blocked') + unknown=self.check('voice','unknown/model','model.safetensors') + self.assertTrue(all(s['state']=='blocked' for s in unknown['setup_steps'])) + ready=assess(dict(kind='image',repo=QWEN_REPO,file='model.gguf'),{'image':True},dict(runnable=True)) + self.assertTrue(all(s['state']=='complete' for s in ready['setup_steps']))