feat(chat): save and view chat history — admin-only saved_chats, History page, open-a-chat return
This commit is contained in:
+36
-1
@@ -17,6 +17,11 @@ Data model — see ``.agent/PLAN.md`` §Data Model:
|
||||
directories) the Sync button and import_docs
|
||||
import (phase 35; ``kind`` discriminator added in
|
||||
phase 38).
|
||||
* ``saved_chats`` — owner-saved chat conversations: one row per
|
||||
explicitly Saved conversation (auto-``title`` +
|
||||
the ``bor.chat.v1`` message list as JSONB, phase
|
||||
14 shape) — phase 50; ``/api/chat`` stays
|
||||
stateless.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -35,7 +40,7 @@ from sqlalchemy import (
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.config import get_settings
|
||||
@@ -162,3 +167,33 @@ class GitSource(Base):
|
||||
#: Unique — Postgres treats NULLs as distinct under a unique index.
|
||||
path: Mapped[str | None] = mapped_column(Text, unique=True)
|
||||
added_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class SavedChat(Base):
|
||||
"""One owner-saved chat conversation (phase 50).
|
||||
|
||||
Only conversations the owner explicitly **Saves** are stored —
|
||||
``/api/chat`` itself stays stateless (A10, owner-locked extension
|
||||
2026-08-29) and nothing is stored about a conversation that was not
|
||||
saved. ``messages`` holds the exact ``bor.chat.v1`` localStorage
|
||||
record shape (phase 14 — raw text, never HTML), so a saved chat
|
||||
restores pixel-identical through the existing
|
||||
``renderStoredMessage`` path. No share-related columns here —
|
||||
phase 51 adds ``share_token`` in migration 0009.
|
||||
"""
|
||||
|
||||
__tablename__ = "saved_chats"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
#: Auto-title (the first question, API-side). Plain column on purpose:
|
||||
#: a future rename needs no migration.
|
||||
title: Mapped[str] = mapped_column(String(500))
|
||||
#: ``list[dict]`` in the ``bor.chat.v1`` record shape
|
||||
#: (``{who, text, sources?, deflected?, suggestions?, thinking?,
|
||||
#: tools?, stopped?}``); the API always supplies a list, so no
|
||||
#: default is needed.
|
||||
messages: Mapped[list] = mapped_column(JSONB)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user