Integrate YuE2 as an exclusive Athena mode
This commit is contained in:
@@ -13,6 +13,7 @@ Sie betreibt:
|
|||||||
- FLUX.2 Klein 9B FP8 Beta für Textbilder und Referenzbild-Bearbeitung,
|
- FLUX.2 Klein 9B FP8 Beta für Textbilder und Referenzbild-Bearbeitung,
|
||||||
- Qwen3-TTS für Sprache,
|
- Qwen3-TTS für Sprache,
|
||||||
- ACE-Step 1.5 XL-SFT als exklusiven Musikstudio-Modus,
|
- ACE-Step 1.5 XL-SFT als exklusiven Musikstudio-Modus,
|
||||||
|
- YuE2-3B mit Ladypoly-WebUI als zweiten, getrennten Musikstudio-Modus,
|
||||||
- TRELLIS.2 4B Q8 als exklusives Bild-zu-3D-Studio,
|
- TRELLIS.2 4B Q8 als exklusives Bild-zu-3D-Studio,
|
||||||
- das Athena-Dashboard,
|
- das Athena-Dashboard,
|
||||||
- Portainer CE als optionale Ansicht auf die laufenden Docker-Container,
|
- Portainer CE als optionale Ansicht auf die laufenden Docker-Container,
|
||||||
@@ -31,6 +32,7 @@ werden keine zweiten Instanzen dieser Dienste angelegt.
|
|||||||
| `/data/llama-dashboard` | historische Dashboard-Messwerte |
|
| `/data/llama-dashboard` | historische Dashboard-Messwerte |
|
||||||
| `/data/docker-backups` | automatische Athena-Backups |
|
| `/data/docker-backups` | automatische Athena-Backups |
|
||||||
| `/data/trellis-studio` | trellis.cpp-Runtime und erzeugte 3D-Modelle |
|
| `/data/trellis-studio` | trellis.cpp-Runtime und erzeugte 3D-Modelle |
|
||||||
|
| `/data/models/yue2`, `/data/music/yue2` | YuE2-Gewichte und dauerhafte Ergebnisse |
|
||||||
| `/etc/mike-ai` | lokale Konfiguration und Secrets, niemals Git |
|
| `/etc/mike-ai` | lokale Konfiguration und Secrets, niemals Git |
|
||||||
|
|
||||||
Portainer läuft als separater, optionaler Verwaltungscontainer
|
Portainer läuft als separater, optionaler Verwaltungscontainer
|
||||||
@@ -74,6 +76,8 @@ Qwen-Profil wird vom Profile Controller verwaltet.
|
|||||||
- ACE-Step 1.5 XL-SFT: exklusiver Musikmodus auf der RTX 5080. Dashboard und
|
- ACE-Step 1.5 XL-SFT: exklusiver Musikmodus auf der RTX 5080. Dashboard und
|
||||||
die Routerbefehle `/athena music`, `/athena llm`, `/athena status` bedienen
|
die Routerbefehle `/athena music`, `/athena llm`, `/athena status` bedienen
|
||||||
dieselbe persistente Zustandsmaschine; siehe `docs/OPERATING_MODES.md`.
|
dieselbe persistente Zustandsmaschine; siehe `docs/OPERATING_MODES.md`.
|
||||||
|
- YuE2-3B: eigener exklusiver Musikmodus mit Score- und Remix-Funktionen unter
|
||||||
|
`http://192.168.1.212:8014`. Der Routerbefehl lautet `/athena yue2`.
|
||||||
- TRELLIS.2 4B Q8: exklusives Bild-zu-3D-Profil auf der RTX 5080. Die
|
- TRELLIS.2 4B Q8: exklusives Bild-zu-3D-Profil auf der RTX 5080. Die
|
||||||
browserbasierte Oberfläche läuft unter `http://192.168.1.212:8013`, erzeugt
|
browserbasierte Oberfläche läuft unter `http://192.168.1.212:8013`, erzeugt
|
||||||
GLB und verwendet standardmäßig `1024 · cascade`. Der 1536er Pfad kann die
|
GLB und verwendet standardmäßig `1024 · cascade`. Der 1536er Pfad kann die
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ Betriebsmodus gestartet. Zum Zeitpunkt der Aufnahme war Applio/RVC aktiv.
|
|||||||
| mike-ai-music-ui | läuft | Community-Weboberfläche für ACE-Step; das |
|
| mike-ai-music-ui | läuft | Community-Weboberfläche für ACE-Step; das |
|
||||||
| | | eigentliche Musikmodell wird separat geladen.|
|
| | | eigentliche Musikmodell wird separat geladen.|
|
||||||
+-----------------------------------+-------------------+----------------------------------------------+
|
+-----------------------------------+-------------------+----------------------------------------------+
|
||||||
|
| mike-ai-yue2-playground | gestoppt/bereit | Eigenständiges YuE2-Musikstudio mit Score-, |
|
||||||
|
| | | Generierungs- und Remix-Funktionen auf :8014.|
|
||||||
|
+-----------------------------------+-------------------+----------------------------------------------+
|
||||||
| mike-ai-stem-separator | gestoppt/bereit | Trennt Gesang, Begleitung und Instrumente |
|
| mike-ai-stem-separator | gestoppt/bereit | Trennt Gesang, Begleitung und Instrumente |
|
||||||
| | | mit BS-RoFormer und Demucs. |
|
| | | mit BS-RoFormer und Demucs. |
|
||||||
+-----------------------------------+-------------------+----------------------------------------------+
|
+-----------------------------------+-------------------+----------------------------------------------+
|
||||||
@@ -185,6 +188,8 @@ benötigten Modelle.
|
|||||||
+---------------+------------------------------------------------------------+
|
+---------------+------------------------------------------------------------+
|
||||||
| Musik | ACE-Step 1.5 für Musikgenerierung. |
|
| Musik | ACE-Step 1.5 für Musikgenerierung. |
|
||||||
+---------------+------------------------------------------------------------+
|
+---------------+------------------------------------------------------------+
|
||||||
|
| YuE2 Studio | YuE2-3B für Musik, Score-Steuerung und Audio-Remix. |
|
||||||
|
+---------------+------------------------------------------------------------+
|
||||||
| Audio trennen | BS-RoFormer, Demucs oder MossFormer2. |
|
| Audio trennen | BS-RoFormer, Demucs oder MossFormer2. |
|
||||||
+---------------+------------------------------------------------------------+
|
+---------------+------------------------------------------------------------+
|
||||||
| Voice Studio | OmniVoice für referenzbasierte Text-zu-Sprache-Ausgabe. |
|
| Voice Studio | OmniVoice für referenzbasierte Text-zu-Sprache-Ausgabe. |
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ Details, Installation, Prüfung und Rollback stehen in
|
|||||||
- Applio (RVC-Inferenz, Modelle und Training): `http://192.168.1.212:8011`
|
- Applio (RVC-Inferenz, Modelle und Training): `http://192.168.1.212:8011`
|
||||||
- Mikes Applio UI (geführte RVC-Oberfläche): `http://192.168.1.212:8012`
|
- Mikes Applio UI (geführte RVC-Oberfläche): `http://192.168.1.212:8012`
|
||||||
- 3D Studio (TRELLIS.2 Q8, GLB-Ausgabe): `http://192.168.1.212:8013`
|
- 3D Studio (TRELLIS.2 Q8, GLB-Ausgabe): `http://192.168.1.212:8013`
|
||||||
|
- YuE2 Studio (YuE2-3B, Generierung und Audio-Remix): `http://192.168.1.212:8014`
|
||||||
|
|
||||||
Der Betriebsmodus lässt sich dort direkt umschalten. In Hermes funktionieren
|
Der Betriebsmodus lässt sich dort direkt umschalten. In Hermes funktionieren
|
||||||
außerdem `/athena music`, `/athena stems`, `/athena voice`,
|
außerdem `/athena music`, `/athena stems`, `/athena voice`,
|
||||||
|
|||||||
@@ -487,10 +487,12 @@ services:
|
|||||||
RESTORE_WORKER: restore
|
RESTORE_WORKER: restore
|
||||||
TTS_WORKER: qwen3
|
TTS_WORKER: qwen3
|
||||||
MUSIC_WORKER: acestep
|
MUSIC_WORKER: acestep
|
||||||
|
YUE2_WORKER: yue2
|
||||||
SEPARATOR_WORKER: bs-roformer
|
SEPARATOR_WORKER: bs-roformer
|
||||||
VOICE_WORKER: vevo2
|
VOICE_WORKER: vevo2
|
||||||
VOICE_CHANGE_WORKER: xvc
|
VOICE_CHANGE_WORKER: xvc
|
||||||
APPLIO_WORKER: applio
|
APPLIO_WORKER: applio
|
||||||
|
TRELLIS_WORKER: trellis2-q8
|
||||||
networks: [control]
|
networks: [control]
|
||||||
security_opt: ["no-new-privileges:true"]
|
security_opt: ["no-new-privileges:true"]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -526,6 +528,8 @@ services:
|
|||||||
PROFILE_CONTROL_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}"
|
PROFILE_CONTROL_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}"
|
||||||
SWITCH_TIMEOUT: "600"
|
SWITCH_TIMEOUT: "600"
|
||||||
REQUEST_TIMEOUT: "600"
|
REQUEST_TIMEOUT: "600"
|
||||||
|
YUE2_START_TIMEOUT: "600"
|
||||||
|
TRELLIS_START_TIMEOUT: "900"
|
||||||
# Last-resort guard for every OpenAI-compatible client. Without a
|
# Last-resort guard for every OpenAI-compatible client. Without a
|
||||||
# request limit llama.cpp uses n_predict=-1 and a reasoning loop can
|
# request limit llama.cpp uses n_predict=-1 and a reasoning loop can
|
||||||
# consume the complete context before yielding visible output.
|
# consume the complete context before yielding visible output.
|
||||||
@@ -748,6 +752,8 @@ services:
|
|||||||
VOICE_CHANGE_UI_URL: "${VOICE_CHANGE_UI_URL:-http://192.168.1.212:8009/}"
|
VOICE_CHANGE_UI_URL: "${VOICE_CHANGE_UI_URL:-http://192.168.1.212:8009/}"
|
||||||
APPLIO_UI_URL: "${APPLIO_UI_URL:-http://192.168.1.212:8011/}"
|
APPLIO_UI_URL: "${APPLIO_UI_URL:-http://192.168.1.212:8011/}"
|
||||||
MIKES_APPLIO_UI_URL: "${MIKES_APPLIO_UI_URL:-http://192.168.1.212:8012/}"
|
MIKES_APPLIO_UI_URL: "${MIKES_APPLIO_UI_URL:-http://192.168.1.212:8012/}"
|
||||||
|
TRELLIS_UI_URL: "${TRELLIS_UI_URL:-http://192.168.1.212:8013/}"
|
||||||
|
YUE2_UI_URL: "${YUE2_UI_URL:-http://192.168.1.212:8014/}"
|
||||||
HOST_PROC: /host/proc
|
HOST_PROC: /host/proc
|
||||||
HOST_DATA: /host/data
|
HOST_DATA: /host/data
|
||||||
HOST_MODELS: /host/models
|
HOST_MODELS: /host/models
|
||||||
|
|||||||
@@ -80,6 +80,13 @@ class DashboardModeTests(unittest.TestCase):
|
|||||||
self.assertEqual(body, {"status": "accepted"})
|
self.assertEqual(body, {"status": "accepted"})
|
||||||
self.assertEqual(json.loads(urlopen.call_args.args[0].data), {"mode": "applio"})
|
self.assertEqual(json.loads(urlopen.call_args.args[0].data), {"mode": "applio"})
|
||||||
|
|
||||||
|
def test_yue2_mode_is_forwarded_to_router(self):
|
||||||
|
with patch.object(self.dashboard.urllib.request, "urlopen", return_value=_Response()) as urlopen:
|
||||||
|
status, body = self.dashboard.change_mode("yue2")
|
||||||
|
self.assertEqual(status, 202)
|
||||||
|
self.assertEqual(body, {"status": "accepted"})
|
||||||
|
self.assertEqual(json.loads(urlopen.call_args.args[0].data), {"mode": "yue2"})
|
||||||
|
|
||||||
def test_unknown_mode_is_rejected_without_router_request(self):
|
def test_unknown_mode_is_rejected_without_router_request(self):
|
||||||
with patch.object(self.dashboard.urllib.request, "urlopen") as urlopen:
|
with patch.object(self.dashboard.urllib.request, "urlopen") as urlopen:
|
||||||
status, body = self.dashboard.change_mode("unknown")
|
status, body = self.dashboard.change_mode("unknown")
|
||||||
@@ -104,6 +111,15 @@ class DashboardModeTests(unittest.TestCase):
|
|||||||
self.assertIn("http://192.168.1.212:8011/", html)
|
self.assertIn("http://192.168.1.212:8011/", html)
|
||||||
self.assertIn("http://192.168.1.212:8012/", html)
|
self.assertIn("http://192.168.1.212:8012/", html)
|
||||||
|
|
||||||
|
def test_dashboard_keeps_ace_step_and_offers_yue2_separately(self):
|
||||||
|
html = self.dashboard.HTML
|
||||||
|
|
||||||
|
self.assertIn("ACE-Step Studio", html)
|
||||||
|
self.assertIn("YuE2 Studio", html)
|
||||||
|
self.assertIn("setMode('music')", html)
|
||||||
|
self.assertIn("setMode('yue2')", html)
|
||||||
|
self.assertIn("http://192.168.1.212:8014/", html)
|
||||||
|
|
||||||
def test_dashboard_lists_only_portable_encrypted_backups(self):
|
def test_dashboard_lists_only_portable_encrypted_backups(self):
|
||||||
valid = self.backup_dir / "athena-portable-2026-09-10T10-00-00Z.tar.zst.age"
|
valid = self.backup_dir / "athena-portable-2026-09-10T10-00-00Z.tar.zst.age"
|
||||||
valid.write_bytes(b"encrypted")
|
valid.write_bytes(b"encrypted")
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ def music_item(state="exited"):
|
|||||||
"Labels": {controller.MUSIC_LABEL_KEY: "acestep"}}
|
"Labels": {controller.MUSIC_LABEL_KEY: "acestep"}}
|
||||||
|
|
||||||
|
|
||||||
|
def yue2_item(state="exited"):
|
||||||
|
return {"Id": "id-yue2", "State": state,
|
||||||
|
"Labels": {controller.MUSIC_LABEL_KEY: "yue2"}}
|
||||||
|
|
||||||
|
|
||||||
def separator_item(state="exited"):
|
def separator_item(state="exited"):
|
||||||
return {"Id": "id-separator", "State": state,
|
return {"Id": "id-separator", "State": state,
|
||||||
"Labels": {controller.SEPARATOR_LABEL_KEY: "bs-roformer"}}
|
"Labels": {controller.SEPARATOR_LABEL_KEY: "bs-roformer"}}
|
||||||
@@ -57,6 +62,30 @@ def voice_change_item(state="exited"):
|
|||||||
|
|
||||||
|
|
||||||
class ProfileControllerTests(unittest.TestCase):
|
class ProfileControllerTests(unittest.TestCase):
|
||||||
|
def test_yue2_start_exclusively_stops_llm_and_ace_step(self):
|
||||||
|
profiles = {name: item(name) for name in controller.ALLOWED}
|
||||||
|
profiles["ultra"] = item("ultra", "running")
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def request(method, path):
|
||||||
|
calls.append((method, path))
|
||||||
|
return 204, b""
|
||||||
|
|
||||||
|
with patch.object(controller, "YUE2_WORKER", "yue2"), \
|
||||||
|
patch.object(controller, "MUSIC_WORKER", "acestep"), \
|
||||||
|
patch.object(controller, "containers", return_value=profiles), \
|
||||||
|
patch.object(controller, "yue2_container", return_value=yue2_item()), \
|
||||||
|
patch.object(controller, "music_container", return_value=music_item("running")), \
|
||||||
|
patch.object(controller, "image_containers", return_value=[image_item("running")]), \
|
||||||
|
patch.object(controller, "tts_container", return_value=tts_item()), \
|
||||||
|
patch.object(controller, "docker_request", side_effect=request):
|
||||||
|
result = controller.set_yue2_worker(True)
|
||||||
|
|
||||||
|
self.assertEqual(result, {"yue2_worker": "yue2", "state": "running"})
|
||||||
|
self.assertIn(("POST", "/containers/id-ultra/stop?t=120"), calls)
|
||||||
|
self.assertIn(("POST", "/containers/id-music/stop?t=30"), calls)
|
||||||
|
self.assertEqual(calls[-1], ("POST", "/containers/id-yue2/start"))
|
||||||
|
|
||||||
def test_voice_change_start_exclusively_stops_gpu_workers(self):
|
def test_voice_change_start_exclusively_stops_gpu_workers(self):
|
||||||
profiles = {name: item(name) for name in controller.ALLOWED}
|
profiles = {name: item(name) for name in controller.ALLOWED}
|
||||||
profiles["medium"] = item("medium", "running")
|
profiles["medium"] = item("medium", "running")
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ nicht automatisch ein ungenutzter Rest.
|
|||||||
| `mike-ai-router` | kein eigenes Modell | Einzige OpenAI-kompatible Modelladresse; koordiniert Profile, Bildaufträge, Sprache und Betriebsarten. |
|
| `mike-ai-router` | kein eigenes Modell | Einzige OpenAI-kompatible Modelladresse; koordiniert Profile, Bildaufträge, Sprache und Betriebsarten. |
|
||||||
| `mike-ai-stem-separator` | BS-RoFormer Viperx 1297, `htdemucs_ft`, `htdemucs_6s`, `MossFormer2_SE_48K` | Trennt Gesang, Instrumente oder Sprache/Hintergrundgeräusche im exklusiven Separationsmodus. |
|
| `mike-ai-stem-separator` | BS-RoFormer Viperx 1297, `htdemucs_ft`, `htdemucs_6s`, `MossFormer2_SE_48K` | Trennt Gesang, Instrumente oder Sprache/Hintergrundgeräusche im exklusiven Separationsmodus. |
|
||||||
| `mike-ai-trellis-studio` | TRELLIS.2 4B Q8 über trellis.cpp 0.6.0 | Erzeugt im exklusiven 3D-Modus aus einem Bild ein texturiertes, geschlossen aufbereitetes GLB-Mesh. Nutzt ausschließlich die RTX 5080 und wird über Port 8013 bedient. |
|
| `mike-ai-trellis-studio` | TRELLIS.2 4B Q8 über trellis.cpp 0.6.0 | Erzeugt im exklusiven 3D-Modus aus einem Bild ein texturiertes, geschlossen aufbereitetes GLB-Mesh. Nutzt ausschließlich die RTX 5080 und wird über Port 8013 bedient. |
|
||||||
|
| `mike-ai-yue2-playground` | YuE2-3B mit Ladypoly `YuE2_WebUI` | Eigenständiges Musikstudio für Generierung, Score-basierte Steuerung und SheetSage2-Audioanalyse/Remix. Läuft exklusiv zu ACE-Step und allen übrigen GPU-Diensten; Zugriff über Port 8014. |
|
||||||
| `mike-ai-tts-gateway` | kein eigenes Modell | Normalisiert Text, konvertiert Ausgabeformate und stellt Qwen3-TTS sowie natives PCM-Streaming über eine stabile interne API bereit. |
|
| `mike-ai-tts-gateway` | kein eigenes Modell | Normalisiert Text, konvertiert Ausgabeformate und stellt Qwen3-TTS sowie natives PCM-Streaming über eine stabile interne API bereit. |
|
||||||
| `mike-ai-voice-studio` | `k2-fsa/OmniVoice` 0.2.1 mit Whisper-ASR | Erzeugt Text-to-Speech mit einer Referenzstimme; kein Audio-to-Audio-Voice-Changer. |
|
| `mike-ai-voice-studio` | `k2-fsa/OmniVoice` 0.2.1 mit Whisper-ASR | Erzeugt Text-to-Speech mit einer Referenzstimme; kein Audio-to-Audio-Voice-Changer. |
|
||||||
| `mike-ai-whisper` | Whisper.cpp `ggml-small` | Lokale deutsche Spracherkennung auf der CPU über `/v1/audio/transcriptions`. |
|
| `mike-ai-whisper` | Whisper.cpp `ggml-small` | Lokale deutsche Spracherkennung auf der CPU über `/v1/audio/transcriptions`. |
|
||||||
|
|||||||
+16
-3
@@ -1,9 +1,11 @@
|
|||||||
# Athena-Betriebsmodi
|
# Athena-Betriebsmodi
|
||||||
|
|
||||||
Athena besitzt sieben gegenseitig exklusive Betriebsmodi:
|
Athena besitzt acht gegenseitig exklusive Betriebsmodi:
|
||||||
|
|
||||||
- `llm`: ein llama.cpp-Profil und Qwen3-TTS laufen; Spezialdienste sind gestoppt.
|
- `llm`: ein llama.cpp-Profil und Qwen3-TTS laufen; Spezialdienste sind gestoppt.
|
||||||
- `music`: ACE-Step 1.5 XL-SFT läuft; alle LLM-, Bild-, TTS- und Separator-Worker sind gestoppt.
|
- `music`: ACE-Step 1.5 XL-SFT läuft; alle LLM-, Bild-, TTS- und Separator-Worker sind gestoppt.
|
||||||
|
- `yue2`: YuE2-3B und die angepasste `YuE2_WebUI` laufen; alle anderen
|
||||||
|
GPU-Dienste einschließlich ACE-Step sind gestoppt.
|
||||||
- `separation`: BS-RoFormer und Demucs trennen Musikspuren; ClearVoice trennt
|
- `separation`: BS-RoFormer und Demucs trennen Musikspuren; ClearVoice trennt
|
||||||
Sprache von Hintergrundgeräuschen. LLM, Bild, TTS und ACE-Step sind gestoppt.
|
Sprache von Hintergrundgeräuschen. LLM, Bild, TTS und ACE-Step sind gestoppt.
|
||||||
- `voice`: OmniVoice erzeugt Sprache aus Text mit einer gewählten
|
- `voice`: OmniVoice erzeugt Sprache aus Text mit einer gewählten
|
||||||
@@ -23,7 +25,7 @@ wird persistent gespeichert und beim Verlassen eines Spezialmodus wieder geladen
|
|||||||
|
|
||||||
## Bedienung
|
## Bedienung
|
||||||
|
|
||||||
Im Athena-Dashboard stehen **LLM-Betrieb**, **Musikstudio**, **Audio trennen**,
|
Im Athena-Dashboard stehen **LLM-Betrieb**, **ACE-Step Studio**, **YuE2 Studio**, **Audio trennen**,
|
||||||
**Voice Studio**, **X-VC**, **Applio / RVC** und **3D Studio** bereit. Im Musikmodus werden zwei Oberflächen angeboten:
|
**Voice Studio**, **X-VC**, **Applio / RVC** und **3D Studio** bereit. Im Musikmodus werden zwei Oberflächen angeboten:
|
||||||
|
|
||||||
- **Original UI · stabil** öffnet die zum laufenden ACE-Step-Image gehörende
|
- **Original UI · stabil** öffnet die zum laufenden ACE-Step-Image gehörende
|
||||||
@@ -47,6 +49,15 @@ veröffentlicht. `ace-step-ui` ist reproduzierbar auf Commit
|
|||||||
`a1fdf91829ec6f7b98844f80e323529cd155dbf2` fixiert und greift intern über das
|
`a1fdf91829ec6f7b98844f80e323529cd155dbf2` fixiert und greift intern über das
|
||||||
Docker-Netz `mike-ai-music` auf `http://music-worker:7860` zu.
|
Docker-Netz `mike-ai-music` auf `http://music-worker:7860` zu.
|
||||||
|
|
||||||
|
Das getrennte **YuE2 Studio** ist über WireGuard unter
|
||||||
|
`http://192.168.1.212:8014` erreichbar. Es verwendet YuE2-3B und die auf einen
|
||||||
|
festen Commit gesetzte `YuE2_WebUI` von Ladypoly. Analyse/Remix über
|
||||||
|
SheetSage2, Score-Übernahme und freie Generierung bleiben damit unabhängig vom
|
||||||
|
ACE-Step-Stack. Der Container trägt das Router-Label
|
||||||
|
`com.mike-ai.music-worker=yue2` und hängt als `yue2-studio` im privaten
|
||||||
|
Frontend-Netz. Ein Moduswechsel stoppt ihn zuverlässig, bevor LLM,
|
||||||
|
Audio-Trenner oder ein anderer GPU-Dienst gestartet werden.
|
||||||
|
|
||||||
Im Trennmodus öffnet das Dashboard die private Athena-Oberfläche unter
|
Im Trennmodus öffnet das Dashboard die private Athena-Oberfläche unter
|
||||||
`http://192.168.1.212:8007`. Sie nimmt WAV, FLAC, MP3, M4A und weitere
|
`http://192.168.1.212:8007`. Sie nimmt WAV, FLAC, MP3, M4A und weitere
|
||||||
übliche Formate an. Gewählt wird die herauszulösende Quelle: Gesang,
|
übliche Formate an. Gewählt wird die herauszulösende Quelle: Gesang,
|
||||||
@@ -98,6 +109,7 @@ Router lokal beantwortet, auch wenn gerade kein LLM geladen ist:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
/athena music
|
/athena music
|
||||||
|
/athena yue2
|
||||||
/athena stems
|
/athena stems
|
||||||
/athena voice
|
/athena voice
|
||||||
/athena voicechange
|
/athena voicechange
|
||||||
@@ -113,6 +125,7 @@ Die HTTP-Schnittstelle verwendet authentifizierte Requests:
|
|||||||
```text
|
```text
|
||||||
GET /mode
|
GET /mode
|
||||||
POST /mode {"mode":"music"}
|
POST /mode {"mode":"music"}
|
||||||
|
POST /mode {"mode":"yue2"}
|
||||||
POST /mode {"mode":"separation"}
|
POST /mode {"mode":"separation"}
|
||||||
POST /mode {"mode":"voice"}
|
POST /mode {"mode":"voice"}
|
||||||
POST /mode {"mode":"voicechange"}
|
POST /mode {"mode":"voicechange"}
|
||||||
@@ -123,7 +136,7 @@ POST /mode {"mode":"llm"}
|
|||||||
|
|
||||||
Der Wechsel läuft asynchron. Fortschritt und Fehler stehen unter `mode` in
|
Der Wechsel läuft asynchron. Fortschritt und Fehler stehen unter `mode` in
|
||||||
`GET /status`. Der Profile-Controller akzeptiert ausschließlich den mit
|
`GET /status`. Der Profile-Controller akzeptiert ausschließlich den mit
|
||||||
`com.mike-ai.music-worker=acestep` beziehungsweise
|
`com.mike-ai.music-worker=acestep` oder `com.mike-ai.music-worker=yue2` beziehungsweise
|
||||||
`com.mike-ai.stem-separator=bs-roformer` oder
|
`com.mike-ai.stem-separator=bs-roformer` oder
|
||||||
`com.mike-ai.voice-worker=vevo2` beziehungsweise
|
`com.mike-ai.voice-worker=vevo2` beziehungsweise
|
||||||
`com.mike-ai.voice-change-worker=xvc` oder
|
`com.mike-ai.voice-change-worker=xvc` oder
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ services:
|
|||||||
image: mike-ai/yue2:3b-0.1.6
|
image: mike-ai/yue2:3b-0.1.6
|
||||||
container_name: mike-ai-yue2-playground
|
container_name: mike-ai-yue2-playground
|
||||||
restart: "no"
|
restart: "no"
|
||||||
|
labels:
|
||||||
|
com.mike-ai.music-worker: yue2
|
||||||
entrypoint: ["python", "/opt/yue2/webui/server.py"]
|
entrypoint: ["python", "/opt/yue2/webui/server.py"]
|
||||||
environment:
|
environment:
|
||||||
NVIDIA_VISIBLE_DEVICES: "GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe"
|
NVIDIA_VISIBLE_DEVICES: "GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe"
|
||||||
@@ -44,6 +46,9 @@ services:
|
|||||||
TRANSFORMERS_OFFLINE: "1"
|
TRANSFORMERS_OFFLINE: "1"
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8014:8014"
|
- "127.0.0.1:8014:8014"
|
||||||
|
networks:
|
||||||
|
frontend:
|
||||||
|
aliases: [yue2-studio]
|
||||||
volumes:
|
volumes:
|
||||||
- /data/models/yue2:/opt/yue2/models:ro
|
- /data/models/yue2:/opt/yue2/models:ro
|
||||||
- /data/music/yue2:/workspace/runs
|
- /data/music/yue2:/workspace/runs
|
||||||
@@ -54,6 +59,7 @@ services:
|
|||||||
- driver: nvidia
|
- driver: nvidia
|
||||||
device_ids: ["GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe"]
|
device_ids: ["GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe"]
|
||||||
capabilities: [gpu]
|
capabilities: [gpu]
|
||||||
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8014/api/state', timeout=2)"]
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8014/api/state', timeout=2)"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
@@ -91,3 +97,8 @@ services:
|
|||||||
- driver: nvidia
|
- driver: nvidia
|
||||||
device_ids: ["GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe"]
|
device_ids: ["GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe"]
|
||||||
capabilities: [gpu]
|
capabilities: [gpu]
|
||||||
|
|
||||||
|
networks:
|
||||||
|
frontend:
|
||||||
|
external: true
|
||||||
|
name: mike-ai_frontend
|
||||||
|
|||||||
@@ -100,12 +100,20 @@ Wechsel zu `llm` wird das zuvor gemerkte Profil wiederhergestellt.
|
|||||||
| `llm` | ein Qwen-Profil + Qwen3-TTS | profilabhängig beide GPUs; TTS RTX 3060 | Router `:8081` |
|
| `llm` | ein Qwen-Profil + Qwen3-TTS | profilabhängig beide GPUs; TTS RTX 3060 | Router `:8081` |
|
||||||
| Bildauftrag | FLUX.2 Klein 9B FP8 + Qwen3-8B NF4 | RTX 5080 + RTX 3060, transaktional | über Router |
|
| Bildauftrag | FLUX.2 Klein 9B FP8 + Qwen3-8B NF4 | RTX 5080 + RTX 3060, transaktional | über Router |
|
||||||
| `music` | ACE-Step 1.5 XL-SFT | RTX 5080 | `:7862` original, `:7861` Community |
|
| `music` | ACE-Step 1.5 XL-SFT | RTX 5080 | `:7862` original, `:7861` Community |
|
||||||
|
| `yue2` | YuE2-3B + Ladypoly `YuE2_WebUI` | RTX 5080 | `:8014` |
|
||||||
| `separation` | BS-RoFormer, Demucs, MossFormer2 | RTX 5080 | `:8007` |
|
| `separation` | BS-RoFormer, Demucs, MossFormer2 | RTX 5080 | `:8007` |
|
||||||
| `voice` | OmniVoice | RTX 5080 | `:8008` |
|
| `voice` | OmniVoice | RTX 5080 | `:8008` |
|
||||||
| `voicechange` | X-VC + optional Resemble Enhance | RTX 5080 | `:8009` |
|
| `voicechange` | X-VC + optional Resemble Enhance | RTX 5080 | `:8009` |
|
||||||
| `applio` | Applio/RVC | RTX 5080 | `:8011`, eigene UI `:8012` |
|
| `applio` | Applio/RVC | RTX 5080 | `:8011`, eigene UI `:8012` |
|
||||||
| `trellis` | TRELLIS.2 4B Q8 über trellis.cpp 0.6.0 | ausschließlich RTX 5080 | `:8013` |
|
| `trellis` | TRELLIS.2 4B Q8 über trellis.cpp 0.6.0 | ausschließlich RTX 5080 | `:8013` |
|
||||||
|
|
||||||
|
YuE2 liegt unter `/opt/mike-ai/yue2-3b`, seine Gewichte unter
|
||||||
|
`/data/models/yue2` und Ergebnisse unter `/data/music/yue2`. Der Container
|
||||||
|
trägt `com.mike-ai.music-worker=yue2` und muss mit dem Alias `yue2-studio` am
|
||||||
|
externen Netz `mike-ai_frontend` hängen. YuE2 niemals außerhalb der
|
||||||
|
Router-Zustandsmaschine dauerhaft starten: Sonst bleibt sein VRAM belegt und
|
||||||
|
der nächste LLM- oder Separator-Start kann mit OOM scheitern.
|
||||||
|
|
||||||
TRELLIS liegt unter `/opt/mike-ai/trellis-studio`. Seine Q8-Gewichte liegen
|
TRELLIS liegt unter `/opt/mike-ai/trellis-studio`. Seine Q8-Gewichte liegen
|
||||||
unter `/data/models/trellis2-q8`, die Runtime und Ausgaben unter
|
unter `/data/models/trellis2-q8`, die Runtime und Ausgaben unter
|
||||||
`/data/trellis-studio`. Die Oberfläche liefert GLB. `1024 · cascade` ist der
|
`/data/trellis-studio`. Die Oberfläche liefert GLB. `1024 · cascade` ist der
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ TTS_LABEL_KEY = "com.mike-ai.tts-worker"
|
|||||||
TTS_WORKER = os.environ.get("TTS_WORKER", "qwen3")
|
TTS_WORKER = os.environ.get("TTS_WORKER", "qwen3")
|
||||||
MUSIC_LABEL_KEY = "com.mike-ai.music-worker"
|
MUSIC_LABEL_KEY = "com.mike-ai.music-worker"
|
||||||
MUSIC_WORKER = os.environ.get("MUSIC_WORKER", "").strip()
|
MUSIC_WORKER = os.environ.get("MUSIC_WORKER", "").strip()
|
||||||
|
YUE2_WORKER = os.environ.get("YUE2_WORKER", "").strip()
|
||||||
SEPARATOR_LABEL_KEY = "com.mike-ai.stem-separator"
|
SEPARATOR_LABEL_KEY = "com.mike-ai.stem-separator"
|
||||||
SEPARATOR_WORKER = os.environ.get("SEPARATOR_WORKER", "").strip()
|
SEPARATOR_WORKER = os.environ.get("SEPARATOR_WORKER", "").strip()
|
||||||
VOICE_LABEL_KEY = "com.mike-ai.voice-worker"
|
VOICE_LABEL_KEY = "com.mike-ai.voice-worker"
|
||||||
@@ -38,6 +39,8 @@ VOICE_CHANGE_LABEL_KEY = "com.mike-ai.voice-change-worker"
|
|||||||
VOICE_CHANGE_WORKER = os.environ.get("VOICE_CHANGE_WORKER", "").strip()
|
VOICE_CHANGE_WORKER = os.environ.get("VOICE_CHANGE_WORKER", "").strip()
|
||||||
APPLIO_LABEL_KEY = "com.mike-ai.applio-worker"
|
APPLIO_LABEL_KEY = "com.mike-ai.applio-worker"
|
||||||
APPLIO_WORKER = os.environ.get("APPLIO_WORKER", "").strip()
|
APPLIO_WORKER = os.environ.get("APPLIO_WORKER", "").strip()
|
||||||
|
TRELLIS_LABEL_KEY = "com.mike-ai.trellis-worker"
|
||||||
|
TRELLIS_WORKER = os.environ.get("TRELLIS_WORKER", "").strip()
|
||||||
LOCK = threading.Lock()
|
LOCK = threading.Lock()
|
||||||
log = logging.getLogger("profile-controller")
|
log = logging.getLogger("profile-controller")
|
||||||
|
|
||||||
@@ -117,6 +120,17 @@ def music_container() -> dict:
|
|||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
|
def yue2_container() -> dict:
|
||||||
|
if not YUE2_WORKER:
|
||||||
|
raise RuntimeError("YuE2 worker is not configured")
|
||||||
|
matches = [item for item in labelled_containers(MUSIC_LABEL_KEY)
|
||||||
|
if item.get("Labels", {}).get(MUSIC_LABEL_KEY) == YUE2_WORKER]
|
||||||
|
if len(matches) != 1:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"expected exactly one YuE2 worker {YUE2_WORKER!r}, found {len(matches)}")
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
def separator_container() -> dict:
|
def separator_container() -> dict:
|
||||||
if not SEPARATOR_WORKER:
|
if not SEPARATOR_WORKER:
|
||||||
raise RuntimeError("stem separator is not configured")
|
raise RuntimeError("stem separator is not configured")
|
||||||
@@ -161,11 +175,27 @@ def applio_container() -> dict:
|
|||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
|
def trellis_container() -> dict:
|
||||||
|
if not TRELLIS_WORKER:
|
||||||
|
raise RuntimeError("TRELLIS worker is not configured")
|
||||||
|
matches = [item for item in labelled_containers(TRELLIS_LABEL_KEY)
|
||||||
|
if item.get("Labels", {}).get(TRELLIS_LABEL_KEY) == TRELLIS_WORKER]
|
||||||
|
if len(matches) != 1:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"expected exactly one TRELLIS worker {TRELLIS_WORKER!r}, found {len(matches)}")
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
def stop_music_if_configured() -> None:
|
def stop_music_if_configured() -> None:
|
||||||
if MUSIC_WORKER:
|
if MUSIC_WORKER:
|
||||||
stop_container(music_container(), timeout=30)
|
stop_container(music_container(), timeout=30)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_yue2_if_configured() -> None:
|
||||||
|
if YUE2_WORKER:
|
||||||
|
stop_container(yue2_container(), timeout=30)
|
||||||
|
|
||||||
|
|
||||||
def stop_separator_if_configured() -> None:
|
def stop_separator_if_configured() -> None:
|
||||||
if SEPARATOR_WORKER:
|
if SEPARATOR_WORKER:
|
||||||
stop_container(separator_container(), timeout=30)
|
stop_container(separator_container(), timeout=30)
|
||||||
@@ -186,6 +216,11 @@ def stop_applio_if_configured() -> None:
|
|||||||
stop_container(applio_container(), timeout=30)
|
stop_container(applio_container(), timeout=30)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_trellis_if_configured() -> None:
|
||||||
|
if TRELLIS_WORKER:
|
||||||
|
stop_container(trellis_container(), timeout=30)
|
||||||
|
|
||||||
|
|
||||||
def stop_voice_tools(except_kind: str | None = None) -> None:
|
def stop_voice_tools(except_kind: str | None = None) -> None:
|
||||||
if except_kind != "voice":
|
if except_kind != "voice":
|
||||||
stop_voice_if_configured()
|
stop_voice_if_configured()
|
||||||
@@ -233,8 +268,10 @@ def set_image_worker(running: bool, kind: str = IMAGE_WORKER) -> dict:
|
|||||||
# Qwen3-TTS. TTS is unavailable during this exclusive GPU phase.
|
# Qwen3-TTS. TTS is unavailable during this exclusive GPU phase.
|
||||||
stop_container(tts_container(), timeout=30)
|
stop_container(tts_container(), timeout=30)
|
||||||
stop_music_if_configured()
|
stop_music_if_configured()
|
||||||
|
stop_yue2_if_configured()
|
||||||
stop_separator_if_configured()
|
stop_separator_if_configured()
|
||||||
stop_voice_tools()
|
stop_voice_tools()
|
||||||
|
stop_trellis_if_configured()
|
||||||
for other in image_containers():
|
for other in image_containers():
|
||||||
if other["Id"] != item["Id"]:
|
if other["Id"] != item["Id"]:
|
||||||
stop_container(other, timeout=20)
|
stop_container(other, timeout=20)
|
||||||
@@ -262,6 +299,8 @@ def set_music_worker(running: bool) -> dict:
|
|||||||
stop_container(tts_container(), timeout=30)
|
stop_container(tts_container(), timeout=30)
|
||||||
stop_separator_if_configured()
|
stop_separator_if_configured()
|
||||||
stop_voice_tools()
|
stop_voice_tools()
|
||||||
|
stop_yue2_if_configured()
|
||||||
|
stop_trellis_if_configured()
|
||||||
start_container(item)
|
start_container(item)
|
||||||
else:
|
else:
|
||||||
stop_container(item, timeout=30)
|
stop_container(item, timeout=30)
|
||||||
@@ -269,6 +308,27 @@ def set_music_worker(running: bool) -> dict:
|
|||||||
"state": "running" if running else "stopped"}
|
"state": "running" if running else "stopped"}
|
||||||
|
|
||||||
|
|
||||||
|
def set_yue2_worker(running: bool) -> dict:
|
||||||
|
"""Start YuE2 exclusively, or stop it before another mode is loaded."""
|
||||||
|
with LOCK:
|
||||||
|
item = yue2_container()
|
||||||
|
if running:
|
||||||
|
for profile_item in containers().values():
|
||||||
|
stop_container(profile_item)
|
||||||
|
for worker in image_containers():
|
||||||
|
stop_container(worker, timeout=20)
|
||||||
|
stop_container(tts_container(), timeout=30)
|
||||||
|
stop_music_if_configured()
|
||||||
|
stop_separator_if_configured()
|
||||||
|
stop_voice_tools()
|
||||||
|
stop_trellis_if_configured()
|
||||||
|
start_container(item)
|
||||||
|
else:
|
||||||
|
stop_container(item, timeout=30)
|
||||||
|
return {"yue2_worker": YUE2_WORKER,
|
||||||
|
"state": "running" if running else "stopped"}
|
||||||
|
|
||||||
|
|
||||||
def set_separator_worker(running: bool) -> dict:
|
def set_separator_worker(running: bool) -> dict:
|
||||||
"""Start vocal separation exclusively, or stop it before LLM restoration."""
|
"""Start vocal separation exclusively, or stop it before LLM restoration."""
|
||||||
with LOCK:
|
with LOCK:
|
||||||
@@ -280,7 +340,9 @@ def set_separator_worker(running: bool) -> dict:
|
|||||||
stop_container(worker, timeout=20)
|
stop_container(worker, timeout=20)
|
||||||
stop_container(tts_container(), timeout=30)
|
stop_container(tts_container(), timeout=30)
|
||||||
stop_music_if_configured()
|
stop_music_if_configured()
|
||||||
|
stop_yue2_if_configured()
|
||||||
stop_voice_tools()
|
stop_voice_tools()
|
||||||
|
stop_trellis_if_configured()
|
||||||
start_container(item)
|
start_container(item)
|
||||||
else:
|
else:
|
||||||
stop_container(item, timeout=30)
|
stop_container(item, timeout=30)
|
||||||
@@ -299,8 +361,10 @@ def set_voice_worker(running: bool) -> dict:
|
|||||||
stop_container(worker, timeout=20)
|
stop_container(worker, timeout=20)
|
||||||
stop_container(tts_container(), timeout=30)
|
stop_container(tts_container(), timeout=30)
|
||||||
stop_music_if_configured()
|
stop_music_if_configured()
|
||||||
|
stop_yue2_if_configured()
|
||||||
stop_separator_if_configured()
|
stop_separator_if_configured()
|
||||||
stop_voice_tools("voice")
|
stop_voice_tools("voice")
|
||||||
|
stop_trellis_if_configured()
|
||||||
start_container(item)
|
start_container(item)
|
||||||
else:
|
else:
|
||||||
stop_container(item, timeout=30)
|
stop_container(item, timeout=30)
|
||||||
@@ -319,8 +383,10 @@ def set_voice_change_worker(running: bool) -> dict:
|
|||||||
stop_container(worker, timeout=20)
|
stop_container(worker, timeout=20)
|
||||||
stop_container(tts_container(), timeout=30)
|
stop_container(tts_container(), timeout=30)
|
||||||
stop_music_if_configured()
|
stop_music_if_configured()
|
||||||
|
stop_yue2_if_configured()
|
||||||
stop_separator_if_configured()
|
stop_separator_if_configured()
|
||||||
stop_voice_tools("voicechange")
|
stop_voice_tools("voicechange")
|
||||||
|
stop_trellis_if_configured()
|
||||||
start_container(item)
|
start_container(item)
|
||||||
else:
|
else:
|
||||||
stop_container(item, timeout=30)
|
stop_container(item, timeout=30)
|
||||||
@@ -339,8 +405,10 @@ def set_applio_worker(running: bool) -> dict:
|
|||||||
stop_container(worker, timeout=20)
|
stop_container(worker, timeout=20)
|
||||||
stop_container(tts_container(), timeout=30)
|
stop_container(tts_container(), timeout=30)
|
||||||
stop_music_if_configured()
|
stop_music_if_configured()
|
||||||
|
stop_yue2_if_configured()
|
||||||
stop_separator_if_configured()
|
stop_separator_if_configured()
|
||||||
stop_voice_tools("applio")
|
stop_voice_tools("applio")
|
||||||
|
stop_trellis_if_configured()
|
||||||
start_container(item)
|
start_container(item)
|
||||||
else:
|
else:
|
||||||
stop_container(item, timeout=30)
|
stop_container(item, timeout=30)
|
||||||
@@ -348,6 +416,27 @@ def set_applio_worker(running: bool) -> dict:
|
|||||||
"state": "running" if running else "stopped"}
|
"state": "running" if running else "stopped"}
|
||||||
|
|
||||||
|
|
||||||
|
def set_trellis_worker(running: bool) -> dict:
|
||||||
|
"""Start TRELLIS.2 exclusively, or stop it before another mode is loaded."""
|
||||||
|
with LOCK:
|
||||||
|
item = trellis_container()
|
||||||
|
if running:
|
||||||
|
for profile_item in containers().values():
|
||||||
|
stop_container(profile_item)
|
||||||
|
for worker in image_containers():
|
||||||
|
stop_container(worker, timeout=20)
|
||||||
|
stop_container(tts_container(), timeout=30)
|
||||||
|
stop_music_if_configured()
|
||||||
|
stop_yue2_if_configured()
|
||||||
|
stop_separator_if_configured()
|
||||||
|
stop_voice_tools()
|
||||||
|
start_container(item)
|
||||||
|
else:
|
||||||
|
stop_container(item, timeout=30)
|
||||||
|
return {"trellis_worker": TRELLIS_WORKER,
|
||||||
|
"state": "running" if running else "stopped"}
|
||||||
|
|
||||||
|
|
||||||
def active_profile(items: dict[str, dict] | None = None) -> str | None:
|
def active_profile(items: dict[str, dict] | None = None) -> str | None:
|
||||||
items = items or containers()
|
items = items or containers()
|
||||||
active = [name for name, item in items.items() if item.get("State") == "running"]
|
active = [name for name, item in items.items() if item.get("State") == "running"]
|
||||||
@@ -364,8 +453,10 @@ def activate(profile: str) -> dict:
|
|||||||
for worker in image_containers():
|
for worker in image_containers():
|
||||||
stop_container(worker)
|
stop_container(worker)
|
||||||
stop_music_if_configured()
|
stop_music_if_configured()
|
||||||
|
stop_yue2_if_configured()
|
||||||
stop_separator_if_configured()
|
stop_separator_if_configured()
|
||||||
stop_voice_tools()
|
stop_voice_tools()
|
||||||
|
stop_trellis_if_configured()
|
||||||
start_container(tts_container())
|
start_container(tts_container())
|
||||||
items = containers()
|
items = containers()
|
||||||
missing = [name for name in ALLOWED if name not in items]
|
missing = [name for name in ALLOWED if name not in items]
|
||||||
@@ -437,6 +528,13 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"unhealthy" if "(unhealthy)" in music_status else
|
"unhealthy" if "(unhealthy)" in music_status else
|
||||||
"starting" if music.get("State") == "running" else
|
"starting" if music.get("State") == "running" else
|
||||||
"stopped")
|
"stopped")
|
||||||
|
yue2 = yue2_container() if YUE2_WORKER else {}
|
||||||
|
yue2_status = yue2.get("Status", "")
|
||||||
|
yue2_health = ("disabled" if not YUE2_WORKER else
|
||||||
|
"healthy" if "(healthy)" in yue2_status else
|
||||||
|
"unhealthy" if "(unhealthy)" in yue2_status else
|
||||||
|
"starting" if yue2.get("State") == "running" else
|
||||||
|
"stopped")
|
||||||
separator = separator_container() if SEPARATOR_WORKER else {}
|
separator = separator_container() if SEPARATOR_WORKER else {}
|
||||||
separator_status = separator.get("Status", "")
|
separator_status = separator.get("Status", "")
|
||||||
separator_health = ("disabled" if not SEPARATOR_WORKER else
|
separator_health = ("disabled" if not SEPARATOR_WORKER else
|
||||||
@@ -465,9 +563,18 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"unhealthy" if "(unhealthy)" in applio_status else
|
"unhealthy" if "(unhealthy)" in applio_status else
|
||||||
"starting" if applio.get("State") == "running" else
|
"starting" if applio.get("State") == "running" else
|
||||||
"stopped")
|
"stopped")
|
||||||
|
trellis = trellis_container() if TRELLIS_WORKER else {}
|
||||||
|
trellis_status = trellis.get("Status", "")
|
||||||
|
trellis_health = ("disabled" if not TRELLIS_WORKER else
|
||||||
|
"healthy" if "(healthy)" in trellis_status else
|
||||||
|
"unhealthy" if "(unhealthy)" in trellis_status else
|
||||||
|
"starting" if trellis.get("State") == "running" else
|
||||||
|
"stopped")
|
||||||
self.reply(200, {"active_profile": active_profile(items),
|
self.reply(200, {"active_profile": active_profile(items),
|
||||||
"music_worker": music.get("State", "disabled"),
|
"music_worker": music.get("State", "disabled"),
|
||||||
"music_health": music_health,
|
"music_health": music_health,
|
||||||
|
"yue2_worker": yue2.get("State", "disabled"),
|
||||||
|
"yue2_health": yue2_health,
|
||||||
"separator_worker": separator.get("State", "disabled"),
|
"separator_worker": separator.get("State", "disabled"),
|
||||||
"separator_health": separator_health,
|
"separator_health": separator_health,
|
||||||
"voice_worker": voice.get("State", "disabled"),
|
"voice_worker": voice.get("State", "disabled"),
|
||||||
@@ -476,6 +583,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"voice_change_health": voice_change_health,
|
"voice_change_health": voice_change_health,
|
||||||
"applio_worker": applio.get("State", "disabled"),
|
"applio_worker": applio.get("State", "disabled"),
|
||||||
"applio_health": applio_health,
|
"applio_health": applio_health,
|
||||||
|
"trellis_worker": trellis.get("State", "disabled"),
|
||||||
|
"trellis_health": trellis_health,
|
||||||
"profiles": {name: items.get(name, {}).get(
|
"profiles": {name: items.get(name, {}).get(
|
||||||
"State", "missing") for name in ALLOWED}})
|
"State", "missing") for name in ALLOWED}})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -500,6 +609,13 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
log.exception("music worker transition failed")
|
log.exception("music worker transition failed")
|
||||||
self.reply(503, {"error": str(exc)})
|
self.reply(503, {"error": str(exc)})
|
||||||
return
|
return
|
||||||
|
if self.path in {"/workers/yue2/start", "/workers/yue2/stop"}:
|
||||||
|
try:
|
||||||
|
self.reply(200, set_yue2_worker(self.path.endswith("/start")))
|
||||||
|
except Exception as exc:
|
||||||
|
log.exception("YuE2 worker transition failed")
|
||||||
|
self.reply(503, {"error": str(exc)})
|
||||||
|
return
|
||||||
if self.path in {"/workers/separator/start", "/workers/separator/stop"}:
|
if self.path in {"/workers/separator/start", "/workers/separator/stop"}:
|
||||||
try:
|
try:
|
||||||
self.reply(200, set_separator_worker(self.path.endswith("/start")))
|
self.reply(200, set_separator_worker(self.path.endswith("/start")))
|
||||||
@@ -528,6 +644,13 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
log.exception("Applio worker transition failed")
|
log.exception("Applio worker transition failed")
|
||||||
self.reply(503, {"error": str(exc)})
|
self.reply(503, {"error": str(exc)})
|
||||||
return
|
return
|
||||||
|
if self.path in {"/workers/trellis/start", "/workers/trellis/stop"}:
|
||||||
|
try:
|
||||||
|
self.reply(200, set_trellis_worker(self.path.endswith("/start")))
|
||||||
|
except Exception as exc:
|
||||||
|
log.exception("TRELLIS worker transition failed")
|
||||||
|
self.reply(503, {"error": str(exc)})
|
||||||
|
return
|
||||||
worker_paths = {
|
worker_paths = {
|
||||||
"/workers/image/start": (IMAGE_WORKER, True),
|
"/workers/image/start": (IMAGE_WORKER, True),
|
||||||
"/workers/image/stop": (IMAGE_WORKER, False),
|
"/workers/image/stop": (IMAGE_WORKER, False),
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ start_proxy 8008 voice-studio:8008
|
|||||||
start_proxy 8009 xvc-studio:8009
|
start_proxy 8009 xvc-studio:8009
|
||||||
start_proxy 8011 applio-studio:6969
|
start_proxy 8011 applio-studio:6969
|
||||||
start_proxy 8012 mikes-applio-ui:8012
|
start_proxy 8012 mikes-applio-ui:8012
|
||||||
|
start_proxy 8013 trellis-studio:8080
|
||||||
|
start_proxy 8014 yue2-studio:8014
|
||||||
start_proxy 8202 mcp-athena-operator:8000
|
start_proxy 8202 mcp-athena-operator:8000
|
||||||
start_proxy 9443 portainer:9443
|
start_proxy 9443 portainer:9443
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ APPLIO_UI_URL = os.getenv("APPLIO_UI_URL", "http://192.168.1.212:8011/")
|
|||||||
MIKES_APPLIO_UI_URL = os.getenv(
|
MIKES_APPLIO_UI_URL = os.getenv(
|
||||||
"MIKES_APPLIO_UI_URL", "http://192.168.1.212:8012/"
|
"MIKES_APPLIO_UI_URL", "http://192.168.1.212:8012/"
|
||||||
)
|
)
|
||||||
|
TRELLIS_UI_URL = os.getenv("TRELLIS_UI_URL", "http://192.168.1.212:8013/")
|
||||||
|
YUE2_UI_URL = os.getenv("YUE2_UI_URL", "http://192.168.1.212:8014/")
|
||||||
HOST_PROC = Path(os.getenv("HOST_PROC", "/host/proc"))
|
HOST_PROC = Path(os.getenv("HOST_PROC", "/host/proc"))
|
||||||
HOST_DATA = os.getenv("HOST_DATA", "/host/data")
|
HOST_DATA = os.getenv("HOST_DATA", "/host/data")
|
||||||
HOST_MODELS = Path(os.getenv("HOST_MODELS", "/host/models"))
|
HOST_MODELS = Path(os.getenv("HOST_MODELS", "/host/models"))
|
||||||
@@ -311,7 +313,8 @@ def router_status() -> tuple[dict[str, Any], str | None]:
|
|||||||
|
|
||||||
|
|
||||||
def change_mode(mode: str) -> tuple[int, dict[str, Any]]:
|
def change_mode(mode: str) -> tuple[int, dict[str, Any]]:
|
||||||
if mode not in {"llm", "music", "separation", "voice", "voicechange", "applio"}:
|
if mode not in {"llm", "music", "yue2", "separation", "voice",
|
||||||
|
"voicechange", "applio", "trellis"}:
|
||||||
return 400, {"error": "invalid mode"}
|
return 400, {"error": "invalid mode"}
|
||||||
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
||||||
if ROUTER_API_KEY:
|
if ROUTER_API_KEY:
|
||||||
@@ -670,7 +673,7 @@ HTML = r'''<!doctype html>
|
|||||||
</style></head><body><main>
|
</style></head><body><main>
|
||||||
<div class="top"><div><div class="eyebrow">Mike AI · Live Telemetry</div><h1>Athena llama.cpp Dashboard</h1></div><div class="live"><span class="dot" id="dot"></span><span id="updated">verbinde …</span></div></div>
|
<div class="top"><div><div class="eyebrow">Mike AI · Live Telemetry</div><h1>Athena llama.cpp Dashboard</h1></div><div class="live"><span class="dot" id="dot"></span><span id="updated">verbinde …</span></div></div>
|
||||||
<section class="grid">
|
<section class="grid">
|
||||||
<article class="card span12"><div class="mode-row"><div><div class="label">Athena Betriebsmodus</div><div class="value" id="operatingMode">–</div><div class="sub" id="modeStatus">Status wird geladen …</div></div><div class="mode-buttons"><button id="llmMode" onclick="setMode('llm')">LLM-Betrieb</button><button id="musicMode" onclick="setMode('music')">Musikstudio</button><button id="separationMode" onclick="setMode('separation')">Audio trennen</button><button id="voiceMode" onclick="setMode('voice')">Voice Studio</button><button id="voiceChangeMode" onclick="setMode('voicechange')">X-VC</button><button id="applioMode" onclick="setMode('applio')">Applio / RVC · Modell nötig</button><span id="musicOpen" hidden><a class="stable" href="__MUSIC_ORIGINAL_UI_URL__" target="_blank" rel="noopener">Original UI · stabil</a><a class="experimental" href="__MUSIC_COMMUNITY_UI_URL__" target="_blank" rel="noopener">Community UI · experimentell</a></span><span id="separatorOpen" hidden><a class="stable" href="__SEPARATOR_UI_URL__" target="_blank" rel="noopener">Separator öffnen</a></span><span id="voiceOpen" hidden><a class="stable" href="__VOICE_UI_URL__" target="_blank" rel="noopener">Voice Studio öffnen</a></span><span id="voiceChangeOpen" hidden><a class="experimental" href="__VOICE_CHANGE_UI_URL__" target="_blank" rel="noopener">X-VC öffnen</a></span><span id="applioOpen" hidden><a class="stable" href="__APPLIO_UI_URL__" target="_blank" rel="noopener">Original Applio UI</a><a class="stable" href="__MIKES_APPLIO_UI_URL__" target="_blank" rel="noopener">Mikes Applio UI</a></span></div></div></article>
|
<article class="card span12"><div class="mode-row"><div><div class="label">Athena Betriebsmodus</div><div class="value" id="operatingMode">–</div><div class="sub" id="modeStatus">Status wird geladen …</div></div><div class="mode-buttons"><button id="llmMode" onclick="setMode('llm')">LLM-Betrieb</button><button id="musicMode" onclick="setMode('music')">ACE-Step Studio</button><button id="yue2Mode" onclick="setMode('yue2')">YuE2 Studio</button><button id="separationMode" onclick="setMode('separation')">Audio trennen</button><button id="voiceMode" onclick="setMode('voice')">Voice Studio</button><button id="voiceChangeMode" onclick="setMode('voicechange')">X-VC</button><button id="applioMode" onclick="setMode('applio')">Applio / RVC · Modell nötig</button><button id="trellisMode" onclick="setMode('trellis')">3D Studio</button><span id="musicOpen" hidden><a class="stable" href="__MUSIC_ORIGINAL_UI_URL__" target="_blank" rel="noopener">Original UI · stabil</a><a class="experimental" href="__MUSIC_COMMUNITY_UI_URL__" target="_blank" rel="noopener">Community UI · experimentell</a></span><span id="yue2Open" hidden><a class="stable" href="__YUE2_UI_URL__" target="_blank" rel="noopener">YuE2 Studio öffnen</a></span><span id="separatorOpen" hidden><a class="stable" href="__SEPARATOR_UI_URL__" target="_blank" rel="noopener">Separator öffnen</a></span><span id="voiceOpen" hidden><a class="stable" href="__VOICE_UI_URL__" target="_blank" rel="noopener">Voice Studio öffnen</a></span><span id="voiceChangeOpen" hidden><a class="experimental" href="__VOICE_CHANGE_UI_URL__" target="_blank" rel="noopener">X-VC öffnen</a></span><span id="applioOpen" hidden><a class="stable" href="__APPLIO_UI_URL__" target="_blank" rel="noopener">Original Applio UI</a><a class="stable" href="__MIKES_APPLIO_UI_URL__" target="_blank" rel="noopener">Mikes Applio UI</a></span><span id="trellisOpen" hidden><a class="stable" href="__TRELLIS_UI_URL__" target="_blank" rel="noopener">3D Studio öffnen</a></span></div></div></article>
|
||||||
<article class="card span3"><div class="label">Aktives Profil</div><div class="value" id="profile">–</div><div class="sub" id="profileSub">Router wird abgefragt</div></article>
|
<article class="card span3"><div class="label">Aktives Profil</div><div class="value" id="profile">–</div><div class="sub" id="profileSub">Router wird abgefragt</div></article>
|
||||||
<article class="card span3"><div class="label">Modell</div><div class="value" id="model">–</div><div class="sub" id="modelSub">–</div></article>
|
<article class="card span3"><div class="label">Modell</div><div class="value" id="model">–</div><div class="sub" id="modelSub">–</div></article>
|
||||||
<article class="card span3"><div class="label">CPU</div><div class="value" id="cpu">–</div><div class="bar"><div class="fill" id="cpuBar"></div></div><div class="sub" id="load">–</div></article>
|
<article class="card span3"><div class="label">CPU</div><div class="value" id="cpu">–</div><div class="bar"><div class="fill" id="cpuBar"></div></div><div class="sub" id="load">–</div></article>
|
||||||
@@ -711,8 +714,8 @@ const $=id=>document.getElementById(id); const pct=n=>n==null?'–':`${n.toFixed
|
|||||||
function gpuCard(g){let total=g.memory_total_mib||0,used=g.memory_used_mib||0,p=total?used/total*100:0,load=Math.max(0,Math.min(100,g.gpu_percent||0));return `<article class="card span6"><div class="gpu-title"><div><div class="label">GPU ${g.index}</div><div class="value">${g.name}</div></div><span class="badge">${g.pstate||'–'}</span></div><div class="metrics"><div class="metric"><b>${pct(g.gpu_percent)}</b><span>GPU-Kern</span></div><div class="metric"><b>${(used/1024).toFixed(1)} / ${(total/1024).toFixed(1)} GiB</b><span>VRAM</span></div><div class="metric"><b>${g.temperature_c??'–'} °C</b><span>Temperatur</span></div><div class="metric"><b>${g.power_w??'–'} / ${g.power_limit_w??'–'} W</b><span>Leistung</span></div><div class="metric"><b>${g.graphics_clock_mhz??'–'} MHz</b><span>Grafiktakt</span></div><div class="metric"><b>${g.memory_clock_mhz??'–'} MHz</b><span>Speichertakt</span></div><div class="metric"><b>${pct(g.memory_controller_percent)}</b><span>Memory Controller</span></div><div class="metric"><b>${pct(g.fan_percent)}</b><span>Lüfter</span></div></div><div class="bar-label"><span>GPU-Auslastung</span><span>${load.toFixed(1)} %</span></div><div class="bar"><div class="fill gpu-load" style="width:${load}%"></div></div><div class="bar-label"><span>VRAM-Belegung</span><span>${p.toFixed(1)} %</span></div><div class="bar"><div class="fill" style="width:${Math.min(100,p)}%"></div></div><div class="sub">${(g.memory_free_mib/1024).toFixed(1)} GiB VRAM frei</div></article>`}
|
function gpuCard(g){let total=g.memory_total_mib||0,used=g.memory_used_mib||0,p=total?used/total*100:0,load=Math.max(0,Math.min(100,g.gpu_percent||0));return `<article class="card span6"><div class="gpu-title"><div><div class="label">GPU ${g.index}</div><div class="value">${g.name}</div></div><span class="badge">${g.pstate||'–'}</span></div><div class="metrics"><div class="metric"><b>${pct(g.gpu_percent)}</b><span>GPU-Kern</span></div><div class="metric"><b>${(used/1024).toFixed(1)} / ${(total/1024).toFixed(1)} GiB</b><span>VRAM</span></div><div class="metric"><b>${g.temperature_c??'–'} °C</b><span>Temperatur</span></div><div class="metric"><b>${g.power_w??'–'} / ${g.power_limit_w??'–'} W</b><span>Leistung</span></div><div class="metric"><b>${g.graphics_clock_mhz??'–'} MHz</b><span>Grafiktakt</span></div><div class="metric"><b>${g.memory_clock_mhz??'–'} MHz</b><span>Speichertakt</span></div><div class="metric"><b>${pct(g.memory_controller_percent)}</b><span>Memory Controller</span></div><div class="metric"><b>${pct(g.fan_percent)}</b><span>Lüfter</span></div></div><div class="bar-label"><span>GPU-Auslastung</span><span>${load.toFixed(1)} %</span></div><div class="bar"><div class="fill gpu-load" style="width:${load}%"></div></div><div class="bar-label"><span>VRAM-Belegung</span><span>${p.toFixed(1)} %</span></div><div class="bar"><div class="fill" style="width:${Math.min(100,p)}%"></div></div><div class="sub">${(g.memory_free_mib/1024).toFixed(1)} GiB VRAM frei</div></article>`}
|
||||||
const imagePhaseLabel=p=>({"stopping-qwen":"Qwen wird entladen","loading-image":"Bildmodell wird geladen","generating":"Bild wird generiert","unloading-image":"Bildmodell wird entladen","restoring-qwen":"Qwen wird wiederhergestellt"}[p]||p||'bereit');
|
const imagePhaseLabel=p=>({"stopping-qwen":"Qwen wird entladen","loading-image":"Bildmodell wird geladen","generating":"Bild wird generiert","unloading-image":"Bildmodell wird entladen","restoring-qwen":"Qwen wird wiederhergestellt"}[p]||p||'bereit');
|
||||||
let modeBusy=false;
|
let modeBusy=false;
|
||||||
async function setMode(mode){if(modeBusy)return;modeBusy=true;for(const id of ['llmMode','musicMode','separationMode','voiceMode','voiceChangeMode','applioMode'])$(id).disabled=true;$('modeStatus').textContent='Umschaltung angefordert …';try{let r=await fetch('/api/mode',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mode})});let d=await r.json();if(!r.ok)throw Error(d?.error?.message||d?.error||`HTTP ${r.status}`);$('modeStatus').textContent='Umschaltung läuft …'}catch(e){$('modeStatus').textContent=e.message;$('modeStatus').classList.add('mode-error')}finally{modeBusy=false;setTimeout(refresh,250)}}
|
async function setMode(mode){if(modeBusy)return;modeBusy=true;for(const id of ['llmMode','musicMode','yue2Mode','separationMode','voiceMode','voiceChangeMode','applioMode','trellisMode'])$(id).disabled=true;$('modeStatus').textContent='Umschaltung angefordert …';try{let r=await fetch('/api/mode',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mode})});let d=await r.json();if(!r.ok)throw Error(d?.error?.message||d?.error||`HTTP ${r.status}`);$('modeStatus').textContent='Umschaltung läuft …'}catch(e){$('modeStatus').textContent=e.message;$('modeStatus').classList.add('mode-error')}finally{modeBusy=false;setTimeout(refresh,250)}}
|
||||||
async function refresh(){try{let r=await fetch('/api/status',{cache:'no-store'});if(!r.ok)throw Error(`HTTP ${r.status}`);let d=await r.json(),c=d.cpu||{},m=c.memory||{},rt=d.router||{},up=rt.upstream||{},q=rt.qwen||{},lr=d.llama_runtime||{},img=rt.image||{},imageActive=img.phase&&img.phase!=='idle';let md=rt.mode||{},switchingMode=md.phase&&md.phase!=='ready',modeName=({llm:'LLM-Betrieb',music:'Musikstudio',separation:'Stimmtrennung',voice:'Voice Studio',voicechange:'X-VC Voice Changer',applio:'Applio / RVC'})[md.active]||'Unbekannt';$('operatingMode').textContent=modeName;$('modeStatus').textContent=switchingMode?`Umschaltung: ${md.phase}`:(md.last_error||`Musik: ${md.music_worker||'–'} · Separator: ${md.separator_worker||'–'} · Voice: ${md.voice_worker||'–'}${md.return_profile?` · Rückkehr zu ${md.return_profile}`:''}`);$('modeStatus').classList.toggle('mode-error',!!md.last_error);$('llmMode').classList.toggle('active',md.active==='llm');$('musicMode').classList.toggle('active',md.active==='music');$('separationMode').classList.toggle('active',md.active==='separation');$('voiceMode').classList.toggle('active',md.active==='voice');$('voiceChangeMode').classList.toggle('active',md.active==='voicechange');$('applioMode').classList.toggle('active',md.active==='applio');let modeControlsBusy=modeBusy||switchingMode||!md.enabled;for(const id of ['llmMode','musicMode','separationMode','voiceMode','voiceChangeMode','applioMode'])$(id).disabled=modeControlsBusy;$('musicOpen').hidden=md.active!=='music';$('separatorOpen').hidden=md.active!=='separation';$('voiceOpen').hidden=md.active!=='voice';$('voiceChangeOpen').hidden=md.active!=='voicechange';$('applioOpen').hidden=md.active!=='applio';$('profile').textContent=imageActive?'Bildgenerierung':(rt.current_profile||'nicht geladen');$('profileSub').textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching?`Wechsel zu ${rt.switching}`:`Kontext: ${up.ctx?up.ctx.toLocaleString('de-DE'):'–'} Token`);$('model').textContent=imageActive?(img.model||'Bildmodell'):(up.model||'–');$('modelSub').textContent=imageActive?`${img.model_loaded?'geladen':'wird vorbereitet'} · Worker ${img.worker||'–'}`:(lr.model_file|| (up.reachable?'llama.cpp erreichbar':'llama.cpp nicht erreichbar'));$('cpu').textContent=pct(c.usage_percent);$('cpuBar').style.width=`${c.usage_percent||0}%`;$('load').textContent=`${c.logical_cpus||'–'} Threads · Load ${(c.load||[]).join(' / ')}`;let rp=m.total?m.used/m.total*100:0;$('ram').textContent=pct(rp);$('ramBar').style.width=`${rp}%`;$('ramSub').textContent=`${gib(m.used)} / ${gib(m.total)}`;$('gpuCards').innerHTML=(d.gpus||[]).map(gpuCard).join('')||'<article class="card span12 error">Keine GPU-Daten verfügbar</article>';$('availability').textContent=imageActive?imagePhaseLabel(img.phase):(q.available?'bereit':'nicht bereit');$('availability').className=`value status ${(imageActive||q.available)?'':'bad'}`;$('activeChats').textContent=q.active_chats??'–';$('routerUptime').textContent=dur(rt.uptime_seconds);$('switching').textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching||'nein');let disk=c.disk_data||{},dp=disk.total?disk.used/disk.total*100:null;$('dataDisk').textContent=pct(dp);$('processes').innerHTML=(d.gpu_processes||[]).map(p=>`<tr><td>${(d.gpus||[]).find(g=>g.uuid===p.gpu_uuid)?.index??'–'}</td><td>${p.name}</td><td>${p.pid}</td><td>${p.memory_mib??'–'} MiB</td></tr>`).join('')||'<tr><td colspan="4">Keine Compute-Prozesse gemeldet</td></tr>';let runtime=[['Modell-Datei',lr.model_file],['PID',lr.pid],['Kontext',lr.context_size?lr.context_size.toLocaleString('de-DE'):'–'],['Batch / µBatch',`${lr.batch_size??'–'} / ${lr.ubatch_size??'–'}`],['Parallel',lr.parallel],['Threads',`${lr.threads??'–'} / ${lr.threads_batch??'–'}`],['Geräte',lr.device],['Tensor-Split',lr.tensor_split],['KV-Cache',`${lr.cache_k??'–'} / ${lr.cache_v??'–'}`],['Flash Attention',lr.flash_attention?'an':'aus'],['Prompt-Cache',lr.prompt_cache?'an':'aus'],['MTP Draft',lr.mtp_draft_tokens]];$('runtime').innerHTML=runtime.map(([k,v])=>`<div class="metric"><b>${v??'–'}</b><span>${k}</span></div>`).join('');let es=Object.entries(d.errors||{}).filter(([,v])=>v);$('errors').hidden=!es.length;$('errors').textContent=es.map(([k,v])=>`${k}: ${v}`).join('\n');$('updated').textContent=`Live · ${new Date(d.timestamp*1000).toLocaleTimeString('de-DE')}`;$('dot').style.background='var(--green)'}catch(e){$('updated').textContent=`Verbindung gestört: ${e.message}`;$('dot').style.background='var(--red)'}}refresh();setInterval(refresh,1000);
|
async function refresh(){try{let r=await fetch('/api/status',{cache:'no-store'});if(!r.ok)throw Error(`HTTP ${r.status}`);let d=await r.json(),c=d.cpu||{},m=c.memory||{},rt=d.router||{},up=rt.upstream||{},q=rt.qwen||{},lr=d.llama_runtime||{},img=rt.image||{},imageActive=img.phase&&img.phase!=='idle';let md=rt.mode||{},switchingMode=md.phase&&md.phase!=='ready',modeName=({llm:'LLM-Betrieb',music:'ACE-Step Studio',yue2:'YuE2 Studio',separation:'Stimmtrennung',voice:'Voice Studio',voicechange:'X-VC Voice Changer',applio:'Applio / RVC',trellis:'3D Studio'})[md.active]||'Unbekannt';$('operatingMode').textContent=modeName;$('modeStatus').textContent=switchingMode?`Umschaltung: ${md.phase}`:(md.last_error||`ACE-Step: ${md.music_worker||'–'} · YuE2: ${md.yue2_worker||'–'} · Separator: ${md.separator_worker||'–'} · Voice: ${md.voice_worker||'–'} · 3D: ${md.trellis_worker||'–'}${md.return_profile?` · Rückkehr zu ${md.return_profile}`:''}`);$('modeStatus').classList.toggle('mode-error',!!md.last_error);$('llmMode').classList.toggle('active',md.active==='llm');$('musicMode').classList.toggle('active',md.active==='music');$('yue2Mode').classList.toggle('active',md.active==='yue2');$('separationMode').classList.toggle('active',md.active==='separation');$('voiceMode').classList.toggle('active',md.active==='voice');$('voiceChangeMode').classList.toggle('active',md.active==='voicechange');$('applioMode').classList.toggle('active',md.active==='applio');$('trellisMode').classList.toggle('active',md.active==='trellis');let modeControlsBusy=modeBusy||switchingMode||!md.enabled;for(const id of ['llmMode','musicMode','yue2Mode','separationMode','voiceMode','voiceChangeMode','applioMode','trellisMode'])$(id).disabled=modeControlsBusy;$('musicOpen').hidden=md.active!=='music';$('yue2Open').hidden=md.active!=='yue2';$('separatorOpen').hidden=md.active!=='separation';$('voiceOpen').hidden=md.active!=='voice';$('voiceChangeOpen').hidden=md.active!=='voicechange';$('applioOpen').hidden=md.active!=='applio';$('trellisOpen').hidden=md.active!=='trellis';$('profile').textContent=imageActive?'Bildgenerierung':(rt.current_profile||'nicht geladen');$('profileSub').textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching?`Wechsel zu ${rt.switching}`:`Kontext: ${up.ctx?up.ctx.toLocaleString('de-DE'):'–'} Token`);$('model').textContent=imageActive?(img.model||'Bildmodell'):(up.model||'–');$('modelSub').textContent=imageActive?`${img.model_loaded?'geladen':'wird vorbereitet'} · Worker ${img.worker||'–'}`:(lr.model_file|| (up.reachable?'llama.cpp erreichbar':'llama.cpp nicht erreichbar'));$('cpu').textContent=pct(c.usage_percent);$('cpuBar').style.width=`${c.usage_percent||0}%`;$('load').textContent=`${c.logical_cpus||'–'} Threads · Load ${(c.load||[]).join(' / ')}`;let rp=m.total?m.used/m.total*100:0;$('ram').textContent=pct(rp);$('ramBar').style.width=`${rp}%`;$('ramSub').textContent=`${gib(m.used)} / ${gib(m.total)}`;$('gpuCards').innerHTML=(d.gpus||[]).map(gpuCard).join('')||'<article class="card span12 error">Keine GPU-Daten verfügbar</article>';$('availability').textContent=imageActive?imagePhaseLabel(img.phase):(q.available?'bereit':'nicht bereit');$('availability').className=`value status ${(imageActive||q.available)?'':'bad'}`;$('activeChats').textContent=q.active_chats??'–';$('routerUptime').textContent=dur(rt.uptime_seconds);$('switching').textContent=imageActive?imagePhaseLabel(img.phase):(rt.switching||'nein');let disk=c.disk_data||{},dp=disk.total?disk.used/disk.total*100:null;$('dataDisk').textContent=pct(dp);$('processes').innerHTML=(d.gpu_processes||[]).map(p=>`<tr><td>${(d.gpus||[]).find(g=>g.uuid===p.gpu_uuid)?.index??'–'}</td><td>${p.name}</td><td>${p.pid}</td><td>${p.memory_mib??'–'} MiB</td></tr>`).join('')||'<tr><td colspan="4">Keine Compute-Prozesse gemeldet</td></tr>';let runtime=[['Modell-Datei',lr.model_file],['PID',lr.pid],['Kontext',lr.context_size?lr.context_size.toLocaleString('de-DE'):'–'],['Batch / µBatch',`${lr.batch_size??'–'} / ${lr.ubatch_size??'–'}`],['Parallel',lr.parallel],['Threads',`${lr.threads??'–'} / ${lr.threads_batch??'–'}`],['Geräte',lr.device],['Tensor-Split',lr.tensor_split],['KV-Cache',`${lr.cache_k??'–'} / ${lr.cache_v??'–'}`],['Flash Attention',lr.flash_attention?'an':'aus'],['Prompt-Cache',lr.prompt_cache?'an':'aus'],['MTP Draft',lr.mtp_draft_tokens]];$('runtime').innerHTML=runtime.map(([k,v])=>`<div class="metric"><b>${v??'–'}</b><span>${k}</span></div>`).join('');let es=Object.entries(d.errors||{}).filter(([,v])=>v);$('errors').hidden=!es.length;$('errors').textContent=es.map(([k,v])=>`${k}: ${v}`).join('\n');$('updated').textContent=`Live · ${new Date(d.timestamp*1000).toLocaleTimeString('de-DE')}`;$('dot').style.background='var(--green)'}catch(e){$('updated').textContent=`Verbindung gestört: ${e.message}`;$('dot').style.background='var(--red)'}}refresh();setInterval(refresh,1000);
|
||||||
async function refreshBackups(){try{let r=await fetch('/api/backups',{cache:'no-store'});if(!r.ok)throw Error(`HTTP ${r.status}`);let d=await r.json(),rows=d.backups||[];$('backupFiles').innerHTML=rows.map(b=>`<tr><td>${new Date(b.modified*1000).toLocaleString('de-DE')}</td><td>${gib(b.size)}</td><td class="hash">${b.sha256||'Prüfsumme fehlt'}</td><td><a class="download" href="${b.download_url}">Herunterladen</a></td></tr>`).join('')||'<tr><td colspan="4">Noch kein portables Backup vorhanden</td></tr>';$('backupNote').textContent=rows.length?`${rows.length} von maximal 5 Generationen · verschlüsselt mit Age`:'Der erste Lauf startet spätestens fünf Stunden nach Aktivierung.'}catch(e){$('backupNote').textContent=`Backup-Liste nicht verfügbar: ${e.message}`}}refreshBackups();setInterval(refreshBackups,60000);
|
async function refreshBackups(){try{let r=await fetch('/api/backups',{cache:'no-store'});if(!r.ok)throw Error(`HTTP ${r.status}`);let d=await r.json(),rows=d.backups||[];$('backupFiles').innerHTML=rows.map(b=>`<tr><td>${new Date(b.modified*1000).toLocaleString('de-DE')}</td><td>${gib(b.size)}</td><td class="hash">${b.sha256||'Prüfsumme fehlt'}</td><td><a class="download" href="${b.download_url}">Herunterladen</a></td></tr>`).join('')||'<tr><td colspan="4">Noch kein portables Backup vorhanden</td></tr>';$('backupNote').textContent=rows.length?`${rows.length} von maximal 5 Generationen · verschlüsselt mit Age`:'Der erste Lauf startet spätestens fünf Stunden nach Aktivierung.'}catch(e){$('backupNote').textContent=`Backup-Liste nicht verfügbar: ${e.message}`}}refreshBackups();setInterval(refreshBackups,60000);
|
||||||
</script><script src="/full.js"></script><script src="/history.js"></script></body></html>'''.replace(
|
</script><script src="/full.js"></script><script src="/history.js"></script></body></html>'''.replace(
|
||||||
"__MUSIC_ORIGINAL_UI_URL__", MUSIC_ORIGINAL_UI_URL
|
"__MUSIC_ORIGINAL_UI_URL__", MUSIC_ORIGINAL_UI_URL
|
||||||
@@ -721,7 +724,9 @@ async function refreshBackups(){try{let r=await fetch('/api/backups',{cache:'no-
|
|||||||
).replace("__VOICE_UI_URL__", VOICE_UI_URL
|
).replace("__VOICE_UI_URL__", VOICE_UI_URL
|
||||||
).replace("__VOICE_CHANGE_UI_URL__", VOICE_CHANGE_UI_URL
|
).replace("__VOICE_CHANGE_UI_URL__", VOICE_CHANGE_UI_URL
|
||||||
).replace("__APPLIO_UI_URL__", APPLIO_UI_URL
|
).replace("__APPLIO_UI_URL__", APPLIO_UI_URL
|
||||||
).replace("__MIKES_APPLIO_UI_URL__", MIKES_APPLIO_UI_URL)
|
).replace("__MIKES_APPLIO_UI_URL__", MIKES_APPLIO_UI_URL).replace(
|
||||||
|
"__TRELLIS_UI_URL__", TRELLIS_UI_URL
|
||||||
|
).replace("__YUE2_UI_URL__", YUE2_UI_URL)
|
||||||
|
|
||||||
|
|
||||||
FULL_JS = r'''
|
FULL_JS = r'''
|
||||||
|
|||||||
@@ -108,10 +108,12 @@ PROFILE_CONTROL_TOKEN_FILE = os.environ.get(
|
|||||||
ENABLE_MUSIC_MODE = os.environ.get(
|
ENABLE_MUSIC_MODE = os.environ.get(
|
||||||
"ENABLE_MUSIC_MODE", "false").lower() in {"1", "true", "yes"}
|
"ENABLE_MUSIC_MODE", "false").lower() in {"1", "true", "yes"}
|
||||||
MUSIC_START_TIMEOUT = float(os.environ.get("MUSIC_START_TIMEOUT", "600"))
|
MUSIC_START_TIMEOUT = float(os.environ.get("MUSIC_START_TIMEOUT", "600"))
|
||||||
|
YUE2_START_TIMEOUT = float(os.environ.get("YUE2_START_TIMEOUT", "600"))
|
||||||
SEPARATOR_START_TIMEOUT = float(os.environ.get("SEPARATOR_START_TIMEOUT", "600"))
|
SEPARATOR_START_TIMEOUT = float(os.environ.get("SEPARATOR_START_TIMEOUT", "600"))
|
||||||
VOICE_START_TIMEOUT = float(os.environ.get("VOICE_START_TIMEOUT", "600"))
|
VOICE_START_TIMEOUT = float(os.environ.get("VOICE_START_TIMEOUT", "600"))
|
||||||
VOICE_CHANGE_START_TIMEOUT = float(os.environ.get("VOICE_CHANGE_START_TIMEOUT", "600"))
|
VOICE_CHANGE_START_TIMEOUT = float(os.environ.get("VOICE_CHANGE_START_TIMEOUT", "600"))
|
||||||
APPLIO_START_TIMEOUT = float(os.environ.get("APPLIO_START_TIMEOUT", "900"))
|
APPLIO_START_TIMEOUT = float(os.environ.get("APPLIO_START_TIMEOUT", "900"))
|
||||||
|
TRELLIS_START_TIMEOUT = float(os.environ.get("TRELLIS_START_TIMEOUT", "900"))
|
||||||
|
|
||||||
# Optional worker APIs. The clean Docker baseline deliberately ships only
|
# Optional worker APIs. The clean Docker baseline deliberately ships only
|
||||||
# text/multimodal chat; absent workers must fail explicitly instead of trying
|
# text/multimodal chat; absent workers must fail explicitly instead of trying
|
||||||
@@ -429,6 +431,11 @@ def _wait_music_ready() -> None:
|
|||||||
f"ACE-Step nach {MUSIC_START_TIMEOUT:.0f} s nicht bereit")
|
f"ACE-Step nach {MUSIC_START_TIMEOUT:.0f} s nicht bereit")
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_yue2_ready() -> None:
|
||||||
|
_wait_aux_voice_ready("yue2_worker", "yue2_health",
|
||||||
|
"YuE2", YUE2_START_TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
def _wait_separator_ready() -> None:
|
def _wait_separator_ready() -> None:
|
||||||
deadline = time.monotonic() + SEPARATOR_START_TIMEOUT
|
deadline = time.monotonic() + SEPARATOR_START_TIMEOUT
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
@@ -490,9 +497,17 @@ def _wait_applio_ready() -> None:
|
|||||||
"Applio", APPLIO_START_TIMEOUT)
|
"Applio", APPLIO_START_TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_trellis_ready() -> None:
|
||||||
|
_wait_aux_voice_ready("trellis_worker", "trellis_health",
|
||||||
|
"TRELLIS.2", TRELLIS_START_TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
def _special_worker(mode: str) -> tuple[str, str, callable]:
|
def _special_worker(mode: str) -> tuple[str, str, callable]:
|
||||||
if mode == "music":
|
if mode == "music":
|
||||||
return "/workers/music/start", _music_worker_state(), _wait_music_ready
|
return "/workers/music/start", _music_worker_state(), _wait_music_ready
|
||||||
|
if mode == "yue2":
|
||||||
|
return ("/workers/yue2/start", _worker_field("yue2_worker"),
|
||||||
|
_wait_yue2_ready)
|
||||||
if mode == "separation":
|
if mode == "separation":
|
||||||
return "/workers/separator/start", _separator_worker_state(), _wait_separator_ready
|
return "/workers/separator/start", _separator_worker_state(), _wait_separator_ready
|
||||||
if mode == "voice":
|
if mode == "voice":
|
||||||
@@ -503,6 +518,9 @@ def _special_worker(mode: str) -> tuple[str, str, callable]:
|
|||||||
if mode == "applio":
|
if mode == "applio":
|
||||||
return ("/workers/applio/start", _worker_field("applio_worker"),
|
return ("/workers/applio/start", _worker_field("applio_worker"),
|
||||||
_wait_applio_ready)
|
_wait_applio_ready)
|
||||||
|
if mode == "trellis":
|
||||||
|
return ("/workers/trellis/start", _worker_field("trellis_worker"),
|
||||||
|
_wait_trellis_ready)
|
||||||
raise ValueError(f"unbekannter Spezialmodus: {mode}")
|
raise ValueError(f"unbekannter Spezialmodus: {mode}")
|
||||||
|
|
||||||
|
|
||||||
@@ -510,7 +528,8 @@ def set_operating_mode(mode: str) -> dict:
|
|||||||
"""Atomarer Wechsel zwischen LLM und den exklusiven GPU-Werkzeugen."""
|
"""Atomarer Wechsel zwischen LLM und den exklusiven GPU-Werkzeugen."""
|
||||||
if not ENABLE_MUSIC_MODE or not PROFILE_CONTROL_URL:
|
if not ENABLE_MUSIC_MODE or not PROFILE_CONTROL_URL:
|
||||||
raise RuntimeError("Musikmodus ist nicht konfiguriert")
|
raise RuntimeError("Musikmodus ist nicht konfiguriert")
|
||||||
special_modes = {"music", "separation", "voice", "voicechange", "applio"}
|
special_modes = {"music", "yue2", "separation", "voice", "voicechange",
|
||||||
|
"applio", "trellis"}
|
||||||
if mode not in {"llm", *special_modes}:
|
if mode not in {"llm", *special_modes}:
|
||||||
raise ValueError("unbekannter Betriebsmodus")
|
raise ValueError("unbekannter Betriebsmodus")
|
||||||
with STATE.lock:
|
with STATE.lock:
|
||||||
@@ -555,10 +574,12 @@ def set_operating_mode(mode: str) -> dict:
|
|||||||
_set_qwen_unavailable(True)
|
_set_qwen_unavailable(True)
|
||||||
try:
|
try:
|
||||||
_profile_controller_request("POST", "/workers/music/stop")
|
_profile_controller_request("POST", "/workers/music/stop")
|
||||||
|
_profile_controller_request("POST", "/workers/yue2/stop")
|
||||||
_profile_controller_request("POST", "/workers/separator/stop")
|
_profile_controller_request("POST", "/workers/separator/stop")
|
||||||
_profile_controller_request("POST", "/workers/voice/stop")
|
_profile_controller_request("POST", "/workers/voice/stop")
|
||||||
_profile_controller_request("POST", "/workers/voice-change/stop")
|
_profile_controller_request("POST", "/workers/voice-change/stop")
|
||||||
_profile_controller_request("POST", "/workers/applio/stop")
|
_profile_controller_request("POST", "/workers/applio/stop")
|
||||||
|
_profile_controller_request("POST", "/workers/trellis/stop")
|
||||||
_restore_qwen(profile)
|
_restore_qwen(profile)
|
||||||
STATE.mode = "llm"
|
STATE.mode = "llm"
|
||||||
STATE.mode_phase = "ready"
|
STATE.mode_phase = "ready"
|
||||||
@@ -577,7 +598,8 @@ def schedule_operating_mode(mode: str) -> tuple[bool, str]:
|
|||||||
"""Start a transition in the background so chat/UI acknowledgement is instant."""
|
"""Start a transition in the background so chat/UI acknowledgement is instant."""
|
||||||
if not ENABLE_MUSIC_MODE or not PROFILE_CONTROL_URL:
|
if not ENABLE_MUSIC_MODE or not PROFILE_CONTROL_URL:
|
||||||
raise RuntimeError("Musikmodus ist nicht konfiguriert")
|
raise RuntimeError("Musikmodus ist nicht konfiguriert")
|
||||||
if mode not in {"llm", "music", "separation", "voice", "voicechange", "applio"}:
|
if mode not in {"llm", "music", "yue2", "separation", "voice",
|
||||||
|
"voicechange", "applio", "trellis"}:
|
||||||
raise ValueError("unbekannter Betriebsmodus")
|
raise ValueError("unbekannter Betriebsmodus")
|
||||||
with STATE.lock:
|
with STATE.lock:
|
||||||
if STATE.mode_phase not in {"ready", "error"}:
|
if STATE.mode_phase not in {"ready", "error"}:
|
||||||
@@ -612,11 +634,13 @@ def _control_command(data: dict, path: str) -> str | None:
|
|||||||
if not isinstance(text, str):
|
if not isinstance(text, str):
|
||||||
return None
|
return None
|
||||||
command = text.strip().casefold()
|
command = text.strip().casefold()
|
||||||
return command if command in {"/athena music", "/athena stems",
|
return command if command in {"/athena music", "/athena yue2",
|
||||||
|
"/athena stems",
|
||||||
"/athena separation", "/athena llm",
|
"/athena separation", "/athena llm",
|
||||||
"/athena voice",
|
"/athena voice",
|
||||||
"/athena voicechange", "/athena changer",
|
"/athena voicechange", "/athena changer",
|
||||||
"/athena applio",
|
"/athena applio",
|
||||||
|
"/athena 3d", "/athena trellis",
|
||||||
"/athena status"} else None
|
"/athena status"} else None
|
||||||
|
|
||||||
|
|
||||||
@@ -2090,6 +2114,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"phase": STATE.mode_phase,
|
"phase": STATE.mode_phase,
|
||||||
"music_worker": _music_worker_state(),
|
"music_worker": _music_worker_state(),
|
||||||
"music_health": _music_worker_health(),
|
"music_health": _music_worker_health(),
|
||||||
|
"yue2_worker": _worker_field("yue2_worker"),
|
||||||
|
"yue2_health": _worker_field("yue2_health"),
|
||||||
"separator_worker": _separator_worker_state(),
|
"separator_worker": _separator_worker_state(),
|
||||||
"separator_health": _separator_worker_health(),
|
"separator_health": _separator_worker_health(),
|
||||||
"voice_worker": _voice_worker_state(),
|
"voice_worker": _voice_worker_state(),
|
||||||
@@ -2098,6 +2124,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"voice_change_health": _voice_change_worker_health(),
|
"voice_change_health": _voice_change_worker_health(),
|
||||||
"applio_worker": _worker_field("applio_worker"),
|
"applio_worker": _worker_field("applio_worker"),
|
||||||
"applio_health": _worker_field("applio_health"),
|
"applio_health": _worker_field("applio_health"),
|
||||||
|
"trellis_worker": _worker_field("trellis_worker"),
|
||||||
|
"trellis_health": _worker_field("trellis_health"),
|
||||||
"return_profile": state.get("return_profile"),
|
"return_profile": state.get("return_profile"),
|
||||||
"last_error": STATE.mode_error,
|
"last_error": STATE.mode_error,
|
||||||
"enabled": ENABLE_MUSIC_MODE,
|
"enabled": ENABLE_MUSIC_MODE,
|
||||||
@@ -2107,7 +2135,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
try:
|
try:
|
||||||
data = json.loads(self._read_body() or b"{}")
|
data = json.loads(self._read_body() or b"{}")
|
||||||
mode = data.get("mode") if isinstance(data, dict) else None
|
mode = data.get("mode") if isinstance(data, dict) else None
|
||||||
if mode not in {"llm", "music", "separation", "voice", "voicechange", "applio"}:
|
if mode not in {"llm", "music", "yue2", "separation", "voice",
|
||||||
|
"voicechange", "applio", "trellis"}:
|
||||||
raise ValueError("Feld 'mode' enthält einen unbekannten Betriebsmodus")
|
raise ValueError("Feld 'mode' enthält einen unbekannten Betriebsmodus")
|
||||||
started, phase = schedule_operating_mode(mode)
|
started, phase = schedule_operating_mode(mode)
|
||||||
self._send_json(202 if started else 200, {
|
self._send_json(202 if started else 200, {
|
||||||
@@ -2770,22 +2799,28 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
profile = current_profile()
|
profile = current_profile()
|
||||||
text = (f"Athena läuft im {mode['active'].upper()}-Modus. "
|
text = (f"Athena läuft im {mode['active'].upper()}-Modus. "
|
||||||
f"Phase: {mode['phase']}. Musik-Worker: "
|
f"Phase: {mode['phase']}. Musik-Worker: "
|
||||||
f"{mode['music_worker']}. Stem-Separator: "
|
f"{mode['music_worker']}. YuE2: "
|
||||||
|
f"{mode['yue2_worker']}. Stem-Separator: "
|
||||||
f"{mode['separator_worker']}. Voice Studio: "
|
f"{mode['separator_worker']}. Voice Studio: "
|
||||||
f"{mode['voice_worker']}. Voice Changer: "
|
f"{mode['voice_worker']}. Voice Changer: "
|
||||||
f"{mode['voice_change_worker']}. LLM-Profil: {profile or 'entladen'}.")
|
f"{mode['voice_change_worker']}. 3D Studio: "
|
||||||
|
f"{mode['trellis_worker']}. LLM-Profil: {profile or 'entladen'}.")
|
||||||
else:
|
else:
|
||||||
target = ("music" if command == "/athena music" else
|
target = ("music" if command == "/athena music" else
|
||||||
|
"yue2" if command == "/athena yue2" else
|
||||||
"separation" if command in {"/athena stems", "/athena separation"}
|
"separation" if command in {"/athena stems", "/athena separation"}
|
||||||
else "voice" if command == "/athena voice"
|
else "voice" if command == "/athena voice"
|
||||||
else "voicechange" if command in {"/athena voicechange", "/athena changer"}
|
else "voicechange" if command in {"/athena voicechange", "/athena changer"}
|
||||||
else "applio" if command == "/athena applio"
|
else "applio" if command == "/athena applio"
|
||||||
|
else "trellis" if command in {"/athena 3d", "/athena trellis"}
|
||||||
else "llm")
|
else "llm")
|
||||||
try:
|
try:
|
||||||
started, phase = schedule_operating_mode(target)
|
started, phase = schedule_operating_mode(target)
|
||||||
if started:
|
if started:
|
||||||
text = ("Musikstudio wird gestartet. LLM und TTS werden entladen."
|
text = ("Musikstudio wird gestartet. LLM und TTS werden entladen."
|
||||||
if target == "music" else
|
if target == "music" else
|
||||||
|
"YuE2 Studio wird gestartet. LLM und TTS werden entladen."
|
||||||
|
if target == "yue2" else
|
||||||
"Stimmtrennung wird gestartet. LLM und TTS werden entladen."
|
"Stimmtrennung wird gestartet. LLM und TTS werden entladen."
|
||||||
if target == "separation" else
|
if target == "separation" else
|
||||||
"Voice Studio wird gestartet. LLM und TTS werden entladen."
|
"Voice Studio wird gestartet. LLM und TTS werden entladen."
|
||||||
@@ -2794,6 +2829,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
if target == "voicechange" else
|
if target == "voicechange" else
|
||||||
"Applio wird gestartet. LLM und TTS werden entladen."
|
"Applio wird gestartet. LLM und TTS werden entladen."
|
||||||
if target == "applio" else
|
if target == "applio" else
|
||||||
|
"3D Studio wird gestartet. LLM und TTS werden entladen."
|
||||||
|
if target == "trellis" else
|
||||||
"Spezialmodus wird beendet und das vorherige LLM-Profil wiederhergestellt.")
|
"Spezialmodus wird beendet und das vorherige LLM-Profil wiederhergestellt.")
|
||||||
else:
|
else:
|
||||||
text = (f"Athena ist bereits im {target.upper()}-Modus "
|
text = (f"Athena ist bereits im {target.upper()}-Modus "
|
||||||
@@ -3086,7 +3123,9 @@ def _startup_reconcile() -> None:
|
|||||||
log.info("Startup-Retention: %d alte Bilder entfernt", len(removed))
|
log.info("Startup-Retention: %d alte Bilder entfernt", len(removed))
|
||||||
|
|
||||||
special_mode = previous.get("mode")
|
special_mode = previous.get("mode")
|
||||||
if ENABLE_MUSIC_MODE and special_mode in {"music", "separation", "voice", "voicechange"}:
|
if ENABLE_MUSIC_MODE and special_mode in {"music", "yue2", "separation",
|
||||||
|
"voice", "voicechange", "applio",
|
||||||
|
"trellis"}:
|
||||||
STATE.mode = special_mode
|
STATE.mode = special_mode
|
||||||
STATE.mode_phase = f"starting-{special_mode}"
|
STATE.mode_phase = f"starting-{special_mode}"
|
||||||
_set_qwen_unavailable(True)
|
_set_qwen_unavailable(True)
|
||||||
|
|||||||
Reference in New Issue
Block a user