102 lines
3.1 KiB
Python
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)}
|