--- title: SmartNotes AI emoji: 📝 colorFrom: blue colorTo: green sdk: docker app_port: 7860 pinned: false --- # SmartNotes PDF OCR ## Project Metadata | Field | Value | | --- | --- | | Project | SmartNotes AI | | App type | FastAPI web app for PDF OCR and RAG-based question answering | | Runtime | Python 3.12 | | Default URL | `http://127.0.0.1:8000` | | Main entrypoint | `app.py` | | UI files | `templates/index.html`, `static/app.js`, `static/styles.css` | | OCR pipeline | PyMuPDF, OpenCV, Gemini Vision OCR, TrOCR fallback | | RAG pipeline | Text cleaning, metadata extraction, parent/child chunks, embeddings, BM25, reranking, citations | | Storage | SQLite local fallback in `data/smartnotes.sqlite`; optional PostgreSQL via `POSTGRES_DSN` | | Vector store | Local fallback; optional Qdrant via `QDRANT_URL` | | Docker | `Dockerfile` exposes port `8000` | | Secrets | Keep API keys in `.env`; do not commit `.env` | FastAPI app for PDF-to-text extraction using this route: 1. Try PyMuPDF direct text extraction. 2. If text exists, skip OCR, clean text, return final text. 3. If no direct text exists, convert PDF pages to images with PyMuPDF. 4. Preprocess images with OpenCV. 5. For 20 pages or fewer, run Gemini Vision OCR. 6. For more than 20 pages, run TrOCR first, then send low-confidence pages to Gemini Vision. 7. Merge, clean, and return final text. The UI uses `/api/pdf-to-text-stream`, so extracted text appears page by page instead of waiting for the full PDF to finish. After extraction, the UI automatically calls `/api/index-text-stream` and runs the locked RAG workflow: - status `processing` - cleaning and metadata - parent chunks - fixed, recursive, and semantic child chunks - chunk recommendation - embeddings and embedding evaluation - Qdrant store when configured, local vector fallback otherwise - BM25 index - status `indexed` - query validation, rewrite, hybrid retrieval, top-50 candidates, reranking, grading - parent fetch, duplicate parent removal, context fitting, answer, citations, retrieved chunk view, logs, feedback Useful optional env values: ```text POSTGRES_DSN=postgresql://user:password@localhost:5432/smartnotes QDRANT_URL=http://localhost:6333 QDRANT_API_KEY= EMBEDDING_MODEL=BAAI/bge-small-en-v1.5 RERANKER_MODEL=BAAI/bge-reranker-base USE_LOCAL_EMBEDDING_MODEL=1 USE_LOCAL_RERANKER_MODEL=1 ANSWER_PROVIDER=gemini ANSWER_MODEL=gemini-2.5-flash ``` ## Setup ```powershell python -m venv .venv .\.venv\Scripts\Activate.ps1 pip install -r requirements.txt $env:GEMINI_API_KEY="your_api_key" uvicorn app:app --reload ``` Open `http://127.0.0.1:8000`. You can also put these values in `env` or `.env`: ```text GEMINI_API_KEY_1=your_first_api_key GEMINI_API_KEY_2=your_second_api_key GEMINI_API_KEY_3=your_third_api_key GEMINI_API_KEY_4=your_fourth_api_key GEMINI_MODELS=gemini-3.5-flash,gemini-2.5-flash,gemini-2.5-flash-lite GEMINI_MAX_RETRIES=2 GEMINI_RETRY_DELAY=1.5 GEMINI_KEY_QUOTA_COOLDOWN=300 ``` If one Gemini key returns a quota or rate-limit error, the app skips it temporarily and tries the next configured key. Successful calls advance through the keys in order, so `_1`, `_2`, `_3`, and `_4` are used in rotation. If Gemini returns temporary `503 UNAVAILABLE` high-demand errors, the app retries and then tries the next model in `GEMINI_MODELS`.