Offer isolated VoxCPM runtime installation from model setup

This commit is contained in:
Mikei386 committed 2026-10-02 11:55:25 +02:00
1 parent 1f0fc42344
commit aee31359b6
18 files changed
+168 -21

No files matched your search

+15 -2
View File
@@ -16,8 +16,7 @@ nicht Bestandteil dieses Grundpakets.
Das Dateirezept ist anhand des [offiziellen Modells](https://huggingface.co/openbmb/VoxCPM2)
und des [originalen Loaders](https://github.com/OpenBMB/VoxCPM/blob/main/src/voxcpm/model/voxcpm2.py)
geprüft. Es ist keine Zusage getesteter Inferenz: **Die VoxCPM-Python/PyTorch-Laufzeit
und ihr Deck-Worker sind noch nicht angebunden.** Eine vorhandene audio.cpp- oder
geprüft. Es ist keine Zusage getesteter Inferenz: **Der Deck-Generierungsworker ist noch nicht angebunden.** Eine vorhandene audio.cpp- oder
llama.cpp-Installation ersetzt diese Anbindung nicht. Das Profil bleibt gesperrt.
Der Katalog erlaubt `.pth` ausschließlich für die exakt benannte offizielle
@@ -30,3 +29,17 @@ als allgemeine Konfiguration. „Alle fehlenden Dateien herunterladen“ prüft
ob für alle Pflichtkomponenten eine herunterladbare Datei angeboten wird. Erst danach
werden Downloads eingereiht; bei einem späteren Fehler zeigt Deck bereits eingereihte
Dateien an. Manuelle, ungeprüfte Listen erhalten weiterhin keinen Sammeldownload.
## Laufzeit über die Oberfläche installieren
Unter **Laufzeiten → VoxCPM** oder im Komponentenbereich über
**Passende Laufzeit installieren** lässt sich das offizielle Paket VoxCPM 2.0.3
in einer eigenen Python-Umgebung installieren und abbrechen. Die Installation
prüft CUDA-PyTorch, Paketkonsistenz und den VoxCPM2-Loader, lädt aber weder
Modellgewichte noch ein Modell in die GPUs. Vorhandene CUDA-Pakete der
Bildlaufzeit werden lesend wiederverwendet. Versionen stehen in
`deploy/voxcpm-requirements.lock`.
Backup exportiert den Installationswunsch und die Version; Restore installiert
diese Laufzeit erneut. **Eine installierte Laufzeit schaltet das Profil noch
nicht frei: Der Generierungsworker muss zusätzlich angebunden werden.**
+1 -1
View File
@@ -28,7 +28,7 @@ if(['separator-runtime','separator-test','separation'].includes(page)){Separator
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;}
if(['chat','image','audio','stt','music','voice','video','runtime','runtimes','image-runtime','stt-runtime','tts-runtime','voxcpm-runtime','docker-runtime','services'].includes(page)){Studio.render(page);return;}
if(page==='backup'){BackupUI.render();return;}
if(page==='access'){await accessPage();return;}
if(page==='appearance'){appearancePage();return;}
+11 -2
View File
@@ -75,7 +75,7 @@ def validate(doc):
validate_profiles(settings['separator-profiles.json'])
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','audio_cpp','separator'} 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','separator','voxcpm'} or not {'llama','llama_builds','image','tts','enhancers','swarm_nodes'}<=set(runtimes):raise ValueError('Ungültiges Laufzeitrezept.')
if 'separator' in runtimes:
from separator_runtime import MODELS
value=runtimes['separator']
@@ -83,6 +83,7 @@ def validate(doc):
if 'sources' in value:
from separator_runtime import SOURCES
if value['sources']!={name:SOURCES[name] for name in value['models']}:raise ValueError('Audio-Separator-Quellen stimmen nicht mit dem geprüften Rezept überein.')
if 'voxcpm' in runtimes and type(runtimes['voxcpm']) is not bool:raise ValueError('Ungültige VoxCPM-Laufzeit.')
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.')
@@ -152,6 +153,8 @@ class Backup:
if getattr(s,'separator_runtime',None):
from separator_runtime import SOURCES
separator=s.separator_runtime.status();document['runtimes']['separator']=dict(installed=separator['installed'],models=[m['id'] for m in separator['models'] if m['installed']],sources={m['id']:SOURCES[m['id']] for m in separator['models'] if m['installed']});document['runtime_versions']['separator']=separator['version']
if getattr(s,'voxcpm_runtime',None):
document['runtimes']['voxcpm']=s.voxcpm_runtime.status()['installed'];document['runtime_versions']['voxcpm']=s.voxcpm_runtime.status()['version']
if document['runtimes']['audio_cpp']:
from audio_cpp_runtime import REVISION
document['runtime_versions']['audio_cpp']=REVISION
@@ -184,6 +187,9 @@ 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('voxcpm'):
from voxcpm_runtime import VERSION as VOXCPM_VERSION
if versions['voxcpm']!=VOXCPM_VERSION:blockers.append('Backup verlangt eine andere VoxCPM-Version. Passende Deck-Version verwenden.')
if versions.get('separator'):
from separator_runtime import VERSION as SEPARATOR_VERSION
if versions['separator']!=SEPARATOR_VERSION:blockers.append('Backup verlangt eine andere Audio-Separator-Version.')
@@ -193,7 +199,7 @@ class Backup:
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 not in ('ltx_original','audio_cpp','separator')}!=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','separator','voxcpm')}!=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 not c.get('build_recipe'):warnings.append(c['name']+': Image nur lokal vorhanden; Registry-/Buildquelle fehlt.')
ports=s.endpoint.allowed_ports
@@ -246,6 +252,8 @@ class Backup:
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('voxcpm') and not s.voxcpm_runtime.status()['installed']:
self._phase('VoxCPM-Laufzeit installieren');s.voxcpm_runtime.start();self._wait(s.voxcpm_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)
if r.get('separator',{}).get('installed'):
@@ -396,6 +404,7 @@ class Backup:
if getattr(self.server,'separator_runtime',None):self.server.separator_runtime.stop()
if getattr(self.server,'separator_tests',None):self.server.separator_tests.stop()
if getattr(self.server,'music',None):self.server.music.stop()
if getattr(self.server,'voxcpm_runtime',None):self.server.voxcpm_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}
+4 -2
View File
@@ -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 separator_sources.json separator.py separator_runtime.py separator_worker.py music.py 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 separator-ui.js music-ui.js 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 voxcpm_runtime.py separator_sources.json separator.py separator_runtime.py separator_worker.py music.py 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 voxcpm-ui.js separator-ui.js music-ui.js 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 \
@@ -26,3 +26,5 @@ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
DECK_NETWORK_MODE=disabled
USER 65534:65534
CMD ["python3", "server.py", "--port", "8108"]
COPY deploy/voxcpm-requirements.lock /app/deploy/voxcpm-requirements.lock
+4 -2
View File
@@ -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 separator_sources.json separator.py separator_runtime.py separator_worker.py music.py 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 separator-ui.js music-ui.js 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 voxcpm_runtime.py separator_sources.json separator.py separator_runtime.py separator_worker.py music.py 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 voxcpm-ui.js separator-ui.js music-ui.js 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 \
@@ -16,3 +16,5 @@ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
DECK_NETWORK_MODE=disabled
USER 65534:65534
CMD ["python3", "server.py", "--port", "8108"]
COPY deploy/voxcpm-requirements.lock /app/deploy/voxcpm-requirements.lock
+2 -2
View File
@@ -14,7 +14,7 @@ import urllib.request
ROOT = Path(__file__).resolve().parent.parent
LABEL = 'de.casaderoll.athena-deck.standalone'
FILES = ['network/native.py','deploy/setup_network_helper.py','separator_sources.json','deploy/separator-web/Dockerfile','deploy/separator-web/app.py','deploy/separator-web/index.html','separator.py','separator_runtime.py','separator_worker.py','separator-ui.js','deploy/ladypoly/Dockerfile','deploy/ladypoly/bridge.py','music.py','music-ui.js','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']
FILES = ['voxcpm_runtime.py','voxcpm-ui.js','deploy/voxcpm-requirements.lock','network/native.py','deploy/setup_network_helper.py','separator_sources.json','deploy/separator-web/Dockerfile','deploy/separator-web/app.py','deploy/separator-web/index.html','separator.py','separator_runtime.py','separator_worker.py','separator-ui.js','deploy/ladypoly/Dockerfile','deploy/ladypoly/bridge.py','music.py','music-ui.js','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):
@@ -206,7 +206,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('music-verification','music-test-temp','music-jobs','separator-runtime','separator-tests','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-*'))
shutil.copytree(base/'state',base/'backups'/stamp,ignore=shutil.ignore_patterns('music-verification','music-test-temp','music-jobs','separator-runtime','separator-tests','voxcpm-runtime','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)
+5
View File
@@ -0,0 +1,5 @@
# Maintained VoxCPM installer constraints. CUDA wheels use the official cu128 index.
voxcpm==2.0.3
torch==2.11.0
torchaudio==2.11.0
transformers==4.57.6
+3 -2
View File
@@ -22,7 +22,7 @@ def _assess(model, installed, profile=None):
result['family']='Qwen3-TTS CustomVoice'
result.update(runtime='Qwen3-TTS CustomVoice',route='#tts-runtime',components=['Vollständiges Modellpaket und Sprach-Tokenizer (im Einrichtungsassistenten enthalten)'])
elif voice_recipe(model):
result.update(family='VoxCPM2',evidence='Offizielles OpenBMB-Modellpaket',state='adapter_missing',label='VoxCPM2 erkannt · Ausführung noch nicht angebunden',runtime=None,components=[x['label'] for x in voice_recipe(model).values()],message='Die heruntergeladenen Dateien bilden das Modellpaket. Für die Ausführung fehlt noch die VoxCPM-Python/PyTorch-Laufzeit samt Deck-Worker. audio.cpp und llama.cpp führen diese Safetensors-Datei nicht aus. Textencoder und Vision-Projektor sind nicht separat erforderlich. Unter Komponenten werden die tatsächlich benötigten Dateien angeboten.')
result.update(family='VoxCPM2',evidence='Offizielles OpenBMB-Modellpaket',state='adapter_missing',label='VoxCPM2 erkannt · Ausführung noch nicht angebunden',runtime=None,route='#voxcpm-runtime',components=[x['label'] for x in voice_recipe(model).values()],message='Die heruntergeladenen Dateien bilden das Modellpaket. Für die Ausführung fehlt noch die VoxCPM-Python/PyTorch-Laufzeit samt Deck-Worker. audio.cpp und llama.cpp führen diese Safetensors-Datei nicht aus. Textencoder und Vision-Projektor sind nicht separat erforderlich. Unter Komponenten werden die tatsächlich benötigten Dateien angeboten.')
elif kind=='chat' and filename.lower().endswith('.gguf'):
result.update(runtime='llama.cpp',route='#runtime',state='check',label='Kompatibilität beim Laden prüfen',message='llama.cpp ist der mögliche Ausführungsweg für dieses Chat-GGUF. Architektur und gewählte Optionen müssen vom installierten Build unterstützt werden. Der Testchat prüft die tatsächliche Ausführung.')
elif kind=='image' and filename.lower().endswith(('.safetensors','.gguf')) and (meta.get('pipeline_tag') in ('text-to-image','image-to-image') or meta.get('library_name') in ('diffusers','comfyui')):
@@ -59,7 +59,8 @@ def assess(model, installed, profile=None):
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 voice_recipe(model):
add('voxcpm','VoxCPM · Python/PyTorch','voxcpm',None,'Offizielle VoxCPM2-Laufzeit; noch kein Deck-Installer oder Worker vorhanden',False)
result['installed']=bool(installed.get('voxcpm'));result['message']='VoxCPM-Laufzeit '+('bereits installiert' if result['installed'] else 'fehlt: unter Ausführung einrichten installieren')+'. Die Modellkomponenten werden separat zugeordnet. Der Deck-Worker für Generierung fehlt noch.'
add('voxcpm','VoxCPM · Python/PyTorch','voxcpm','#voxcpm-runtime','Originale VoxCPM-Laufzeit · GUI-Installer verfügbar; Generierungsworker noch nicht angebunden',True)
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'):
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Athena Deck</title><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/dashboard.css"><link rel="stylesheet" href="/themes.css"><script src="/network-ui.js" defer></script><script src="/access-ui.js" defer></script><script src="/catalog-ui.js" defer></script><script src="/runtime-ui.js" defer></script><script src="/profiles-ui.js" defer></script><script src="/auto-test-ui.js" defer></script><script src="/chat-test-ui.js" defer></script><script src="/image-test-ui.js" defer></script><script src="/docker-ui.js" defer></script><script src="/stt-ui.js" defer></script><script src="/tts-ui.js" defer></script><script src="/studio.js" defer></script><script src="/separator-ui.js" defer></script><script src="/music-ui.js" defer></script><script src="/audio-cpp-ui.js" defer></script><script src="/ltx-original-ui.js" defer></script><script src="/video-ui.js" defer></script><script src="/endpoint-ui.js" defer></script><script src="/dashboard-ui.js" defer></script><script src="/backup-ui.js" defer></script><script src="/app.js" defer></script></head><body><aside><a class="brand" href="#dashboard"><span>Α</span> ATHENA <b>DECK</b></a><p class="eyebrow">WORKSPACE / ATHENA</p><nav aria-label="Hauptnavigation"><p class="nav-heading">Übersicht</p><a href="#dashboard">Dashboard</a><a href="#home">Steuerung</a><p class="nav-heading">KI-Werkzeuge</p><a href="#chat">Chat &amp; Sprachmodelle</a><a href="#image">Bildgenerierung</a><a href="#music">Musikgenerierung</a><a href="#separation">Audio-Trennung</a><a href="#audio">Sprachausgabe (TTS)</a><a href="#stt">Spracherkennung (STT)</a><a href="#voice">Stimmwerkzeuge</a><a href="#video">Videogenerierung</a><p class="nav-heading">Anwendungen</p><a href="#services">Weitere Dienste</a><p class="nav-heading">Einstellungen</p><a href="#runtimes">Laufzeiten</a><a href="#endpoint">Endpunkt</a><a href="#network">Netzwerk</a><a href="#access">Zugang &amp; API-Token</a><a href="#appearance">Darstellung</a><a href="#backup">Backup &amp; Wiederherstellung</a></nav><footer><i></i> Isolierte Entwicklungsumgebung<p>Eigener Modell-Router<br>Hardware · nur lesend</p></footer></aside><main><header><span>ATHENA CONTROL SURFACE</span><span class="pill">v0.7 · Router</span></header><div id="view"></div><p id="error" role="alert"></p></main></body></html>
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Athena Deck</title><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/dashboard.css"><link rel="stylesheet" href="/themes.css"><script src="/network-ui.js" defer></script><script src="/access-ui.js" defer></script><script src="/catalog-ui.js" defer></script><script src="/runtime-ui.js" defer></script><script src="/profiles-ui.js" defer></script><script src="/auto-test-ui.js" defer></script><script src="/chat-test-ui.js" defer></script><script src="/image-test-ui.js" defer></script><script src="/docker-ui.js" defer></script><script src="/stt-ui.js" defer></script><script src="/tts-ui.js" defer></script><script src="/voxcpm-ui.js" defer></script><script src="/studio.js" defer></script><script src="/separator-ui.js" defer></script><script src="/music-ui.js" defer></script><script src="/audio-cpp-ui.js" defer></script><script src="/ltx-original-ui.js" defer></script><script src="/video-ui.js" defer></script><script src="/endpoint-ui.js" defer></script><script src="/dashboard-ui.js" defer></script><script src="/backup-ui.js" defer></script><script src="/app.js" defer></script></head><body><aside><a class="brand" href="#dashboard"><span>Α</span> ATHENA <b>DECK</b></a><p class="eyebrow">WORKSPACE / ATHENA</p><nav aria-label="Hauptnavigation"><p class="nav-heading">Übersicht</p><a href="#dashboard">Dashboard</a><a href="#home">Steuerung</a><p class="nav-heading">KI-Werkzeuge</p><a href="#chat">Chat &amp; Sprachmodelle</a><a href="#image">Bildgenerierung</a><a href="#music">Musikgenerierung</a><a href="#separation">Audio-Trennung</a><a href="#audio">Sprachausgabe (TTS)</a><a href="#stt">Spracherkennung (STT)</a><a href="#voice">Stimmwerkzeuge</a><a href="#video">Videogenerierung</a><p class="nav-heading">Anwendungen</p><a href="#services">Weitere Dienste</a><p class="nav-heading">Einstellungen</p><a href="#runtimes">Laufzeiten</a><a href="#endpoint">Endpunkt</a><a href="#network">Netzwerk</a><a href="#access">Zugang &amp; API-Token</a><a href="#appearance">Darstellung</a><a href="#backup">Backup &amp; Wiederherstellung</a></nav><footer><i></i> Isolierte Entwicklungsumgebung<p>Eigener Modell-Router<br>Hardware · nur lesend</p></footer></aside><main><header><span>ATHENA CONTROL SURFACE</span><span class="pill">v0.7 · Router</span></header><div id="view"></div><p id="error" role="alert"></p></main></body></html>
+1 -1
View File
@@ -37,7 +37,7 @@ window.ProfilesUI=(()=>{
el('profile-editor').innerHTML='<section class="card editor"><p role="status">Komponenten werden geprüft …</p></section>';
try{
const data=await api('profiles/components?'+new URLSearchParams({model_id:profile.model_id,...(extraRepo?{repo:extraRepo}:{})}));if(!root.isConnected||sequence!==panelSequence)return;
el('profile-editor').innerHTML=`<section class="card editor"><div class="card-top"><h2>Komponenten · ${e(profile.name)}</h2><button class="secondary" id="components-close">Schließen</button></div><p>${e(data.message)}</p><details><summary>Andere Komponentenquelle auswählen (ungeprüft)</summary><label>Hugging-Face-Link oder Repository<input id="component-source" placeholder="https://huggingface.co/Organisation/Modell"></label><button id="component-source-open" type="button">Dateien anzeigen</button></details>${data.supported?`<p>Quelle: <a class="link" href="${e(data.source)}" target="_blank" rel="noopener noreferrer">Modellkarte und Lizenz ↗</a> · ${e(data.license||'Lizenz nicht angegeben')}</p><p>Laufzeit: ${e(data.runtime)} · ${data.manual?'Zuordnung ungeprüft':data.runtime_installed?'installiert':'noch nicht eingerichtet'}</p>${!data.manual?'<button type="button" id="components-download-all">Alle fehlenden Dateien herunterladen</button>':'<p>Nur benötigte Dateien einzeln auswählen; diese Liste ist kein automatischer Downloadplan.</p>'}<form id="components-form">${data.requirements.map(r=>`<section class="card component-card"><h3>${e(r.label)}</h3><label>Datei aus der Bibliothek<select name="${r.role}"><option value="">Noch nicht zugeordnet</option>${r.available.map(f=>`<option value="${f.id}" ${(profile.components?.[r.role]===f.id||(!data.manual&&!profile.components?.[r.role]&&r.available.length===1))?'selected':''}>${e(f.file)} · ${(f.size/1024**3).toFixed(2)} GiB</option>`).join('')}</select></label>${r.candidates.map((f,i)=>`<div class="note"><p data-download-feedback="${r.role}:${i}" role="status"></p><p class="hub-filename">${e(f.name)} · ${(f.size/1024**3).toFixed(2)} GiB · Version ${e(f.revision.slice(0,12))}</p>${f.gated&&!f.auth_configured?'<p>Hugging-Face-Zugang unter <a href="#access">Einstellungen → Zugang</a> hinterlegen und Modellfreigabe prüfen.</p>':''}<button type="button" class="secondary" data-component-download="${r.role}:${i}" ${(f.gated&&!f.auth_configured)||r.available.some(x=>x.file===f.name&&x.revision===f.revision)?'disabled':''}>${r.available.some(x=>x.file===f.name&&x.revision===f.revision)?'Bereits in Bibliothek':'Datei herunterladen'}</button></div>`).join('')||'<p>Keine passende Quelldatei verfügbar.</p>'}</section>`).join('')}<div class="card-actions"><button id="components-save">Zuordnung auf Athena speichern</button><button type="button" class="secondary" id="components-refresh">Bibliothek aktualisieren</button></div><p class="small">Lade je eine Datei pro benötigter Komponente. Downloads erscheinen im Reiter Downloads. Danach Bibliothek aktualisieren, Dateien auswählen und die Zuordnung speichern. Es startet kein Modell.</p></form>`:''}<p id="components-message" role="status"></p></section>`;
el('profile-editor').innerHTML=`<section class="card editor"><div class="card-top"><h2>Komponenten · ${e(profile.name)}</h2><button class="secondary" id="components-close">Schließen</button></div><p>${e(data.message)}</p><details><summary>Andere Komponentenquelle auswählen (ungeprüft)</summary><label>Hugging-Face-Link oder Repository<input id="component-source" placeholder="https://huggingface.co/Organisation/Modell"></label><button id="component-source-open" type="button">Dateien anzeigen</button></details>${data.supported?`<p>Quelle: <a class="link" href="${e(data.source)}" target="_blank" rel="noopener noreferrer">Modellkarte und Lizenz ↗</a> · ${e(data.license||'Lizenz nicht angegeben')}</p>${data.runtime_route?`<p><a class="link" href="${e(data.runtime_route)}">${data.runtime_installed?'Laufzeit ansehen':'Passende Laufzeit installieren'} →</a></p>`:''}<p>Laufzeit: ${e(data.runtime)} · ${data.manual?'Zuordnung ungeprüft':data.runtime_installed?'installiert':'noch nicht eingerichtet'}</p>${!data.manual?'<button type="button" id="components-download-all">Alle fehlenden Dateien herunterladen</button>':'<p>Nur benötigte Dateien einzeln auswählen; diese Liste ist kein automatischer Downloadplan.</p>'}<form id="components-form">${data.requirements.map(r=>`<section class="card component-card"><h3>${e(r.label)}</h3><label>Datei aus der Bibliothek<select name="${r.role}"><option value="">Noch nicht zugeordnet</option>${r.available.map(f=>`<option value="${f.id}" ${(profile.components?.[r.role]===f.id||(!data.manual&&!profile.components?.[r.role]&&r.available.length===1))?'selected':''}>${e(f.file)} · ${(f.size/1024**3).toFixed(2)} GiB</option>`).join('')}</select></label>${r.candidates.map((f,i)=>`<div class="note"><p data-download-feedback="${r.role}:${i}" role="status"></p><p class="hub-filename">${e(f.name)} · ${(f.size/1024**3).toFixed(2)} GiB · Version ${e(f.revision.slice(0,12))}</p>${f.gated&&!f.auth_configured?'<p>Hugging-Face-Zugang unter <a href="#access">Einstellungen → Zugang</a> hinterlegen und Modellfreigabe prüfen.</p>':''}<button type="button" class="secondary" data-component-download="${r.role}:${i}" ${(f.gated&&!f.auth_configured)||r.available.some(x=>x.file===f.name&&x.revision===f.revision)?'disabled':''}>${r.available.some(x=>x.file===f.name&&x.revision===f.revision)?'Bereits in Bibliothek':'Datei herunterladen'}</button></div>`).join('')||'<p>Keine passende Quelldatei verfügbar.</p>'}</section>`).join('')}<div class="card-actions"><button id="components-save">Zuordnung auf Athena speichern</button><button type="button" class="secondary" id="components-refresh">Bibliothek aktualisieren</button></div><p class="small">Lade je eine Datei pro benötigter Komponente. Downloads erscheinen im Reiter Downloads. Danach Bibliothek aktualisieren, Dateien auswählen und die Zuordnung speichern. Es startet kein Modell.</p></form>`:''}<p id="components-message" role="status"></p></section>`;
const missing=data.requirements?.filter(r=>!r.available.length&&!r.optional)||[];
if(el('components-download-all')){el('components-download-all').disabled=!missing.length;el('components-download-all').onclick=async()=>{
const b=el('components-download-all');b.disabled=true;let count=0;
+1 -1
View File
@@ -277,7 +277,7 @@ class Profiles:
except ValueError:pass
candidates=[dict(f,repo=source.get('repo',info.get('repo',model['repo'])),revision=source['revision'],gated=source['gated'],auth_configured=source.get('auth_configured',False)) for name in info['files'] for f in source['files'] if f['name']==name]
requirements.append(dict(role=role,label=info['label'],available=available,candidates=candidates))
if voice_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='VoxCPM · eigene Python/PyTorch-Laufzeit',runtime_installed=False,worker_available=False,message='VoxCPM2 benötigt genau diese fünf Zusatzdateien derselben Quellversion. Ein separater Textencoder und ein Vision-Projektor werden nicht benötigt. tokenizer.json und tokenizer_config.json gehören zusammen, sie sind keine doppelten Modelle. Vorhandene Dateien werden wiederverwendet. Der VoxCPM-Worker ist in Deck noch nicht angebunden; auch ein vollständiges Dateipaket kann deshalb noch nicht gestartet werden.')
if voice_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='VoxCPM · eigene Python/PyTorch-Laufzeit',runtime_installed=bool(getattr(self,'voxcpm_runtime_ready',lambda:False)()),runtime_route='#voxcpm-runtime',worker_available=False,message='VoxCPM2 benötigt genau diese fünf Zusatzdateien derselben Quellversion. Ein separater Textencoder und ein Vision-Projektor werden nicht benötigt. tokenizer.json und tokenizer_config.json gehören zusammen, sie sind keine doppelten Modelle. Vorhandene Dateien werden wiederverwendet. Die passende Laufzeit kann unter Einstellungen → Laufzeiten → VoxCPM installiert werden. Der Deck-Worker fehlt noch; ein vollständiges Dateipaket allein kann noch nicht gestartet werden.')
if music_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='audio.cpp',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Passendes YuE2-GGUF-Paket: je eine VAE-Datei und die vier Zusatzdateien derselben Quellversion. F16-VAE ist die erste Downloadempfehlung. Vorhandene Dateien wiederverwenden, fehlende herunterladen und danach zuordnen. Die YuE2-Musikanbindung verwendet diese Dateien über audio.cpp. Speichern startet kein Modell; anschließend Musik → Testen verwenden.')
if video:return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='ComfyUI · gemeinsame Bild- und Videolaufzeit',runtime_installed=bool(getattr(self,'video_runtime_ready',lambda:False)()),message='Geprüftes Dateirezept nach Herstellerdokumentation für die zweistufige Distilled-Pipeline mit fester Bildanzahl. Alle Komponenten stammen aus derselben Version wie dein Transformer. Kein beliebiger Gemma-Encoder; keine Comfy-INT8-Dateien. Optionaler Duration-Head, Temporal-Upsampling und DFR sind nicht Teil dieses Rezepts. Vorhandene ComfyUI-Laufzeit wiederverwenden; Videomodell im Bereich Video aktivieren. Vollständige Dateien sind keine Speicherzusage.')
if not image_recipe(model):return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime='audio.cpp · Paketdefinition',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Zusatzdateien aus der installierten Laufzeit-Paketdefinition. Weitere modellabhängige Pakete können erforderlich sein; dies ist keine vollständige Worker-Zusage.')
+13 -3
View File
@@ -9,6 +9,7 @@ import secrets
from http.cookies import SimpleCookie, CookieError
from auth import CredentialStore, verify_password
from catalog import Catalog
from voxcpm_runtime import VoxCPMRuntime
from tts_runtime import TTSRuntime
from stt import STT,read_upload
from audio_policy import AudioPolicy
@@ -84,6 +85,8 @@ class Server(ThreadingHTTPServer):
self.image_tests.enhancer=self.prompt_enhancer
self.profiles.prompt_enhancer_ready=lambda task:bool(self.prompt_enhancer.installed(task))
self.profiles.image_runtime_ready=lambda:self.image_tests.status()["runtime_installed"]
self.voxcpm_runtime=VoxCPMRuntime(self.catalog.root.parent/'voxcpm-runtime',self.image_runtime)
self.profiles.voxcpm_runtime_ready=lambda:self.voxcpm_runtime.status()['installed']
self.tts_runtime=TTSRuntime(self.catalog.root.parent/'tts-runtime')
self.tts_tests=TTSTests(self.catalog.root.parent/'tts-tests',self.profiles,self.tts_runtime)
self.profiles.tts_blockers=self.tts_tests.blockers
@@ -308,7 +311,7 @@ class Handler(BaseHTTPRequestHandler):
'audio':self.server.tts_runtime.status().get('installed',False),
'video':video_runtime.status().get('installed',False),
'audio_cpp':self.server.audio_cpp_runtime.status()['installed'],
'music_worker':True,'separator':self.server.separator_runtime.status()['installed']}
'voxcpm':self.server.voxcpm_runtime.status()['installed'],'music_worker':True,'separator':self.server.separator_runtime.status()['installed']}
def execution_setup(self, model, profile=None, installed=None):
return execution_assess(model, self.execution_runtime_snapshot() if installed is None else installed, profile)
@@ -324,7 +327,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 = {'/separator-ui.js':('separator-ui.js','text/javascript'),'/music-ui.js':('music-ui.js','text/javascript'),'/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')}
routes = {'/voxcpm-ui.js':('voxcpm-ui.js','text/javascript'),'/separator-ui.js':('separator-ui.js','text/javascript'),'/music-ui.js':('music-ui.js','text/javascript'),'/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)
@@ -347,6 +350,8 @@ class Handler(BaseHTTPRequestHandler):
if urlsplit(self.path).path == '/api/v1/music/audio':
try:return self.respond(self.server.music.audio(parse_qs(urlsplit(self.path).query).get('id',[''])[0]),mime='audio/wav')
except (OSError,ValueError):return self.respond({'error':'Musik nicht verfügbar.'},404)
if self.path == '/api/v1/voxcpm-runtime':return self.respond(self.server.voxcpm_runtime.status())
if self.path == '/api/v1/voxcpm-runtime/log':return self.respond(self.server.voxcpm_runtime.log())
if self.path == '/api/v1/separator-runtime':return self.respond(self.server.separator_runtime.status())
if self.path == '/api/v1/separator-runtime/log':return self.respond(self.server.separator_runtime.log())
if self.path == '/api/v1/separator-tests':return self.respond(self.server.separator_tests.status())
@@ -547,6 +552,11 @@ class Handler(BaseHTTPRequestHandler):
if self.path == '/api/v1/separator-profiles/save':
try:return self.respond(self.server.separator_tests.configure(self.read_json()))
except (ValueError,OSError) as exc:return self.respond({'error':str(exc)},400)
if self.path in ('/api/v1/voxcpm-runtime/install','/api/v1/voxcpm-runtime/cancel'):
try:
if self.read_json():raise ValueError('Keine Parameter erwartet.')
return self.respond(self.server.voxcpm_runtime.stop() if self.path.endswith('/cancel') else self.server.voxcpm_runtime.start())
except (ValueError,OSError) as exc:return self.respond({'error':str(exc)},400)
if self.path in ('/api/v1/separator-runtime/install','/api/v1/separator-runtime/cancel','/api/v1/separator-runtime/download'):
try:
data=self.read_json()
@@ -751,7 +761,7 @@ def main():
server.endpoint.close()
server.stt.stop()
server.tts_tests.stop()
server.tts_runtime.stop()
server.tts_runtime.stop();server.voxcpm_runtime.stop()
server.image_runtime.stop()
server.ltx_original_runtime.stop()
server.separator_tests.stop();server.separator_runtime.stop();server.music.stop();server.audio_cpp_runtime.stop()
+3 -1
View File
@@ -8,10 +8,11 @@ const Studio=(()=>{
if(!force&&document.querySelector('#studio')?.dataset.page===page)return;
if(category!==page){category=page;section='discover';}
if(modelId)section='profiles';
const body=page==='stt-runtime'?STTUI.html(true):page==='tts-runtime'?TTSUI.html(true):['docker-runtime','services'].includes(page)?DockerUI.html(page==='services'):page==='runtimes'?runtimeOverview():page==='image-runtime'?imageRuntime():page==='runtime'?RuntimeUI.html():`<div class="kicker">ATHENA / MODELLVERWALTUNG</div><h1>${labels[page]}</h1><p>${audioDescriptions[page]||'Modelle entdecken, herunterladen und mit gespeicherten Profilen konfigurieren.'}</p>${page==='voice'?'<p><a class="link" href="#separator-runtime">Audio Separator einrichten →</a> · <a class="link" href="#separation">Audio-Trennung →</a></p><p class="note">Audio-Trennung nutzt die unterstützten Pakete im Audio-Separator-Bereich. Andere Hub-Dateien und Voice-Profile benötigen weiterhin eine geprüfte Anbindung.</p>':''}${audioDescriptions[page]&&!['audio','stt','music','voice'].includes(page)?'<p class="note">Entdecken und Downloads sind verfügbar. Für diese Audio-Bereiche ist noch keine ausführbare Laufzeit in Deck angebunden. Gespeicherte Profile sind vorbereitend.</p>':''}<div class="studio-tabs" role="tablist" aria-label="Modellverwaltung">${[['discover','Entdecken'],['library','Bibliothek'],['downloads','Downloads'],['profiles','Profile'],...(['audio','stt'].includes(page)?[['setup','Einrichten']]:[]),...(['image','chat','audio','stt','music'].includes(page)?[['test','Testen'],...(page==='chat'?[['auto','Auto-Test']]:[])]:[])].map(([id,label])=>`<button role="tab" aria-selected="${section===id}" class="${section===id?'chosen':'secondary'}" data-section="${id}">${label}</button>`).join('')}</div><div role="tabpanel">${section==='setup'?(page==='stt'?STTUI.html(true):TTSUI.html(true)):['test','running'].includes(section)&&page==='music'?MusicUI.html():['test','running'].includes(section)&&page==='stt'?STTUI.html():['test','running'].includes(section)&&page==='audio'?TTSUI.html():section==='auto'?AutoTestUI.html():section==='test'?(page==='chat'?ChatTestUI.html():ImageTestUI.html()):section==='running'&&page==='image'?ImageTestUI.html(true):section==='profiles'?ProfilesUI.html():section==='running'&&page==='chat'?EndpointUI.runningHTML():section==='running'?'<section class="card"><h2>Keine von Deck gestarteten Modelle</h2><p>Profile werden auf Athena gespeichert. Für diesen Bereich ist noch kein Modellworker angebunden. Fehlende Komponenten stehen beim jeweiligen Profil.</p><a class="link" href="#runtime">llama.cpp-Builds verwalten →</a></section>':CatalogUI.html(section==='library',section==='downloads')}</div>`;
const body=page==='voxcpm-runtime'?VoxCPMUI.html():page==='stt-runtime'?STTUI.html(true):page==='tts-runtime'?TTSUI.html(true):['docker-runtime','services'].includes(page)?DockerUI.html(page==='services'):page==='runtimes'?runtimeOverview():page==='image-runtime'?imageRuntime():page==='runtime'?RuntimeUI.html():`<div class="kicker">ATHENA / MODELLVERWALTUNG</div><h1>${labels[page]}</h1><p>${audioDescriptions[page]||'Modelle entdecken, herunterladen und mit gespeicherten Profilen konfigurieren.'}</p>${page==='voice'?'<p><a class="link" href="#separator-runtime">Audio Separator einrichten →</a> · <a class="link" href="#separation">Audio-Trennung →</a></p><p class="note">Audio-Trennung nutzt die unterstützten Pakete im Audio-Separator-Bereich. Andere Hub-Dateien und Voice-Profile benötigen weiterhin eine geprüfte Anbindung.</p>':''}${audioDescriptions[page]&&!['audio','stt','music','voice'].includes(page)?'<p class="note">Entdecken und Downloads sind verfügbar. Für diese Audio-Bereiche ist noch keine ausführbare Laufzeit in Deck angebunden. Gespeicherte Profile sind vorbereitend.</p>':''}<div class="studio-tabs" role="tablist" aria-label="Modellverwaltung">${[['discover','Entdecken'],['library','Bibliothek'],['downloads','Downloads'],['profiles','Profile'],...(['audio','stt'].includes(page)?[['setup','Einrichten']]:[]),...(['image','chat','audio','stt','music'].includes(page)?[['test','Testen'],...(page==='chat'?[['auto','Auto-Test']]:[])]:[])].map(([id,label])=>`<button role="tab" aria-selected="${section===id}" class="${section===id?'chosen':'secondary'}" data-section="${id}">${label}</button>`).join('')}</div><div role="tabpanel">${section==='setup'?(page==='stt'?STTUI.html(true):TTSUI.html(true)):['test','running'].includes(section)&&page==='music'?MusicUI.html():['test','running'].includes(section)&&page==='stt'?STTUI.html():['test','running'].includes(section)&&page==='audio'?TTSUI.html():section==='auto'?AutoTestUI.html():section==='test'?(page==='chat'?ChatTestUI.html():ImageTestUI.html()):section==='running'&&page==='image'?ImageTestUI.html(true):section==='profiles'?ProfilesUI.html():section==='running'&&page==='chat'?EndpointUI.runningHTML():section==='running'?'<section class="card"><h2>Keine von Deck gestarteten Modelle</h2><p>Profile werden auf Athena gespeichert. Für diesen Bereich ist noch kein Modellworker angebunden. Fehlende Komponenten stehen beim jeweiligen Profil.</p><a class="link" href="#runtime">llama.cpp-Builds verwalten →</a></section>':CatalogUI.html(section==='library',section==='downloads')}</div>`;
document.querySelector('#view').innerHTML=`<div id="studio" data-page="${page}">${body}</div>`;
if(['docker-runtime','services'].includes(page)){DockerUI.bind(page==='services');return;}
if(page==='stt-runtime'){STTUI.bind(true);return;}
if(page==='voxcpm-runtime'){VoxCPMUI.bind();return;}
if(page==='tts-runtime'){TTSUI.bind(true);return;}
if(page==='runtime'){RuntimeUI.bind();return;}
if(page==='image-runtime'){ImageTestUI.bind(true);ImageTestUI.bindInstaller();return;}
@@ -36,6 +37,7 @@ const Studio=(()=>{
['audio.cpp','Musik & Audio','audio-cpp-runtime','/api/v1/audio-cpp-runtime','CUDA-Build einmal erstellen. YuE2 ist angebunden; andere Modellfamilien brauchen eine geprüfte Worker-Anbindung.'],
['LTX Original','Video','ltx-original-runtime','/api/v1/ltx-original-runtime','Originales LTX-Backend für LTX DeskWEB. Modelle und Zusatzdateien bleiben in der Bibliothek.'],
['Docker Engine','Weitere Dienste','docker-runtime','/api/v1/docker','Optionale Container-Umgebung. Erstinstallation über den Systemhelfer; vorhandenes Docker wird nicht automatisch aktualisiert.'],
['VoxCPM','Stimmwerkzeuge','voxcpm-runtime','/api/v1/voxcpm-runtime','Originale VoxCPM-Python-Laufzeit installieren. Vorhandene CUDA-Pakete wiederverwenden; Modellkomponenten separat. Deck-Generierungsworker noch nicht angebunden.'],
['Audio Separator','Audio-Trennung','separator-runtime','/api/v1/separator-runtime','Native CUDA-Laufzeit für BS-Roformer, MelBand und Demucs FT/6s. Vollständige Modellpakete mit überprüften Originalquellen und Prüfsummen.']
];
const runtimeEscape=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
+13
View File
@@ -51,6 +51,19 @@ class RestoreTests(unittest.TestCase):
doc=open_backup(self.server.backup.export('backup secure password'),'backup secure password')
self.assertNotIn('PRIVATE-CHAT-LOG',json.dumps(doc));self.assertNotIn('model-weights-not-in-backup',json.dumps(doc));self.assertIn('history',doc);validate(doc)
self.assertEqual(doc['models'][0]['id'],ident);self.assertEqual(len(doc['settings']['profiles.json']),1)
def test_voxcpm_runtime_export_and_restore(self):
from voxcpm_runtime import VERSION
runtime=Mock();runtime.status.return_value={'installed':True,'version':VERSION,'job':{'state':'complete'}}
self.server.voxcpm_runtime=runtime
doc=self.server.backup.snapshot();validate(doc)
self.assertTrue(doc['runtimes']['voxcpm']);self.assertEqual(doc['runtime_versions']['voxcpm'],VERSION)
bad=copy.deepcopy(doc);bad['runtimes']['voxcpm']='yes'
with self.assertRaises(ValueError):validate(bad)
runtime.status.return_value={'installed':False,'version':VERSION,'job':{'state':'complete'}}
summary=self.server.backup.inspect(seal(doc,'backup secure password'),'backup secure password')
self.assertFalse(summary['blockers'])
self.server.backup.start(dict(id=summary['id'],services=[],confirm=True,restore_credentials=False));job=self.wait()
self.assertEqual(job['state'],'complete',job);runtime.start.assert_called_once()
def test_separator_restore_installs_runtime_and_redownloads_package(self):
from separator_runtime import MODELS
doc=self.server.backup.snapshot();name=next(iter(MODELS));doc['runtimes']['separator']={'installed':True,'models':[name]}
+1 -1
View File
@@ -31,4 +31,4 @@ class VoxCPMComponentsTests(unittest.TestCase):
self.assertEqual([f['name'] for f in c.files('other/VoxCPM2')['files']],['model.safetensors'])
def test_execution_does_not_claim_installed_audio_cpp_can_run_vox(self):
result=assess(dict(kind='voice',repo='openbmb/VoxCPM2',file='model.safetensors'),dict(audio_cpp=True))
self.assertEqual(result['family'],'VoxCPM2');self.assertEqual(result['state'],'adapter_missing');self.assertEqual(result['candidates'][0]['id'],'voxcpm');self.assertFalse(result['candidates'][0]['installation_available'])
self.assertEqual(result['family'],'VoxCPM2');self.assertEqual(result['state'],'adapter_missing');self.assertEqual(result['candidates'][0]['id'],'voxcpm');self.assertTrue(result['candidates'][0]['installation_available']);self.assertEqual(result['candidates'][0]['route'],'#voxcpm-runtime')
+28
View File
@@ -0,0 +1,28 @@
import json,tempfile,unittest
from pathlib import Path
from unittest.mock import Mock,patch
from voxcpm_runtime import VoxCPMRuntime,VERSION
class VoxCPMRuntimeTests(unittest.TestCase):
def test_requires_verified_marker_and_valid_owned_environment(self):
with tempfile.TemporaryDirectory() as d:
r=VoxCPMRuntime(d,None);self.assertFalse(r.status()['installed'])
base=Path(d)/('a'*32);(base/'python/bin').mkdir(parents=True);(base/'python/bin/python').touch();(Path(d)/'active.json').write_text(json.dumps(dict(id=base.name)))
(base/'ready').write_text('wrong');self.assertFalse(r.status()['installed'])
(base/'ready').write_text(VERSION);self.assertTrue(r.status()['installed']);self.assertFalse(r.status()['worker_available'])
def test_success_and_failure_do_not_touch_shared_runtime_or_load_weights(self):
for fail in (False,True):
with tempfile.TemporaryDirectory() as d:
shared=Mock();shared.paths.return_value=(Path('/does/not/exist'),Path('/does/not/exist'))
r=VoxCPMRuntime(d,shared);r.job=dict(id='b'*32,state='running',phase='test')
calls=[]
def run(args,env=None):
calls.append([str(a) for a in args])
if fail:raise RuntimeError('synthetic install failure')
r._command=run;r._run();self.assertEqual(r.job['state'],'failed' if fail else 'complete')
if fail:self.assertFalse((Path(d)/'active.json').exists());self.assertFalse((Path(d)/('b'*32)).exists())
else:
flat=' '.join(' '.join(x) for x in calls);self.assertIn('voxcpm=='+VERSION,flat);self.assertIn('pip check',flat);self.assertNotIn('snapshot_download',flat);self.assertNotIn('model.generate(',flat)
def test_cancel_does_not_activate_partial_environment(self):
with tempfile.TemporaryDirectory() as d:
r=VoxCPMRuntime(d,None);r.job=dict(id='c'*32,state='running',phase='test');r._command=Mock(side_effect=InterruptedError());r._run()
self.assertEqual(r.job['state'],'cancelled');self.assertFalse((Path(d)/'active.json').exists())
+11
View File
@@ -0,0 +1,11 @@
const VoxCPMUI=(()=>{
const e=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
function html(){return `<div class="kicker">EINSTELLUNGEN / LAUFZEITEN</div><h1>VoxCPM</h1><section class="card" id="voxcpm-panel"><h2>VoxCPM2 einrichten</h2><p>Diese Modellfamilie benötigt die originale VoxCPM-Python-Laufzeit. Eine eigene Umgebung schützt bestehende Laufzeiten; vorhandene CUDA-Pakete werden nach Möglichkeit wiederverwendet.</p><p id="voxcpm-status" role="status">Status wird geprüft …</p><p id="voxcpm-details"></p><progress id="voxcpm-progress" hidden aria-label="Laufzeitinstallation"></progress><div class="card-actions"><button id="voxcpm-install">Passende Laufzeit installieren</button><button id="voxcpm-cancel" class="secondary" disabled>Installation abbrechen</button></div><p class="note">Dieser Schritt installiert die Laufzeit. Er lädt keine Modellgewichte und startet kein Modell. Die Deck-Anbindung für Generierung und Stimmklonen fehlt noch; das Profil bleibt deshalb gesperrt.</p><details><summary>Installationsausgabe</summary><button id="voxcpm-log" class="secondary">Ausgabe aktualisieren</button><pre id="voxcpm-output" style="white-space:pre-wrap"></pre></details><p id="voxcpm-error" role="alert"></p><a class="link" href="#voice">Zurück zu Stimmwerkzeugen →</a></section>`;}
function bind(){const root=document.querySelector('#voxcpm-panel');if(!root)return;const el=id=>root.querySelector('#'+id);let busy=false;
async function api(path='',body){const r=await fetch('/api/v1/voxcpm-runtime'+path,body===undefined?{}:{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});const d=await r.json();if(!r.ok)throw Error(d.error||'Einrichtung fehlgeschlagen');return d;}
async function refresh(){try{const d=await api();if(!root.isConnected)return;el('voxcpm-status').textContent=d.job?.phase||(d.installed?'VoxCPM-Laufzeit installiert.':'Passende VoxCPM-Laufzeit fehlt.');el('voxcpm-details').textContent=`Version ${d.version} · mindestens ${d.required_disk_gib} GiB freier Speicher · ${d.shared_cuda?'CUDA-Pakete können wiederverwendet werden':'CUDA-Pakete werden mitinstalliert'}`;el('voxcpm-progress').hidden=d.job?.state!=='running';el('voxcpm-install').disabled=busy||d.installed||d.job?.state==='running';el('voxcpm-cancel').disabled=busy||d.job?.state!=='running';}catch(err){if(root.isConnected)el('voxcpm-error').textContent=err.message;}finally{if(root.isConnected)setTimeout(refresh,2000);}}
async function action(path){busy=true;el('voxcpm-install').disabled=true;el('voxcpm-error').textContent='';try{await api(path,{});}catch(err){el('voxcpm-error').textContent=err.message;}finally{busy=false;}}
el('voxcpm-install').onclick=()=>action('/install');el('voxcpm-cancel').onclick=()=>action('/cancel');el('voxcpm-log').onclick=async()=>{try{const d=await api('/log');el('voxcpm-output').textContent=d.text;}catch(err){el('voxcpm-error').textContent=err.message;}};refresh();
}
return {html,bind};
})();
+51
View File
@@ -0,0 +1,51 @@
"""Owned, GUI-installable VoxCPM runtime; never downloads or loads models."""
import json,os,shutil,sys,time
from pathlib import Path
from ltx_original_runtime import LTXOriginalRuntime
VERSION='2.0.3'
class VoxCPMRuntime(LTXOriginalRuntime):
name='VoxCPM'
def paths(self):
try:
ident=json.loads((self.root/'active.json').read_text())['id']
if len(ident)==32 and all(c in '0123456789abcdef' for c in ident):
base=self.root/ident
if (base/'ready').read_text().strip()==VERSION:return base/'python/bin/python',base
except (OSError,ValueError,KeyError):pass
return self.root/'missing-python',self.root/'missing-runtime'
def status(self):
python,base=self.paths();shared=self.image_runtime.paths()[0].is_file() if self.image_runtime else False
return dict(installed=python.is_file() and (base/'ready').is_file(),version=VERSION,job=dict(self.job) if self.job else None,required_disk_gib=8 if shared else 25,shared_cuda=shared,worker_available=False,origin='OpenBMB/VoxCPM · offizielles Python-Paket',message='Laufzeitinstallation lädt keine Gewichte. Für die Generierung fehlt noch der Deck-Worker.')
def start(self):
if not shutil.which('ffmpeg'):raise ValueError('FFmpeg fehlt. Systemvoraussetzungen über den Deck-Installer einrichten.')
return super().start()
def _run(self):
base=self.root/self.job['id'];state='failed';phase='VoxCPM-Einrichtung fehlgeschlagen.'
try:
base.mkdir(mode=0o700);tmp=base/'tmp';tmp.mkdir()
# A new environment may reference existing CUDA packages but never alters them.
env={k:v for k,v in os.environ.items() if not k.startswith(('HF_','DECK_','LLAMA_'))}
env.update(TMPDIR=str(tmp),PIP_NO_INPUT='1',PIP_DISABLE_PIP_VERSION_CHECK='1',HF_HUB_OFFLINE='1',OMP_NUM_THREADS='2')
shared=self.image_runtime.paths()[0] if self.image_runtime else Path('/missing')
bootstrap=shared if shared.is_file() else Path(sys.executable)
self._phase('Eigene VoxCPM-Python-Umgebung erstellen');self._command([bootstrap,'-m','venv',base/'python'],env)
python=base/'python/bin/python'
self._command([python,'-c','import sys; assert (3,10)<=sys.version_info[:2]<(3,13), "VoxCPM benötigt Python 3.10 bis 3.12"'],env)
if shared.is_file():
self._phase('Vorhandene CUDA-Pakete lesend wiederverwenden')
self._command([python,'-c','import json,site,subprocess,sys;from pathlib import Path;p=json.loads(subprocess.check_output([sys.argv[1],"-c","import json,site;print(json.dumps(site.getsitepackages()))"],text=True));Path(site.getsitepackages()[0],"deck-shared-cuda.pth").write_text("\\n".join(p)+"\\n")',shared],env)
self._phase('CUDA-PyTorch prüfen / installieren')
self._command([python,'-m','pip','install','--no-cache-dir','torch==2.11.0','torchaudio==2.11.0','--index-url','https://download.pytorch.org/whl/cu128'],env)
self._phase('VoxCPM '+VERSION+' und Abhängigkeiten installieren')
self._command([python,'-m','pip','install','--no-cache-dir','-c',Path(__file__).parent/'deploy/voxcpm-requirements.lock','voxcpm=='+VERSION],env)
self._phase('Paketkonsistenz und VoxCPM2-Unterstützung prüfen')
self._command([python,'-m','pip','check'],env)
self._command([python,'-c','import torch,soundfile;from voxcpm import VoxCPM;from voxcpm.model.voxcpm2 import VoxCPM2Model;assert torch.version.cuda;assert callable(VoxCPM.from_pretrained)'],env)
if self.cancel.is_set():raise InterruptedError()
(base/'ready').write_text(VERSION);marker=self.root/'active.tmp';marker.write_text(json.dumps(dict(id=base.name)));marker.replace(self.root/'active.json')
state='complete';phase='VoxCPM-Laufzeit installiert · noch kein Deck-Worker und kein Modell geladen'
except InterruptedError:state='cancelled';phase='VoxCPM-Einrichtung abgebrochen.'
except Exception as exc:phase=str(exc)
finally:
if state!='complete':shutil.rmtree(base,ignore_errors=True)
with self.lock:self.process=None;self.job.update(state=state,phase=phase,finished_at=time.time());self._save()