feat(T07): KI-Copilot with OpenRouter chat, voice input, action system, chat history
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
"""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"<CopilotSession id={self.id} user_id={self.user_id} title={self.title}>"
|
||||
|
||||
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"<CopilotChat id={self.id} role={self.role} session_id={self.session_id}>"
|
||||
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user