from sqlalchemy import Column, Integer, String, Text, DateTime, Float, Boolean, ForeignKey, JSON, Index from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from sqlalchemy.sql import func from datetime import datetime from typing import Optional, Dict, Any, List Base = declarative_base() class Character(Base): __tablename__ = "characters" id = Column(Integer, primary_key=True, index=True) name = Column(String(100), unique=True, nullable=False, index=True) personality = Column(Text, nullable=False) system_prompt = Column(Text, nullable=False) interests = Column(JSON, nullable=False, default=list) speaking_style = Column(Text, nullable=False) background = Column(Text, nullable=False) avatar_url = Column(String(500)) is_active = Column(Boolean, default=True) creation_date = Column(DateTime, default=func.now()) last_active = Column(DateTime, default=func.now()) last_message_id = Column(Integer, ForeignKey("messages.id"), nullable=True) # Relationships messages = relationship("Message", back_populates="character", foreign_keys="Message.character_id") memories = relationship("Memory", back_populates="character", cascade="all, delete-orphan") relationships_as_a = relationship("CharacterRelationship", back_populates="character_a", foreign_keys="CharacterRelationship.character_a_id") relationships_as_b = relationship("CharacterRelationship", back_populates="character_b", foreign_keys="CharacterRelationship.character_b_id") evolution_history = relationship("CharacterEvolution", back_populates="character", cascade="all, delete-orphan") def to_dict(self) -> Dict[str, Any]: return { "id": self.id, "name": self.name, "personality": self.personality, "system_prompt": self.system_prompt, "interests": self.interests, "speaking_style": self.speaking_style, "background": self.background, "avatar_url": self.avatar_url, "is_active": self.is_active, "creation_date": self.creation_date.isoformat() if self.creation_date else None, "last_active": self.last_active.isoformat() if self.last_active else None } class Conversation(Base): __tablename__ = "conversations" id = Column(Integer, primary_key=True, index=True) channel_id = Column(String(50), nullable=False, index=True) topic = Column(String(200)) participants = Column(JSON, nullable=False, default=list) start_time = Column(DateTime, default=func.now()) last_activity = Column(DateTime, default=func.now()) is_active = Column(Boolean, default=True) message_count = Column(Integer, default=0) # Relationships messages = relationship("Message", back_populates="conversation", cascade="all, delete-orphan") __table_args__ = ( Index('ix_conversations_channel_active', 'channel_id', 'is_active'), ) class Message(Base): __tablename__ = "messages" id = Column(Integer, primary_key=True, index=True) conversation_id = Column(Integer, ForeignKey("conversations.id"), nullable=False) character_id = Column(Integer, ForeignKey("characters.id"), nullable=False) content = Column(Text, nullable=False) timestamp = Column(DateTime, default=func.now()) metadata = Column(JSON, nullable=True) discord_message_id = Column(String(50), unique=True, nullable=True) response_to_message_id = Column(Integer, ForeignKey("messages.id"), nullable=True) emotion = Column(String(50)) # Relationships conversation = relationship("Conversation", back_populates="messages") character = relationship("Character", back_populates="messages", foreign_keys=[character_id]) response_to = relationship("Message", remote_side=[id]) __table_args__ = ( Index('ix_messages_character_timestamp', 'character_id', 'timestamp'), Index('ix_messages_conversation_timestamp', 'conversation_id', 'timestamp'), ) class Memory(Base): __tablename__ = "memories" id = Column(Integer, primary_key=True, index=True) character_id = Column(Integer, ForeignKey("characters.id"), nullable=False) memory_type = Column(String(50), nullable=False) # 'conversation', 'relationship', 'experience', 'fact' content = Column(Text, nullable=False) importance_score = Column(Float, default=0.5) timestamp = Column(DateTime, default=func.now()) last_accessed = Column(DateTime, default=func.now()) access_count = Column(Integer, default=0) related_message_id = Column(Integer, ForeignKey("messages.id"), nullable=True) related_character_id = Column(Integer, ForeignKey("characters.id"), nullable=True) tags = Column(JSON, nullable=False, default=list) # Relationships character = relationship("Character", back_populates="memories", foreign_keys=[character_id]) related_message = relationship("Message", foreign_keys=[related_message_id]) related_character = relationship("Character", foreign_keys=[related_character_id]) __table_args__ = ( Index('ix_memories_character_type', 'character_id', 'memory_type'), Index('ix_memories_importance', 'importance_score'), ) class CharacterRelationship(Base): __tablename__ = "character_relationships" id = Column(Integer, primary_key=True, index=True) character_a_id = Column(Integer, ForeignKey("characters.id"), nullable=False) character_b_id = Column(Integer, ForeignKey("characters.id"), nullable=False) relationship_type = Column(String(50), nullable=False) # 'friend', 'rival', 'neutral', 'mentor', 'student' strength = Column(Float, default=0.5) # 0.0 to 1.0 last_interaction = Column(DateTime, default=func.now()) interaction_count = Column(Integer, default=0) notes = Column(Text) # Relationships character_a = relationship("Character", back_populates="relationships_as_a", foreign_keys=[character_a_id]) character_b = relationship("Character", back_populates="relationships_as_b", foreign_keys=[character_b_id]) __table_args__ = ( Index('ix_relationships_characters', 'character_a_id', 'character_b_id'), ) class CharacterEvolution(Base): __tablename__ = "character_evolution" id = Column(Integer, primary_key=True, index=True) character_id = Column(Integer, ForeignKey("characters.id"), nullable=False) change_type = Column(String(50), nullable=False) # 'personality', 'interests', 'speaking_style', 'system_prompt' old_value = Column(Text) new_value = Column(Text) reason = Column(Text) timestamp = Column(DateTime, default=func.now()) triggered_by_message_id = Column(Integer, ForeignKey("messages.id"), nullable=True) # Relationships character = relationship("Character", back_populates="evolution_history") triggered_by_message = relationship("Message", foreign_keys=[triggered_by_message_id]) __table_args__ = ( Index('ix_evolution_character_timestamp', 'character_id', 'timestamp'), ) class ConversationSummary(Base): __tablename__ = "conversation_summaries" id = Column(Integer, primary_key=True, index=True) conversation_id = Column(Integer, ForeignKey("conversations.id"), nullable=False) summary = Column(Text, nullable=False) key_points = Column(JSON, nullable=False, default=list) participants = Column(JSON, nullable=False, default=list) created_at = Column(DateTime, default=func.now()) message_range_start = Column(Integer, nullable=False) message_range_end = Column(Integer, nullable=False) # Relationships conversation = relationship("Conversation", foreign_keys=[conversation_id]) __table_args__ = ( Index('ix_summaries_conversation', 'conversation_id', 'created_at'), )