238 lines
5.0 KiB
Python
238 lines
5.0 KiB
Python
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}
|