Files
pablan/backend/app/api/conversations/view.py
T
ProfessorNovaandClaude Opus 5 97dbff309c Pablan, as it stands
Self-hosted knowledge management for SMEs: a split-screen Markdown editor
whose sections an LLM refines while you write, and RAG question answering
over the documents that result. FastAPI + Postgres/pgvector on the back,
SvelteKit on the front, everything OpenAI-compatible and self-hostable.

Squashed into a single commit; the development history stays local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
2026-09-04 08:36:17 +02:00

44 lines
1.5 KiB
Python

"""Rows to API shapes.
A conversation has no title column: the first message is the title, derived
here so the list and the detail can never disagree about what a conversation
is called.
"""
from app.api.conversations.schemas import MessageOut, MessageSource
from app.models import Message
from app.modes.query import excerpt as clean_excerpt
TITLE_LENGTH = 80
def title(first_message: str | None) -> str | None:
if not first_message:
return None
flattened = " ".join(first_message.split())
if len(flattened) <= TITLE_LENGTH:
return flattened
return flattened[: TITLE_LENGTH - 1] + "…"
def message_out(message: Message) -> MessageOut:
"""Message + its citation snapshot from `meta` (assistant turns only).
The excerpt is re-cleaned on the way out, not just on the way in. It is
a presentation detail frozen at answer time, so an improvement to the
cleaning would otherwise only reach conversations created afterwards,
and every existing citation would keep showing raw Markdown forever.
Cleaning is idempotent, so text stored by a newer backend passes
through untouched.
"""
meta = message.meta or {}
sources = [
MessageSource.model_validate(item).model_copy(
update={"excerpt": clean_excerpt(item.get("excerpt", ""))}
)
for item in meta.get("sources", [])
]
return MessageOut.model_validate(message).model_copy(
update={"sources": sources, "fallback": meta.get("fallback")}
)