Skip to content

Latest commit

 

History

History
477 lines (397 loc) · 20 KB

File metadata and controls

477 lines (397 loc) · 20 KB

Judgment Finder AI

A local-first, offline-capable document RAG (Retrieval-Augmented Generation) desktop application. Point it at a folder of PDFs, Word documents, and images (including scanned/photographed pages), and chat with an AI that answers questions with citations back to the exact source file.

Everything runs on the user's machine — documents, embeddings, vector search, and OCR never leave the device. Only the optional LLM call (Ollama local, or Groq cloud) needs network access, and that's a deliberate choice per-deployment.


Table of contents


For end users

No coding knowledge needed — this section is for someone who just wants to use the app.

  1. Install: run Judgment Finder AI Setup <version>.exe. Pick an install folder if prompted; everything else (OCR engine, AI models, Python runtime) is bundled inside, nothing else to download or install.
  2. First launch: the app takes ~20–30 seconds to start the first time while it loads the AI model — you'll see an "App is starting..." spinner.
  3. Pick a folder: choose the folder containing your PDFs, Word documents, or images. The app scans it and shows live progress as each file is read (including OCR for scanned/photographed pages).
  4. Ask questions: open the chat, type a question in English or Urdu, and get an answer with clickable source chips — click one to open that exact file.
  5. Keep adding files: anything dropped into the same folder later is picked up automatically (if "Watch folder in real time" is on in Settings) — no need to re-pick the folder.
  6. Choose your AI provider: in Settings, use Ollama for a fully offline experience (requires Ollama installed separately with a model pulled), or Groq for faster cloud-based answers (requires a free API key from console.groq.com).

If something doesn't look right, check Troubleshooting below.

System requirements

Minimum
OS Windows 10/11, 64-bit
Disk space ~2 GB free (installer + extracted runtime, models, and your indexed data)
RAM 4 GB minimum, 8 GB+ recommended (more if also running Ollama locally)
Internet Not required, except for Groq cloud answers or pulling Ollama models
Admin rights Not required — installs to the current user's profile

Privacy & data handling

  • Your documents are read and indexed entirely on your own machine. Text extraction, OCR, embeddings, and vector search all run locally — none of it is uploaded anywhere.
  • All data (settings.json, the SQLite tracking/chat database, and the vector index) is stored locally under %USERPROFILE%\.rag-desktop\. Uninstalling the app does not delete this folder — remove it manually if you want a clean wipe.
  • The only thing that can leave your machine is the question text and retrieved document snippets sent to an LLM, and only if you choose the Groq cloud provider in Settings. Choosing Ollama keeps the entire pipeline, including the chat model, fully offline.
  • Your Groq API key is stored locally in settings.json and is never sent anywhere except directly to Groq's API.

Screenshots

Dashboard — indexed files, status, and chunk counts:

Dashboard

Settings — LLM provider, OCR languages, Top-K, and watch/convert options:

Settings

Chat — multi-session sidebar, cited answers with clickable sources, and the AI disclaimer:

Chat

Why this exists

Searching through hundreds of legal judgments, scanned PDFs, and WhatsApp screenshot exports by hand doesn't scale. Judgment Finder AI indexes a folder once, keeps it in sync as files are added, and lets you ask plain English/Urdu questions like "what did the court decide about the wife's inheritance share?" and get back an answer with the exact file (and page, where applicable) it came from.

Feature overview

  • Multi-format ingestion — PDF (text + OCR fallback for scans), .docx, and images (.png/.jpg/.jpeg/etc.) via Tesseract OCR.
  • Multilingual semantic search — embeddings model supports English and Urdu (and other languages) so a question in one language can match content in another.
  • Incremental indexing — a SQLite-backed file tracker only re-processes files that are new or changed; a watchdog file-system watcher picks up additions in real time without a manual rescan.
  • ChatGPT-style chat — multi-session chat history persisted in SQLite, collapsible sidebar, session rename/delete.
  • Accurate citations — only sources the model actually referenced in its answer are shown as clickable chips; clicking one opens the source file directly.
  • Pluggable LLM backend — local Ollama (fully offline) or Groq cloud API, switchable from Settings.
  • Self-contained Windows installer — ships its own Python runtime, ML models, and Tesseract OCR binaries. No Python, no Tesseract install, no admin rights required on the end-user machine.

