from datetime import datetime, timezone from sqlalchemy import String, Text, DateTime, ForeignKey, Integer, Float from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.database import Base class Session(Base): __tablename__ = "sessions" id: Mapped[str] = mapped_column(String, primary_key=True) title: Mapped[str] = mapped_column(String, default="New Chat") model: Mapped[str] = mapped_column(String, default="") created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) messages: Mapped[list["Message"]] = relationship(back_populates="session", cascade="all, delete-orphan", order_by="Message.created_at") memories: Mapped[list["Memory"]] = relationship(back_populates="session", cascade="all, delete-orphan") class Message(Base): __tablename__ = "messages" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) session_id: Mapped[str] = mapped_column(ForeignKey("sessions.id", ondelete="CASCADE")) role: Mapped[str] = mapped_column(String) # "user" | "assistant" | "system" content: Mapped[str] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) session: Mapped["Session"] = relationship(back_populates="messages") tags: Mapped[list["MessageTag"]] = relationship(back_populates="message", cascade="all, delete-orphan") class MessageTag(Base): __tablename__ = "message_tags" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) message_id: Mapped[int] = mapped_column(ForeignKey("messages.id", ondelete="CASCADE")) tag: Mapped[str] = mapped_column(String) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) message: Mapped["Message"] = relationship(back_populates="tags") class Memory(Base): __tablename__ = "memories" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) session_id: Mapped[str] = mapped_column(ForeignKey("sessions.id", ondelete="CASCADE")) summary: Mapped[str] = mapped_column(Text) embedding_json: Mapped[str | None] = mapped_column(Text, nullable=True) # 将来のベクトル検索用 created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) session: Mapped["Session"] = relationship(back_populates="memories") tags: Mapped[list["MemoryTag"]] = relationship(back_populates="memory", cascade="all, delete-orphan") class MemoryTag(Base): __tablename__ = "memory_tags" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) memory_id: Mapped[int] = mapped_column(ForeignKey("memories.id", ondelete="CASCADE")) tag: Mapped[str] = mapped_column(String) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) memory: Mapped["Memory"] = relationship(back_populates="tags") class KnowledgeEntry(Base): """Web検索で得た知識。セッションを横断して蓄積し、以後の会話のシステムプロンプトに注入する。""" __tablename__ = "knowledge_entries" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) query: Mapped[str] = mapped_column(String) summary: Mapped[str] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) sources: Mapped[list["KnowledgeSource"]] = relationship(back_populates="entry", cascade="all, delete-orphan") class KnowledgeSource(Base): __tablename__ = "knowledge_sources" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) entry_id: Mapped[int] = mapped_column(ForeignKey("knowledge_entries.id", ondelete="CASCADE")) title: Mapped[str] = mapped_column(String) url: Mapped[str] = mapped_column(String) entry: Mapped["KnowledgeEntry"] = relationship(back_populates="sources") class LocalKnowledgeDoc(Base): """backend/knowledge_base/ 配下のMarkdownファイルとその埋め込みベクトル。 話題に応じて関連ファイルをシステムプロンプトに注入するために使う。 ファイル内容のハッシュが変わった時だけ埋め込みを再計算する。 """ __tablename__ = "local_knowledge_docs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) path: Mapped[str] = mapped_column(String, unique=True) title: Mapped[str] = mapped_column(String) content_hash: Mapped[str] = mapped_column(String) embedding_json: Mapped[str] = mapped_column(Text) updated_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) class NotePage(Base): """ユーザーとAIが共同で編集する共有ノートの1ページ。UIではタブとして表示する。 ページのタイトル一覧は毎回システムプロンプトに注入され、本文はモデルが read_note ツールで読み出す(app.core.notes を参照)。 """ __tablename__ = "note_pages" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) title: Mapped[str] = mapped_column(String) content: Mapped[str] = mapped_column(Text, default="") position: Mapped[int] = mapped_column(Integer, default=0) # タブの並び順 created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) class AppSetting(Base): """GUIの設定画面から編集する接続情報(APIキー等)。app.core.settings_store が読み書きする。""" __tablename__ = "app_settings" key: Mapped[str] = mapped_column(String, primary_key=True) value: Mapped[str] = mapped_column(Text) updated_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) class ModelPreset(Base): """モデル選択プルダウン(ModelSelector.vue)に表示するプリセット。GUIの設定画面から編集する。""" __tablename__ = "model_presets" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) label: Mapped[str] = mapped_column(String) value: Mapped[str] = mapped_column(String, unique=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) class ApiLog(Base): __tablename__ = "api_logs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) session_id: Mapped[str | None] = mapped_column(ForeignKey("sessions.id", ondelete="SET NULL"), nullable=True) model: Mapped[str] = mapped_column(String) request_json: Mapped[str] = mapped_column(Text) # JSON: messages array response_text: Mapped[str | None] = mapped_column(Text, nullable=True) error: Mapped[str | None] = mapped_column(Text, nullable=True) latency_ms: Mapped[float | None] = mapped_column(Float, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))