"""Router voix : catalogue GLOBAL, déploiement par tablette, ingestion admin, verrou d'exclusivité, ré-enrôlement, déclencheur auto.""" from __future__ import annotations from fastapi import APIRouter from pydantic import BaseModel from kazeia_central.core.context import Ctx from . import orchestrator as vo class _Sync(BaseModel): delete_source: bool = False class _AutoSync(BaseModel): enabled: bool delete_source: bool | None = None class _Lock(BaseModel): profile_id: str class _Deploy(BaseModel): voice_id: str def make_router(ctx: Ctx) -> APIRouter: r = APIRouter(prefix="/api/voices", tags=["voices"]) g, now = ctx.guard, ctx.now_ms @r.get("/health") def voices_health(): return {"encoder_available": ctx.bridge.available(), "ov_python": ctx.bridge.ov_python} # ⚠️ Littéraux (autosync, catalog) AVANT /{serial} — ordre de déclaration. @r.get("/autosync") def autosync_status(): return ctx.autosync.status() @r.post("/autosync") def autosync_set(body: _AutoSync): ctx.autosync.set_enabled(body.enabled, delete_source=body.delete_source) ctx.store.audit("autosync_set", actor=ctx.operator, detail={"enabled": body.enabled, "delete_source": ctx.autosync.delete_source}, now=now()) return ctx.autosync.status() # ---- catalogue GLOBAL (vue sans tablette) ----------------------------- @r.get("/catalog") def voices_catalog(): return g(lambda: vo.catalog(ctx.store)) @r.post("/catalog/{voice_id}/lock") def voices_lock(voice_id: str, body: _Lock): g(lambda: ctx.store.lock_voice(voice_id, body.profile_id, now=now())) ctx.store.audit("voice_lock", actor=ctx.operator, target=voice_id, detail={"profile_id": body.profile_id}, now=now()) return {"voice_id": voice_id, "locked_profile_id": body.profile_id} @r.post("/catalog/{voice_id}/unlock") def voices_unlock(voice_id: str): g(lambda: ctx.store.unlock_voice(voice_id, now=now())) ctx.store.audit("voice_unlock", actor=ctx.operator, target=voice_id, now=now()) return {"voice_id": voice_id, "locked_profile_id": None} @r.post("/catalog/{voice_id}/reenroll") def voices_reenroll(voice_id: str): return g(lambda: vo.reenroll_voice(ctx.adb, ctx.store, ctx.bridge, voice_id, now=now(), actor=ctx.operator)) # ---- vue par tablette : chargées + chargeables ------------------------ @r.get("/{serial}") def voices_for_tablet(serial: str): return g(lambda: vo.list_for_tablet(ctx.adb, ctx.store, serial)) @r.get("/{serial}/admin") def voices_admin_list(serial: str): return g(lambda: vo.list_admin_voices(ctx.adb, ctx.store, serial)) @r.post("/{serial}/admin/sync") def voices_admin_sync(serial: str, body: _Sync): return g(lambda: vo.sync_admin(ctx.adb, ctx.store, ctx.bridge, serial, delete_source=body.delete_source, now=now(), actor=ctx.operator)) @r.post("/{serial}/deploy") def voices_deploy(serial: str, body: _Deploy): return g(lambda: vo.deploy_voice(ctx.adb, ctx.store, ctx.bridge, serial, body.voice_id, now=now(), actor=ctx.operator)) @r.post("/{serial}/undeploy") def voices_undeploy(serial: str, body: _Deploy): return g(lambda: vo.undeploy_voice(ctx.adb, ctx.store, serial, body.voice_id, now=now(), actor=ctx.operator)) return r