from datetime import datetime from enum import Enum from typing import Optional from pydantic import BaseModel, Field class GamePhase(str, Enum): INTRO = "intro" ROUND1_SPEAK = "round1_speak" ROUND1_SEARCH = "round1_search" ROUND2_SPEAK = "round2_speak" ROUND2_SEARCH = "round2_search" FINAL_DISCUSS = "final_discuss" VOTING = "voting" REVEAL = "reveal" @classmethod def ordered_phases(cls) -> list[str]: return [ "intro", "round1_speak", "round1_search", "round2_speak", "round2_search", "final_discuss", "voting", "reveal" ] @classmethod def phase_count(cls) -> int: return len(cls.ordered_phases()) class CharacterRole(str, Enum): DETECTIVE = "detective" SUSPECT = "suspect" WITNESS = "witness" VICTIM = "victim" KILLER = "killer" class CharacterStatus(str, Enum): ALIVE = "alive" DEAD = "dead" INACTIVE = "inactive" class MessageType(str, Enum): SYSTEM = "system" DM = "dm" CHARACTER = "character" VOTE = "vote" CLUE = "clue" class ClueType(str, Enum): PHYSICAL = "physical" TESTIMONY = "testimony" MOTIVE = "motive" ALIBI = "alibi" FORENSIC = "forensic" class ClueVisibility(str, Enum): ALL = "all" SPECIFIC = "specific" HIDDEN = "hidden" class ScriptUploadRequest(BaseModel): title: str content: str file_type: str = "natural_language" class ScriptParsePreview(BaseModel): title: str background: str characters: list[dict] clues: list[dict] phases: list[dict] class ScriptImportRequest(BaseModel): title: str background: str = "" characters: list[dict] = Field(default_factory=list) clues: list[dict] = Field(default_factory=list) phases: list[dict] = Field(default_factory=list) class ScriptResponse(BaseModel): id: str title: str background: str character_count: int created_at: datetime model_config = {"from_attributes": True} class CharacterCreate(BaseModel): name: str role: CharacterRole = CharacterRole.SUSPECT personality: str = "" speaking_style: str = "" background: str = "" secret: str = "" motive: str = "" avatar_url: str = "" class CharacterResponse(BaseModel): id: str script_id: str name: str role: CharacterRole status: CharacterStatus personality: str speaking_style: str background: str secret: str motive: str avatar_url: str hermes_profile: str soul_md: str is_revealed_killer: bool created_at: datetime model_config = {"from_attributes": True} class SoulUpdateRequest(BaseModel): soul_md: str knowledge_base: str = "" class CharacterSpeakRequest(BaseModel): prompt: str = "" target_character_id: Optional[str] = None class CharacterGenerateRequest(BaseModel): script_id: str character_names: list[str] class MessageCreate(BaseModel): session_id: str = "" character_id: Optional[str] = None game_phase: str = "" msg_type: str = "character" content: str target_character_id: Optional[str] = None class MessageResponse(BaseModel): id: str session_id: str character_id: Optional[str] = None game_phase: str msg_type: str content: str target_character_id: Optional[str] = None clue_id: Optional[str] = None metadata: dict created_at: datetime character_name: Optional[str] = None character_role: Optional[CharacterRole] = None model_config = {"from_attributes": True} class ClueCreate(BaseModel): script_id: str name: str content: str clue_type: ClueType = ClueType.PHYSICAL owner_id: Optional[str] = None phase: str = "round1_search" visibility: ClueVisibility = ClueVisibility.ALL visible_to: list[str] = Field(default_factory=list) class ClueUnlockRequest(BaseModel): character_id: str class ClueResponse(BaseModel): id: str script_id: str name: str content: str clue_type: ClueType owner_id: Optional[str] = None phase: str visibility: ClueVisibility visible_to: list is_unlocked: bool unlocked_by: Optional[str] = None unlocked_at: Optional[datetime] = None created_at: datetime model_config = {"from_attributes": True} class DmSpeakRequest(BaseModel): content: str phase: str = "" class GameStateResponse(BaseModel): id: str current_phase: GamePhase current_speaker_id: Optional[str] = None speaker_order: list active_script_id: Optional[str] = None is_running: bool is_paused: bool phase_started_at: Optional[datetime] = None config: dict progress_percent: float = 0.0 model_config = {"from_attributes": True} class VoteCreate(BaseModel): voter_id: str target_id: str reason: str = "" class VoteResponse(BaseModel): id: str voter_id: str target_id: str round_number: int reason: str created_at: datetime model_config = {"from_attributes": True}