Reduce controller polling and bound chat admission waits

This commit is contained in:
Mikei386
2026-09-20 19:10:59 +02:00
parent 53320fa6c3
commit 6071b1a2cd
10 changed files with 303 additions and 134 deletions
@@ -323,6 +323,8 @@ def stop_container(item: dict, timeout: int = 120) -> None:
def start_container(item: dict) -> None:
if item.get("State") == "running":
return
status, _ = docker_request("POST", f"/containers/{item['Id']}/start")
if status not in (204, 304):
raise RuntimeError(f"failed to start container: HTTP {status}")
@@ -571,7 +573,7 @@ def set_video_worker(running: bool) -> dict:
def active_profile(items: dict[str, dict] | None = None) -> str | None:
items = items or containers()
items = containers() if items is None else items
active = [name for name, item in items.items() if item.get("State") == "running"]
if len(active) > 1:
raise RuntimeError(f"multiple llama profiles active: {', '.join(active)}")
@@ -582,6 +584,11 @@ def activate(profile: str) -> dict:
if profile not in ALLOWED:
raise ValueError("profile is not allowlisted")
with LOCK:
# Validate before stopping any working service.
items = containers()
missing = [name for name in ALLOWED if name not in items]
if missing:
raise RuntimeError("profile containers missing: " + ", ".join(missing))
# Defensive mutual exclusion even if a caller bypasses the router.
for worker in image_containers():
stop_container(worker)
@@ -592,10 +599,6 @@ def activate(profile: str) -> dict:
stop_trellis_if_configured()
stop_video_if_configured()
start_container(tts_container())
items = containers()
missing = [name for name in ALLOWED if name not in items]
if missing:
raise RuntimeError("profile containers missing: " + ", ".join(missing))
current = active_profile(items)
if current == profile:
return {"active_profile": current, "changed": False}
@@ -613,6 +616,58 @@ def activate(profile: str) -> dict:
return {"active_profile": profile, "changed": True}
def status_snapshot() -> dict:
"""One Docker snapshot; missing optional workers cannot hide the LLM."""
status, body = docker_request("GET", "/containers/json?all=1")
if status != 200:
raise RuntimeError(f"Docker list failed with HTTP {status}")
records = json.loads(body)
profiles = {}
for item in records:
name = item.get("Labels", {}).get(LABEL_KEY)
if name in ALLOWED:
if name in profiles:
raise RuntimeError(f"duplicate container for profile {name}")
profiles[name] = item
result = {"active_profile": active_profile(profiles),
"profiles": {name: profiles.get(name, {}).get("State", "missing")
for name in ALLOWED}, "worker_errors": {}}
workers = (
("music", MUSIC_LABEL_KEY, MUSIC_WORKER),
("yue2", MUSIC_LABEL_KEY, YUE2_WORKER),
("separator", SEPARATOR_LABEL_KEY, SEPARATOR_WORKER),
("voice", VOICE_LABEL_KEY, VOICE_WORKER),
("voice_change", VOICE_CHANGE_LABEL_KEY, VOICE_CHANGE_WORKER),
("applio", APPLIO_LABEL_KEY, APPLIO_WORKER),
("trellis", TRELLIS_LABEL_KEY, TRELLIS_WORKER),
("video", VIDEO_LABEL_KEY, VIDEO_WORKER),
)
result["video_ui"] = "unavailable"
for name, label, kind in workers:
matches = [item for item in records
if kind and item.get("Labels", {}).get(label) == kind]
if not kind:
state = health = "disabled"
elif len(matches) != 1:
state = health = "missing" if not matches else "error"
result["worker_errors"][name] = f"expected one container, found {len(matches)}"
else:
item = matches[0]
state = item.get("State", "unknown")
description = item.get("Status", "")
health = ("healthy" if "(healthy)" in description else
"unhealthy" if "(unhealthy)" in description else
"starting" if state == "running" else "stopped")
if name == "video":
try:
result["video_ui"] = cached_video_ui_state(item)
except Exception as exc:
result["worker_errors"]["video_ui"] = str(exc)
result[f"{name}_worker"] = state
result[f"{name}_health"] = health
return result
def load_token() -> str:
token = os.environ.get("CONTROLLER_TOKEN", "").strip()
if not token:
@@ -647,90 +702,18 @@ class Handler(BaseHTTPRequestHandler):
if self.path == "/health":
self.reply(200, {"status": "ok"})
return
if self.path != "/status":
if self.path not in {"/status", "/profiles/status"}:
self.reply(404, {"error": "not found"})
return
if not self.authenticated():
self.reply(401, {"error": "unauthorized"})
return
try:
items = containers()
music = music_container() if MUSIC_WORKER else {}
music_status = music.get("Status", "")
music_health = ("disabled" if not MUSIC_WORKER else
"healthy" if "(healthy)" in music_status else
"unhealthy" if "(unhealthy)" in music_status else
"starting" if music.get("State") == "running" else
"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_status = separator.get("Status", "")
separator_health = ("disabled" if not SEPARATOR_WORKER else
"healthy" if "(healthy)" in separator_status else
"unhealthy" if "(unhealthy)" in separator_status else
"starting" if separator.get("State") == "running" else
"stopped")
voice = voice_container() if VOICE_WORKER else {}
voice_status = voice.get("Status", "")
voice_health = ("disabled" if not VOICE_WORKER else
"healthy" if "(healthy)" in voice_status else
"unhealthy" if "(unhealthy)" in voice_status else
"starting" if voice.get("State") == "running" else
"stopped")
voice_change = voice_change_container() if VOICE_CHANGE_WORKER else {}
voice_change_status = voice_change.get("Status", "")
voice_change_health = ("disabled" if not VOICE_CHANGE_WORKER else
"healthy" if "(healthy)" in voice_change_status else
"unhealthy" if "(unhealthy)" in voice_change_status else
"starting" if voice_change.get("State") == "running" else
"stopped")
applio = applio_container() if APPLIO_WORKER else {}
applio_status = applio.get("Status", "")
applio_health = ("disabled" if not APPLIO_WORKER else
"healthy" if "(healthy)" in applio_status else
"unhealthy" if "(unhealthy)" in applio_status else
"starting" if applio.get("State") == "running" else
"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")
video = video_container() if VIDEO_WORKER else {}
video_status = video.get("Status", "")
video_health = ("disabled" if not VIDEO_WORKER else
"healthy" if "(healthy)" in video_status else
"unhealthy" if "(unhealthy)" in video_status else
"starting" if video.get("State") == "running" else
"stopped")
self.reply(200, {"active_profile": active_profile(items),
"music_worker": music.get("State", "disabled"),
"music_health": music_health,
"yue2_worker": yue2.get("State", "disabled"),
"yue2_health": yue2_health,
"separator_worker": separator.get("State", "disabled"),
"separator_health": separator_health,
"voice_worker": voice.get("State", "disabled"),
"voice_health": voice_health,
"voice_change_worker": voice_change.get("State", "disabled"),
"voice_change_health": voice_change_health,
"applio_worker": applio.get("State", "disabled"),
"applio_health": applio_health,
"trellis_worker": trellis.get("State", "disabled"),
"trellis_health": trellis_health,
"video_worker": video.get("State", "disabled"),
"video_health": video_health,
"video_ui": cached_video_ui_state(video) if video else "unavailable",
"profiles": {name: items.get(name, {}).get(
"State", "missing") for name in ALLOWED}})
if self.path == "/profiles/status":
items = containers()
self.reply(200, {"active_profile": active_profile(items)})
else:
self.reply(200, status_snapshot())
except Exception as exc:
log.exception("status failed")
self.reply(503, {"error": str(exc)})