Architecture

flowchart TB
    subgraph Electron["Electron Desktop Shell"]
        Main["main.ts<br/>(process lifecycle, IPC, native dialogs)"]
        Preload["preload.ts<br/>(contextBridge)"]
        Renderer["React UI<br/>(Onboarding / Dashboard / Chat)"]
        Main -- spawns --> Backend
        Main <-- IPC --> Preload
        Preload <-- window.electronAPI --> Renderer
    end

    subgraph Backend["FastAPI Sidecar (127.0.0.1:8756)"]
        API["main.py<br/>REST + SSE endpoints"]
        Indexer["indexer.py<br/>extract -> chunk -> embed -> store"]
        Watcher["watcher.py<br/>watchdog file events"]
        Tracking["tracking.py<br/>SQLite: files + chat history"]
        Chat["chat.py<br/>retrieve -> prompt -> LLM -> cite"]
        Extractors["extractors/<br/>pdf.py - docx_reader.py - image.py"]
        OCR["ocr_setup.py<br/>bundled Tesseract resolver"]
        Embed["embeddings.py<br/>sentence-transformers"]
        VectorStore["vectorstore.py<br/>ChromaDB"]
        LLM["llm/<br/>ollama_provider.py - groq_provider.py"]

        API --> Indexer
        API --> Chat
        Watcher --> Indexer
        Indexer --> Extractors
        Extractors --> OCR
        Indexer --> Tracking
        Indexer --> Embed
        Embed --> VectorStore
        Chat --> VectorStore
        Chat --> LLM
        Chat --> Tracking
    end

    Renderer -- "HTTP / SSE" --> API

    subgraph External["Local / External Services"]
        Ollama["Ollama (localhost)"]
        Groq["Groq Cloud API"]
    end

    LLM --> Ollama
    LLM --> Groq

    subgraph Storage["Disk (~/.rag-desktop)"]
        SQLite["tracking.db<br/>(files, chat_sessions, chat_messages)"]
        Chroma["chroma/<br/>(vector index)"]
        SettingsJSON["settings.json"]
    end

    Tracking --> SQLite
    VectorStore --> Chroma
    API --> SettingsJSON
Loading

Data flow

Indexing a folder

sequenceDiagram
    participant U as User
    participant R as React UI
    participant E as Electron Main
    participant API as FastAPI
    participant Idx as Indexer
    participant Ext as Extractor (PDF/DOCX/Image)
    participant OCR as Tesseract (bundled)
    participant Emb as Embedding Model
    participant DB as ChromaDB + SQLite

    U->>R: Choose folder
    R->>E: selectFolder() (native dialog)
    E-->>R: folder path
    R->>API: POST /folder { path }
    API->>Idx: scan folder
    loop for each new/changed file
        Idx->>Ext: extract(file)
        alt scanned/image content
            Ext->>OCR: run OCR
            OCR-->>Ext: extracted text
        else native text (PDF/DOCX)
            Ext-->>Idx: extracted text
        end
        Idx->>Idx: chunk(text)
        Idx->>Emb: embed(chunks)
        Emb-->>Idx: vectors
        Idx->>DB: upsert chunks + vectors + file row
    end
    API-->>R: SSE progress updates (/status/stream)
    R-->>U: live indexing progress
Loading

Asking a question

sequenceDiagram
    participant U as User
    participant R as Chat UI
    participant API as FastAPI
    participant V as ChromaDB
    participant L as LLM (Ollama/Groq)
    participant DB as SQLite (chat history)

    U->>R: Type question
    R->>API: POST /chat { session_id, question }
    API->>V: similarity search (top_k)
    V-->>API: top-k chunks + source metadata
    API->>L: prompt (question + retrieved context)
    L-->>API: generated answer (with inline [source: ...] refs)
    API->>API: keep only sources actually referenced in answer
    API->>DB: save user + assistant messages
    API-->>R: { answer, sources[] }
    R-->>U: render answer + clickable source chips
    U->>R: click a source chip
    R->>API: POST /open-file { path }
    API-->>U: opens file in default OS viewer
Loading

Tech stack

