Integrate YuE2 as an exclusive Athena mode

This commit is contained in:
Mikei386
2026-09-10 23:53:19 +02:00
parent 5c34afa7fa
commit f2052adb78
14 changed files with 278 additions and 15 deletions
+46 -7
View File
@@ -108,10 +108,12 @@ PROFILE_CONTROL_TOKEN_FILE = os.environ.get(
ENABLE_MUSIC_MODE = os.environ.get(
"ENABLE_MUSIC_MODE", "false").lower() in {"1", "true", "yes"}
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"))
VOICE_START_TIMEOUT = float(os.environ.get("VOICE_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"))
TRELLIS_START_TIMEOUT = float(os.environ.get("TRELLIS_START_TIMEOUT", "900"))
# Optional worker APIs. The clean Docker baseline deliberately ships only
# 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")
def _wait_yue2_ready() -> None:
_wait_aux_voice_ready("yue2_worker", "yue2_health",
"YuE2", YUE2_START_TIMEOUT)
def _wait_separator_ready() -> None:
deadline = time.monotonic() + SEPARATOR_START_TIMEOUT
while time.monotonic() < deadline:
@@ -490,9 +497,17 @@ def _wait_applio_ready() -> None:
"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]:
if mode == "music":
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":
return "/workers/separator/start", _separator_worker_state(), _wait_separator_ready
if mode == "voice":
@@ -503,6 +518,9 @@ def _special_worker(mode: str) -> tuple[str, str, callable]:
if mode == "applio":
return ("/workers/applio/start", _worker_field("applio_worker"),
_wait_applio_ready)
if mode == "trellis":
return ("/workers/trellis/start", _worker_field("trellis_worker"),
_wait_trellis_ready)
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."""
if not ENABLE_MUSIC_MODE or not PROFILE_CONTROL_URL:
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}:
raise ValueError("unbekannter Betriebsmodus")
with STATE.lock:
@@ -555,10 +574,12 @@ def set_operating_mode(mode: str) -> dict:
_set_qwen_unavailable(True)
try:
_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/voice/stop")
_profile_controller_request("POST", "/workers/voice-change/stop")
_profile_controller_request("POST", "/workers/applio/stop")
_profile_controller_request("POST", "/workers/trellis/stop")
_restore_qwen(profile)
STATE.mode = "llm"
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."""
if not ENABLE_MUSIC_MODE or not PROFILE_CONTROL_URL:
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")
with STATE.lock:
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):
return None
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 voice",
"/athena voicechange", "/athena changer",
"/athena applio",
"/athena 3d", "/athena trellis",
"/athena status"} else None
@@ -2090,6 +2114,8 @@ class Handler(BaseHTTPRequestHandler):
"phase": STATE.mode_phase,
"music_worker": _music_worker_state(),
"music_health": _music_worker_health(),
"yue2_worker": _worker_field("yue2_worker"),
"yue2_health": _worker_field("yue2_health"),
"separator_worker": _separator_worker_state(),
"separator_health": _separator_worker_health(),
"voice_worker": _voice_worker_state(),
@@ -2098,6 +2124,8 @@ class Handler(BaseHTTPRequestHandler):
"voice_change_health": _voice_change_worker_health(),
"applio_worker": _worker_field("applio_worker"),
"applio_health": _worker_field("applio_health"),
"trellis_worker": _worker_field("trellis_worker"),
"trellis_health": _worker_field("trellis_health"),
"return_profile": state.get("return_profile"),
"last_error": STATE.mode_error,
"enabled": ENABLE_MUSIC_MODE,
@@ -2107,7 +2135,8 @@ class Handler(BaseHTTPRequestHandler):
try:
data = json.loads(self._read_body() or b"{}")
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")
started, phase = schedule_operating_mode(mode)
self._send_json(202 if started else 200, {
@@ -2770,22 +2799,28 @@ class Handler(BaseHTTPRequestHandler):
profile = current_profile()
text = (f"Athena läuft im {mode['active'].upper()}-Modus. "
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['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:
target = ("music" if command == "/athena music" else
"yue2" if command == "/athena yue2" else
"separation" if command in {"/athena stems", "/athena separation"}
else "voice" if command == "/athena voice"
else "voicechange" if command in {"/athena voicechange", "/athena changer"}
else "applio" if command == "/athena applio"
else "trellis" if command in {"/athena 3d", "/athena trellis"}
else "llm")
try:
started, phase = schedule_operating_mode(target)
if started:
text = ("Musikstudio wird gestartet. LLM und TTS werden entladen."
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."
if target == "separation" else
"Voice Studio wird gestartet. LLM und TTS werden entladen."
@@ -2794,6 +2829,8 @@ class Handler(BaseHTTPRequestHandler):
if target == "voicechange" else
"Applio wird gestartet. LLM und TTS werden entladen."
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.")
else:
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))
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_phase = f"starting-{special_mode}"
_set_qwen_unavailable(True)