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:
co-authored by
Claude Opus 5
parent
68d3a43191
commit
784b76baf7
@@ -0,0 +1,96 @@
|
||||
"""The Mode protocol.
|
||||
|
||||
Every interaction type implements `Mode` and yields `ModeEvent`s; the
|
||||
conversations router converts them 1:1 into SSE. Modes know nothing about
|
||||
HTTP; routers know nothing about mode logic.
|
||||
|
||||
Capture is NOT a Mode: it is writing into a Document directly (see
|
||||
`app/authoring/`), not a conversation. The only core Mode is query (RAG
|
||||
Q&A); EE registers the insights mode.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models import Conversation
|
||||
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
text: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class SourceChunk:
|
||||
document_id: uuid.UUID
|
||||
title: str
|
||||
heading_path: str
|
||||
excerpt: str = ""
|
||||
# Whether this passage was actually passed to the model (grounding the
|
||||
# answer), or only retrieved and then dropped as too weak (the no-answer
|
||||
# path). Drives the "?" context inspector; the cited-source badges show
|
||||
# only `used` chunks.
|
||||
used: bool = True
|
||||
# The document has an unanswered request to check it: readable, but not
|
||||
# settled. Marked on the citation, because trusting an answer means
|
||||
# trusting what it leaned on.
|
||||
review_pending: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sources:
|
||||
chunks: list[SourceChunk] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StateChanged:
|
||||
"""Progress signal for the UI — metadata only.
|
||||
|
||||
Query mode reports a phase (e.g. "searching" / "no_answer") and a count
|
||||
(documents found). No content ever rides this frame.
|
||||
"""
|
||||
|
||||
phase: str
|
||||
count: int | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Done:
|
||||
"""Emitted by the ROUTER after persisting the assistant message.
|
||||
Modes normally end their iterator instead of yielding this."""
|
||||
|
||||
message_id: uuid.UUID | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Error:
|
||||
"""Why the turn failed, as a code the frontend phrases (CLAUDE.md: the
|
||||
backend never renders UI-language strings)."""
|
||||
|
||||
code: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Degraded:
|
||||
"""No model could be reached, so this turn has no generated answer: the
|
||||
accompanying `Sources` are what a plain full-text search found, for the
|
||||
user to read themselves. `code` is the endpoint failure that caused it
|
||||
(`LLMError.code`); the frontend says what it means."""
|
||||
|
||||
code: str
|
||||
|
||||
|
||||
ModeEvent = Token | Sources | StateChanged | Done | Error | Degraded
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class Mode(Protocol):
|
||||
name: str
|
||||
|
||||
def handle_turn(
|
||||
self, conversation: Conversation, user_message: str, db: AsyncSession
|
||||
) -> AsyncIterator[ModeEvent]: ...
|
||||
Reference in New Issue
Block a user