Layer Choice Why
Desktop shell Electron + TypeScript Native folder picker, file opening, single packaged .exe
UI React + Tailwind CSS v4 + Zustand Small, fast, no heavyweight component library needed
Backend FastAPI (Python), spawned as a sidecar process REST + Server-Sent Events for live indexing progress
PDF text PyMuPDF Fast native text extraction
Word docs python-docx Native .docx parsing
OCR Tesseract (bundled binaries) + pytesseract Scanned PDFs and photographed/screenshot images
Embeddings sentence-transformers (paraphrase-multilingual-MiniLM-L12-v2) One model handles English + Urdu (+ other languages)
Vector store ChromaDB (persistent, local) Embedded, no separate server process
File tracking / chat history SQLite Zero-config, single file, survives restarts
File watching watchdog Real-time incremental indexing on file add/change
LLM Ollama (local) or Groq (cloud), pluggable User chooses fully offline vs. faster cloud inference
Packaging PyInstaller (backend) + electron-builder/NSIS (installer) Single .exe installer, no Python/Tesseract setup for end users

Project layout

rag-desktop/
├── electron/              Electron main process (TypeScript)
│   ├── main.ts             window lifecycle, IPC handlers, spawns backend
│   └── preload.ts          contextBridge -> window.electronAPI
├── src/                   React renderer
│   ├── pages/              Onboarding.tsx, Dashboard.tsx, Chat.tsx
│   ├── components/         IndexingStatus, MessageBubble, SourceChip, PoweredByFooter
│   ├── lib/api.ts          typed fetch wrapper around the FastAPI sidecar
│   └── store.ts            Zustand app state (settings, current view)
├── backend/               FastAPI sidecar (Python)
│   ├── main.py              REST + SSE API surface
│   ├── indexer.py           orchestrates extract -> chunk -> embed -> store
│   ├── watcher.py           watchdog-based folder watcher
│   ├── chunker.py           text chunking with overlap
│   ├── embeddings.py        sentence-transformers wrapper
│   ├── vectorstore.py       ChromaDB client wrapper
│   ├── tracking.py          SQLite: file index + chat sessions/messages
│   ├── chat.py              retrieval -> prompt -> LLM -> citation filtering
│   ├── config.py            Settings model, persisted to settings.json
│   ├── ocr_setup.py         resolves bundled vs. system Tesseract path
│   ├── pdf_to_word.py       optional PDF -> DOCX conversion
│   ├── extractors/          pdf.py, docx_reader.py, image.py
│   ├── llm/                 ollama_provider.py, groq_provider.py, base.py
│   └── build.spec           PyInstaller spec for freezing the backend
├── vendor/tesseract/       Bundled Tesseract binary + DLLs + tessdata
├── build/icon.ico          App icon (multi-resolution)
└── package.json            electron-builder config + npm scripts

Runtime data lives outside the install directory, in %USERPROFILE%\.rag-desktop\:

~/.rag-desktop/
├── settings.json     user settings (provider, OCR languages, top-k, ...)
├── tracking.db        SQLite: indexed files, chat sessions, chat messages
└── chroma/            persistent vector index

API reference

The FastAPI sidecar listens on http://127.0.0.1:8756.

Method Path Purpose
POST /folder Set the folder to index and start scanning
GET /status One-shot indexing/watch status
GET /status/stream SSE stream of indexing progress
GET /files List indexed files with status and chunk counts
POST /reindex Force a full re-scan of the current folder
POST /chat Ask a question within a chat session
POST /chat/sessions Create a new chat session
GET /chat/sessions List chat sessions
GET /chat/sessions/{id}/messages Get message history for a session
DELETE /chat/sessions/{id} Delete a chat session
POST /convert-pdf Convert a PDF to a .docx
POST /open-file Open a file path in the OS default viewer
GET / POST /settings Read/update persisted settings

Settings

Setting Values Notes
llm_provider ollama | groq Switch between fully local and cloud inference
ollama_model e.g. qwen2.5-coder:7b Must already be pulled locally
groq_api_key string Stored locally; masked as a password field in the UI
ocr_languages eng, urd Tesseract language packs used for image/scanned-PDF OCR
top_k 1–15 How many chunks are retrieved as context per question — higher catches more but adds noise and latency
watch_realtime bool Auto-index new files dropped into the folder without a manual rescan
auto_convert_pdf_to_word bool Also produce an editable .docx next to each indexed PDF

Getting started (development)

