| --- |
| title: DocuAsk |
| emoji: π |
| colorFrom: green |
| colorTo: gray |
| sdk: static |
| app_build_command: cd frontend && npm ci && npm run build |
| app_file: frontend/dist/index.html |
| pinned: false |
| license: mit |
| --- |
| |
| # DocuAsk |
|
|
| [](https://github.com/Sriyansh-28/docuask/actions/workflows/ci.yml) |
|
|
| **DocuAsk** is a full-stack document Q&A web app. Upload a PDF (or paste text), |
| ask questions in a chat box, and get answers with the **source passage** shown |
| underneath β then rate each answer π/π and watch the usage metrics update on a |
| live dashboard. It's a small, end-to-end product loop: a React front end, a |
| FastAPI back end, a lightweight retrieval layer, and a SQLite telemetry layer, |
| all runnable with one command and deployable as a single container. |
|
|
| π **Live demo:** **[docuask-production-c732.up.railway.app](https://docuask-production-c732.up.railway.app)** |
| β frontend + API from a single Railway container; dashboard at |
| [`/#/dashboard`](https://docuask-production-c732.up.railway.app/#/dashboard). |
|
|
| ## Screenshots |
|
|
| | Chat β answer with source passage | Dashboard β live metrics | |
| | :---: | :---: | |
| |  |  | |
|
|
| ## Features |
|
|
| - **Upload & parse** β drag-and-drop a PDF or paste text; the backend extracts |
| (pypdf), chunks, and indexes it in memory. Encrypted / broken / non-PDF / |
| empty / oversize inputs all fail with a clear, friendly message. |
| - **Chat with sources** β every answer shows the passage it was drawn from, so |
| the retrieval is transparent. |
| - **Hybrid retrieval** β BM25 (`rank_bm25`) combined with a FAISS cosine search |
| over TF-IDF vectors; deliberately lightweight, no heavyweight model. |
| - **Optional LLM answers (free)** β set `LLM_PROVIDER` (`groq` | `gemini` | |
| `openrouter`) and `LLM_API_KEY`, and answers become grounded summaries written |
| from the retrieved passages (the source passage is still shown). All three are |
| free OpenAI-compatible providers with email/Google sign-in. Without a key it |
| falls back to an extractive answer, so the app runs with no credentials and no |
| cost. |
| - **Feedback & telemetry** β each question is logged to SQLite with its measured |
| latency; π/π feedback and a `/dashboard` page show total questions, median |
| latency, and thumbs-up rate live from the DB. |
|
|
| ## Run it (one command) |
|
|
| ```bash |
| docker compose up --build |
| ``` |
|
|
| Then open **http://localhost:5173**. The frontend calls the API through nginx, |
| so there's nothing else to configure. |
|
|
| ## Run without Docker (dev) |
|
|
| **Backend** |
|
|
| ```bash |
| cd backend |
| pip install -r requirements.txt |
| uvicorn app.main:app --reload # http://localhost:8000 |
| ``` |
|
|
| **Frontend** |
|
|
| ```bash |
| cd frontend |
| npm install |
| npm run dev # http://localhost:5173 |
| ``` |
|
|
| In dev the Vite server proxies `/api/*` to the backend, so no CORS setup is |
| needed. |
|
|
| ## Tests |
|
|
| ```bash |
| cd backend |
| pytest |
| ``` |
|
|
| The suite covers `/health`, upload success and every failure path, retrieval and |
| `/ask`, `/feedback`, `/stats`, and the combined deploy entrypoint. CI runs it on |
| every push. |
|
|
| ## API |
|
|
| | Method | Path | Purpose | |
| | ------ | ------------------- | -------------------------------------------------- | |
| | GET | `/health` | Liveness probe. | |
| | POST | `/documents` | Ingest a PDF file or raw text β `document_id`. | |
| | GET | `/documents/{id}` | Document metadata. | |
| | POST | `/ask` | `{document_id, question}` β answer + source passage. | |
| | POST | `/feedback` | Attach π/π (`up`/`down`) to an interaction. | |
| | GET | `/stats` | Totals, median latency, thumbs-up rate, over-time. | |
|
|
| ## Deploy |
|
|
| The frontend deploys to a **Hugging Face Static Space** and the API to any host |
| that runs a Python web process. They're wired together with one Space variable β |
| no rebuild needed to change the API URL. |
|
|
| ### 1. Frontend β Hugging Face Static Space |
|
|
| The README front matter (`sdk: static`) tells HF to run the |
| `app_build_command` (`cd frontend && npm ci && npm run build`) and serve |
| `frontend/dist`. |
|
|
| 1. Create a new **Space** β **Static** SDK, owner `Sri-28`, name `Docuask` |
| (the workflow default; case-sensitive on HF). |
| 2. Push this repo to the Space (the [`sync-to-hf`](./.github/workflows/sync-to-hf.yml) |
| workflow does this automatically β see below). |
| 3. In the Space's **Settings β Variables**, add `DOCUASK_API_URL` set to your |
| deployed API's URL. The frontend reads it at runtime via |
| `window.huggingface.variables`, so you can change it without rebuilding. |
|
|
| The Space serves at **https://sri-28-docuask.static.hf.space**. |
|
|
| ### 2. Backend β Railway |
|
|
| The API is a standard uvicorn app. On **Railway**: |
|
|
| 1. **New Project β Deploy from GitHub repo** β select `docuask`. |
| 2. Open the service β **Settings β Root Directory** = `backend`. This makes |
| [`backend/railway.json`](./backend/railway.json) build |
| [`backend/Dockerfile`](./backend/Dockerfile) (which installs faiss's |
| `libgomp1` and honors Railway's injected `$PORT`). |
| 3. **Settings β Networking β Generate Domain** to get a public URL. |
| 4. *(Optional)* In the service **Variables**, set `LLM_PROVIDER` (e.g. `gemini`) |
| and `LLM_API_KEY` (a free key β Gemini's is at |
| [aistudio.google.com/apikey](https://aistudio.google.com/apikey), Google |
| sign-in) to enable LLM-written answers; otherwise the API serves extractive |
| answers. Add a **Volume** at `/data` + `DOCUASK_DB=/data/docuask.db` to |
| persist telemetry across restarts. |
| 5. Copy the public URL β you'll set it as `DOCUASK_API_URL` in the HF Space |
| (step 3 above). |
|
|
| The Static Space origin (`https://sri-28-docuask.static.hf.space`) is already in |
| the backend's default CORS allow-list; add more via the `CORS_ORIGINS` env var. |
| See [`backend/.env.example`](./backend/.env.example). |
|
|
| > Other hosts work too: any Docker host can build `backend/Dockerfile`, and |
| > native-Python hosts (Render, β¦) can use [`backend/Procfile`](./backend/Procfile). |
|
|
| ### Keep the demo in sync automatically |
|
|
| The [`sync-to-hf`](./.github/workflows/sync-to-hf.yml) workflow mirrors `main` to |
| your Space on every push. Configure it once under **Settings β Secrets and |
| variables β Actions**: |
|
|
| - Secret `HF_TOKEN` β a Hugging Face token with write scope. **This is the only |
| required step** (username defaults to `Sri-28`, Space to `Docuask`). |
| - Optionally override the `HF_USERNAME` / `HF_SPACE` variables. |
|
|
| Until `HF_TOKEN` is set the workflow no-ops, so it never fails the branch. |
|
|
| > **One-origin alternative:** the root [`Dockerfile`](./Dockerfile) + |
| > [`app/server.py`](./backend/app/server.py) build a single image that serves the |
| > frontend and API together under one origin (API mounted at `/api`, no CORS). |
| > Use this on any Docker host if you'd rather run one service than two. |
|
|
| ## Project structure |
|
|
| ``` |
| docuask/ |
| backend/ # FastAPI app + tests |
| app/ |
| main.py # API: /health, /documents, /ask, /feedback, /stats |
| parsing.py # PDF/text extraction + chunking |
| retrieval.py # BM25 + FAISS (TF-IDF) hybrid index |
| store.py # in-memory document store |
| db.py # SQLite interaction telemetry |
| server.py # combined static + API entrypoint (deploy) |
| tests/ # pytest suite |
| frontend/ # Vite + React + Tailwind |
| src/ |
| App.jsx # layout + hash routing (Chat / Dashboard) |
| DocumentUploader.jsx, Chat.jsx, Dashboard.jsx |
| Dockerfile # single-image build for Hugging Face Spaces |
| docker-compose.yml # local: frontend + backend together |
| .github/workflows/ # CI: pytest + frontend build on every push |
| ``` |
|
|
| ## Tech stack |
|
|
| - **Frontend:** React + Vite + Tailwind CSS |
| - **Backend:** FastAPI (Python) |
| - **Retrieval:** BM25 + FAISS over TF-IDF |
| - **Persistence:** SQLite (interaction telemetry) |
| - **Tests:** pytest |
| - **CI:** GitHub Actions (pytest + build on every push) |
| - **Deploy:** Hugging Face Static Space (frontend) + any Python host (API); Docker Compose locally |
|
|
| ## Roadmap |
|
|
| See [`PROJECT_SPEC.md`](./PROJECT_SPEC.md) for the full plan: |
|
|
| 1. β
Skeleton end to end (health check wired browser β API) |
| 2. β
Upload & parse flow (PDF/text β chunked, indexed) |
| 3. β
Retrieval + chat loop (BM25 + FAISS, source passages) |
| 4. β
Data-collection & feedback layer (SQLite, π/π, `/stats`, dashboard) |
| 5. β
Polish, tests, deploy (single-image Docker Space) |
|
|
| ## License |
|
|
| [MIT](./LICENSE) |
|
|