102 lines
3.1 KiB
Python

"""Skill registry CRUD endpoints + skill execution."""
from __future__ import annotations
from fastapi import APIRouter, HTTPException, UploadFile, File
from pydantic import BaseModel
from Project.Sanad.config import AUDIO_RECORDINGS_DIR
from Project.Sanad.dashboard.routes._safe_io import (
safe_path_under, check_upload_size, atomic_write_bytes,
)
router = APIRouter()
class SkillCreate(BaseModel):
id: str = ""
audio_file: str = ""
motion_file: str = ""
callback: str = ""
sync_mode: str = "parallel"
enabled: bool = True
description: str = ""
class SkillUpdate(BaseModel):
audio_file: str | None = None
motion_file: str | None = None
callback: str | None = None
sync_mode: str | None = None
enabled: bool | None = None
description: str | None = None
@router.get("/")
async def list_skills():
from Project.Sanad.main import brain
return {"skills": brain.registry.list_skills()}
@router.get("/{skill_id}")
async def get_skill(skill_id: str):
from Project.Sanad.main import brain
skill = brain.registry.get(skill_id)
if skill is None:
raise HTTPException(404, f"Skill not found: {skill_id}")
return skill.to_dict()
@router.post("/")
async def create_skill(payload: SkillCreate):
from Project.Sanad.main import brain
from Project.Sanad.core.skill_registry import Skill
try:
skill = Skill(**payload.model_dump())
created = brain.registry.add(skill)
except ValueError as exc:
raise HTTPException(400, str(exc))
return {"ok": True, "skill": created.to_dict()}
@router.put("/{skill_id}")
async def update_skill(skill_id: str, payload: SkillUpdate):
from Project.Sanad.main import brain
updates = {k: v for k, v in payload.model_dump().items() if v is not None}
try:
updated = brain.registry.update(skill_id, updates)
except ValueError as exc:
raise HTTPException(400, str(exc))
if updated is None:
raise HTTPException(404, f"Skill not found: {skill_id}")
return {"ok": True, "skill": updated.to_dict()}
@router.delete("/{skill_id}")
async def delete_skill(skill_id: str):
from Project.Sanad.main import brain
deleted = brain.registry.delete(skill_id)
if not deleted:
raise HTTPException(404, f"Skill not found: {skill_id}")
return {"ok": True, "deleted": deleted}
@router.post("/{skill_id}/execute")
async def execute_skill(skill_id: str):
from Project.Sanad.main import brain
result = await brain.execute_skill(skill_id)
return result
@router.post("/upload-audio")
async def upload_audio(file: UploadFile = File(...)):
"""Upload a .wav file for skill binding."""
if not file.filename or not file.filename.lower().endswith(".wav"):
raise HTTPException(400, "Only .wav files are accepted.")
AUDIO_RECORDINGS_DIR.mkdir(parents=True, exist_ok=True)
dest = safe_path_under(AUDIO_RECORDINGS_DIR, file.filename)
content = await file.read()
check_upload_size(content)
atomic_write_bytes(dest, content)
return {"ok": True, "path": str(dest), "size_bytes": len(content)}