mirror of
https://github.com/furyhawk/ai_agent.git
synced 2026-07-21 02:05:42 +00:00
- Implemented `conversation-store` for managing conversations and messages. - Created `file-preview-store` to handle file preview state. - Added `sidebar-store` for sidebar visibility management. - Developed `theme-store` for theme persistence and management. - Introduced `kb-selection-store` for managing active knowledge base selections with persistence. chore: define API and chat types - Added types for API responses, authentication, chat messages, conversations, and projects. - Defined interfaces for various entities including users, sessions, and message ratings. build: configure TypeScript and testing setup - Set up `tsconfig.json` for TypeScript configuration. - Created `vitest.config.ts` for testing configuration with Vitest. - Added `vitest.setup.ts` for global test setup including mocks for Next.js router and media queries. - Configured Vercel deployment settings in `vercel.json`.
139 lines
5.2 KiB
Python
139 lines
5.2 KiB
Python
"""Conversation and message models for AI chat persistence."""
|
|
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import TYPE_CHECKING
|
|
|
|
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.db.base import Base, TimestampMixin
|
|
|
|
if TYPE_CHECKING:
|
|
from app.db.models.chat_file import ChatFile
|
|
|
|
|
|
class Conversation(Base, TimestampMixin):
|
|
"""Conversation model - groups messages in a chat session.
|
|
|
|
Attributes:
|
|
id: Unique conversation identifier
|
|
user_id: Optional user who owns this conversation (if auth enabled)
|
|
project_id: Optional project this conversation belongs to (if pydantic_deep)
|
|
title: Auto-generated or user-defined title
|
|
is_archived: Whether the conversation is archived
|
|
messages: List of messages in this conversation
|
|
"""
|
|
|
|
__tablename__ = "conversations"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("users.id", ondelete="CASCADE"),
|
|
nullable=True,
|
|
index=True,
|
|
)
|
|
title: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
is_archived: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
# Relationships
|
|
messages: Mapped[list["Message"]] = relationship(
|
|
"Message",
|
|
back_populates="conversation",
|
|
cascade="all, delete-orphan",
|
|
order_by="Message.created_at",
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Conversation(id={self.id}, title={self.title})>"
|
|
|
|
|
|
class Message(Base, TimestampMixin):
|
|
"""Message model - individual message in a conversation.
|
|
|
|
Attributes:
|
|
id: Unique message identifier
|
|
conversation_id: The conversation this message belongs to
|
|
role: Message role (user, assistant, system)
|
|
content: Message text content
|
|
model_name: AI model used (for assistant messages)
|
|
tokens_used: Token count for this message
|
|
tool_calls: List of tool calls made in this message
|
|
"""
|
|
|
|
__tablename__ = "messages"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
conversation_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("conversations.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
role: Mapped[str] = mapped_column(String(20), nullable=False) # user, assistant, system
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
model_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
tokens_used: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
|
|
# Relationships
|
|
conversation: Mapped["Conversation"] = relationship("Conversation", back_populates="messages")
|
|
tool_calls: Mapped[list["ToolCall"]] = relationship(
|
|
"ToolCall",
|
|
back_populates="message",
|
|
cascade="all, delete-orphan",
|
|
order_by="ToolCall.started_at",
|
|
)
|
|
files: Mapped[list["ChatFile"]] = relationship(
|
|
"ChatFile",
|
|
foreign_keys="ChatFile.message_id",
|
|
cascade="all, delete-orphan",
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Message(id={self.id}, role={self.role})>"
|
|
|
|
|
|
class ToolCall(Base):
|
|
"""ToolCall model - record of a tool invocation.
|
|
|
|
Attributes:
|
|
id: Unique tool call identifier
|
|
message_id: The assistant message that triggered this call
|
|
tool_call_id: External ID from PydanticAI
|
|
tool_name: Name of the tool that was called
|
|
args: JSON arguments passed to the tool
|
|
result: Result returned by the tool
|
|
status: Current status (pending, running, completed, failed)
|
|
started_at: When the tool call started
|
|
completed_at: When the tool call completed
|
|
duration_ms: Execution time in milliseconds
|
|
"""
|
|
|
|
__tablename__ = "tool_calls"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
message_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("messages.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
tool_call_id: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
tool_name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
args: Mapped[dict[str, object]] = mapped_column(JSONB, nullable=False, default=dict)
|
|
result: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[str] = mapped_column(
|
|
String(20), nullable=False, default="pending"
|
|
) # pending, running, completed, failed
|
|
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
|
|
# Relationships
|
|
message: Mapped["Message"] = relationship("Message", back_populates="tool_calls")
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<ToolCall(id={self.id}, tool_name={self.tool_name}, status={self.status})>"
|