AI-Powered Recruitment Platform — Automate resume screening, match candidates to jobs with LLM-driven scoring, and query your talent pool through a RAG-powered chatbot.
HireMinds AI is a full-stack monorepo application built with React and Flask that leverages the Llama 3.3 70B large language model (via Groq's free, OpenAI-compatible API) to transform recruitment workflows. Upload resumes, get structured candidate profiles, score them against job descriptions across 4 weighted criteria, and ask natural language questions about your candidates — all with complete multi-tenant data isolation.
- Features
- Architecture
- Tech Stack
- Project Structure
- Getting Started
- Database Setup
- API Reference
- Theme System
- Data Flow
- Configuration Reference
- Contributing
- License
- Drag-and-drop upload supporting PDF, DOCX, and TXT formats
- Batch upload up to 50 files at once (10MB per file)
- Regex-based structured extraction — no LLM needed for parsing, keeping it fast and reliable
- Extracts name, email, phone, skills (75 tech terms), experience years, education, and certifications
- Full raw text preserved for RAG search
-
Paste a job description and score all candidates automatically
-
4-criteria weighted scoring (100 points total):
Criterion Weight What It Measures Skills Match 40 pts Required and preferred skills coverage Experience Fit 25 pts Years of experience vs. requirements Education 20 pts Education level alignment Certifications 15 pts Relevant professional certifications -
LLM-generated explanations for each score
-
Match badges: Strong (80+), Good (50-79), Weak (<50)
-
Animated score breakdowns with detailed criteria visualization
- Ask natural language questions about your candidate pool
- Lightweight keyword-based RAG — no vector database, no embeddings, ~0 extra RAM
- Extracts keywords from questions, scores candidates by relevance, builds context from top 7 matches
- Source citations linking answers back to specific candidates
- Multi-turn conversations with history (last 10 messages)
- Real-time streaming via Server-Sent Events (SSE)
- Clerk JWT-based authentication with JWKS verification
- Complete user data isolation — every database query is scoped by
user_id - Secure sign-in/sign-up flows with custom themed UI
- 8 visual themes (3 light + 5 dark) with 40+ color tokens each
- Persisted to localStorage, applied via CSS custom properties
- Smooth 200ms theme transitions
- WCAG AA contrast ratios
Browser (React SPA, port 5173)
|
|-- Clerk Auth (JWT tokens)
|-- API calls (fetch + Bearer token)
v
Flask Backend (port 8000)
|-- @require_auth middleware (JWKS verification)
|-- g.user_id set from JWT 'sub' claim
|
|-- Services Layer:
| |-- llm_client.py --> LLM API client (Groq / Llama 3.3 70B)
| |-- resume_parser.py --> PDF/DOCX/TXT regex extraction
| |-- matcher.py --> JD extraction + candidate scoring
| |-- rag_engine.py --> Keyword-based RAG + LLM Q&A
|
|-- Data Layer:
|-- Supabase (PostgreSQL) -- 5 tables + 4 RPC functions
|-- File Storage (data/resumes/) -- uploaded files
Resume Upload:
File Drop --> FormData --> POST /api/upload --> extract_text() --> parse_resume() (regex)
--> INSERT INTO candidates (user_id scoped) --> Return structured candidate data
Job Matching:
Job Description --> POST /api/match --> LLM extracts requirements --> Per-candidate LLM scoring
--> INSERT INTO scores --> Return sorted rankings with badges
RAG Chat:
Question --> POST /api/ask-stream --> Extract keywords --> Score candidates by relevance
--> Top 7 excerpts --> LLM synthesis --> SSE stream with source citations
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, TailwindCSS, React Router v6, Lucide Icons |
| Auth | Clerk (JWT + JWKS RS256 verification) |
| Backend | Python Flask 3.1, Flask-CORS, Gunicorn |
| LLM | Llama 3.3 70B via Groq (free, OpenAI-compatible API) |
| Database | Supabase (PostgreSQL) with RPC functions |
| RAG | Lightweight keyword-based search (~0 extra RAM) |
| File Parsing | pypdf, python-docx |
| Containerization | Docker + Docker Compose, Nginx reverse proxy |
hireminds-ai/
|
|-- backend/
| |-- app.py # Flask entry point, 13 API routes
| |-- config.py # Central configuration with startup validation
| |-- requirements.txt # Python dependencies
| |-- Dockerfile # Gunicorn production image
| |-- .env.example # Environment variable template
| |-- middleware/
| | |-- auth.py # Clerk JWT verification (thread-safe JWKS cache)
| |-- models/
| | |-- database.py # Supabase client singleton
| |-- services/
| | |-- llm_client.py # OpenAI-compatible LLM wrapper (text/JSON/stream)
| | |-- resume_parser.py # Regex-based resume extraction (no LLM)
| | |-- matcher.py # LLM-powered 4-criteria job matching
| | |-- rag_engine.py # Keyword-based RAG pipeline
| |-- prompts/ # Jinja-style LLM prompt templates
| |-- extract_jd.txt # Job description requirement extraction
| |-- score_candidate.txt # Candidate scoring prompt
| |-- rag_qa.txt # RAG Q&A with source citations
|
|-- frontend/
| |-- package.json
| |-- vite.config.js
| |-- Dockerfile # Multi-stage build (Node + Nginx)
| |-- nginx.conf # SPA routing + API proxy + SSE support
| |-- .env.example # Frontend environment template
| |-- src/
| |-- main.jsx # ClerkProvider entry point
| |-- App.jsx # React Router + lazy-loaded pages
| |-- api.js # useApi() hook with AbortController support
| |-- styles/
| | |-- globals.css # 18 custom animations, CSS custom properties
| |-- themes/
| | |-- themes.js # 8 theme definitions (40+ tokens each)
| |-- context/
| | |-- ThemeContext.jsx # Theme state management
| | |-- AppContext.jsx # App-wide state (candidates, stats, conversations)
| |-- components/
| |-- Layout/ # Sidebar, TopBar, Layout wrapper
| |-- Dashboard/ # StatCards, SkillsCloud, PipelineOverview
| |-- Upload/ # DropZone, CandidateTable, CandidateDetail
| |-- Matcher/ # JDInput, ScoreCard, ranked results
| |-- Chat/ # ChatPage, ChatSidebar, MessageBubble
| |-- common/ # ErrorBoundary, SkillTag, Toast, LoadingSpinner
|
|-- data/ # Auto-created at runtime
| |-- resumes/ # Uploaded resume files
|
|-- docker-compose.yml # Full-stack orchestration
|-- supabase_schema.sql # Database schema + RPC functions
|-- CLAUDE.md # AI assistant project context
- Node.js >= 18
- Python >= 3.10
- Supabase project (supabase.com)
- Clerk account (clerk.com)
- LLM API access (free Groq key, or any OpenAI-compatible endpoint)
- Docker & Docker Compose (optional, for containerized deployment)
cp backend/.env.example backend/.env# LLM Configuration
LLM_API_KEY=your_llm_api_key_here
LLM_BASE_URL=https://api.groq.com/openai/v1
LLM_MODEL=llama-3.3-70b-versatile
# Authentication
CLERK_SECRET_KEY=your_clerk_secret_key_here
# Database
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your_supabase_anon_key_herecp frontend/.env.example frontend/.envVITE_CLERK_PUBLISHABLE_KEY=pk_test_your_clerk_key_hereNote: The backend exits immediately on startup if
SUPABASE_URLorSUPABASE_KEYis missing. Other missing variables produce warnings.
Backend:
cd backend
pip install -r requirements.txt
python app.py
# Server starts on http://localhost:8000Frontend:
cd frontend
npm install
npm run dev
# Dev server starts on http://localhost:5173# Build and start all services
docker compose up --build
# Start in background
docker compose up -d
# Follow backend logs
docker compose logs -f backend
# Stop all services
docker compose downThe Docker setup includes:
- Backend: Python 3.11-slim + Gunicorn (4 workers, gthread)
- Frontend: Multi-stage build (Node 20 builder + Nginx Alpine)
- Nginx: SPA routing, API reverse proxy, SSE support, gzip, static asset caching
- Health checks on both services
- Persistent volume for uploaded resumes
Important: Set
VITE_CLERK_PUBLISHABLE_KEYas an environment variable before building — it's injected at build time via Docker build args.
Before first use, run the schema in the Supabase SQL Editor:
# Copy the contents of supabase_schema.sql and execute in:
# Supabase Dashboard > SQL Editor > New QueryThe schema creates:
| Table | Purpose |
|---|---|
candidates |
Parsed resume data (name, skills, experience, raw_text, etc.) |
jobs |
Job postings with extracted requirements |
scores |
Match scores linking candidates to jobs (4 criteria + explanation) |
conversations |
RAG chat conversations |
messages |
Individual chat messages with source citations |
Plus 4 RPC functions for dashboard analytics:
get_rankings(jid)— Join scores with candidate dataget_avg_match_score(uid)— Average match score across all jobsget_total_qas(uid)— Total Q&A messages countget_top_candidates(uid)— Top 5 candidates by average score
All tables include user_id columns with indexes for multi-tenant data isolation. Foreign keys use CASCADE deletes.
All endpoints return a consistent response format:
{
"success": true,
"data": { ... },
"error": null
}Every endpoint (except health check) requires a Authorization: Bearer <jwt> header.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/health |
No | Health check |
POST |
/api/upload |
Yes | Upload resumes (multipart FormData, max 50 files) |
GET |
/api/candidates |
Yes | List candidates (?sort=, ?order=, ?skill=) |
GET |
/api/candidates/:id |
Yes | Get single candidate (includes raw_text) |
DELETE |
/api/candidates/:id |
Yes | Delete candidate + file + related scores |
POST |
/api/match |
Yes | Score candidates against a JD — body {"description": "<JD text, min 20 chars>", "title": "<optional>"} |
GET |
/api/rankings/:job_id |
Yes | Get cached match rankings (via RPC) |
POST |
/api/ask |
Yes | RAG Q&A question (creates conversation if needed) |
POST |
/api/ask-stream |
Yes | Streaming RAG via SSE (sources, token, done events) |
GET |
/api/conversations |
Yes | List all conversations |
GET |
/api/conversations/:id |
Yes | Get conversation + messages |
DELETE |
/api/conversations/:id |
Yes | Delete conversation + messages |
GET |
/api/stats |
Yes | Dashboard analytics (via RPC) |
GET |
/api/llm-usage |
Yes | Live LLM free-tier quota (requests/day, tokens/min + reset timers) |
HireMinds includes 8 built-in visual themes, each with 40+ color tokens:
| Theme | Accent | Mood |
|---|---|---|
| Aurora | #2563EB (Blue) |
Clean, professional SaaS |
| Sunset Warm | #EA580C (Orange) |
Warm, human-centered |
| Rose Gold | #E11D48 (Rose) |
Elegant, modern |
| Theme | Accent | Mood |
|---|---|---|
| Midnight Cosmos | #06B6D4 (Cyan) |
Deep, sophisticated |
| Forest Night | #34D399 (Emerald) |
Organic, nature-inspired |
| Obsidian Purple | #8B5CF6 (Violet) |
Bold, AI-forward |
| Ocean Deep | #3B82F6 (Blue) |
Calm, focused |
| Charcoal | #F97316 (Orange) |
Minimal, neutral |
Themes are applied via CSS custom properties on <html> and persisted to localStorage. All components use inline theme tokens (not Tailwind color classes), enabling instant 200ms transitions.
All settings are driven by environment variables with sensible defaults. Configured in backend/config.py.
| Variable | Default | Description |
|---|---|---|
LLM_API_KEY |
— | API key for the LLM endpoint |
LLM_BASE_URL |
https://api.groq.com/openai/v1 |
OpenAI-compatible LLM API base URL |
LLM_MODEL |
llama-3.3-70b-versatile |
Model identifier |
LLM_MAX_TOKENS |
4096 |
Maximum tokens per LLM response |
LLM_TIMEOUT |
180.0 |
LLM request timeout in seconds |
CLERK_SECRET_KEY |
— | Clerk secret key for JWT verification |
CLERK_JWKS_URL |
Auto-detected | Clerk JWKS endpoint URL |
SUPABASE_URL |
— | Required. Supabase project URL |
SUPABASE_KEY |
— | Required. Supabase anon/service key |
RAG_TOP_K |
7 |
Number of candidates to include in RAG context |
RAG_MAX_PROMPT_CHARS |
16000 |
Maximum prompt length before truncation |
MAX_FILE_SIZE |
10485760 |
Max upload file size in bytes (10MB) |
MAX_BATCH_FILES |
50 |
Max files per upload batch |
MAX_RESUME_CHARS |
6000 |
Max resume characters sent to LLM |
LOG_LEVEL |
INFO |
Python logging level |
- Every API route (except
/api/health) must use the@require_authdecorator - All database queries must filter by
user_id— no cross-tenant data leakage - API responses always use the
{success, data, error}format - Model name is
llama-3.3-70b-versatile— always referenceconfig.LLM_MODEL, never hardcode <think>tags must be stripped —llm_client.pyhandles this via regex (defensive; a no-op for non-reasoning models)- FormData requests must skip the
Content-Typeheader — let the browser set the multipart boundary - Never commit
.envfiles or API keys
PyJWTmust be installed with[crypto]extras — RS256 verification silently fails without it- RAG uses keyword search, not vector embeddings — do not add ChromaDB, FAISS, or sentence-transformers
resume_parser.pyuses regex only — theextract_resume.txtprompt template exists but is unused- Supabase schema must be created manually before first use (run
supabase_schema.sql) - LLM JSON responses use retry logic — models occasionally produce malformed JSON
- Streaming skips reasoning tokens — yields
delta.content, withdelta.reasoning_contentchecked as a fallback for split-stream reasoning models
The backend validates configuration at startup and will:
- Exit immediately if
SUPABASE_URLorSUPABASE_KEYis missing - Warn if
LLM_API_KEYorCLERK_SECRET_KEYis missing - Log the active model and endpoint on successful startup
This project is licensed under the GNU GPLv3 License.
Built with Llama 3.3 70B on Groq · Maintained by Aayush Charde