Kazeia-central/kazeia_central/features/voices/router.py

99 lines
3.5 KiB
Python

"""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 ...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