Prerequisites: Node.js, Python 3.11+, and either Ollama running locally or a Groq API key.

npm install
cd backend
python -m venv venv
venv\Scripts\pip install -r requirements.txt
cd ..

Tesseract is required for OCR in dev mode (packaged builds bundle their own copy — see below). Install it via the UB-Mannheim build and make sure tesseract.exe is on PATH, or drop urd.traineddata etc. into ~/.rag-desktop/tessdata.

Run everything (Vite + Electron + backend) with hot reload:

npm run dev:all

If your shell has ELECTRON_RUN_AS_NODE=1 set, unset it first — Electron refuses to launch its GUI process with that flag set.

Packaging a Windows installer

npm run package

This runs, in order:

  1. vite build — production React bundle
  2. tsc -p electron — compile the Electron main/preload process
  3. PyInstaller (backend/build.spec) — freezes the FastAPI backend + all Python dependencies (torch, chromadb, onnxruntime, etc.) into a standalone backend/dist/main/ folder (~900MB; these libraries dominate the size)
  4. electron-builder — packages everything into release/Judgment Finder AI Setup <version>.exe (NSIS installer) plus an unpacked release/win-unpacked/ folder for quick testing

Notes:

  • A clean PyInstaller build takes 10–15 minutes.
  • First launch of a packaged build takes ~20–30 seconds before /status responds — it's loading the embedding model from inside the frozen bundle (and Windows Defender scanning a large new exe for the first time). The UI shows an "App is starting..." spinner during this window.

How OCR is bundled (no separate install)

End users should never have to manually install Tesseract. To guarantee that:

  • vendor/tesseract/ contains tesseract.exe, its ~50 dependent DLLs, and tessdata/ (eng, urd, osd) — trimmed from a full Tesseract install by dropping training tools and docs (176MB vs. 239MB).
  • package.json's extraResources copies that folder into resources/tesseract/ next to the packaged backend exe.
  • backend/ocr_setup.py detects whether it's running frozen (sys.frozen) and, if so, points pytesseract directly at resources/tesseract/tesseract.exe — no PATH lookup, no registry, no admin rights needed. In dev mode it falls back to a system PATH / C:\Program Files\Tesseract-OCR install.

This was verified end-to-end: a fresh image dropped into a watched folder on a clean packaged build was OCR'd correctly and the extracted text was retrievable via chat with an accurate citation, with no Tesseract installed on the test machine outside of what the installer shipped.

Known limitations

  • OCR quality on heavily stylized Urdu Nastaliq calligraphy (e.g. low-res phone screenshots) can be poor — this is a Tesseract model limitation, not a retrieval/embedding bug.
  • Citation matching uses filename substring containment against the LLM's answer text rather than strict structured citation parsing, since LLMs don't always reproduce the exact [source: ..., page N] format requested in the prompt.
  • Only one folder can be indexed at a time per app instance.

Troubleshooting

Symptom Cause Fix
"Backend sidecar is not reachable" Backend process failed to start or is still loading models Wait for the startup spinner; check Electron logs for the spawned process's stderr
Electron window never opens ELECTRON_RUN_AS_NODE=1 set in the shell unset ELECTRON_RUN_AS_NODE before launching
Rebuild fails with file-lock/EPERM errors A previous app instance (or orphaned main.exe/Electron process) still holds files open Kill Judgment Finder AI.exe / main.exe / stray node.exe processes, then rebuild
OCR returns empty/garbage text Source image is low quality or in a script not covered by the loaded language packs Enable the relevant OCR language in Settings; quality of the source image is the ceiling
Groq answers fail Invalid or missing API key Re-enter the key in Settings (stored locally, masked in the UI)

Contributing

  1. Fork/branch off main.
  2. Follow the dev setup and run npm run dev:all.
  3. Keep changes scoped — one feature/fix per PR, with a short description of why.
  4. Before opening a PR, sanity-check:
    • npm run lint
    • cd backend && venv\Scripts\python -m pytest (extractor tests)
    • The app still boots via npm run dev:all and can index a test folder.
  5. Open a PR against main describing the change and how you tested it.

Bug reports and feature requests are welcome via GitHub Issues.

License

All rights reserved. This project is currently closed-source/proprietary; no license is granted to copy, modify, or redistribute without explicit permission from the author.

Acknowledgments


Powered by Mavi