from fastapi import APIRouter, HTTPException, File, UploadFile from pydantic import BaseModel from typing import Optional from models.schemas import PersonaCreate from services.personas import ( get_all_personas, get_persona, create_persona, delete_persona, patch_persona, ) router = APIRouter(prefix="/personas", tags=["personas"]) @router.get("/") async def list_personas(): personas = await get_all_personas() return [{"persona_id": pid, **data} for pid, data in personas.items()] @router.get("/{persona_id}") async def get_one_persona(persona_id: str): persona = await get_persona(persona_id) if not persona: raise HTTPException(status_code=404, detail="Персонаж не найден") return {"persona_id": persona_id, **persona} @router.post("/") async def create_new_persona(data: PersonaCreate): persona = await create_persona( persona_id=data.persona_id, name=data.name, emoji=data.emoji, description=data.description, prompt=data.prompt, sd_enabled=data.sd_enabled, lora_name=data.lora_name, lora_weight=data.lora_weight, appearance_tags=data.appearance_tags, personality=data.personality, scenario=data.scenario, first_mes=data.first_mes, mes_example=data.mes_example, lorebook_json=data.lorebook_json, ) return {"persona_id": data.persona_id, **persona} class PersonaPatch(BaseModel): name: Optional[str] = None emoji: Optional[str] = None description: Optional[str] = None prompt: Optional[str] = None sd_enabled: Optional[bool] = None lora_name: Optional[str] = None lora_weight: Optional[float] = None appearance_tags: Optional[str] = None appearance_prose: Optional[str] = None personality: Optional[str] = None scenario: Optional[str] = None first_mes: Optional[str] = None mes_example: Optional[str] = None lorebook_json: Optional[str] = None avatar_path: Optional[str] = None @router.patch("/{persona_id}") async def patch_one_persona(persona_id: str, body: PersonaPatch): fields = {k: v for k, v in body.model_dump().items() if v is not None} ok = await patch_persona(persona_id, fields) if not ok: raise HTTPException(status_code=400, detail="Нельзя редактировать этого персонажа") persona = await get_persona(persona_id) if not persona: raise HTTPException(status_code=404, detail="Персонаж не найден") return {"persona_id": persona_id, **persona} @router.post("/{persona_id}/avatar") async def upload_persona_avatar(persona_id: str, file: UploadFile = File(...)): # only custom personas editable persona = await get_persona(persona_id) if not persona: raise HTTPException(status_code=404, detail="Персонаж не найден") if not persona.get("custom"): raise HTTPException(status_code=400, detail="Нельзя менять аватар встроенного персонажа") content = await file.read() if not content.startswith(b"\x89PNG"): raise HTTPException(status_code=400, detail="Нужен PNG") from pathlib import Path import uuid avatars_dir = Path("static/avatars") avatars_dir.mkdir(parents=True, exist_ok=True) fname = f"persona_{persona_id}_{uuid.uuid4().hex[:8]}.png" path = avatars_dir / fname path.write_bytes(content) rel = f"avatars/{fname}" ok = await patch_persona(persona_id, {"avatar_path": rel}) if not ok: raise HTTPException(status_code=400, detail="Нельзя изменить аватар") return {"avatar_path": f"/static/{rel}"} @router.delete("/{persona_id}") async def remove_persona(persona_id: str): if not await delete_persona(persona_id): raise HTTPException(status_code=400, detail="Нельзя удалить встроенного персонажа") return {"status": "deleted", "persona_id": persona_id}