Skip to content

Architecture

Overview

DocFinder is organized as a modular Python project with clear separation of concerns.

src/docfinder/
├── cli.py               # Typer CLI entry point
├── config.py             # AppConfig
├── gui.py                # Desktop GUI launcher (pywebview)
├── models.py             # Data models (ChunkRecord, etc.)
├── settings.py           # Hotkey & settings
├── embedding/
│   ├── encoder.py        # EmbeddingModel (SentenceTransformer)
├── index/
│   ├── indexer.py        # Indexer orchestration
│   ├── reranker.py       # Cross-encoder reranking
│   ├── search.py         # Searcher
│   └── storage.py        # SQLiteVectorStore
├── ingestion/
│   └── pdf_loader.py     # Text extraction & chunking
├── rag/
│   ├── engine.py         # RAG engine
│   └── llm.py            # LocalLLM wrapper
├── utils/
│   ├── files.py          # File discovery
│   ├── memory.py         # Memory detection
│   ├── notify.py         # OS notifications
│   └── text.py           # Text normalization & chunking
└── web/
    ├── app.py            # FastAPI application
    ├── frontend.py       # Static file serving
    └── templates/        # HTML templates

Core Pipeline

File Discovery → Text Extraction → Chunking → Embedding → Storage → Search

1. File Discovery (utils/files.py)

Scans directories recursively, filtering by SUPPORTED_EXTENSIONS. Supports all common document formats.

2. Text Extraction (ingestion/pdf_loader.py)

Dispatches to the correct parser based on file extension. Each parser implements both a flat iter_text_parts(path) and a paged iter_text_parts_*_paged(path) variant.

3. Chunking (utils/text.py)

The chunk_text_stream_paged function takes a stream of (page_num, text) tuples and produces overlapping chunks with configurable size and overlap.

4. Embedding (embedding/encoder.py)

EmbeddingModel wraps SentenceTransformer with automatic backend detection: - Detection order: CUDA → MPS → ROCm → CPU (MPS is preferred on Apple Silicon) - Optional ONNX/OpenVINO backends via Optimum

5. Storage (index/storage.py)

SQLiteVectorStore persists chunks and embeddings using: - WAL mode for concurrent access - Cosine similarity via NumPy - Batch inserts with executemany() - A meta table recording the embedding model: indexes built with a different model are cleared automatically before re-indexing, and searching a stale index returns an explicit "re-index" error instead of failing cryptically

6. Search (index/search.py)

Searcher embeds the query, computes cosine similarity against all stored embeddings, and returns the top-K results.

Desktop GUI Flow

pywebview window
    │
    ▼
Python backend (threaded)
    │
    ▼
FastAPI app (embedded uvicorn)
    │
    ▼
Core pipeline

The GUI launches a thread running uvicorn hosting the FastAPI app, then wraps it in a pywebview native window.

Web Layer

The FastAPI app (web/app.py) provides:

Indexing - POST /index — start indexing (returns job_id, async background processing) - GET /index/status/{job_id} — poll indexing progress - POST /index/scan — preview files before indexing

Search - POST /search — semantic search (returns 409 with guidance if the index was built with a different embedding model) - GET /search/folders — indexed directories for search-time filtering

Documents - GET /documents — list indexed documents - DELETE /documents/{doc_id} · POST /documents/delete · DELETE /documents/cleanup — remove documents - POST /open — open a document with the system handler

AI chat (RAG) - POST /rag/chat — answer a question from a chunk's context window - GET /rag/models — available GGUF tiers - POST /rag/download + GET /rag/download/status — model download

Settings & system - GET / POST /settings · GET /system/info

(A few additional routes serve the desktop GUI internals.)

Model loading is thread-safe with double-checked locking:

def _get_embedder():
    if _embedder is None:
        with _lock:
            if _embedder is None:
                _embedder = EmbeddingModel()
    return _embedder