diff --git a/app.js b/app.js index b52079d..d6504f2 100644 --- a/app.js +++ b/app.js @@ -24,6 +24,7 @@ function appearancePage(){ sync(); } async function refresh(){if(refreshing)return;refreshing=true;const page=location.hash.slice(1)||'dashboard';document.querySelectorAll('nav a').forEach(a=>a.classList.toggle('active',a.hash==='#'+page));try{error.textContent='';let html; +if(page==='audio-cpp-runtime'){AudioCppUI.render();return;} if(page==='ltx-original-runtime'){LTXOriginalUI.render();return;} if(['video','video-runtime'].includes(page)){VideoUI.runtime();return;} if(['chat','image','audio','stt','music','voice','video','runtime','runtimes','image-runtime','stt-runtime','tts-runtime','docker-runtime','services'].includes(page)){Studio.render(page);return;} diff --git a/audio-cpp-ui.js b/audio-cpp-ui.js new file mode 100644 index 0000000..455cdd6 --- /dev/null +++ b/audio-cpp-ui.js @@ -0,0 +1,10 @@ +window.AudioCppUI=(()=>{ + const e=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + async function api(path='',data){const r=await fetch('/api/v1/audio-cpp-runtime'+path,data?{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)}:{});const s=await r.json();if(!r.ok)throw Error(s.error||'Anfrage fehlgeschlagen');return s;} + function render(){const view=document.querySelector('#view');if(view.querySelector('#audio-cpp-page'))return; + view.innerHTML='

audio.cpp

Gemeinsame C++-/CUDA-Laufzeit für unterstützte Audio-Modellfamilien. Passendes GGUF-Paket und ein Deck-Worker sind zusätzlich erforderlich.

Herunterladen und bereitmachen

Eigene Build-Ausgabe

YuE2 und Ladypoly WebUI

Modellfamilie erkannt. Für Generierung fehlen noch der Deck-Musikworker und die Verbindung zur Ladypoly-Oberfläche. Dieser Build lädt keine Gewichte, startet kein Modell und gibt kein Profil am Endpunkt frei.

Zur Musik-Bibliothek →
'; + const root=view.firstChild;let timer; + async function poll(){if(!root.isConnected)return;try{const s=await api();if(!root.isConnected)return;const running=s.job?.state==='running';root.querySelector('#ac-install').disabled=s.installed||running;root.querySelector('#ac-cancel').disabled=!running;root.querySelector('#ac-status').innerHTML=`

${s.installed?'Installiert · Worker-Anbindung noch offen':'Noch nicht installiert'}

Version ${e(s.version)} · Commit ${e(s.revision)}

Mindestens ${e(s.required_disk_gib)} GiB freier Speicher. Eigener Build mit sechs Jobs, CUDA-Ziele werden aus den vorhandenen GPUs ermittelt. Bestehende Laufzeiten werden nicht verändert.

${s.job?`

${e(s.job.state)} · ${e(s.job.phase)}

${running?'':''}`:''}`;if(s.job){const log=await api('/log');if(root.isConnected)root.querySelector('#ac-log').textContent=log.text;}}catch(x){if(root.isConnected)root.querySelector('#ac-message').textContent=x.message;}if(root.isConnected){clearTimeout(timer);timer=setTimeout(poll,3000);}} + for(const [id,path] of [['ac-install','/install'],['ac-cancel','/cancel']])root.querySelector('#'+id).onclick=async()=>{root.querySelector('#'+id).disabled=true;try{await api(path,{});root.querySelector('#ac-message').textContent='Aktion angenommen.';}catch(x){root.querySelector('#ac-message').textContent=x.message;}poll();};poll();} + return {render}; +})(); diff --git a/audio_cpp_runtime.py b/audio_cpp_runtime.py new file mode 100644 index 0000000..8a5501a --- /dev/null +++ b/audio_cpp_runtime.py @@ -0,0 +1,73 @@ +"""Pinned audio.cpp build, isolated from model execution and existing runtimes.""" +import json +import os +from pathlib import Path +import shutil +import sys +import time +from ltx_original_runtime import LTXOriginalRuntime + +REVISION='f2b4937306daa25f5c78520f3c626ed31495a37a' +VERSION='v0.8.1' +class AudioCppRuntime(LTXOriginalRuntime): + name="audio.cpp" + def __init__(self,root): + super().__init__(root,None) + request=self.root/'resume-request' + if request.is_file(): + request.unlink() + self.start() + def paths(self): + p=self.root/'active.json' + if p.is_file(): + ident=json.loads(p.read_text()).get('id','') + if len(ident)==32 and all(c in '0123456789abcdef' for c in ident): + base=self.root/ident + if (base/'ready').is_file():return base/'build/bin/audiocpp_cli',base/'source' + return self.root/'missing-cli',self.root/'missing-source' + def status(self): + cli,source=self.paths() + return dict(installed=cli.is_file() and (source/'model_specs/yue2.json').is_file(),revision=REVISION,version=VERSION,job=dict(self.job) if self.job else None,required_disk_gib=6,adapter_ready=False,origin='0xShug0/audio.cpp',message='Gemeinsame Laufzeit. Ein Build startet kein Modell; YuE2-Worker und Ladypoly-Anbindung sind noch nicht eingerichtet.') + def start(self): + if sys.platform!='linux':raise ValueError('audio.cpp wird auf dem Debian-Server gebaut.') + missing=[name for name in ('git','cmake','g++','nvcc','nvidia-smi') if not shutil.which(name)] + if missing:raise ValueError('Build-Werkzeuge fehlen: '+', '.join(missing)+'. Keine automatische Host-Paket- oder Treiberänderung.') + self.resume_id=(self.job or {}).get('id') + return super().start() + def _run(self): + base=self.root/self.job['id'];state='failed';phase='audio.cpp-Build fehlgeschlagen.' + try: + previous=self.root/getattr(self,'resume_id','missing') if getattr(self,'resume_id',None) else None + if previous and (previous/'source/.git').is_dir() and not (previous/'ready').exists(): + base=previous;self.job['id']=previous.name;self._save() + else:base.mkdir(mode=0o700) + tmp=base/'tmp';tmp.mkdir(exist_ok=True) + env=dict(os.environ,TMPDIR=str(tmp),GIT_TERMINAL_PROMPT='0',OMP_NUM_THREADS='2') + source=base/'source';build=base/'build' + self._phase('Offizielle audio.cpp-Quelle herunterladen') + if not (source/'.git').is_dir(): + self._command(['git','init',source],env) + self._command(['git','-C',source,'fetch','--depth','1','https://github.com/0xShug0/audio.cpp.git',REVISION],env) + self._command(['git','-C',source,'checkout','--detach','FETCH_HEAD'],env) + else:self._command(['git','-C',source,'rev-parse','--verify',REVISION+'^{commit}'],env) + # Compile for the actual available GPUs; no weight allocation or inference. + import subprocess + raw=subprocess.check_output(['nvidia-smi','--query-gpu=compute_cap','--format=csv,noheader'],text=True) + arch=sorted({line.strip().replace('.','') for line in raw.splitlines() if line.strip()}) + if not arch or any(not a.isdigit() for a in arch):raise ValueError('CUDA-Architekturen nicht ermittelbar.') + self._phase('CUDA-Build konfigurieren · GPU-Ziele '+', '.join(arch)) + self._command(['cmake','-S',source,'-B',build,'-DCMAKE_BUILD_TYPE=Release','-DENGINE_ENABLE_CUDA=ON','-DCMAKE_CUDA_ARCHITECTURES='+';'.join(arch),'-DAUDIOCPP_DEPLOYMENT_BUILD=ON','-DAUDIOCPP_BUILD_SERVER_FRONTENDS=OFF','-DENGINE_BUILD_TESTS=OFF'],env) + self._phase('audio.cpp bauen · sechs parallele Build-Jobs') + self._command(['cmake','--build',build,'--target','audiocpp_cli','audiocpp_server','--parallel','6'],env) + self._phase('Programmstart ohne Modell prüfen') + self._command([build/'bin/audiocpp_cli','--help'],env) + if not (source/'model_specs/yue2.json').is_file():raise ValueError('YuE2-Modellspezifikation fehlt.') + if self.cancel.is_set():raise InterruptedError() + (base/'ready').write_text(REVISION) + marker=self.root/'active.tmp';marker.write_text(json.dumps({'id':base.name}));marker.replace(self.root/'active.json') + state='complete';phase='audio.cpp installiert. Kein Modell gestartet; Deck-Worker noch erforderlich.' + except InterruptedError:state='cancelled';phase='Build abgebrochen.' + except Exception as exc:phase=str(exc) + finally: + # Keep unfinished compiler artifacts so retry resumes the same pinned build. + with self.lock:self.process=None;self.job.update(state=state,phase=phase,finished_at=time.time());self._save() diff --git a/backup.py b/backup.py index a3a1802..50060cb 100644 --- a/backup.py +++ b/backup.py @@ -72,7 +72,8 @@ def validate(doc): if 'video/original-work/settings.json' in settings and (not isinstance(settings['video/original-work/settings.json'],dict) or len(json.dumps(settings['video/original-work/settings.json']))>65536):raise ValueError('Ungültige LTX-Einstellungen.') validate_record(doc.get('credentials')) runtimes=doc.get('runtimes') - if not isinstance(runtimes,dict) or set(runtimes)-{'llama','llama_builds','image','tts','enhancers','swarm_nodes','ltx_original'} or not {'llama','llama_builds','image','tts','enhancers','swarm_nodes'}<=set(runtimes):raise ValueError('Ungültiges Laufzeitrezept.') + if not isinstance(runtimes,dict) or set(runtimes)-{'llama','llama_builds','image','tts','enhancers','swarm_nodes','ltx_original','audio_cpp'} or not {'llama','llama_builds','image','tts','enhancers','swarm_nodes'}<=set(runtimes):raise ValueError('Ungültiges Laufzeitrezept.') + if 'audio_cpp' in runtimes and type(runtimes['audio_cpp']) is not bool:raise ValueError('Ungültige audio.cpp-Laufzeit.') if 'ltx_original' in runtimes and type(runtimes['ltx_original']) is not bool:raise ValueError('Ungültige LTX-Laufzeit.') if not isinstance(runtimes['llama_builds'],list) or len(runtimes['llama_builds'])>50 or any(not isinstance(b,dict) or set(b)!={'commit','backend'} or not re.fullmatch('[a-f0-9]{40}',b.get('commit','')) or b.get('backend') not in ('CUDA','CPU') for b in runtimes['llama_builds']):raise ValueError('Ungültige Buildrezepte.') if runtimes['llama'] is not None: @@ -137,7 +138,10 @@ class Backup: if not s.network.disabled: network=s.network.call('backup-export') else:warnings.append('Deck-Netzwerkmodul deaktiviert; fremder WireGuard-Gateway wird nicht gesichert.') - document=dict(format='athena-deck',version=VERSION,created_at=time.time(),settings=settings,models=models,credentials=s.credentials.read(),theme=theme,network=network,services=services,runtime_versions=dict(image=dict(comfy=s.image_runtime.status()['comfy_revision'],gguf=s.image_runtime.status()['gguf_revision']),tts=s.tts_runtime.status()['revision']),runtimes=dict(ltx_original=bool(getattr(s,'ltx_original_runtime',None) and s.ltx_original_runtime.status()['installed']),llama=llama,llama_builds=[dict(commit=b['commit'],backend=b['backend']) for b in state['builds']],image=s.image_runtime.status()['installed'],tts=s.tts_runtime.status()['installed'],enhancers={task:data['revision'] for task in ('t2i','i2i') if (data:=s.prompt_enhancer.installed(task))},swarm_nodes=(self.root/'video/swarm-comfy-nodes').is_dir()),warnings=warnings) + document=dict(format='athena-deck',version=VERSION,created_at=time.time(),settings=settings,models=models,credentials=s.credentials.read(),theme=theme,network=network,services=services,runtime_versions=dict(image=dict(comfy=s.image_runtime.status()['comfy_revision'],gguf=s.image_runtime.status()['gguf_revision']),tts=s.tts_runtime.status()['revision']),runtimes=dict(audio_cpp=bool(getattr(s,'audio_cpp_runtime',None) and s.audio_cpp_runtime.status()['installed']),ltx_original=bool(getattr(s,'ltx_original_runtime',None) and s.ltx_original_runtime.status()['installed']),llama=llama,llama_builds=[dict(commit=b['commit'],backend=b['backend']) for b in state['builds']],image=s.image_runtime.status()['installed'],tts=s.tts_runtime.status()['installed'],enhancers={task:data['revision'] for task in ('t2i','i2i') if (data:=s.prompt_enhancer.installed(task))},swarm_nodes=(self.root/'video/swarm-comfy-nodes').is_dir()),warnings=warnings) + if document['runtimes']['audio_cpp']: + from audio_cpp_runtime import REVISION + document['runtime_versions']['audio_cpp']=REVISION if document['runtimes']['ltx_original']: from ltx_original_runtime import DESKTOP_REV,LTX_REV document['runtime_versions']['ltx_original']=dict(desktop=DESKTOP_REV,inference=LTX_REV) @@ -167,10 +171,13 @@ class Backup: elif s.network.status().get('enabled') or s.network.status().get('mode')!='lan':blockers.append('Deck-Tunnel vor Netzwerk-Restore im bestätigten LAN-Modus deaktivieren.') if doc['network']['mode']!='lan':warnings.append('Tunnelzugang wird mit Rückfallzeit aktiviert und muss über den Tunnel bestätigt werden.') versions=doc.get('runtime_versions',{}) + if versions.get('audio_cpp'): + from audio_cpp_runtime import REVISION + if versions['audio_cpp']!=REVISION:blockers.append('Backup verlangt eine andere audio.cpp-Version. Passende Deck-Version verwenden.') if versions.get('ltx_original'): from ltx_original_runtime import DESKTOP_REV,LTX_REV if versions['ltx_original']!=dict(desktop=DESKTOP_REV,inference=LTX_REV):blockers.append('Backup verlangt eine andere originale LTX-Version. Passende Deck-Version verwenden.') - if versions and {k:v for k,v in versions.items() if k!='ltx_original'}!=dict(image=dict(comfy=s.image_runtime.status()['comfy_revision'],gguf=s.image_runtime.status()['gguf_revision']),tts=s.tts_runtime.status()['revision']):blockers.append('Backup verlangt andere Bild-/TTS-Laufzeitversionen. Passende Deck-Version oder geprüftes Runtime-Upgrade verwenden.') + if versions and {k:v for k,v in versions.items() if k not in ('ltx_original','audio_cpp')}!=dict(image=dict(comfy=s.image_runtime.status()['comfy_revision'],gguf=s.image_runtime.status()['gguf_revision']),tts=s.tts_runtime.status()['revision']):blockers.append('Backup verlangt andere Bild-/TTS-Laufzeitversionen. Passende Deck-Version oder geprüftes Runtime-Upgrade verwenden.') for c in doc['services']: if not c.get('registry') and c.get('build_recipe')!='swarm-ui':warnings.append(c['name']+': Image nur lokal vorhanden; Registry-/Buildquelle fehlt.') ports=s.endpoint.allowed_ports @@ -195,7 +202,7 @@ class Backup: if len(set(data['services']))!=len(data['services']) or any(name not in services for name in data['services']):raise ValueError('Unbekannter Zusatzdienst.') if any(not services[name].get('registry') and not services[name].get('build_recipe') for name in data['services']):raise ValueError('Gewählter Dienst hat keine Wiederherstellungsquelle.') s=self.server - if s.catalog.pending or s.catalog.job and s.catalog.job['state']=='downloading' or s.runtime.busy or any((getattr(o,'job',None) or {}).get('state')=='running' for o in (s.image_runtime,s.tts_runtime,s.ltx_original_runtime,s.prompt_enhancer,s.auto_tests,s.chat_tests,s.image_tests,s.tts_tests,s.stt)):raise ValueError('Zuerst laufende Downloads, Builds und Tests abschließen oder abbrechen.') + if s.catalog.pending or s.catalog.job and s.catalog.job['state']=='downloading' or s.runtime.busy or any((getattr(o,'job',None) or {}).get('state')=='running' for o in (s.image_runtime,s.tts_runtime,s.ltx_original_runtime,getattr(s,'audio_cpp_runtime',None),s.prompt_enhancer,s.auto_tests,s.chat_tests,s.image_tests,s.tts_tests,s.stt)):raise ValueError('Zuerst laufende Downloads, Builds und Tests abschließen oder abbrechen.') self.cancel.clear();self.job=dict(id=secrets.token_hex(16),state='running',phase='Wiederherstellung vorbereiten',completed=0,total=len(doc['models'])+len(data['services'])+5,items=[],started_at=time.time(),theme=doc.get('theme','deck')) self._save();threading.Thread(target=self._restore,args=(copy.deepcopy(doc),data['services'],data['restore_credentials']),daemon=True).start();self.plan=None return self.status() @@ -221,6 +228,8 @@ class Backup: self._phase('Vorhandene Bild-/Videolaufzeit prüfen oder installieren');s.image_runtime.start();self._wait(s.image_runtime.status) if r['tts'] and not s.tts_runtime.status()['installed']: self._phase('TTS-Laufzeit und Zusatzdateien installieren');s.tts_runtime.start();self._wait(s.tts_runtime.status) + if r.get('audio_cpp') and not s.audio_cpp_runtime.status()['installed']: + self._phase('audio.cpp-Laufzeit installieren');s.audio_cpp_runtime.start();self._wait(s.audio_cpp_runtime.status) if r.get('ltx_original') and not s.ltx_original_runtime.status()['installed']: self._phase('Originale LTX-Laufzeit installieren');s.ltx_original_runtime.start();self._wait(s.ltx_original_runtime.status) targets=r['llama_builds']+([r['llama']] if r['llama'] and r['llama'] not in r['llama_builds'] else []) @@ -360,5 +369,7 @@ class Backup: with self.lock: self.cancel.set() if self.busy(): - self.server.catalog.stop();self.server.runtime.stop();self.server.image_runtime.stop();self.server.tts_runtime.stop();self.server.ltx_original_runtime.stop();self.server.prompt_enhancer.stop() + self.server.catalog.stop();self.server.runtime.stop();self.server.image_runtime.stop();self.server.tts_runtime.stop();self.server.ltx_original_runtime.stop(); + if getattr(self.server,'audio_cpp_runtime',None):self.server.audio_cpp_runtime.stop() + self.server.prompt_enhancer.stop() return {'cancellation_requested':True} diff --git a/catalog-ui.js b/catalog-ui.js index ca98eed..eb14c7f 100644 --- a/catalog-ui.js +++ b/catalog-ui.js @@ -9,7 +9,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(' · ')}. Bei Profilen unter „Komponenten“ zuordnen; TTS enthält das komplette Paket.

`:''}${x.route?`${x.installed?'Einrichtung ansehen':'Ausführung einrichten'} →`:''}${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)}

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.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.

`; } 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/catalog.py b/catalog.py index 9d63bd9..6a7599a 100644 --- a/catalog.py +++ b/catalog.py @@ -197,7 +197,7 @@ class Catalog: files=[] for f in data.get('siblings',[]): name=f['rfilename'];p=PurePosixPath(name) - if p.is_absolute() or '..' in p.parts or p.suffix.lower() not in ('.gguf','.safetensors','.json'):continue + if p.is_absolute() or '..' in p.parts or p.suffix.lower() not in ('.gguf','.safetensors','.json','.tiktoken'):continue size=f.get('size',f.get('lfs',{}).get('size')) if not isinstance(size,int) or size<1:continue files.append(dict(name=name,size=size,sha256=f.get('lfs',{}).get('sha256'))) diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 5c50044..83b7483 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -16,8 +16,8 @@ WORKDIR /app COPY deploy/image-requirements.lock /app/deploy/image-requirements.lock COPY deploy/tts-requirements.lock /app/deploy/tts-requirements.lock COPY deploy/ltx-original-requirements.lock /app/deploy/ltx-original-requirements.lock -COPY ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/ -COPY ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/ +COPY audio_cpp_runtime.py ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/ +COPY audio-cpp-ui.js ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/ COPY deploy/docker_backup.py /app/deploy/docker_backup.py COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \ diff --git a/deploy/Dockerfile.app-update b/deploy/Dockerfile.app-update index 5b8154f..4822bb5 100644 --- a/deploy/Dockerfile.app-update +++ b/deploy/Dockerfile.app-update @@ -6,8 +6,8 @@ WORKDIR /app COPY deploy/image-requirements.lock /app/deploy/image-requirements.lock COPY deploy/tts-requirements.lock /app/deploy/tts-requirements.lock COPY deploy/ltx-original-requirements.lock /app/deploy/ltx-original-requirements.lock -COPY ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/ -COPY ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/ +COPY audio_cpp_runtime.py ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/ +COPY audio-cpp-ui.js ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/ COPY deploy/docker_backup.py /app/deploy/docker_backup.py COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \ diff --git a/deploy/install.py b/deploy/install.py index 5f68234..d7ecb5f 100644 --- a/deploy/install.py +++ b/deploy/install.py @@ -14,7 +14,7 @@ import urllib.request ROOT = Path(__file__).resolve().parent.parent LABEL = 'de.casaderoll.athena-deck.standalone' -FILES = ['ltx_original_runtime.py','ltx_original_entry.py','video_original.py','ltx-original-ui.js','deploy/ltx-original-requirements.lock','backup.py','backup_codec.py','backup-ui.js','deploy/docker_backup.py','deploy/swarm-ui/Dockerfile','deploy/swarm-ui/patch-source.py','deploy/swarm-ui/proxy.py','deploy/swarm-ui/entrypoint.py','deploy/Dockerfile.app-update','video_comfy.py','video_comfy_node.py','video_comfy_proxy.py','prompt_enhancer.py','prompt_enhancer_worker.py','image_upload.py','audio_policy.py','deploy/tts-requirements.lock','stt.py','stt-ui.js','api_compat.py','execution_setup.py','tts_runtime.py','tts_test.py','tts_worker.py','tts-ui.js','auto_test.py','auto-test-ui.js','chat_test.py','chat-test-ui.js','endpoint.py','inference.py','endpoint-ui.js','docker_support.py','docker-ui.js','deploy/docker_helper.py','deploy/setup_docker_helper.py','image_encoder_node.py','image_runtime.py','image_test.py','image-test-ui.js','profiles.py','profiles-ui.js','capacity.py','runtime.py','runtime-ui.js','video.py','video_proxy.py','video-ui.js','catalog.py','hub_auth.py','catalog-ui.js','server.py','auth.py','collect_hardware.py','dashboard_data.py','dashboard_history.py','dashboard-ui.js','dashboard.css','themes.css','deploy/import_dashboard_history.py','index.html','app.js','studio.js','style.css','login.html','login.js','access-ui.js','network-ui.js','network/__init__.py','network/client.py','network/config.py','network/rpc.py','deploy/Dockerfile','deploy/image-requirements.lock'] +FILES = ['audio_cpp_runtime.py','audio-cpp-ui.js','ltx_original_runtime.py','ltx_original_entry.py','video_original.py','ltx-original-ui.js','deploy/ltx-original-requirements.lock','backup.py','backup_codec.py','backup-ui.js','deploy/docker_backup.py','deploy/swarm-ui/Dockerfile','deploy/swarm-ui/patch-source.py','deploy/swarm-ui/proxy.py','deploy/swarm-ui/entrypoint.py','deploy/Dockerfile.app-update','video_comfy.py','video_comfy_node.py','video_comfy_proxy.py','prompt_enhancer.py','prompt_enhancer_worker.py','image_upload.py','audio_policy.py','deploy/tts-requirements.lock','stt.py','stt-ui.js','api_compat.py','execution_setup.py','tts_runtime.py','tts_test.py','tts_worker.py','tts-ui.js','auto_test.py','auto-test-ui.js','chat_test.py','chat-test-ui.js','endpoint.py','inference.py','endpoint-ui.js','docker_support.py','docker-ui.js','deploy/docker_helper.py','deploy/setup_docker_helper.py','image_encoder_node.py','image_runtime.py','image_test.py','image-test-ui.js','profiles.py','profiles-ui.js','capacity.py','runtime.py','runtime-ui.js','video.py','video_proxy.py','video-ui.js','catalog.py','hub_auth.py','catalog-ui.js','server.py','auth.py','collect_hardware.py','dashboard_data.py','dashboard_history.py','dashboard-ui.js','dashboard.css','themes.css','deploy/import_dashboard_history.py','index.html','app.js','studio.js','style.css','login.html','login.js','access-ui.js','network-ui.js','network/__init__.py','network/client.py','network/config.py','network/rpc.py','deploy/Dockerfile','deploy/image-requirements.lock'] def run(*args, check=True, interactive=False): @@ -95,7 +95,7 @@ def launch(config): base=Path(config['base']) args=['docker','run','-d','--name',config['name'],'--label',LABEL+'='+str(base), '--restart','unless-stopped','--read-only','--cap-drop','ALL','--security-opt','no-new-privileges:true', - '--pids-limit','256' if config.get('image_runtime') else '128','--memory','32g' if config.get('image_runtime') else '8g','--cpus','2', '--tmpfs','/tmp:rw,nosuid,nodev,size=256m', + '--pids-limit','256' if config.get('image_runtime') else '128','--memory','32g' if config.get('image_runtime') else '8g','--cpus','6', '--tmpfs','/tmp:rw,nosuid,nodev,size=256m', '-v',str(base/'state')+':/var/lib/deck:rw','--mount','type=bind,src=/proc,dst=/host/proc,readonly','-e','DECK_HOST_PROC=/host/proc','-e','DECK_ALLOWED_HOSTS=127.0.0.1:'+str(config['port'])+',localhost:'+str(config['port']), '-p','127.0.0.1:'+str(config['port'])+':8108', '--health-cmd', 'python3 -c "import urllib.request,json; assert json.load(urllib.request.urlopen(\'http://127.0.0.1:8108/api/v1/auth/status\',timeout=3))[\'initialized\']"', @@ -204,7 +204,7 @@ def update(base, rollback=False, force=False, api_ports=None, reuse_runtime=Fals backup_name=config['name']+'-previous' if inspect(backup_name):raise RuntimeError('Rückfall-Containername belegt. Keine Änderung ausgeführt.') stamp=str(time.time_ns()) - shutil.copytree(base/'state',base/'backups'/stamp,ignore=shutil.ignore_patterns('ltx-original-runtime','original-work','tts-runtime','tts-tests','models','runtime','runtime-verification','image-tests','image-verification','image-verification-*','image-runtime','video-runtime','video-verification','video-verification-*','comfy-work','router-verification-*')) + shutil.copytree(base/'state',base/'backups'/stamp,ignore=shutil.ignore_patterns('audio-cpp-runtime','ltx-original-runtime','original-work','tts-runtime','tts-tests','models','runtime','runtime-verification','image-tests','image-verification','image-verification-*','image-runtime','video-runtime','video-verification','video-verification-*','comfy-work','router-verification-*')) was_running=previous['State']['Running'] if was_running:run('docker','stop','--time','15',config['name']) run('docker','rename',config['name'],backup_name) diff --git a/docs/RUNTIME_DISCOVERY.md b/docs/RUNTIME_DISCOVERY.md new file mode 100644 index 0000000..36bf63f --- /dev/null +++ b/docs/RUNTIME_DISCOVERY.md @@ -0,0 +1,17 @@ +# Laufzeiten erkennen und vorbereiten + +In **Bibliothek → Modell** und bei der Einrichtung von Profilen zeigt Deck die erkannte Modellfamilie, den Ausführungsstatus und **Mögliche Laufzeiten**. Das gilt für Sprache/Chat, Bild, TTS, STT, Musik, Stimme und Video. + +Eine vorhandene Laufzeit wird wiederverwendet. Ein fehlender Worker oder Workflow bleibt ausdrücklich sichtbar. Unbekannte Familien erhalten nur gekennzeichnete Recherchehinweise aus ihrer Kategorie; das ist keine Kompatibilitätszusage. Es wird nichts automatisch installiert. + +Über den Link „Laufzeit herunterladen / bereitmachen“ gelangt man zur jeweiligen Laufzeitseite. Dort wird die Installation bewusst gestartet. Ohne gepflegten Installer zeigt Deck dies an, statt eine Installation vorzutäuschen. + +## YuE2 GGUF / audio.cpp + +`audio-cpp/Yue2-3B-GGUF`, Hauptdateien `yue2-3b-*.gguf`, werden als YuE2 erkannt. Die VAE-Dateien sind Komponenten, keine eigenständigen Modelle. Das Paket braucht zusätzlich VAE, Modell-/Generierungskonfiguration, VAE-Konfiguration und Qwen-Tokenizer; `.tiktoken` ist herunterladbar. + +**Einstellungen → Laufzeiten → audio.cpp** bietet einen isolierten CUDA-Build aus `0xShug0/audio.cpp`, Version v0.8.1, Commit `f2b4937306daa25f5c78520f3c626ed31495a37a`. Voraussetzung: Debian/Linux, Git, CMake, g++, nvcc, nvidia-smi und mindestens 6 GiB frei. GPU-Architekturen werden gelesen; gebaut wird mit sechs Jobs. Keine Host-Pakete, Treiber oder bestehenden Laufzeiten werden verändert. Fehlende Voraussetzungen werden gemeldet. + +Installation liegt im Deck-Zustandsverzeichnis unter `audio-cpp-runtime`. Status, Abbruch und eigene Build-Ausgabe sind in der GUI verfügbar. Der CLI-Start wird ohne Modell geprüft. Dieser Build lädt **keine Gewichte**. Der ausführbare Deck-Musikworker und die Ladypoly-WebUI-Anbindung fehlen noch; sie sind separat umzusetzen. Auch andere audio.cpp-Familien brauchen geprüfte modellspezifische Anbindungen. + +Interne Admin-API: `GET /api/v1/audio-cpp-runtime`, `GET /api/v1/audio-cpp-runtime/log`, `POST /api/v1/audio-cpp-runtime/install` und `/cancel` (JSON `{}`, vorhandene Sitzung und CSRF-Header). Backup/Restore enthält das gepinnte Laufzeitrezept und baut es bei Bedarf neu; keine Binärdateien im Backup. diff --git a/execution_setup.py b/execution_setup.py index 59fcf86..f4ed6ca 100644 --- a/execution_setup.py +++ b/execution_setup.py @@ -4,7 +4,7 @@ from stt import supported as stt_supported from profiles import image_recipe, video_recipe 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','') 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 @@ -46,3 +46,36 @@ def assess(model, installed, profile=None): elif result['installed']: result.update(state='profile_incomplete',label='Profil prüfen – Voraussetzungen fehlen') return result + + +def assess(model, installed, profile=None): + result=_assess(model,installed,profile) + result['candidates']=[] + if result['state']=='component':return result + kind=model.get('kind');repo=model.get('repo','').lower();name=model.get('file','').lower() + meta=model.get('runtime_metadata') or {};family=str(meta.get('architecture') or '').lower() + def add(id,label,key,route,reason,maintained=True): + result['candidates'].append(dict(id=id,label=label,installed=bool(installed.get(key)),route=route,installation_available=maintained,evidence=reason)) + if result.get('runtime'): + add('current',result['runtime'],kind,result['route'],'Vorhandene Deck-Zuordnung') + if kind=='music' and repo=='audio-cpp/yue2-3b-gguf' and name.startswith('yue2-3b-') and name.endswith('.gguf'): + result.update(family='YuE2',evidence='audio-cpp/Yue2-3B-GGUF · Hauptmodell',state='adapter_missing',label='YuE2 erkannt · Deck-Worker fehlt',message='Passende Laufzeit: audio.cpp. Sie kann einmalig heruntergeladen und gebaut werden. Zur Generierung fehlen noch der Deck-Musikworker und die Ladypoly-Anbindung. Installation allein schaltet dieses Modell nicht frei.',components=['YuE2-VAE GGUF','Modell-/Generierungskonfiguration, VAE-Konfiguration und Qwen-Tokenizer (sidecars)']) + add('audio_cpp','audio.cpp','audio_cpp','#audio-cpp-runtime','Geprüfte GGUF-Quelle für die YuE2-Modellfamilie') + elif kind=='music' and ('yue2' in repo or 'yue2' in family): + result.update(family='YuE2',evidence='Repository-/Architekturhinweis; Ausführung ungeprüft') + add('yue2','YuE2 Python / PyTorch','yue2',None,'Originale YuE2-Pipeline; Originalgewichte erforderlich',False) + if kind in ('audio','stt','music','voice') and not result['candidates']: + if 'audio-cpp/' in repo and name.endswith('.gguf'): + add('audio_cpp','audio.cpp','audio_cpp','#audio-cpp-runtime','audio-cpp-Repository; konkrete Familie und Zusatzdateien noch prüfen') + elif meta.get('library_name')=='transformers': + add('transformers','Hugging Face Transformers / PyTorch','transformers',None,'Hub nennt Transformers; modellabhängiger Worker erforderlich',False) + if kind=='video' and not result['candidates'] and meta.get('library_name')=='diffusers': + add('diffusers','Diffusers / PyTorch','diffusers',None,'Hub nennt Diffusers; passende Video-Pipeline noch prüfen',False) + if not result['candidates']: + # Category-level options are explicit research hints, never compatibility claims. + options={'chat':('llama.cpp','chat','#runtime'), 'image':('ComfyUI','image','#image-runtime'), 'video':('ComfyUI','image','#image-runtime'), 'audio':('audio.cpp','audio_cpp','#audio-cpp-runtime'), 'stt':('audio.cpp','audio_cpp','#audio-cpp-runtime'), 'music':('audio.cpp','audio_cpp','#audio-cpp-runtime'), 'voice':('audio.cpp','audio_cpp','#audio-cpp-runtime')} + if kind in options: + label,key,route=options[kind] + 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 diff --git a/index.html b/index.html index eb981a9..7ad7795 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -Athena Deck
ATHENA CONTROL SURFACEv0.7 · Router
+Athena Deck
ATHENA CONTROL SURFACEv0.7 · Router
diff --git a/ltx_original_runtime.py b/ltx_original_runtime.py index 01f9bc5..5029c42 100644 --- a/ltx_original_runtime.py +++ b/ltx_original_runtime.py @@ -14,6 +14,7 @@ DESKTOP_REV='68cd86c15e5fd25f56229ea63c0dbcb0338f7812' LTX_REV='d151147788a9284cca791edc6ce898007e727fe6' class LTXOriginalRuntime: + name="LTX Original" def __init__(self,root,image_runtime): self.root=Path(root);self.image_runtime=image_runtime;self.lock=threading.RLock();self.cancel=threading.Event();self.process=None;self.worker=None;self.job=None path=self.root/'installation.json' @@ -37,8 +38,8 @@ class LTXOriginalRuntime: with self.lock:self.job['phase']=phase;self._save() def start(self): with self.lock: - if self.job and self.job['state']=='running':raise ValueError('LTX-Installation läuft bereits.') - if self.status()['installed']:raise ValueError('LTX Original ist bereits installiert.') + if self.job and self.job['state']=='running':raise ValueError(self.name+'-Installation läuft bereits.') + if self.status()['installed']:raise ValueError(self.name+' ist bereits installiert.') if sys.platform!='linux' or not shutil.which('git'):raise ValueError('Debian/Linux mit Git und Python venv erforderlich.') self.root.mkdir(parents=True,exist_ok=True,mode=0o700) need=self.status()['required_disk_gib'] diff --git a/server.py b/server.py index b973a9a..c3b4f58 100644 --- a/server.py +++ b/server.py @@ -115,6 +115,8 @@ class Server(ThreadingHTTPServer): self.auto_tests.stop();self.chat_tests.stop();self.image_tests.stop();self.prompt_enhancer.stop_preview();self.tts_tests.stop();self.worker.stop() from video_original import VideoRuntimes from ltx_original_runtime import LTXOriginalRuntime + from audio_cpp_runtime import AudioCppRuntime + self.audio_cpp_runtime=AudioCppRuntime(self.catalog.root.parent/'audio-cpp-runtime') self.ltx_original_runtime=LTXOriginalRuntime(self.catalog.root.parent/'ltx-original-runtime',self.image_runtime) self.video=VideoRuntimes(self.scheduler,stop_gpu_work,self.docker,self.catalog.root.parent/'video',self.catalog,self.profiles,self.image_runtime,original_runtime=self.ltx_original_runtime) self.endpoint.video=self.video @@ -278,7 +280,7 @@ class Handler(BaseHTTPRequestHandler): return self.headers.get('Host') in allowed def execution_setup(self, model, profile=None): - installed={'stt':bool(self.server.runtime.status().get('active')),'chat':bool(self.server.runtime.status().get('active')),'image':self.server.image_runtime.status().get('installed',False),'audio':self.server.tts_runtime.status().get('installed',False),'video':self.server.video.status()['runtime']['installed']} + installed={'stt':bool(self.server.runtime.status().get('active')),'chat':bool(self.server.runtime.status().get('active')),'image':self.server.image_runtime.status().get('installed',False),'audio':self.server.tts_runtime.status().get('installed',False),'video':self.server.video.status()['runtime']['installed'],'audio_cpp':self.server.audio_cpp_runtime.status()['installed']} return execution_assess(model,installed,profile) def do_GET(self): @@ -292,7 +294,7 @@ class Handler(BaseHTTPRequestHandler): return self.respond({'error':'Anmeldung erforderlich.'},401) if not self.authenticated() and self.path == '/': return self.respond((ROOT/'login.html').read_bytes(), mime='text/html; charset=utf-8') - routes = {'/ltx-original-ui.js':('ltx-original-ui.js','text/javascript'),'/backup-ui.js':('backup-ui.js','text/javascript'),'/dashboard-ui.js':('dashboard-ui.js','text/javascript'),'/dashboard.css':('dashboard.css','text/css'),'/themes.css':('themes.css','text/css'),'/video-ui.js':('video-ui.js','text/javascript'),'/stt-ui.js':('stt-ui.js','text/javascript'),'/tts-ui.js':('tts-ui.js','text/javascript'),'/auto-test-ui.js':('auto-test-ui.js','text/javascript'),'/chat-test-ui.js':('chat-test-ui.js','text/javascript'),'/endpoint-ui.js':('endpoint-ui.js','text/javascript'),'/docker-ui.js': ('docker-ui.js','text/javascript'), '/': ('index.html', 'text/html; charset=utf-8'), '/app.js': ('app.js', 'text/javascript'), '/style.css': ('style.css', 'text/css'), '/network-ui.js': ('network-ui.js', 'text/javascript'), '/access-ui.js': ('access-ui.js', 'text/javascript'), '/studio.js': ('studio.js', 'text/javascript'), '/catalog-ui.js': ('catalog-ui.js','text/javascript'), '/runtime-ui.js': ('runtime-ui.js','text/javascript'), '/profiles-ui.js': ('profiles-ui.js','text/javascript'), '/image-test-ui.js': ('image-test-ui.js','text/javascript')} + routes = {'/audio-cpp-ui.js':('audio-cpp-ui.js','text/javascript'),'/ltx-original-ui.js':('ltx-original-ui.js','text/javascript'),'/backup-ui.js':('backup-ui.js','text/javascript'),'/dashboard-ui.js':('dashboard-ui.js','text/javascript'),'/dashboard.css':('dashboard.css','text/css'),'/themes.css':('themes.css','text/css'),'/video-ui.js':('video-ui.js','text/javascript'),'/stt-ui.js':('stt-ui.js','text/javascript'),'/tts-ui.js':('tts-ui.js','text/javascript'),'/auto-test-ui.js':('auto-test-ui.js','text/javascript'),'/chat-test-ui.js':('chat-test-ui.js','text/javascript'),'/endpoint-ui.js':('endpoint-ui.js','text/javascript'),'/docker-ui.js': ('docker-ui.js','text/javascript'), '/': ('index.html', 'text/html; charset=utf-8'), '/app.js': ('app.js', 'text/javascript'), '/style.css': ('style.css', 'text/css'), '/network-ui.js': ('network-ui.js', 'text/javascript'), '/access-ui.js': ('access-ui.js', 'text/javascript'), '/studio.js': ('studio.js', 'text/javascript'), '/catalog-ui.js': ('catalog-ui.js','text/javascript'), '/runtime-ui.js': ('runtime-ui.js','text/javascript'), '/profiles-ui.js': ('profiles-ui.js','text/javascript'), '/image-test-ui.js': ('image-test-ui.js','text/javascript')} if self.path in routes: name, mime = routes[self.path] return self.respond((ROOT/name).read_bytes(), mime=mime) @@ -311,6 +313,8 @@ class Handler(BaseHTTPRequestHandler): if urlsplit(self.path).path == '/api/v1/tts/audio': try:return self.respond(self.server.tts_tests.audio(parse_qs(urlsplit(self.path).query).get('id',[''])[0]),mime='audio/wav') except (OSError,ValueError):return self.respond({'error':'Audio nicht verfügbar.'},404) + if self.path == '/api/v1/audio-cpp-runtime':return self.respond(self.server.audio_cpp_runtime.status()) + if self.path == '/api/v1/audio-cpp-runtime/log':return self.respond(self.server.audio_cpp_runtime.log()) if self.path == '/api/v1/ltx-original-runtime':return self.respond(self.server.ltx_original_runtime.status()) if self.path == '/api/v1/ltx-original-runtime/log':return self.respond(self.server.ltx_original_runtime.log()) if self.path == '/api/v1/video':return self.respond(self.server.video.status()) @@ -482,6 +486,11 @@ class Handler(BaseHTTPRequestHandler): if data:raise ValueError('Keine Parameter erwartet.') return self.respond(self.server.chat_tests.stop() if self.path.endswith('/cancel') else self.server.chat_tests.unload()) except ValueError as exc:return self.respond({'error':str(exc)},400) + if self.path in ('/api/v1/audio-cpp-runtime/install','/api/v1/audio-cpp-runtime/cancel'): + try: + if self.read_json():raise ValueError('Keine Installationsparameter erwartet.') + return self.respond(self.server.audio_cpp_runtime.stop() if self.path.endswith('/cancel') else self.server.audio_cpp_runtime.start()) + except (OSError,ValueError) as exc:return self.respond({'error':str(exc)},400) if self.path in ('/api/v1/ltx-original-runtime/install','/api/v1/ltx-original-runtime/cancel'): try: if self.read_json():raise ValueError('Keine Installationsparameter erwartet.') @@ -658,6 +667,7 @@ def main(): server.tts_runtime.stop() server.image_runtime.stop() server.ltx_original_runtime.stop() + server.audio_cpp_runtime.stop() server.prompt_enhancer.stop() server.image_tests.stop() server.runtime.stop() diff --git a/studio.js b/studio.js index 2b6997c..a6f6b37 100644 --- a/studio.js +++ b/studio.js @@ -27,7 +27,7 @@ const Studio=(()=>{ else if(section!=='running')CatalogUI.bind(page,section==='library',section==='downloads'); document.querySelectorAll('[data-section]').forEach(b=>b.onclick=()=>{section=b.dataset.section;render(page,true);}); } - function runtimeOverview(){return `
EINSTELLUNGEN

Laufzeiten

Laufzeiten werden einmal eingerichtet und für kompatible Modelle wiederverwendet. Entdecken, Bibliothek und Profile zeigen den passenden Einrichtungsweg; unbekannte Modelle werden als noch nicht unterstützt gekennzeichnet. Textencoder und VAE werden separat in der Modellbibliothek verwaltet und einem Profil zugeordnet.

Spracherkennung

Qwen3-ASR mit llama.cpp und Audio-Projektor auf der CPU.

STT einrichten →

Text-to-Speech

Qwen3-TTS mit CUDA und eingebauten Stimmen einrichten.

TTS einrichten →

llama.cpp

Sprachmodelle · CPU / CUDA. Versionen herunterladen, bauen und verwalten.

llama.cpp öffnen →

Bildgenerierung

Eigene Bildlaufzeit und modellabhängige Zusatzkomponenten.

Bildlaufzeit öffnen →

LTX Original

Originales LTX-Desktop-Backend mit nativer LTX-API für LTX DeskWEB.

LTX Original einrichten →

Docker

Optionale Container-Umgebung für zusätzliche Oberflächen und Studios.

Docker öffnen →
`;} + function runtimeOverview(){return `
EINSTELLUNGEN

Laufzeiten

Laufzeiten werden einmal eingerichtet und für kompatible Modelle wiederverwendet. Entdecken, Bibliothek und Profile zeigen den passenden Einrichtungsweg; unbekannte Modelle werden als noch nicht unterstützt gekennzeichnet. Textencoder und VAE werden separat in der Modellbibliothek verwaltet und einem Profil zugeordnet.

Spracherkennung

Qwen3-ASR mit llama.cpp und Audio-Projektor auf der CPU.

STT einrichten →

Text-to-Speech

Qwen3-TTS mit CUDA und eingebauten Stimmen einrichten.

TTS einrichten →

llama.cpp

Sprachmodelle · CPU / CUDA. Versionen herunterladen, bauen und verwalten.

llama.cpp öffnen →

Bildgenerierung

Eigene Bildlaufzeit und modellabhängige Zusatzkomponenten.

Bildlaufzeit öffnen →

audio.cpp

Gemeinsame Audio-Laufzeit; Deck-Worker werden separat angebunden.

audio.cpp herunterladen / bereitmachen →

LTX Original

Originales LTX-Desktop-Backend mit nativer LTX-API für LTX DeskWEB.

LTX Original einrichten →

Docker

Optionale Container-Umgebung für zusätzliche Oberflächen und Studios.

Docker öffnen →
`;} function imageRuntime(){return `
EINSTELLUNGEN / LAUFZEITEN

Bildgenerierung

ComfyUI + ComfyUI-GGUF

Eigene Bildlaufzeit für Qwen-Image-2.1 GGUF und das unterstützte FLUX.2-Klein-9B-Modell. Die Umgebung kann über den Deck-Installer mitgeliefert oder hier separat installiert werden. Modelle laden erst bei einem Testauftrag.

Textencoder läuft auf der RTX 3060, Diffusionsmodell und VAE auf der RTX 5080. Bei großen FLUX-Gewichten wird zusätzlich System-RAM genutzt. Beide GPUs müssen frei sein. Pro Test wird ein eigener Prozess gestartet und anschließend beendet.

Installationsstatus wird geprüft …

Bildgenerierung → Testen öffnen
${ImageTestUI.html(true)}`;} function openProfile(page,modelId){render(page,true,modelId);} return {render,openProfile}; diff --git a/test_audio_cpp_runtime.py b/test_audio_cpp_runtime.py new file mode 100644 index 0000000..06a34b6 --- /dev/null +++ b/test_audio_cpp_runtime.py @@ -0,0 +1,44 @@ +import json +from pathlib import Path +import tempfile +import time +import unittest +from unittest.mock import patch +from audio_cpp_runtime import AudioCppRuntime,REVISION + +class AudioCppTests(unittest.TestCase): + def test_marker_cannot_escape_root(self): + with tempfile.TemporaryDirectory() as root: + Path(root,'active.json').write_text(json.dumps({'id':'../../outside'})) + self.assertFalse(AudioCppRuntime(root).status()['installed']) + def test_build_failure_is_not_published(self): + with tempfile.TemporaryDirectory() as root: + runtime=AudioCppRuntime(root);runtime.job=dict(id='a'*32,state='running') + with patch.object(runtime,'_command',side_effect=RuntimeError('synthetic failure')):runtime._run() + self.assertEqual(runtime.job['state'],'failed') + self.assertFalse(runtime.status()['installed']) + self.assertFalse(Path(root,'a'*32,'ready').exists()) + def test_publish_only_after_cli_smoke_and_spec(self): + with tempfile.TemporaryDirectory() as root: + runtime=AudioCppRuntime(root);runtime.job=dict(id='a'*32,state='running');commands=[] + def command(args,env): + commands.append([str(a) for a in args]) + base=Path(root,'a'*32) + if str(args[0])=='cmake' and '--build' in args: + (base/'source/model_specs').mkdir(parents=True) + (base/'source/model_specs/yue2.json').write_text('{}') + (base/'build/bin').mkdir(parents=True) + (base/'build/bin/audiocpp_cli').touch() + with patch.object(runtime,'_command',side_effect=command),patch('subprocess.check_output',return_value='12.0\n8.6\n'):runtime._run() + self.assertEqual(runtime.job['state'],'complete') + self.assertTrue(runtime.status()['installed']) + self.assertFalse(runtime.status()['adapter_ready']) + self.assertIn('-DCMAKE_CUDA_ARCHITECTURES=120;86',commands[3]) + self.assertEqual(commands[-1][-1],'--help') + self.assertEqual(Path(root,'a'*32,'ready').read_text(),REVISION) + def test_missing_prerequisites_dont_start_thread(self): + with tempfile.TemporaryDirectory() as root: + runtime=AudioCppRuntime(root) + with patch('audio_cpp_runtime.sys.platform','linux'),patch('audio_cpp_runtime.shutil.which',return_value=None): + with self.assertRaisesRegex(ValueError,'Build-Werkzeuge fehlen'):runtime.start() + self.assertIsNone(runtime.worker) diff --git a/test_execution_setup.py b/test_execution_setup.py index ae086bb..7c93bba 100644 --- a/test_execution_setup.py +++ b/test_execution_setup.py @@ -33,3 +33,21 @@ class ExecutionSetupTests(unittest.TestCase): self.assertEqual(result['family'],'NewImagePipeline') self.assertIn('Workflow',result['message']) self.assertEqual(assess(dict(model,kind='audio'),{'image':True})['state'],'unsupported') + + def test_every_category_has_explicit_unverified_candidate(self): + for kind in ('chat','image','audio','stt','music','voice','video'): + result=self.check(kind,'unknown/family','weights.safetensors') + self.assertTrue(result['candidates']) + self.assertIn('NICHT bestätigt',result['candidates'][0]['evidence']) + self.assertNotEqual(result['state'],'ready') + def test_yue2_gguf_recognition_does_not_claim_worker(self): + result=self.check('music','audio-cpp/Yue2-3B-GGUF','yue2-3b-q8_0.gguf',{'audio_cpp':True}) + self.assertEqual(result['family'],'YuE2') + self.assertEqual(result['state'],'adapter_missing') + self.assertTrue(result['candidates'][0]['installed']) + self.assertEqual(result['candidates'][0]['route'],'#audio-cpp-runtime') + self.assertTrue(result['components']) + def test_component_never_offers_runtime_or_profile(self): + result=self.check('music','audio-cpp/Yue2-3B-GGUF','yue2-vae-f16.gguf') + self.assertEqual(result['state'],'component') + self.assertEqual(result['candidates'],[])