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
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
import asyncio
|
|
import uuid
|
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI, Request, Response
|
|
|
|
from app.api import api_router
|
|
from app.db import async_session_factory
|
|
from app.errors import register_exception_handlers
|
|
from app.help_import import import_help_documents
|
|
from app.ingestion.handlers import ensure_retention_scheduled
|
|
from app.ingestion.queue import run_queue
|
|
from app.llm.overrides import bootstrap_llm_settings
|
|
from app.llm.overrides import load_config as load_llm_config
|
|
from app.log import correlation_id, setup_logging
|
|
from app.prompts.overrides import load_config as load_prompt_config
|
|
from app.template_catalog import seed_starter_templates
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
setup_logging()
|
|
async with async_session_factory() as db:
|
|
await ensure_retention_scheduled(db)
|
|
await seed_starter_templates(db)
|
|
await import_help_documents(db)
|
|
await bootstrap_llm_settings(db)
|
|
await load_llm_config(db)
|
|
await load_prompt_config(db)
|
|
stop_event = asyncio.Event()
|
|
queue_task = asyncio.create_task(run_queue(stop_event))
|
|
yield
|
|
stop_event.set()
|
|
try:
|
|
await asyncio.wait_for(queue_task, timeout=10)
|
|
except TimeoutError: # pragma: no cover — a handler refused to finish
|
|
queue_task.cancel()
|
|
|
|
|
|
app = FastAPI(title="Pablan", version="0.1.0", lifespan=lifespan)
|
|
register_exception_handlers(app)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def add_correlation_id(
|
|
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
) -> Response:
|
|
cid = request.headers.get("x-request-id") or uuid.uuid4().hex[:16]
|
|
token = correlation_id.set(cid)
|
|
try:
|
|
response = await call_next(request)
|
|
finally:
|
|
correlation_id.reset(token)
|
|
response.headers["x-request-id"] = cid
|
|
return response
|
|
|
|
|
|
app.include_router(api_router)
|