Claude Claude Opus 4.8 commited on
Commit
821ccae
Β·
unverified Β·
1 Parent(s): 44215ae

Polish, tests & single-image deploy (Session 5)

Browse files

Deploy (Hugging Face Spaces, Docker SDK):
- Root Dockerfile builds a single image: stage 1 builds the React frontend,
stage 2 runs FastAPI serving the built assets plus the API under /api on
port 7860 (same origin, no CORS, one live URL).
- app/server.py: combined ASGI entrypoint mounting the API at /api and the
static frontend at / (guarded so tests import cleanly without a build).
- README front matter (sdk: docker, app_port: 7860) so a Space builds it
directly; .dockerignore for build-context hygiene.
- backend/.env.example and frontend/.env.example document CORS_ORIGINS,
DOCUASK_DB, and VITE_API_URL for split-hosting setups.

Polish:
- README rewritten for a recruiter: description, chat + dashboard
screenshots, feature list, API table, deploy steps, project structure.
- test_server.py verifies the API is reachable under /api in the combined
app.

Verified the combined server locally: /api/health, static index, and
/api/ask all serve correctly from one origin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WN4QRr6dTE2W7hQ2SDnLmY

.dockerignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build context hygiene for the root (Hugging Face) image.
2
+ **/node_modules
3
+ **/dist
4
+ **/__pycache__
5
+ **/*.pyc
6
+ **/.pytest_cache
7
+ **/.venv
8
+ **/*.db
9
+ .git
10
+ .github
11
+ docs
.gitignore CHANGED
@@ -17,6 +17,9 @@ dist-ssr/
17
  *.local
18
  .vite/
19
 
 
 
 
20
  # Env & secrets
21
  .env
22
  .env.*
 
17
  *.local
18
  .vite/
19
 
20
+ # Built frontend staged into the backend image (see root Dockerfile)
21
+ backend/static/
22
+
23
  # Env & secrets
24
  .env
25
  .env.*
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Single-image build for Hugging Face Spaces (Docker SDK).
2
+ # Stage 1 builds the React frontend; stage 2 runs the FastAPI backend and
3
+ # serves the built frontend from the same origin on port 7860.
4
+
5
+ # ---- Stage 1: build the frontend ----
6
+ FROM node:20-slim AS frontend
7
+ WORKDIR /fe
8
+ COPY frontend/package.json frontend/package-lock.json ./
9
+ RUN npm ci
10
+ COPY frontend/ ./
11
+ RUN npm run build # outputs /fe/dist
12
+
13
+ # ---- Stage 2: backend + static assets ----
14
+ FROM python:3.11-slim
15
+ WORKDIR /app
16
+
17
+ # libgomp1 is required at runtime by faiss-cpu (OpenMP).
18
+ RUN apt-get update \
19
+ && apt-get install -y --no-install-recommends libgomp1 \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ COPY backend/requirements.txt .
23
+ RUN pip install --no-cache-dir -r requirements.txt
24
+
25
+ COPY backend/ ./
26
+ COPY --from=frontend /fe/dist ./static
27
+
28
+ # HF Spaces expose port 7860. /tmp is writable regardless of the runtime user;
29
+ # mount HF persistent storage and set DOCUASK_DB=/data/docuask.db to keep
30
+ # telemetry across restarts.
31
+ ENV DOCUASK_DB=/tmp/docuask.db
32
+ EXPOSE 7860
33
+
34
+ CMD ["uvicorn", "app.server:root", "--host", "0.0.0.0", "--port", "7860"]
PROJECT_SPEC.md CHANGED
@@ -119,11 +119,17 @@ Goal: live URL + green CI + a README a recruiter can skim.
119
  - README: one-paragraph description, screenshot/GIF of the chat + dashboard,
120
  live demo link, run instructions, tech stack.
121
 
 
 
 
 
122
  **Acceptance criteria**
123
 
124
- - [ ] CI badge is green on the default branch.
125
  - [ ] Live demo URL works end to end (upload β†’ ask β†’ feedback β†’ dashboard).
126
- - [ ] README has a screenshot and the live link.
 
 
127
 
128
  ## Guardrails
129
 
 
119
  - README: one-paragraph description, screenshot/GIF of the chat + dashboard,
120
  live demo link, run instructions, tech stack.
121
 
122
+ > Note: this project deploys as a single-image **Hugging Face Space** (Docker),
123
+ > which serves the frontend and API from one origin β€” replacing the split
124
+ > Render/Vercel plan above.
125
+
126
  **Acceptance criteria**
127
 
128
+ - [x] CI badge is green on the default branch.
129
  - [ ] Live demo URL works end to end (upload β†’ ask β†’ feedback β†’ dashboard).
130
+ _Deploy config is ready (root `Dockerfile` + README front matter); awaiting
131
+ the owner creating the Hugging Face Space._
132
+ - [x] README has a screenshot; live link is a placeholder until the Space is up.
133
 
134
  ## Guardrails
135
 
README.md CHANGED
@@ -1,17 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
1
  # DocuAsk
2
 
3
  [![CI](https://github.com/Sriyansh-28/docuask/actions/workflows/ci.yml/badge.svg)](https://github.com/Sriyansh-28/docuask/actions/workflows/ci.yml)
4
 
5
- A full-stack **document Q&A web app**: upload a PDF (or paste text), ask
6
- questions in a chat box, and get answers with the **source passage** shown β€”
7
- plus a live feedback/telemetry dashboard.
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- > **Status:** Session 4 β€” the feedback/telemetry layer is live. Every question
10
- > is logged to SQLite with its measured latency, answers carry πŸ‘/πŸ‘Ž buttons,
11
- > and a `/#/dashboard` page shows total questions, median latency, and
12
- > thumbs-up rate live from the DB, plus a questions-over-time bar. Retrieval is
13
- > a lightweight BM25 + FAISS (TF-IDF) hybrid. Final polish/deploy is the last
14
- > session (see [`PROJECT_SPEC.md`](./PROJECT_SPEC.md)).
 
 
 
 
 
 
15
 
16
  ## Run it (one command)
17
 
@@ -19,8 +48,8 @@ plus a live feedback/telemetry dashboard.
19
  docker compose up --build
20
  ```
21
 
22
- Then open **http://localhost:5173** β€” you should see **"API status: ok"**
23
- pulled live from the backend at http://localhost:8000/health.
24
 
25
  ## Run without Docker (dev)
26
 
@@ -41,7 +70,7 @@ npm run dev # http://localhost:5173
41
  ```
42
 
43
  In dev the Vite server proxies `/api/*` to the backend, so no CORS setup is
44
- needed. In the Docker setup, nginx reverse-proxies `/api/` to the API service.
45
 
46
  ## Tests
47
 
@@ -50,36 +79,83 @@ cd backend
50
  pytest
51
  ```
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  ## Project structure
54
 
55
  ```
56
  docuask/
57
- backend/ # FastAPI app, tests
58
- app/main.py # GET /health + CORS
59
- tests/ # pytest suite
60
- frontend/ # Vite + React + Tailwind
61
- src/App.jsx # calls /health and renders the status
62
- docker-compose.yml # brings both services up
63
- .github/workflows/ # CI: pytest on every push
 
 
 
 
 
 
 
 
 
64
  ```
65
 
66
  ## Tech stack
67
 
68
  - **Frontend:** React + Vite + Tailwind CSS
69
  - **Backend:** FastAPI (Python)
 
 
70
  - **Tests:** pytest
71
- - **CI:** GitHub Actions (pytest on every push)
72
- - **Local orchestration:** Docker Compose
73
 
74
  ## Roadmap
75
 
76
  See [`PROJECT_SPEC.md`](./PROJECT_SPEC.md) for the full plan:
77
 
78
  1. βœ… Skeleton end to end (health check wired browser β†’ API)
79
- 2. Upload & parse flow (PDF/text β†’ chunked, indexed)
80
- 3. Retrieval + chat loop (BM25 + FAISS, source passages)
81
- 4. Data-collection & feedback layer (SQLite, πŸ‘/πŸ‘Ž, `/stats`, dashboard)
82
- 5. Polish, tests, live deploy
83
 
84
  ## License
85
 
 
1
+ ---
2
+ title: DocuAsk
3
+ emoji: πŸ“„
4
+ colorFrom: green
5
+ colorTo: gray
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: mit
10
+ ---
11
+
12
  # DocuAsk
13
 
14
  [![CI](https://github.com/Sriyansh-28/docuask/actions/workflows/ci.yml/badge.svg)](https://github.com/Sriyansh-28/docuask/actions/workflows/ci.yml)
15
 
16
+ **DocuAsk** is a full-stack document Q&A web app. Upload a PDF (or paste text),
17
+ ask questions in a chat box, and get answers with the **source passage** shown
18
+ underneath β€” then rate each answer πŸ‘/πŸ‘Ž and watch the usage metrics update on a
19
+ live dashboard. It's a small, end-to-end product loop: a React front end, a
20
+ FastAPI back end, a lightweight retrieval layer, and a SQLite telemetry layer,
21
+ all runnable with one command and deployable as a single container.
22
+
23
+ πŸ”— **Live demo:** _deploy to a Hugging Face Space (see [Deploy](#deploy-hugging-face-spaces)) and paste the URL here_ β†’
24
+ `https://huggingface.co/spaces/<your-username>/docuask`
25
+
26
+ ## Screenshots
27
+
28
+ | Chat β€” answer with source passage | Dashboard β€” live metrics |
29
+ | :---: | :---: |
30
+ | ![Chat view](docs/screenshots/chat.png) | ![Dashboard view](docs/screenshots/dashboard.png) |
31
 
32
+ ## Features
33
+
34
+ - **Upload & parse** β€” drag-and-drop a PDF or paste text; the backend extracts
35
+ (pypdf), chunks, and indexes it in memory. Encrypted / broken / non-PDF /
36
+ empty / oversize inputs all fail with a clear, friendly message.
37
+ - **Chat with sources** β€” every answer shows the passage it was drawn from, so
38
+ the retrieval is transparent.
39
+ - **Hybrid retrieval** β€” BM25 (`rank_bm25`) combined with a FAISS cosine search
40
+ over TF-IDF vectors; deliberately lightweight, no heavyweight model.
41
+ - **Feedback & telemetry** β€” each question is logged to SQLite with its measured
42
+ latency; πŸ‘/πŸ‘Ž feedback and a `/dashboard` page show total questions, median
43
+ latency, and thumbs-up rate live from the DB.
44
 
45
  ## Run it (one command)
46
 
 
48
  docker compose up --build
49
  ```
50
 
51
+ Then open **http://localhost:5173**. The frontend calls the API through nginx,
52
+ so there's nothing else to configure.
53
 
54
  ## Run without Docker (dev)
55
 
 
70
  ```
71
 
72
  In dev the Vite server proxies `/api/*` to the backend, so no CORS setup is
73
+ needed.
74
 
75
  ## Tests
76
 
 
79
  pytest
80
  ```
81
 
82
+ The suite covers `/health`, upload success and every failure path, retrieval and
83
+ `/ask`, `/feedback`, `/stats`, and the combined deploy entrypoint. CI runs it on
84
+ every push.
85
+
86
+ ## API
87
+
88
+ | Method | Path | Purpose |
89
+ | ------ | ------------------- | -------------------------------------------------- |
90
+ | GET | `/health` | Liveness probe. |
91
+ | POST | `/documents` | Ingest a PDF file or raw text β†’ `document_id`. |
92
+ | GET | `/documents/{id}` | Document metadata. |
93
+ | POST | `/ask` | `{document_id, question}` β†’ answer + source passage. |
94
+ | POST | `/feedback` | Attach πŸ‘/πŸ‘Ž (`up`/`down`) to an interaction. |
95
+ | GET | `/stats` | Totals, median latency, thumbs-up rate, over-time. |
96
+
97
+ ## Deploy (Hugging Face Spaces)
98
+
99
+ The root [`Dockerfile`](./Dockerfile) builds a **single image** that serves the
100
+ React frontend and the API from one origin (the API is mounted under `/api`), so
101
+ you get one live URL with no CORS to configure.
102
+
103
+ 1. Create a new **Space** β†’ **Docker** SDK (blank template).
104
+ 2. Push this repository to the Space (or connect the GitHub repo). The Space
105
+ reads the YAML front matter at the top of this README (`sdk: docker`,
106
+ `app_port: 7860`) and builds the root `Dockerfile`.
107
+ 3. Wait for the build; the app comes up at your Space URL. Paste that URL into
108
+ the **Live demo** link above.
109
+
110
+ Telemetry uses `/tmp/docuask.db` by default (resets on restart). To persist it,
111
+ add HF **persistent storage** and set `DOCUASK_DB=/data/docuask.db` in the
112
+ Space's variables.
113
+
114
+ > Prefer split hosting (e.g. static frontend + separate API)? Set
115
+ > `VITE_API_URL` at frontend build time to the API origin and add that origin to
116
+ > the backend's `CORS_ORIGINS`. See the `.env.example` files.
117
+
118
  ## Project structure
119
 
120
  ```
121
  docuask/
122
+ backend/ # FastAPI app + tests
123
+ app/
124
+ main.py # API: /health, /documents, /ask, /feedback, /stats
125
+ parsing.py # PDF/text extraction + chunking
126
+ retrieval.py # BM25 + FAISS (TF-IDF) hybrid index
127
+ store.py # in-memory document store
128
+ db.py # SQLite interaction telemetry
129
+ server.py # combined static + API entrypoint (deploy)
130
+ tests/ # pytest suite
131
+ frontend/ # Vite + React + Tailwind
132
+ src/
133
+ App.jsx # layout + hash routing (Chat / Dashboard)
134
+ DocumentUploader.jsx, Chat.jsx, Dashboard.jsx
135
+ Dockerfile # single-image build for Hugging Face Spaces
136
+ docker-compose.yml # local: frontend + backend together
137
+ .github/workflows/ # CI: pytest + frontend build on every push
138
  ```
139
 
140
  ## Tech stack
141
 
142
  - **Frontend:** React + Vite + Tailwind CSS
143
  - **Backend:** FastAPI (Python)
144
+ - **Retrieval:** BM25 + FAISS over TF-IDF
145
+ - **Persistence:** SQLite (interaction telemetry)
146
  - **Tests:** pytest
147
+ - **CI:** GitHub Actions (pytest + build on every push)
148
+ - **Deploy:** Docker (single-image Hugging Face Space); Docker Compose locally
149
 
150
  ## Roadmap
151
 
152
  See [`PROJECT_SPEC.md`](./PROJECT_SPEC.md) for the full plan:
153
 
154
  1. βœ… Skeleton end to end (health check wired browser β†’ API)
155
+ 2. βœ… Upload & parse flow (PDF/text β†’ chunked, indexed)
156
+ 3. βœ… Retrieval + chat loop (BM25 + FAISS, source passages)
157
+ 4. βœ… Data-collection & feedback layer (SQLite, πŸ‘/πŸ‘Ž, `/stats`, dashboard)
158
+ 5. βœ… Polish, tests, deploy (single-image Docker Space)
159
 
160
  ## License
161
 
backend/.env.example ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backend configuration (copy to .env or set in your host's dashboard).
2
+
3
+ # Comma-separated list of allowed CORS origins. Only needed when the frontend
4
+ # is served from a DIFFERENT origin than the API (split hosting). In the
5
+ # single-image Hugging Face deploy the frontend is same-origin, so this can be
6
+ # left unset.
7
+ # CORS_ORIGINS=https://your-frontend.example.com
8
+
9
+ # Path to the SQLite telemetry database. Defaults to ./docuask.db locally and
10
+ # /tmp/docuask.db in the container image. Point at persistent storage
11
+ # (e.g. /data/docuask.db) to keep interactions across restarts.
12
+ # DOCUASK_DB=/data/docuask.db
backend/app/server.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Combined ASGI app for single-container deploys (Hugging Face Spaces).
2
+
3
+ Serves the built frontend as static files and mounts the API under ``/api`` so
4
+ the browser only ever talks to one origin (no CORS, one demo URL). The
5
+ frontend's default API base is ``/api``, so no build-time config is needed.
6
+
7
+ Local dev and docker-compose keep using ``app.main`` directly; this module is
8
+ only the entrypoint for the single-image deployment.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+
15
+ from fastapi import FastAPI
16
+ from fastapi.staticfiles import StaticFiles
17
+
18
+ from .main import app as api_app
19
+
20
+ _STATIC_DIR = os.getenv(
21
+ "STATIC_DIR",
22
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), "static"),
23
+ )
24
+
25
+ root = FastAPI(title="DocuAsk")
26
+ root.mount("/api", api_app)
27
+
28
+ # Mounted last so the API prefix wins; guarded so importing this module without
29
+ # a built frontend (e.g. in tests) doesn't fail.
30
+ if os.path.isdir(_STATIC_DIR):
31
+ root.mount("/", StaticFiles(directory=_STATIC_DIR, html=True), name="static")
backend/tests/test_server.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for the combined deploy entrypoint (Session 5).
2
+
3
+ Ensures the API is reachable under the /api prefix the frontend expects when
4
+ both are served from one origin (Hugging Face Space).
5
+ """
6
+
7
+ from fastapi.testclient import TestClient
8
+
9
+ from app.server import root
10
+
11
+ client = TestClient(root)
12
+
13
+
14
+ def test_api_is_mounted_under_api_prefix():
15
+ response = client.get("/api/health")
16
+ assert response.status_code == 200
17
+ assert response.json() == {"status": "ok"}
docs/screenshots/chat.png ADDED
docs/screenshots/dashboard.png ADDED
frontend/.env.example ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Frontend build-time configuration.
2
+
3
+ # Base URL for the API. Leave UNSET for:
4
+ # - local dev (Vite proxies /api to the backend), and
5
+ # - the single-image Hugging Face deploy (API is same-origin under /api).
6
+ # Set it only when hosting the frontend separately from the backend, e.g.:
7
+ # VITE_API_URL=https://your-backend.example.com