from fastapi import APIRouter, File, Form, HTTPException, UploadFile from pydantic import BaseModel from typing import Optional from services.character_card import list_characters, get_character, import_card_file, update_character, update_appearance_tags router = APIRouter(prefix="/characters", tags=["characters"]) class CardPatch(BaseModel): name: Optional[str] = None description: Optional[str] = None personality: Optional[str] = None scenario: Optional[str] = None first_mes: Optional[str] = None mes_example: Optional[str] = None appearance_tags: Optional[str] = None lora_name: Optional[str] = None lora_weight: Optional[float] = None @router.get("/") async def list_all(): return await list_characters() @router.get("/{card_id}") async def get_one(card_id: str): card = await get_character(card_id) if not card: raise HTTPException(status_code=404, detail="Карточка не найдена") return card @router.patch("/{card_id}") async def patch_card(card_id: str, body: CardPatch): card = await get_character(card_id) if not card: raise HTTPException(status_code=404, detail="Карточка не найдена") fields = {k: v for k, v in body.model_dump().items() if v is not None} await update_character(card_id, fields) # sync appearance_tags and lora to persona from services.personas import update_persona_appearance if "appearance_tags" in fields: await update_persona_appearance(f"card_{card_id}", fields["appearance_tags"]) if {"lora_name", "lora_weight"} & fields.keys(): from services.personas import update_persona_lora await update_persona_lora(f"card_{card_id}", fields.get("lora_name"), fields.get("lora_weight")) # rebuild system prompt if character fields changed char_fields = {"name", "description", "personality", "scenario", "first_mes", "mes_example"} if char_fields & fields.keys(): updated = await get_character(card_id) from services.character_card import build_system_prompt from services.personas import update_persona_prompt await update_persona_prompt(f"card_{card_id}", build_system_prompt(updated)) return await get_character(card_id) @router.post("/import") async def import_card( file: UploadFile = File(...), lora_name: str = Form(""), lora_weight: float = Form(0.8), ): content = await file.read() try: card = await import_card_file( content, file.filename or "card.json", lora_name=lora_name, lora_weight=lora_weight, ) except Exception as e: raise HTTPException(status_code=400, detail=str(e)) return { "status": "imported", "card_id": card["card_id"], "persona_id": f"card_{card['card_id']}", "name": card["name"], } @router.delete("/{card_id}") async def remove_card(card_id: str): from services.personas import delete_persona if not await delete_persona(f"card_{card_id}"): raise HTTPException(status_code=404, detail="Карточка не найдена") return {"status": "deleted", "card_id": card_id}