NovaDocs is a multi-workspace document assistant. Upload text, Markdown, or PDF files into isolated workspaces, then ask questions grounded in your own content. Answers include citations, honest refusals when the corpus does not support a claim, and optional tools to save tasks or post Slack summaries.
- Workspaces — Separate knowledge bases per project; the backend enforces ownership on every API call.
- Document ingestion — Upload
.txt,.md, or.pdf; content is chunked, embedded (Gemini), and stored in Supabase Postgres with pgvector. - Semantic search — Search filenames and document content from the dashboard top bar.
- RAG chat — Retrieval-augmented chat with citation chips; refuses when context is insufficient.
- Tool calling —
save_task(task board) andsend_slack_summary(Slack webhook), with audit logs and prompt-injection defenses. - Dashboard — Stats, recent documents, conversations, tasks, and a unified history view (documents + chat + tool-call log).
| Layer | Technology |
|---|---|
| Frontend | Next.js 16 (App Router), Tailwind, Supabase Auth (SSR) |
| Backend | FastAPI, Python 3.12+ |
| Database | Supabase Postgres + pgvector |
| LLM & embeddings | Google Gemini (gemini-2.5-flash, gemini-embedding-001) |
| Notifications | Slack incoming webhook (optional) |
The browser talks only to the FastAPI backend for workspace data, documents, search, and chat. The backend uses the Supabase service role key and validates the user's JWT on each request.
- Node.js 20+
- Python 3.12+
- A Supabase project (Auth + Postgres + pgvector)
- A Google AI Studio API key for Gemini
- Create a Supabase project and enable Email auth (or OAuth providers you plan to use).
- Enable the pgvector extension in the SQL editor:
create extension if not exists vector; - Apply schema migrations. Helper scripts exist under
backend/scripts/(e.g.apply_match_chunks.py,apply_tasks_tool_calls.py) whenDATABASE_URLorSUPABASE_ACCESS_TOKENis set; otherwise run the SQL referenced in those scripts via the Supabase SQL editor. - (Optional) Create a public Storage bucket named
avatarsif you want profile photo uploads in Settings.
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtCopy environment variables (see .env.example) into backend/.env.local:
cp ../.env.example .env.local
# Edit .env.local — set SUPABASE_*, GEMINI_API_KEY, FRONTEND_URL=http://localhost:3000Start the API:
uvicorn main:app --reload --host 0.0.0.0 --port 8000Health check: http://localhost:8000/health
cd frontend
npm installCreate frontend/.env.local with the frontend section from .env.example:
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_API_URL=http://localhost:8000Start the dev server:
npm run devOpen http://localhost:3000, sign up / sign in, create a workspace, upload a document, and chat.
| Variable | Where | Required | Description |
|---|---|---|---|
SUPABASE_URL |
Backend | Yes | Supabase project URL |
SUPABASE_SERVICE_KEY |
Backend | Yes | Service role key (server only) |
GEMINI_API_KEY |
Backend | Yes | Gemini API key for chat and embeddings |
FRONTEND_URL |
Backend | Yes | Allowed CORS origin (e.g. http://localhost:3000 or your Vercel URL) |
SLACK_WEBHOOK_URL |
Backend | No | Slack incoming webhook for send_slack_summary |
DATABASE_URL |
Backend | No | Postgres URL for migration helper scripts |
NEXT_PUBLIC_SUPABASE_URL |
Frontend | Yes | Same Supabase URL as backend |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Frontend | Yes | Supabase anon/public key |
NEXT_PUBLIC_API_URL |
Frontend | Yes | Backend base URL (no trailing slash) |
See .env.example for a copy-paste template with no real secrets.
The app is deployed as two services:
| Service | Platform | Role |
|---|---|---|
| Frontend | Vercel | Next.js UI |
| Backend | Render | FastAPI API |
Replace the placeholder URLs below with your own Vercel and Render service URLs.
-
Import the repository in Vercel.
-
Set Root Directory to
frontend. -
Framework preset: Next.js (default build:
npm run build). -
Add environment variables:
Name Value NEXT_PUBLIC_SUPABASE_URLYour Supabase project URL NEXT_PUBLIC_SUPABASE_ANON_KEYSupabase anon key NEXT_PUBLIC_API_URLhttps://your-api.onrender.com -
Deploy. Note the production URL (e.g.
https://novadocs.vercel.app).
-
Create a Web Service on Render and connect the repository.
-
Set Root Directory to
backend. -
Build command:
pip install -r requirements.txt -
Start command:
uvicorn main:app --host 0.0.0.0 --port $PORT -
Add environment variables:
Name Value SUPABASE_URLYour Supabase project URL SUPABASE_SERVICE_KEYSupabase service role key GEMINI_API_KEYGemini API key FRONTEND_URLYour Vercel URL (e.g. https://novadocs.vercel.app)SLACK_WEBHOOK_URL(optional) Slack webhook -
Deploy. Note the service URL (e.g.
https://novadocs-api.onrender.com).
- Set
NEXT_PUBLIC_API_URLon Vercel to your Render backend URL. - Set
FRONTEND_URLon Render to your Vercel frontend URL (used for CORS). - In Supabase Authentication → URL configuration, add your Vercel URL to Site URL and Redirect URLs (e.g.
https://your-app.vercel.app/**).
Redeploy both services after changing environment variables.
From backend/ with the virtualenv active and API running locally:
python scripts/test_tool_authorization.py # Prompt-injection gate (no API/Gemini)
python scripts/test_ingestion_idempotency.py
python scripts/test_retrieval_isolation.py # Workspace isolation for search
python scripts/test_rag_chat.py # Grounded chat + refusal
python scripts/test_tool_calling.py # Tasks, Slack, injection scenariosMW-Document-Assistant/
├── frontend/ # Next.js app (Vercel)
├── backend/ # FastAPI app (Render)
│ ├── main.py
│ ├── routers/
│ ├── services/
│ └── scripts/ # Integration tests & migration helpers
├── .env.example # Environment template
└── README.md
- Retrieved document text is treated as untrusted data, not instructions.
- Tool calls (
save_task,send_slack_summary) require explicit user intent in the chat message; document-injected commands are blocked server-side. - Workspace boundaries are enforced in the backend via
get_owned_workspaceon everyworkspace_idroute.