""" Chat & Agent Models — Conversations, messages, AI memory, and AI insights. """ import uuid from datetime import datetime from typing import Optional from sqlalchemy import String, Boolean, Text, Integer, Float, Index, ForeignKey from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin class Conversation(UUIDPrimaryKeyMixin, TimestampMixin, Base): """Chat conversation history container.""" __tablename__ = "conversations" __table_args__ = ( Index("ix_conversations_user_id", "user_id"), Index("ix_conversations_created_at", "created_at"), ) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ) title: Mapped[str] = mapped_column(String(255), default="New Chat", nullable=False) mode: Mapped[str] = mapped_column(String(50), default="auto", nullable=False) is_archived: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) message_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) last_message_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) # Relationships messages: Mapped[list["Message"]] = relationship( back_populates="conversation", cascade="all, delete-orphan", lazy="selectin" ) def __repr__(self) -> str: return f"" class Message(UUIDPrimaryKeyMixin, Base): """Chat message in a conversation.""" __tablename__ = "messages" __table_args__ = ( Index("ix_messages_conversation_id", "conversation_id"), Index("ix_messages_created_at", "created_at"), ) conversation_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("conversations.id", ondelete="CASCADE"), nullable=False, ) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ) role: Mapped[str] = mapped_column(String(20), nullable=False) # user, assistant, system content: Mapped[str] = mapped_column(Text, nullable=False) mode: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) sources: Mapped[dict] = mapped_column(JSONB, default=list, nullable=False) metadata_json: Mapped[dict] = mapped_column(JSONB, default=dict, nullable=False) created_at: Mapped[datetime] = mapped_column( nullable=False, server_default="now()" ) # Relationships conversation: Mapped["Conversation"] = relationship(back_populates="messages") def __repr__(self) -> str: return f"" class UserMemory(UUIDPrimaryKeyMixin, TimestampMixin, Base): """Long-term and short-term semantic memory for AI agents.""" __tablename__ = "user_memory" __table_args__ = ( Index("ix_user_memory_user_id", "user_id"), Index("ix_user_memory_layer", "memory_layer"), Index("ix_user_memory_type", "memory_type"), ) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ) memory_layer: Mapped[str] = mapped_column(String(50), nullable=False) memory_type: Mapped[str] = mapped_column(String(50), nullable=False) content: Mapped[dict] = mapped_column(JSONB, nullable=False) embedding: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) metadata_json: Mapped[dict] = mapped_column(JSONB, default=dict, nullable=False) confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True) expires_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) access_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) last_accessed: Mapped[Optional[datetime]] = mapped_column(nullable=True) def __repr__(self) -> str: return f"" class AIInsight(UUIDPrimaryKeyMixin, Base): """Proactive AI insights generated for workspaces/users.""" __tablename__ = "ai_insights" __table_args__ = ( Index("ix_ai_insights_user_id", "user_id"), ) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ) workspace_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) title: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) insight_type: Mapped[str] = mapped_column(String(50), default="anomaly", nullable=False) severity: Mapped[str] = mapped_column(String(20), default="medium", nullable=False) metadata_json: Mapped[dict] = mapped_column(JSONB, default=dict, nullable=False) created_at: Mapped[datetime] = mapped_column( nullable=False, server_default="now()" ) def __repr__(self) -> str: return f""