"""SQLAlchemy models for Copilot chat sessions and messages. Stores per-user chat history with action proposals from the AI assistant. """ import enum import uuid from datetime import datetime from sqlalchemy import DateTime, Enum, ForeignKey, String, Text, func from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class CopilotRole(str, enum.Enum): """Roles for chat messages.""" user = "user" assistant = "assistant" class CopilotSession(Base): """A chat session belonging to a user. Groups related messages.""" __tablename__ = "copilot_sessions" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, ) title: Mapped[str] = mapped_column( String(255), nullable=False, default="Neue Konversation", ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now(), ) messages: Mapped[list["CopilotChat"]] = relationship( back_populates="session", cascade="all, delete-orphan", order_by="CopilotChat.created_at.asc()", lazy="selectin", ) def __repr__(self) -> str: return f"" def to_dict(self) -> dict: """Serialize session for API responses.""" return { "id": str(self.id), "user_id": str(self.user_id), "title": self.title, "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, "messages": [m.to_dict() for m in (self.messages or [])], } class CopilotChat(Base): """A single chat message (user or assistant) within a session.""" __tablename__ = "copilot_chats" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) session_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("copilot_sessions.id", ondelete="CASCADE"), nullable=False, index=True, ) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, ) role: Mapped[CopilotRole] = mapped_column( Enum(CopilotRole, name="copilot_role"), nullable=False, ) content: Mapped[str] = mapped_column(Text, nullable=False) actions: Mapped[list | None] = mapped_column( JSONB, nullable=True, default=None, ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), ) session: Mapped["CopilotSession"] = relationship(back_populates="messages") def __repr__(self) -> str: return f"" def to_dict(self) -> dict: """Serialize chat message for API responses.""" return { "id": str(self.id), "session_id": str(self.session_id), "user_id": str(self.user_id), "role": self.role.value if isinstance(self.role, CopilotRole) else self.role, "content": self.content, "actions": self.actions, "created_at": self.created_at.isoformat() if self.created_at else None, }