""" Pydantic Models для City Backend """ from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any from datetime import datetime # ============================================================================= # City Rooms # ============================================================================= class CityRoomBase(BaseModel): slug: str name: str description: Optional[str] = None class CityRoomCreate(CityRoomBase): pass class CityRoomRead(CityRoomBase): id: str is_default: bool created_at: datetime created_by: Optional[str] = None members_online: int = 0 last_event: Optional[str] = None # Matrix integration matrix_room_id: Optional[str] = None matrix_room_alias: Optional[str] = None # ============================================================================= # City Room Messages # ============================================================================= class CityRoomMessageBase(BaseModel): body: str = Field(..., min_length=1, max_length=10000) class CityRoomMessageCreate(CityRoomMessageBase): pass class CityRoomMessageRead(CityRoomMessageBase): id: str room_id: str author_user_id: Optional[str] = None author_agent_id: Optional[str] = None username: Optional[str] = "Anonymous" # Для frontend created_at: datetime # ============================================================================= # City Room Detail (з повідомленнями) # ============================================================================= class CityRoomDetail(CityRoomRead): messages: List[CityRoomMessageRead] = [] online_members: List[str] = [] # user_ids # ============================================================================= # City Feed Events # ============================================================================= class CityFeedEventRead(BaseModel): id: str kind: str # 'room_message', 'agent_reply', 'system', 'dao_event' room_id: Optional[str] = None user_id: Optional[str] = None agent_id: Optional[str] = None payload: dict created_at: datetime # ============================================================================= # Presence # ============================================================================= class PresenceUpdate(BaseModel): user_id: str status: str # 'online', 'offline', 'away' last_seen: Optional[datetime] = None class PresenceBulkUpdate(BaseModel): users: List[PresenceUpdate] # ============================================================================= # WebSocket Messages # ============================================================================= class WSRoomMessage(BaseModel): event: str # 'room.message', 'room.join', 'room.leave' room_id: Optional[str] = None user_id: Optional[str] = None message: Optional[CityRoomMessageRead] = None class WSPresenceMessage(BaseModel): event: str # 'presence.heartbeat', 'presence.update' user_id: str status: Optional[str] = None # ============================================================================= # City Map (2D Map) # ============================================================================= class CityMapRoom(BaseModel): """Room representation on 2D city map""" id: str slug: str name: str description: Optional[str] = None room_type: str = "public" zone: str = "central" icon: Optional[str] = None color: Optional[str] = None # Map coordinates x: int = 0 y: int = 0 w: int = 1 h: int = 1 # Matrix integration matrix_room_id: Optional[str] = None class CityMapConfig(BaseModel): """Global city map configuration""" grid_width: int = 6 grid_height: int = 3 cell_size: int = 100 background_url: Optional[str] = None class CityMapResponse(BaseModel): """Full city map response""" config: CityMapConfig rooms: List[CityMapRoom] # ============================================================================= # Agents (for Agent Presence) # ============================================================================= class AgentRead(BaseModel): """Agent representation""" id: str display_name: str kind: str = "assistant" # assistant, civic, oracle, builder avatar_url: Optional[str] = None color: str = "cyan" status: str = "offline" # online, offline, busy current_room_id: Optional[str] = None capabilities: List[str] = [] class AgentPresence(BaseModel): """Agent presence in a room""" agent_id: str display_name: str kind: str status: str room_id: Optional[str] = None color: Optional[str] = None node_id: Optional[str] = None district: Optional[str] = None model: Optional[str] = None role: Optional[str] = None avatar_url: Optional[str] = None # ============================================================================= # Citizens # ============================================================================= class CityPresenceRoomView(BaseModel): room_id: Optional[str] = None slug: Optional[str] = None name: Optional[str] = None class CityPresenceView(BaseModel): primary_room_slug: Optional[str] = None rooms: List[CityPresenceRoomView] = [] class HomeNodeView(BaseModel): """Home node information for agent/citizen""" id: Optional[str] = None name: Optional[str] = None hostname: Optional[str] = None roles: List[str] = [] environment: Optional[str] = None class NodeProfile(BaseModel): """Node profile for Node Directory""" node_id: str name: str hostname: Optional[str] = None roles: List[str] = [] environment: str = "unknown" status: str = "offline" gpu_info: Optional[str] = None agents_total: int = 0 agents_online: int = 0 last_heartbeat: Optional[str] = None class ModelBindings(BaseModel): """Agent model bindings for AI capabilities""" primary_model: Optional[str] = None # e.g., "qwen3:8b" supported_kinds: List[str] = [] # e.g., ["text", "vision", "audio"] class UsageStats(BaseModel): """Agent usage statistics""" tokens_total_24h: Optional[int] = None calls_total_24h: Optional[int] = None last_active: Optional[str] = None class MicrodaoBadge(BaseModel): """MicroDAO badge for agent display""" id: str name: str slug: Optional[str] = None role: Optional[str] = None # orchestrator, member, etc. class AgentSummary(BaseModel): """Unified Agent summary for Agent Console and Citizens""" id: str slug: Optional[str] = None display_name: str title: Optional[str] = None # public_title tagline: Optional[str] = None # public_tagline kind: str = "assistant" avatar_url: Optional[str] = None status: str = "offline" # Node info node_id: Optional[str] = None node_label: Optional[str] = None # "НОДА1" / "НОДА2" home_node: Optional[HomeNodeView] = None # Visibility visibility_scope: str = "city" # city, microdao, owner_only is_listed_in_directory: bool = True is_system: bool = False is_public: bool = False # backward compatibility # MicroDAO primary_microdao_id: Optional[str] = None primary_microdao_name: Optional[str] = None primary_microdao_slug: Optional[str] = None district: Optional[str] = None microdaos: List[MicrodaoBadge] = [] microdao_memberships: List[Dict[str, Any]] = [] # backward compatibility # Skills public_skills: List[str] = [] # Future: model bindings and usage stats model_bindings: Optional[ModelBindings] = None usage_stats: Optional[UsageStats] = None class PublicCitizenSummary(BaseModel): slug: str display_name: str public_title: Optional[str] = None public_tagline: Optional[str] = None avatar_url: Optional[str] = None kind: Optional[str] = None district: Optional[str] = None primary_room_slug: Optional[str] = None public_skills: List[str] = [] online_status: Optional[str] = "unknown" status: Optional[str] = None # backward compatibility # Home node info home_node: Optional[HomeNodeView] = None class PublicCitizenProfile(BaseModel): slug: str display_name: str kind: Optional[str] = None public_title: Optional[str] = None public_tagline: Optional[str] = None district: Optional[str] = None avatar_url: Optional[str] = None status: Optional[str] = None node_id: Optional[str] = None public_skills: List[str] = [] city_presence: Optional[CityPresenceView] = None dais_public: Dict[str, Any] interaction: Dict[str, Any] metrics_public: Dict[str, Any] admin_panel_url: Optional[str] = None microdao: Optional[Dict[str, Any]] = None # Home node info home_node: Optional[HomeNodeView] = None class CitizenInteractionInfo(BaseModel): slug: str display_name: str primary_room_slug: Optional[str] = None primary_room_id: Optional[str] = None primary_room_name: Optional[str] = None matrix_user_id: Optional[str] = None district: Optional[str] = None microdao_slug: Optional[str] = None microdao_name: Optional[str] = None class CitizenAskRequest(BaseModel): question: str context: Optional[str] = None class CitizenAskResponse(BaseModel): answer: str agent_display_name: str agent_id: str # ============================================================================= # MicroDAO # ============================================================================= class MicrodaoCitizenView(BaseModel): slug: str display_name: str public_title: Optional[str] = None public_tagline: Optional[str] = None avatar_url: Optional[str] = None district: Optional[str] = None primary_room_slug: Optional[str] = None class MicrodaoSummary(BaseModel): """MicroDAO summary for list view""" id: str slug: str name: str description: Optional[str] = None district: Optional[str] = None orchestrator_agent_id: Optional[str] = None is_active: bool logo_url: Optional[str] = None agents_count: int rooms_count: int channels_count: int class MicrodaoChannelView(BaseModel): """Channel/integration view for MicroDAO""" kind: str # 'matrix' | 'telegram' | 'city_room' | 'crew' ref_id: str display_name: Optional[str] = None is_primary: bool class MicrodaoAgentView(BaseModel): """Agent view within MicroDAO""" agent_id: str display_name: str role: Optional[str] = None is_core: bool class MicrodaoDetail(BaseModel): """Full MicroDAO detail view""" id: str slug: str name: str description: Optional[str] = None district: Optional[str] = None orchestrator_agent_id: Optional[str] = None orchestrator_display_name: Optional[str] = None is_active: bool is_public: bool logo_url: Optional[str] = None agents: List[MicrodaoAgentView] channels: List[MicrodaoChannelView] public_citizens: List[MicrodaoCitizenView] = [] class AgentMicrodaoMembership(BaseModel): microdao_id: str microdao_slug: str microdao_name: str role: Optional[str] = None is_core: bool = False class MicrodaoOption(BaseModel): id: str slug: str name: str district: Optional[str] = None is_active: bool = True