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) 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) 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 geprüft. Es ist keine Zusage getesteter Inferenz: **Der Deck-Generierungsworker ist noch nicht angebunden.** Eine vorhandene audio.cpp- oder
und ihr Deck-Worker sind noch nicht angebunden.** Eine vorhandene audio.cpp- oder
llama.cpp-Installation ersetzt diese Anbindung nicht. Das Profil bleibt gesperrt. llama.cpp-Installation ersetzt diese Anbindung nicht. Das Profil bleibt gesperrt.
Der Katalog erlaubt `.pth` ausschließlich für die exakt benannte offizielle 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 ob für alle Pflichtkomponenten eine herunterladbare Datei angeboten wird. Erst danach
werden Downloads eingereiht; bei einem späteren Fehler zeigt Deck bereits eingereihte werden Downloads eingereiht; bei einem späteren Fehler zeigt Deck bereits eingereihte
Dateien an. Manuelle, ungeprüfte Listen erhalten weiterhin keinen Sammeldownload. 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==='audio-cpp-runtime'){AudioCppUI.render();return;}
if(page==='ltx-original-runtime'){LTXOriginalUI.render();return;} if(page==='ltx-original-runtime'){LTXOriginalUI.render();return;}
if(['video','video-runtime'].includes(page)){VideoUI.runtime();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==='backup'){BackupUI.render();return;}
if(page==='access'){await accessPage();return;} if(page==='access'){await accessPage();return;}
if(page==='appearance'){appearancePage();return;} if(page==='appearance'){appearancePage();return;}
+11 -2
View File
@@ -75,7 +75,7 @@ def validate(doc):
validate_profiles(settings['separator-profiles.json']) validate_profiles(settings['separator-profiles.json'])
validate_record(doc.get('credentials')) validate_record(doc.get('credentials'))
runtimes=doc.get('runtimes') 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: if 'separator' in runtimes:
from separator_runtime import MODELS from separator_runtime import MODELS
value=runtimes['separator'] value=runtimes['separator']
@@ -83,6 +83,7 @@ def validate(doc):
if 'sources' in value: if 'sources' in value:
from separator_runtime import SOURCES 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 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 '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 '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 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): if getattr(s,'separator_runtime',None):
from separator_runtime import SOURCES 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'] 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']: if document['runtimes']['audio_cpp']:
from audio_cpp_runtime import REVISION from audio_cpp_runtime import REVISION
document['runtime_versions']['audio_cpp']=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.') 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.') 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',{}) 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'): if versions.get('separator'):
from separator_runtime import VERSION as SEPARATOR_VERSION from separator_runtime import VERSION as SEPARATOR_VERSION
if versions['separator']!=SEPARATOR_VERSION:blockers.append('Backup verlangt eine andere Audio-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'): if versions.get('ltx_original'):
from ltx_original_runtime import DESKTOP_REV,LTX_REV 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['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']: 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.') 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 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) 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']: 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) 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']: 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) self._phase('Originale LTX-Laufzeit installieren');s.ltx_original_runtime.start();self._wait(s.ltx_original_runtime.status)
if r.get('separator',{}).get('installed'): 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_runtime',None):self.server.separator_runtime.stop()
if getattr(self.server,'separator_tests',None):self.server.separator_tests.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,'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() if getattr(self.server,'audio_cpp_runtime',None):self.server.audio_cpp_runtime.stop()
self.server.prompt_enhancer.stop() self.server.prompt_enhancer.stop()
return {'cancellation_requested':True} 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/image-requirements.lock /app/deploy/image-requirements.lock
COPY deploy/tts-requirements.lock /app/deploy/tts-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 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 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 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-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 deploy/docker_backup.py /app/deploy/docker_backup.py
COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/ COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
@@ -26,3 +26,5 @@ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
DECK_NETWORK_MODE=disabled DECK_NETWORK_MODE=disabled
USER 65534:65534 USER 65534:65534
CMD ["python3", "server.py", "--port", "8108"] 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/image-requirements.lock /app/deploy/image-requirements.lock
COPY deploy/tts-requirements.lock /app/deploy/tts-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 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 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 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-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 deploy/docker_backup.py /app/deploy/docker_backup.py
COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/ COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
@@ -16,3 +16,5 @@ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
DECK_NETWORK_MODE=disabled DECK_NETWORK_MODE=disabled
USER 65534:65534 USER 65534:65534
CMD ["python3", "server.py", "--port", "8108"] 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 ROOT = Path(__file__).resolve().parent.parent
LABEL = 'de.casaderoll.athena-deck.standalone' 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): 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' backup_name=config['name']+'-previous'
if inspect(backup_name):raise RuntimeError('Rückfall-Containername belegt. Keine Änderung ausgeführt.') if inspect(backup_name):raise RuntimeError('Rückfall-Containername belegt. Keine Änderung ausgeführt.')
stamp=str(time.time_ns()) 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'] was_running=previous['State']['Running']
if was_running:run('docker','stop','--time','15',config['name']) if was_running:run('docker','stop','--time','15',config['name'])
run('docker','rename',config['name'],backup_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['family']='Qwen3-TTS CustomVoice'
result.update(runtime='Qwen3-TTS CustomVoice',route='#tts-runtime',components=['Vollständiges Modellpaket und Sprach-Tokenizer (im Einrichtungsassistenten enthalten)']) result.update(runtime='Qwen3-TTS CustomVoice',route='#tts-runtime',components=['Vollständiges Modellpaket und Sprach-Tokenizer (im Einrichtungsassistenten enthalten)'])
elif voice_recipe(model): 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'): 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.') 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')): 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): 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)) result['candidates'].append(dict(id=id,label=label,installed=bool(installed.get(key)),route=route,installation_available=maintained,evidence=reason))
if voice_recipe(model): 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'): if result.get('runtime'):
add('current',result['runtime'],kind,result['route'],'Vorhandene Deck-Zuordnung') 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'): 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>'; el('profile-editor').innerHTML='<section class="card editor"><p role="status">Komponenten werden geprüft …</p></section>';
try{ try{
const data=await api('profiles/components?'+new URLSearchParams({model_id:profile.model_id,...(extraRepo?{repo:extraRepo}:{})}));if(!root.isConnected||sequence!==panelSequence)return; 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)||[]; 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()=>{ 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; 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 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] 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)) 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 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 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.') 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 http.cookies import SimpleCookie, CookieError
from auth import CredentialStore, verify_password from auth import CredentialStore, verify_password
from catalog import Catalog from catalog import Catalog
from voxcpm_runtime import VoxCPMRuntime
from tts_runtime import TTSRuntime from tts_runtime import TTSRuntime
from stt import STT,read_upload from stt import STT,read_upload
from audio_policy import AudioPolicy from audio_policy import AudioPolicy
@@ -84,6 +85,8 @@ class Server(ThreadingHTTPServer):
self.image_tests.enhancer=self.prompt_enhancer self.image_tests.enhancer=self.prompt_enhancer
self.profiles.prompt_enhancer_ready=lambda task:bool(self.prompt_enhancer.installed(task)) 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.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_runtime=TTSRuntime(self.catalog.root.parent/'tts-runtime')
self.tts_tests=TTSTests(self.catalog.root.parent/'tts-tests',self.profiles,self.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 self.profiles.tts_blockers=self.tts_tests.blockers
@@ -308,7 +311,7 @@ class Handler(BaseHTTPRequestHandler):
'audio':self.server.tts_runtime.status().get('installed',False), 'audio':self.server.tts_runtime.status().get('installed',False),
'video':video_runtime.status().get('installed',False), 'video':video_runtime.status().get('installed',False),
'audio_cpp':self.server.audio_cpp_runtime.status()['installed'], '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): def execution_setup(self, model, profile=None, installed=None):
return execution_assess(model, self.execution_runtime_snapshot() if installed is None else installed, profile) 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) return self.respond({'error':'Anmeldung erforderlich.'},401)
if not self.authenticated() and self.path == '/': if not self.authenticated() and self.path == '/':
return self.respond((ROOT/'login.html').read_bytes(), mime='text/html; charset=utf-8') 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: if self.path in routes:
name, mime = routes[self.path] name, mime = routes[self.path]
return self.respond((ROOT/name).read_bytes(), mime=mime) 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': 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') 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) 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':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-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()) 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': if self.path == '/api/v1/separator-profiles/save':
try:return self.respond(self.server.separator_tests.configure(self.read_json())) try:return self.respond(self.server.separator_tests.configure(self.read_json()))
except (ValueError,OSError) as exc:return self.respond({'error':str(exc)},400) 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'): if self.path in ('/api/v1/separator-runtime/install','/api/v1/separator-runtime/cancel','/api/v1/separator-runtime/download'):
try: try:
data=self.read_json() data=self.read_json()
@@ -751,7 +761,7 @@ def main():
server.endpoint.close() server.endpoint.close()
server.stt.stop() server.stt.stop()
server.tts_tests.stop() server.tts_tests.stop()
server.tts_runtime.stop() server.tts_runtime.stop();server.voxcpm_runtime.stop()
server.image_runtime.stop() server.image_runtime.stop()
server.ltx_original_runtime.stop() server.ltx_original_runtime.stop()
server.separator_tests.stop();server.separator_runtime.stop();server.music.stop();server.audio_cpp_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(!force&&document.querySelector('#studio')?.dataset.page===page)return;
if(category!==page){category=page;section='discover';} if(category!==page){category=page;section='discover';}
if(modelId)section='profiles'; 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>`; document.querySelector('#view').innerHTML=`<div id="studio" data-page="${page}">${body}</div>`;
if(['docker-runtime','services'].includes(page)){DockerUI.bind(page==='services');return;} if(['docker-runtime','services'].includes(page)){DockerUI.bind(page==='services');return;}
if(page==='stt-runtime'){STTUI.bind(true);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==='tts-runtime'){TTSUI.bind(true);return;}
if(page==='runtime'){RuntimeUI.bind();return;} if(page==='runtime'){RuntimeUI.bind();return;}
if(page==='image-runtime'){ImageTestUI.bind(true);ImageTestUI.bindInstaller();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.'], ['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.'], ['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.'], ['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.'] ['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])); 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') 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.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) 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): def test_separator_restore_installs_runtime_and_redownloads_package(self):
from separator_runtime import MODELS from separator_runtime import MODELS
doc=self.server.backup.snapshot();name=next(iter(MODELS));doc['runtimes']['separator']={'installed':True,'models':[name]} 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']) 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): 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)) 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()