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.
${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 '
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 DeckATHENA CONTROL SURFACEv0.7 · Router
+Athena DeckATHENA 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.
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.
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'],[])