AI-powered meeting management system that transforms audio recordings into searchable transcripts, summaries, and action items.
- 🎙️ Audio Upload - Upload meeting recordings with secure signed URLs
- 📝 Transcription - Convert audio to text (Vapi/ElevenLabs integration)
- 🤖 AI Summarization - Generate summaries, decisions, and risks
- ✅ Action Extraction - Automatically extract and track action items
- 🔍 Semantic Search - RAG-powered search using OpenAI embeddings + pgvector
- 📄 PDF Export - Generate and view meeting minutes
- 🔒 Secure - Row-level security (RLS) with role-based access control
- 👥 Multi-user - Google Sign-In with project-based permissions
- Frontend: Next.js 15 (App Router), React 19, TailwindCSS
- Backend: Next.js Server Actions
- Database: Supabase (Postgres + RLS + pgvector)
- Storage: Supabase Storage (private buckets)
- Auth: Supabase Auth (Google OAuth)
- AI: OpenAI (embeddings + chat)
- PDF: pdf-lib + PDF.js
- Testing: Playwright
- Node.js 18+ and npm
- Supabase account and project
- OpenAI API key
- Google OAuth credentials (configured in Supabase)
git clone <repository-url>
cd MeetingMiner
npm installCopy .env.local.example to .env.local and fill in your credentials:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx
SUPABASE_SERVICE_ROLE_KEY=xxx
# OpenAI
OPENAI_API_KEY=sk-xxx
OPENAI_EMBEDDING_MODEL=text-embedding-3-large
OPENAI_CHAT_MODEL=gpt-4o-mini
# Vector settings (MUST match embedding model)
VECTOR_DIM=3072 # 3072 for text-embedding-3-large, 1536 for text-embedding-3-small
# Optional: Voice providers
VOICE_PROVIDER=vapi
VAPI_API_KEY=xxx
ELEVEN_API_KEY=xxxRun migrations in Supabase SQL Editor (in order):
# 1. Initial schema
db/migrations/001_initial_schema.sql
# 2. Enable RLS
db/migrations/002_enable_rls.sql
# 3. Vector index
db/migrations/003_vector_index.sqlSee db/README.md for detailed instructions.
- Go to Supabase Dashboard → Authentication → Providers
- Enable Google provider
- Add your Google OAuth credentials
- Set authorized redirect URLs:
http://localhost:3000/auth/callback(development)https://yourdomain.com/auth/callback(production)
In Supabase Dashboard → Storage:
- Create bucket named
meetings - Set to Private (not public)
- No additional policies needed (RLS handles access)
# Start dev server
npm run dev
# Run tests
npm test
# Run tests with UI
npm run test:ui
# Build for production
npm run build
# Start production server
npm startMeetingMiner/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ │ ├── meetings/ # Meeting CRUD
│ │ └── storage/ # Signed URL generation
│ ├── auth/ # Auth callback
│ ├── login/ # Login page
│ ├── (protected)/ # Auth-guarded routes
│ │ ├── projects/ # Projects & meetings
│ │ └── admin/ # Admin section
│ ├── layout.tsx # Root layout
│ └── page.tsx # Home page
├── lib/ # Shared libraries
│ ├── supabase/ # Supabase clients
│ ├── openai/ # OpenAI helpers
│ ├── storage/ # Storage helpers
│ └── vector/ # Vector search
├── db/ # Database migrations
│ └── migrations/ # SQL files
├── tests/ # Tests
│ └── e2e/ # Playwright E2E tests
├── middleware.ts # Auth middleware
└── .env.local # Environment variables
All tables have RLS enabled with policies:
- SELECT: Members can view their project data
- INSERT/UPDATE/DELETE: Owners and editors only
- Actions: Assignees can update their own status
- All buckets are private
- Uploads use
createSignedUploadUrl(2-hour validity) - Downloads use
createSignedUrl(1-hour validity) - Never expose raw storage paths
- Google Sign-In via Supabase Auth
- Session refresh on every request (middleware)
- Protected routes redirect to login
POST /api/meetings- Create meetingPOST /api/meetings/:id/finalize-upload- Save audio file referencePOST /api/meetings/:id/transcribe- Trigger transcriptionPOST /api/meetings/:id/summarize- Generate summaryPOST /api/meetings/:id/actions/extract- Extract actionsPOST /api/meetings/:id/rag/query- RAG searchGET /api/meetings/:id/export.pdf- Export PDF
POST /api/storage/signed-upload- Get signed upload URL
# Run all tests
npm test
# Run specific test file
npx playwright test tests/e2e/auth.spec.ts
# Run with UI
npm run test:ui
# Generate test report
npx playwright show-report- ✅ Auth guard (non-member blocked)
- ✅ Storage privacy (signed URLs only)
- ✅ Upload → Transcribe pipeline
- ✅ Summary generation (valid JSON)
- ✅ Action extraction
- ✅ PDF export
- ✅ RAG search with citations
- ⏱️ Time-to-first summary: P95 ≤ 90s
- 🔍 RAG retrieval: P95 ≤ 300ms (HNSW index)
- 🎯 Action extraction precision: ≥ 85%
If you see errors about embedding dimensions:
- Check
OPENAI_EMBEDDING_MODELin.env.local - Ensure
VECTOR_DIMmatches:text-embedding-3-small= 1536text-embedding-3-large= 3072
- Update
vector_chunkstable if needed:ALTER TABLE vector_chunks ALTER COLUMN embedding TYPE vector(3072);
If queries fail with permission errors:
- Verify user is authenticated
- Check project membership in
project_memberstable - Review RLS policies in
db/migrations/002_enable_rls.sql
- Verify bucket
meetingsexists and is private - Check signed URL hasn't expired (2-hour limit)
- Ensure file size is within limits
- Create feature branch
- Make changes
- Add tests
- Run
npm test - Submit PR
MIT
For issues and questions, please open a GitHub issue.