"""Avatar 域路由(WP-09)。""" from typing import Annotated, Any from fastapi import APIRouter, Depends, HTTPException, Request, status from sqlalchemy import select from app.auth.dependencies import get_current_user, require_state_change from app.avatars import service from app.conversations.routes import _domain_of from app.db.session import DbSession from app.models import AvatarModel, User router = APIRouter(prefix="/v1/avatars", tags=["avatars"]) def _model_out(m: AvatarModel) -> dict[str, Any]: return { "id": m.id, "name": m.name, "format": m.format, "status": m.status, "file_id": m.file_id, "validation_report": m.validation_report, "character_id": m.character_id, "current_command_id": m.current_command_id, "created_at": m.created_at.isoformat(), } def _command_out(c: Any) -> dict[str, Any]: return { "id": c.id, "avatar_id": c.avatar_id, "sequence": c.sequence, "command_type": c.command_type, "payload": c.payload, "undone": c.undone, "created_at": c.created_at.isoformat(), } @router.post("/imports", status_code=201) async def import_avatar( db: DbSession, user: Annotated[User, Depends(require_state_change)], request: Request, body: dict[str, str], ) -> dict[str, Any]: domain = await _domain_of(db, user) model = await service.import_avatar( db, domain.id, body.get("name", ""), body.get("format", ""), body.get("file_id", ""), getattr(request.state, "trace_id", None), ) return _model_out(model) @router.get("") async def list_avatars( db: DbSession, user: Annotated[User, Depends(get_current_user)], ) -> dict[str, Any]: domain = await _domain_of(db, user) rows = list( ( await db.scalars( select(AvatarModel) .where(AvatarModel.domain_id == domain.id) .order_by(AvatarModel.created_at.asc()) ) ).all() ) return {"data": [_model_out(m) for m in rows]} @router.post("/{avatar_id}/commands", status_code=201) async def append_command( avatar_id: str, db: DbSession, user: Annotated[User, Depends(require_state_change)], request: Request, body: dict[str, Any], ) -> dict[str, Any]: domain = await _domain_of(db, user) command = await service.append_command( db, domain.id, avatar_id, body.get("command_type", ""), body.get("payload", {}), getattr(request.state, "trace_id", None), ) return _command_out(command) @router.post("/{avatar_id}/commands/{command_id}/undoings") async def undo_command( avatar_id: str, command_id: str, db: DbSession, user: Annotated[User, Depends(require_state_change)], request: Request, ) -> dict[str, Any]: domain = await _domain_of(db, user) command = await service.undo_command( db, domain.id, avatar_id, command_id, getattr(request.state, "trace_id", None) ) return _command_out(command) @router.get("/{avatar_id}/commands") async def list_commands( avatar_id: str, db: DbSession, user: Annotated[User, Depends(get_current_user)], ) -> dict[str, Any]: from app.models import AvatarCommand domain = await _domain_of(db, user) model = await db.get(AvatarModel, avatar_id) if model is None or model.domain_id != domain.id: raise HTTPException(status.HTTP_404_NOT_FOUND, "模型不存在") rows = list( ( await db.scalars( select(AvatarCommand) .where(AvatarCommand.avatar_id == avatar_id) .order_by(AvatarCommand.sequence.asc()) ) ).all() ) return {"data": [_command_out(c) for c in rows]}