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
This commit is contained in:
ProfessorNova
2026-09-04 08:36:17 +02:00
co-authored by Claude Opus 5
commit 97dbff309c
346 changed files with 43430 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
"""Request and response shapes for conversations and their turns."""
import uuid
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from app.models import ConversationMode, MessageRole
class ConversationCreate(BaseModel):
mode: ConversationMode
class ConversationSummary(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
title: str | None = None
class MessageSource(BaseModel):
document_id: uuid.UUID
title: str
heading_path: str
excerpt: str = ""
# True when the passage was passed to the model; False for passages that
# were retrieved but dropped as too weak (a no-answer turn). Old messages
# predate the flag, so it defaults to True (they were all cited).
used: bool = True
# The cited document has an unanswered request to check it. Snapshotted
# with the citation, so a reload shows what was true when it was answered.
review_pending: bool = False
class MessageOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
role: MessageRole
content: str
created_at: datetime
sources: list[MessageSource] = []
# Set when no model answered this turn and `sources` is a plain full-text
# result list instead: the `llm_*` code that caused it, which the frontend
# phrases. Null on every normal turn.
fallback: str | None = None
class ConversationDetail(ConversationSummary):
messages: list[MessageOut] = []
class SendMessage(BaseModel):
content: str = Field(min_length=1, max_length=8000)