Coverage for src / lilbee / server / routes / documents.py: 100%
32 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
1"""Document management route handlers: add, list, remove, sync."""
3from __future__ import annotations
5from litestar import get, post
6from litestar.exceptions import ValidationException
7from litestar.params import Parameter
8from litestar.response import Stream
9from pydantic import BaseModel, Field
11from lilbee.server import handlers
12from lilbee.server.auth import read_only
13from lilbee.server.models import (
14 AddRequest,
15 DocumentListResponse,
16 DocumentRemoveResponse,
17 SyncRequest,
18)
21class RemoveRequest(BaseModel):
22 """Request body for /api/documents/remove."""
24 names: list[str] = Field(max_length=100)
25 delete_files: bool = False
28@post("/api/sync")
29async def sync_route(data: SyncRequest | None = None) -> Stream:
30 """Re-index changed documents with streaming SSE progress events.
32 Pass ``{"force_rebuild": true}`` to wipe the store and re-ingest every file
33 under the current ``cfg.embedding_model``. This is the recovery path after
34 a ``PUT /api/models/embedding`` that returned ``reindex_required=true``.
35 Pass ``{"retry_skipped": true}`` for the lighter path: retry the files that
36 failed a previous sync without dropping the store.
37 """
38 enable_ocr = data.enable_ocr if data else None
39 force_rebuild = data.force_rebuild if data else False
40 retry_skipped = data.retry_skipped if data else False
41 return Stream(
42 handlers.sync_stream(
43 enable_ocr=enable_ocr, force_rebuild=force_rebuild, retry_skipped=retry_skipped
44 ),
45 media_type="text/event-stream",
46 )
49@post("/api/add")
50async def add_route(data: AddRequest) -> Stream:
51 """Add files to the knowledge base with streaming SSE progress."""
52 try:
53 handlers.validate_add_paths(data.model_dump())
54 except ValueError as exc:
55 raise ValidationException(str(exc)) from exc
56 return Stream(
57 handlers.add_files_stream(data.model_dump()),
58 media_type="text/event-stream",
59 status_code=201,
60 )
63@get("/api/documents")
64@read_only
65async def documents_list_route(
66 search: str = Parameter(query="search", default=""),
67 limit: int = Parameter(query="limit", default=50, le=1000),
68 offset: int = Parameter(query="offset", default=0, ge=0),
69) -> DocumentListResponse:
70 """List indexed documents with metadata, paginated and searchable."""
71 return await handlers.list_documents(search=search, limit=limit, offset=offset)
74@post("/api/documents/remove")
75async def documents_remove_route(data: RemoveRequest) -> DocumentRemoveResponse:
76 """Remove documents from the knowledge base by source name."""
77 return await handlers.delete_documents(data.names, delete_files=data.delete_files)