| title | SmartNotes AI |
|---|---|
| emoji | 📝 |
| colorFrom | blue |
| colorTo | green |
| sdk | docker |
| app_port | 7860 |
| pinned | false |
| 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:
- Try PyMuPDF direct text extraction.
- If text exists, skip OCR, clean text, return final text.
- If no direct text exists, convert PDF pages to images with PyMuPDF.
- Preprocess images with OpenCV.
- For 20 pages or fewer, run Gemini Vision OCR.
- For more than 20 pages, run TrOCR first, then send low-confidence pages to Gemini Vision.
- 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:
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
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
$env:GEMINI_API_KEY="your_api_key"
uvicorn app:app --reloadOpen http://127.0.0.1:8000.
You can also put these values in env or .env:
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.