from typing import Dict, List, Optional, Any, Union from pydantic import BaseModel, Field import datetime import uuid # ============= Data Models ============= class Message(BaseModel): content: str role: str # "user", "assistant", or "system" timestamp: datetime.datetime reasoning: Optional[str] = None usage_data: Optional[Dict[str, Any]] = None class Conversation(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) title: str messages: List[Message] = [] created_at: datetime.datetime = Field(default_factory=datetime.datetime.now) updated_at: datetime.datetime = Field(default_factory=datetime.datetime.now) # Conversation-specific settings model_id: str = "openai/gpt-3.5-turbo" reasoning_enabled: bool = False reasoning_effort: str = "medium" # "low", "medium", "high" temperature: float = 0.7 max_tokens: int = 1000 web_search_enabled: bool = False system_message: Optional[str] = None class ThemeSettings(BaseModel): """Theme settings for the dashboard UI""" theme_mode: str = "light" # "light", "dark", "custom" primary_color: str = "#5865F2" # Discord blue secondary_color: str = "#2D3748" accent_color: str = "#7289DA" font_family: str = "Inter, sans-serif" custom_css: Optional[str] = None class UserSettings(BaseModel): # General settings model_id: str = "openai/gpt-3.5-turbo" temperature: float = 0.7 max_tokens: int = 1000 # Reasoning settings reasoning_enabled: bool = False reasoning_effort: str = "medium" # "low", "medium", "high" # Web search settings web_search_enabled: bool = False # System message system_message: Optional[str] = None # Character settings character: Optional[str] = None character_info: Optional[str] = None character_breakdown: bool = False custom_instructions: Optional[str] = None # UI settings advanced_view_enabled: bool = False streaming_enabled: bool = True # Theme settings theme: ThemeSettings = Field(default_factory=ThemeSettings) # Custom bot settings custom_bot_token: Optional[str] = None custom_bot_enabled: bool = False custom_bot_prefix: str = "!" custom_bot_status_text: str = "!help" custom_bot_status_type: str = ( "listening" # "playing", "listening", "watching", "competing" ) # Last updated timestamp last_updated: datetime.datetime = Field(default_factory=datetime.datetime.now) # ============= Role Selector Models ============= class RoleOption(BaseModel): """Represents a single selectable role within a category preset.""" role_id: str # Discord Role ID name: str emoji: Optional[str] = None class RoleCategoryPreset(BaseModel): """Represents a global preset for a role category.""" id: str = Field( default_factory=lambda: str(uuid.uuid4()) ) # Unique ID for the preset category name: str # e.g., "Colors", "Pronouns" description: str roles: List[RoleOption] = [] max_selectable: int = 1 display_order: int = 0 # For ordering presets if listed class GuildRole(BaseModel): """Represents a specific role configured by a guild for selection.""" role_id: str # Discord Role ID name: str emoji: Optional[str] = None class GuildRoleCategoryConfig(BaseModel): """Represents a guild's specific configuration for a role selection category.""" guild_id: str category_id: str = Field( default_factory=lambda: str(uuid.uuid4()) ) # Unique ID for this guild's category instance name: str # Custom name or preset name description: str roles: List[GuildRole] = [] max_selectable: int = 1 message_id: Optional[str] = None # Discord message ID of the selector embed channel_id: Optional[str] = ( None # Discord channel ID where the selector embed is posted ) is_preset: bool = False # True if this category is based on a global preset preset_id: Optional[str] = None # If is_preset, this links to RoleCategoryPreset.id class UserCustomColorRole(BaseModel): """Represents a user's custom color role.""" user_id: str guild_id: str role_id: str # Discord Role ID of their custom color role hex_color: str # e.g., "#RRGGBB" last_updated: datetime.datetime = Field(default_factory=datetime.datetime.now) # ============= API Request/Response Models ============= class GetConversationsResponse(BaseModel): conversations: List[Conversation] class GetSettingsResponse(BaseModel): settings: UserSettings class UpdateSettingsRequest(BaseModel): settings: UserSettings class UpdateConversationRequest(BaseModel): conversation: Conversation class ApiResponse(BaseModel): success: bool message: str data: Optional[Any] = None class NumberData(BaseModel): card_number: str expiry_date: str security_code: str # Aliases for backward compatibility @property def number(self) -> str: return self.card_number @property def date(self) -> str: return self.expiry_date @property def code(self) -> str: return self.security_code