from datetime import datetime
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean, JSON
from sqlalchemy.orm import relationship
# The declarative_base class is already defined in database.py.
# We will import it from there to ensure all models use the same base.
from .database import Base
# --- SQLAlchemy Models ---
# These classes define the structure of the database tables and how they relate.
class Session(Base):
"""
SQLAlchemy model for the 'sessions' table.
Each session represents a single conversation between a user and the AI.
It links a user to a series of messages.
"""
__tablename__ = 'sessions'
# Primary key for the session.
id = Column(Integer, primary_key=True, index=True)
# The ID of the user who owns this session.
user_id = Column(String, index=True, nullable=False)
# A title for the conversation, which can be generated by the AI.
title = Column(String, index=True, nullable=True)
# The name of the LLM model used for this session (e.g., "Gemini", "DeepSeek").
provider_name = Column(String, nullable=True)
# Timestamp for when the session was created.
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Flag to indicate if the session has been archived or soft-deleted.
is_archived = Column(Boolean, default=False, nullable=False)
# Defines a one-to-many relationship with the Message table.
# 'back_populates' tells SQLAlchemy that there's a corresponding relationship
# on the other side. 'cascade' ensures that when a session is deleted,
# all its associated messages are also deleted.
messages = relationship("Message", back_populates="session", cascade="all, delete-orphan")
def __repr__(self):
"""
Provides a helpful string representation of the object for debugging.
"""
return f"<Session(id={self.id}, title='{self.title}', user_id='{self.user_id}')>"
class Message(Base):
"""
SQLAlchemy model for the 'messages' table.
This table stores the individual chat messages within a session,
including who sent them (user or AI) and the content.
"""
__tablename__ = 'messages'
# Primary key for the message.
id = Column(Integer, primary_key=True, index=True)
# The foreign key that links this message to its parent session.
# This is a critical link for reconstructing chat history.
session_id = Column(Integer, ForeignKey('sessions.id'), nullable=False)
# Identifies the sender of the message, e.g., 'user' or 'assistant'.
sender = Column(String, nullable=False)
# The actual text content of the message.
content = Column(Text, nullable=False)
# Timestamp for when the message was sent.
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# The time taken for the model to generate the response, in seconds.
model_response_time = Column(Integer, nullable=True)
# The number of tokens in the message (both input and output).
token_count = Column(Integer, nullable=True)
# A JSON field to store unstructured metadata about the message, such as tool calls.
# This column has been renamed from 'metadata' to avoid a conflict.
message_metadata = Column(JSON, nullable=True)
# Relationship back to the parent Session.
# This allows us to access the parent Session object from a Message object.
session = relationship("Session", back_populates="messages")
def __repr__(self):
"""
Provides a helpful string representation of the object for debugging.
"""
return f"<Message(id={self.id}, session_id={self.session_id}, sender='{self.sender}')>"
class Document(Base):
"""
SQLAlchemy model for the 'documents' table.
This table stores the metadata and original text content of a document.
The content is the data that will be chunked, embedded, and used for RAG.
"""
__tablename__ = 'documents'
# Primary key for the document, uniquely identifying each entry.
id = Column(Integer, primary_key=True, index=True)
# The title of the document for easy human-readable reference.
title = Column(String, index=True, nullable=False)
# The actual text content of the document. Using Text for potentially long strings.
text = Column(Text, nullable=False)
# The original source URL or path of the document.
source_url = Column(String, nullable=True)
# A string to identify the author of the document.
author = Column(String, nullable=True)
# The current processing status of the document (e.g., 'ready', 'processing', 'failed').
status = Column(String, default="processing", nullable=False)
# Timestamp for when the document was added to the database.
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# A string to identify the user who added the document, useful for multi-user apps.
user_id = Column(String, index=True, nullable=True)
# Defines a one-to-one relationship with the VectorMetadata table.
vector_metadata = relationship(
"VectorMetadata",
back_populates="document",
cascade="all, delete-orphan", # Deletes vector metadata when the document is deleted.
uselist=False
)
def __repr__(self):
"""
Provides a helpful string representation of the object for debugging.
"""
return f"<Document(id={self.id}, title='{self.title}', user_id='{self.user_id}')>"
class VectorMetadata(Base):
"""
SQLAlchemy model for the 'vector_metadata' table.
This table links a document to its corresponding vector representation
in the FAISS index. The primary key `id` of this table serves as the
vector ID in the FAISS store, making the `faiss_index` column redundant.
"""
__tablename__ = 'vector_metadata'
# Primary key for the metadata entry. This will also be the FAISS index.
id = Column(Integer, primary_key=True, index=True)
# Foreign key that links this metadata entry back to its Document.
document_id = Column(Integer, ForeignKey('documents.id'), unique=True)
# Foreign key to link this vector metadata to a specific session.
# This is crucial for retrieving relevant RAG context for a given conversation.
session_id = Column(Integer, ForeignKey('sessions.id'), nullable=True)
# The name of the embedding model used to create the vector.
embedding_model = Column(String, nullable=False)
# Defines a many-to-one relationship with the Document table.
document = relationship("Document", back_populates="vector_metadata")
# Defines a many-to-one relationship with the Session table.
session = relationship("Session")
def __repr__(self):
"""
Provides a helpful string representation of the object for debugging.
"""
return f"<VectorMetadata(id={self.id}, document_id={self.document_id}, session_id={self.session_id})>"