bifroest/app/chat/models.py
2026-10-27 08:34:17 +01:00

51 lines
2.7 KiB
Python

"""Chat-Sessions und Nachrichten (→ Konzept 6.1)."""
import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import JSON, Boolean, ForeignKey, String, Text, Uuid
from sqlalchemy import false as sa_false
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base, now
class ChatSession(Base):
__tablename__ = "chat_sessions"
id: Mapped[int] = mapped_column(primary_key=True)
workspace_id: Mapped[int] = mapped_column(ForeignKey("workspaces.id", ondelete="CASCADE"), index=True)
title: Mapped[str] = mapped_column(String(200))
created_by: Mapped[int] = mapped_column(ForeignKey("users.id"))
created: Mapped[datetime] = mapped_column(default=now)
# Arbeitsnotiz: automatische Zusammenfassung der Nachrichten bis summary_upto, die nicht mehr
# vollständig in den Kontext passen (→ lange Coding-Sessions)
summary: Mapped[str] = mapped_column(Text, default="", server_default="")
summary_upto: Mapped[int | None]
# Plan der KI (update_plan): [{"text": …, "status": "open" | "in_progress" | "done"}]
plan: Mapped[list] = mapped_column(JSON, default=list, server_default="[]")
# Planungsmodus: nur lesende Tools und update_plan, bis jemand „Plan umsetzen“ klickt
plan_mode: Mapped[bool] = mapped_column(Boolean, default=False, server_default=sa_false())
# Ausgangs-Branch je Git-Quelle ({"<source_id>": "feature/x"}), wenn der Chat nicht auf dem Ziel-Branch
# beginnt; der Chat-Branch zweigt davon ab und hat ihn als Ziel (Abgleich, Pull Request)
git_base: Mapped[dict] = mapped_column(JSON, default=dict, server_default="{}")
class Message(Base):
__tablename__ = "messages"
id: Mapped[int] = mapped_column(primary_key=True)
chat_session_id: Mapped[int] = mapped_column(ForeignKey("chat_sessions.id", ondelete="CASCADE"), index=True)
role: Mapped[str] = mapped_column(String(20)) # user | assistant
content: Mapped[str] = mapped_column(Text)
# Antwort-Elemente der Responses API (u. a. verschlüsseltes Reasoning), unverändert
# beim nächsten Aufruf zurückgegeben (→ Konzept 8.1)
items: Mapped[list[dict[str, Any]]] = mapped_column(JSON, default=list)
# Verwendetes Modell und Verbrauch, für Nachvollziehbarkeit
model: Mapped[str] = mapped_column(String(200), default="")
usage: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
# Run, der die Nachricht ausgelöst (user) bzw. erzeugt (assistant) hat
run_id: Mapped[uuid.UUID | None] = mapped_column(Uuid, ForeignKey("task_runs.id", ondelete="SET NULL"))
author_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"))
created: Mapped[datetime] = mapped_column(default=now)