A split-screen chat interface for hackathon participants to ask questions about a PDF document. Built with Next.js 14 (App Router), Claude AI, Voyage AI embeddings, Pinecone vector search, Supabase storage, and Vercel KV.
- Split-screen UI — Chat on the left, PDF viewer on the right
- Semantic + keyword search — Hybrid retrieval with Reciprocal Rank Fusion (RRF)
- Streaming responses — Claude answers stream in real-time
- Page jump — Click the page badge on any response to jump the PDF viewer to the source page
- Admin panel — Upload PDFs, view query logs, manage users
- Next.js 14 (App Router) + TypeScript + Tailwind CSS
- Claude (
claude-haiku-4-5) for chat via Anthropic API - Voyage AI (
voyage-2, 1024 dimensions) for embeddings - Pinecone for vector storage and semantic search
- Supabase Storage for PDF files
- Vercel KV (Redis) for chunks, admin users, and query logs
- react-pdf for in-browser PDF rendering
- bcryptjs + jose for auth
Vercel dashboard → Storage → Create KV Database → Link to project
- Go to Storage → Create bucket
pdfs→ set to private - Copy Project URL and service role key (Settings → API)
- Name:
hackathon-docs, Dimensions:1024, Metric:cosine - Copy API key
ANTHROPIC_API_KEY=
VOYAGE_API_KEY=
PINECONE_API_KEY=
PINECONE_INDEX_NAME=hackathon-docs
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_BUCKET_NAME=pdfs
ADMIN_BOOTSTRAP_PASSWORD=
JWT_SECRET=
KV_REST_API_URL= # auto-injected by Vercel KV
KV_REST_API_TOKEN= # auto-injected by Vercel KV
Visit /admin/login — username: admin, password: your ADMIN_BOOTSTRAP_PASSWORD
Go to /admin/documents and upload the hackathon PDF.
# Install dependencies
npm install
# Pull environment variables from Vercel
vercel env pull .env.local
# Run development server
npm run devOpen http://localhost:3000 for the chat interface. Open http://localhost:3000/admin for the admin panel.
One PDF stored as hackathon.pdf in the pdfs bucket. Uploading a new PDF replaces the existing one.
chunk:{index}→{ text, pageNumber, index }chunks:meta→{ count, filename, uploadedAt, uploadedBy, pageCount }
- Index:
hackathon-docs(1024 dimensions, cosine similarity) - Each vector ID = chunk index as string
- Metadata:
{ pageNumber, index }
- Embed query with Voyage AI (
voyage-2) - Semantic search — top 10 from Pinecone
- Keyword overlap scoring on those 10 chunks
- Reciprocal Rank Fusion (RRF, k=60) to combine scores
- Return top 5 chunks
admin:user:{username}→{ username, passwordHash, createdAt, role }log:{timestamp}→{ query, chunksUsed, topPage, timestamp, responseTimeMs }