Spaces:
No application file
π Enhanced IQKiller with Next.js Vercel version
Browse files⨠Major Features Added:
- Complete Next.js application in iqkiller-vercel/
- Real-time streaming analysis with 7-step pipeline
- Professional interview guide generation
- Question bank integration (750+ categorized questions)
- Smart job URL processing and data extraction
- Comprehensive UI with progress tracking and results display
π― Key Improvements:
- Simplified job input flow (auto-save on URL paste)
- Enhanced streaming analysis with proper completion handling
- Professional guide display with company insights
- Removed complex scraping dependencies for reliability
- Fixed result display and data flow issues
π Technical Enhancements:
- Structured question bank from CSV processing
- Role-based question selection and AI personalization
- Company research and salary insights integration
- Clean streaming API with progress tracking
- Responsive UI components with shadcn/ui
Backup created: backup_20250708_192058
- .gitignore +5 -0
- Question_bank_IQ_categorized/A_B Testing Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Algorithm Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Analytics Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Business Case Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Database Design Questions.xlsx +0 -0
- Question_bank_IQ_categorized/ML System Design Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Machine Learning Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Pandas Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Probability Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Product Metrics Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Python Questions.xlsx +0 -0
- Question_bank_IQ_categorized/SQL Questions.xlsx +0 -0
- Question_bank_IQ_categorized/Statistics Questions.xlsx +0 -0
- Question_bank_IQ_categorized/summary (1).csv +0 -0
- README.md +44 -1
- VERCEL_DEPLOYMENT_ROADMAP.md +417 -0
- VERCEL_DEPLOYMENT_ROADMAP_UPDATED.md +226 -0
- VERCEL_ENV_SETUP.md +96 -0
- components/job-analysis.tsx +120 -0
- env.template +17 -0
- extract_pdf_resume.sh +26 -0
- interview_guide_generator.py +452 -614
- iqkiller-vercel/.eslintrc.json +3 -0
- iqkiller-vercel/.gitignore +36 -0
- iqkiller-vercel/LICENSE +13 -0
- iqkiller-vercel/Question_bank_IQ_categorized/A_B Testing Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Algorithm Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Analytics Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Business Case Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Database Design Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/ML System Design Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Machine Learning Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Pandas Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Probability Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Product Metrics Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Python Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/SQL Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/Statistics Questions.xlsx +0 -0
- iqkiller-vercel/Question_bank_IQ_categorized/summary (1).csv +0 -0
- iqkiller-vercel/README.md +41 -0
- iqkiller-vercel/app/(preview)/actions.ts +21 -0
- iqkiller-vercel/app/(preview)/globals.css +68 -0
- iqkiller-vercel/app/(preview)/layout.tsx +30 -0
- iqkiller-vercel/app/(preview)/opengraph-image.png +0 -0
- iqkiller-vercel/app/(preview)/page.tsx +258 -0
- iqkiller-vercel/app/(preview)/twitter-image.png +0 -0
- iqkiller-vercel/app/api/analyze-stream/route.ts +508 -0
- iqkiller-vercel/app/api/analyze/route.ts +76 -0
- iqkiller-vercel/app/api/generate-comprehensive-guide/route.ts +372 -0
|
@@ -38,6 +38,11 @@ ENV/
|
|
| 38 |
.DS_Store
|
| 39 |
Thumbs.db
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
# Cache
|
| 42 |
.cache/
|
| 43 |
|
|
|
|
| 38 |
.DS_Store
|
| 39 |
Thumbs.db
|
| 40 |
|
| 41 |
+
# Environment variables
|
| 42 |
+
.env
|
| 43 |
+
.env.local
|
| 44 |
+
.env.*.local
|
| 45 |
+
|
| 46 |
# Cache
|
| 47 |
.cache/
|
| 48 |
|
|
Binary file (7.71 kB). View file
|
|
|
|
Binary file (15.1 kB). View file
|
|
|
|
Binary file (10.3 kB). View file
|
|
|
|
Binary file (13.2 kB). View file
|
|
|
|
Binary file (10.1 kB). View file
|
|
|
|
Binary file (8.86 kB). View file
|
|
|
|
Binary file (14.8 kB). View file
|
|
|
|
Binary file (7.92 kB). View file
|
|
|
|
Binary file (11 kB). View file
|
|
|
|
Binary file (11.3 kB). View file
|
|
|
|
Binary file (14.1 kB). View file
|
|
|
|
Binary file (19.1 kB). View file
|
|
|
|
Binary file (11.1 kB). View file
|
|
|
|
The diff for this file is too large to render.
See raw diff
|
|
|
|
@@ -10,4 +10,47 @@ pinned: false
|
|
| 10 |
license: apache-2.0
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
license: apache-2.0
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# IQKiller v2 - AI-Powered Interview Preparation Platform
|
| 14 |
+
|
| 15 |
+
An advanced interview preparation platform that provides personalized interview guides, salary negotiation simulation, and comprehensive analysis.
|
| 16 |
+
|
| 17 |
+
## π Quick Start
|
| 18 |
+
|
| 19 |
+
### 1. Environment Setup
|
| 20 |
+
```bash
|
| 21 |
+
# Set up all API keys automatically
|
| 22 |
+
chmod +x setup-env.sh
|
| 23 |
+
./setup-env.sh
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### 2. Run Python Application
|
| 27 |
+
```bash
|
| 28 |
+
# Activate virtual environment (if using one)
|
| 29 |
+
source venv/bin/activate
|
| 30 |
+
|
| 31 |
+
# Run the main application
|
| 32 |
+
python3 simple_iqkiller.py
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### 3. Run Next.js Application (Vercel-ready)
|
| 36 |
+
```bash
|
| 37 |
+
cd iqkiller-vercel
|
| 38 |
+
npm install
|
| 39 |
+
npm run dev
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
## π Required API Keys
|
| 43 |
+
|
| 44 |
+
- **SERPAPI_KEY**: Job posting search
|
| 45 |
+
- **OPENAI_API_KEY**: Primary LLM processing
|
| 46 |
+
- **ANTHROPIC_API_KEY**: Fallback LLM processing
|
| 47 |
+
- **FIRECRAWL_API_KEY**: Advanced web scraping
|
| 48 |
+
|
| 49 |
+
See `VERCEL_ENV_SETUP.md` for detailed environment variable configuration.
|
| 50 |
+
|
| 51 |
+
## π Project Structure
|
| 52 |
+
|
| 53 |
+
- `simple_iqkiller.py` - Main Python Gradio application
|
| 54 |
+
- `iqkiller-vercel/` - Next.js application for Vercel deployment
|
| 55 |
+
- `env.template` - Environment variables template
|
| 56 |
+
- `setup-env.sh` - Automated environment setup script
|
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π IQKiller Vercel Deployment Roadmap
|
| 2 |
+
|
| 3 |
+
## π Current vs. Target Architecture
|
| 4 |
+
|
| 5 |
+
### Current Architecture (Gradio-based)
|
| 6 |
+
- **Frontend**: Gradio Python components
|
| 7 |
+
- **Backend**: Single Python application with all logic
|
| 8 |
+
- **Deployment**: Traditional server hosting
|
| 9 |
+
- **Limitations**: Not serverless-friendly, monolithic structure
|
| 10 |
+
|
| 11 |
+
### Target Architecture (Vercel-optimized)
|
| 12 |
+
- **Frontend**: Next.js 14 + React + TypeScript
|
| 13 |
+
- **Backend**: Python serverless functions (Vercel API routes)
|
| 14 |
+
- **Database**: Vercel KV (Redis) + PostgreSQL (Neon)
|
| 15 |
+
- **File Storage**: Vercel Blob for PDF uploads
|
| 16 |
+
- **Deployment**: Serverless on Vercel Edge Network
|
| 17 |
+
|
| 18 |
+
## π οΈ Technology Stack
|
| 19 |
+
|
| 20 |
+
### Frontend Stack
|
| 21 |
+
```javascript
|
| 22 |
+
// Core Framework
|
| 23 |
+
Next.js 14 (App Router)
|
| 24 |
+
React 18
|
| 25 |
+
TypeScript
|
| 26 |
+
Tailwind CSS
|
| 27 |
+
|
| 28 |
+
// UI Components
|
| 29 |
+
shadcn/ui components
|
| 30 |
+
Framer Motion (animations)
|
| 31 |
+
React Hook Form (forms)
|
| 32 |
+
Zod (validation)
|
| 33 |
+
|
| 34 |
+
// File Upload
|
| 35 |
+
React Dropzone
|
| 36 |
+
Vercel Blob SDK
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### Backend Stack
|
| 40 |
+
```python
|
| 41 |
+
# API Framework
|
| 42 |
+
FastAPI (for Vercel Python functions)
|
| 43 |
+
Pydantic (data validation)
|
| 44 |
+
|
| 45 |
+
# PDF Processing
|
| 46 |
+
PyPDF2 / pdfplumber
|
| 47 |
+
python-multipart (file uploads)
|
| 48 |
+
|
| 49 |
+
# LLM Integration
|
| 50 |
+
OpenAI SDK
|
| 51 |
+
Anthropic SDK
|
| 52 |
+
httpx (async requests)
|
| 53 |
+
|
| 54 |
+
# Web Scraping
|
| 55 |
+
Firecrawl SDK
|
| 56 |
+
BeautifulSoup4 (fallback)
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### Infrastructure
|
| 60 |
+
```yaml
|
| 61 |
+
# Hosting & CDN
|
| 62 |
+
Vercel (Frontend + Serverless Functions)
|
| 63 |
+
Vercel Edge Network (Global CDN)
|
| 64 |
+
|
| 65 |
+
# Database
|
| 66 |
+
Neon PostgreSQL (managed)
|
| 67 |
+
Vercel KV (Redis cache)
|
| 68 |
+
|
| 69 |
+
# Storage
|
| 70 |
+
Vercel Blob (PDF files)
|
| 71 |
+
Environment Variables (API keys)
|
| 72 |
+
|
| 73 |
+
# Monitoring
|
| 74 |
+
Vercel Analytics
|
| 75 |
+
Sentry (error tracking)
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
## π Project Structure
|
| 79 |
+
|
| 80 |
+
```
|
| 81 |
+
iqkiller-vercel/
|
| 82 |
+
βββ π± Frontend (Next.js)
|
| 83 |
+
β βββ app/
|
| 84 |
+
β β βββ (dashboard)/
|
| 85 |
+
β β β βββ analyze/
|
| 86 |
+
β β β β βββ page.tsx
|
| 87 |
+
β β β β βββ loading.tsx
|
| 88 |
+
β β β βββ results/
|
| 89 |
+
β β β β βββ [id]/
|
| 90 |
+
β β β β βββ page.tsx
|
| 91 |
+
β β β βββ layout.tsx
|
| 92 |
+
β β βββ api/
|
| 93 |
+
β β β βββ auth/
|
| 94 |
+
β β β βββ upload/
|
| 95 |
+
β β β βββ webhook/
|
| 96 |
+
β β βββ globals.css
|
| 97 |
+
β β βββ layout.tsx
|
| 98 |
+
β β βββ page.tsx
|
| 99 |
+
β βββ components/
|
| 100 |
+
β β βββ ui/
|
| 101 |
+
β β β βββ button.tsx
|
| 102 |
+
β β β βββ card.tsx
|
| 103 |
+
β β β βββ input.tsx
|
| 104 |
+
β β β βββ progress.tsx
|
| 105 |
+
β β βββ pdf-upload.tsx
|
| 106 |
+
β β βββ job-url-input.tsx
|
| 107 |
+
β β βββ analysis-results.tsx
|
| 108 |
+
β β βββ header.tsx
|
| 109 |
+
β βββ lib/
|
| 110 |
+
β β βββ utils.ts
|
| 111 |
+
β β βββ api.ts
|
| 112 |
+
β β βββ validations.ts
|
| 113 |
+
β β βββ constants.ts
|
| 114 |
+
β βββ hooks/
|
| 115 |
+
β β βββ use-upload.ts
|
| 116 |
+
β β βββ use-analysis.ts
|
| 117 |
+
β βββ types/
|
| 118 |
+
β βββ api.ts
|
| 119 |
+
β βββ analysis.ts
|
| 120 |
+
βββ π Backend (Python Functions)
|
| 121 |
+
β βββ api/
|
| 122 |
+
β β βββ pdf/
|
| 123 |
+
β β β βββ extract.py
|
| 124 |
+
β β β βββ enhance.py
|
| 125 |
+
β β βββ analysis/
|
| 126 |
+
β β β βββ job.py
|
| 127 |
+
β β β βββ resume.py
|
| 128 |
+
β β β βββ match.py
|
| 129 |
+
β β βββ scraping/
|
| 130 |
+
β β β βββ firecrawl.py
|
| 131 |
+
β β β βββ fallback.py
|
| 132 |
+
β β βββ llm/
|
| 133 |
+
β β βββ openai.py
|
| 134 |
+
β β βββ anthropic.py
|
| 135 |
+
β βββ lib/
|
| 136 |
+
β β βββ database.py
|
| 137 |
+
β β βββ cache.py
|
| 138 |
+
β β βββ utils.py
|
| 139 |
+
β β βββ config.py
|
| 140 |
+
β βββ requirements.txt
|
| 141 |
+
βββ π Database
|
| 142 |
+
β βββ schema.sql
|
| 143 |
+
β βββ migrations/
|
| 144 |
+
β βββ seed.sql
|
| 145 |
+
βββ π Deployment
|
| 146 |
+
β βββ vercel.json
|
| 147 |
+
β βββ .env.example
|
| 148 |
+
β βββ .gitignore
|
| 149 |
+
βββ π Documentation
|
| 150 |
+
βββ README.md
|
| 151 |
+
βββ API.md
|
| 152 |
+
βββ DEPLOYMENT.md
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
## ποΈ Development Roadmap (6-Week Timeline)
|
| 156 |
+
|
| 157 |
+
### Week 1: Foundation & Setup
|
| 158 |
+
**Days 1-2: Project Initialization**
|
| 159 |
+
- [ ] Create Next.js 14 project with TypeScript
|
| 160 |
+
- [ ] Set up Tailwind CSS and shadcn/ui
|
| 161 |
+
- [ ] Configure Vercel deployment structure
|
| 162 |
+
- [ ] Set up development environment
|
| 163 |
+
|
| 164 |
+
**Days 3-4: Core UI Components**
|
| 165 |
+
- [ ] Build header and navigation
|
| 166 |
+
- [ ] Create PDF upload component with drag & drop
|
| 167 |
+
- [ ] Build job URL input with validation
|
| 168 |
+
- [ ] Design analysis results display layout
|
| 169 |
+
|
| 170 |
+
**Days 5-7: Backend Foundation**
|
| 171 |
+
- [ ] Set up Python serverless function structure
|
| 172 |
+
- [ ] Configure FastAPI for Vercel functions
|
| 173 |
+
- [ ] Implement basic PDF processing endpoint
|
| 174 |
+
- [ ] Set up environment variables and configs
|
| 175 |
+
|
| 176 |
+
### Week 2: Core Features - PDF Processing
|
| 177 |
+
**Days 8-10: PDF Upload & Processing**
|
| 178 |
+
- [ ] Implement Vercel Blob storage integration
|
| 179 |
+
- [ ] Build PDF text extraction service
|
| 180 |
+
- [ ] Create LLM enhancement for resume parsing
|
| 181 |
+
- [ ] Add file validation and error handling
|
| 182 |
+
|
| 183 |
+
**Days 11-12: Frontend Integration**
|
| 184 |
+
- [ ] Connect frontend to PDF upload API
|
| 185 |
+
- [ ] Implement real-time upload progress
|
| 186 |
+
- [ ] Add preview of extracted text
|
| 187 |
+
- [ ] Handle upload errors gracefully
|
| 188 |
+
|
| 189 |
+
**Days 13-14: Testing & Optimization**
|
| 190 |
+
- [ ] Test with various PDF formats
|
| 191 |
+
- [ ] Optimize for Vercel function size limits
|
| 192 |
+
- [ ] Implement caching for processed files
|
| 193 |
+
- [ ] Add loading states and animations
|
| 194 |
+
|
| 195 |
+
### Week 3: Job Analysis & Web Scraping
|
| 196 |
+
**Days 15-17: Web Scraping Service**
|
| 197 |
+
- [ ] Implement Firecrawl integration
|
| 198 |
+
- [ ] Build fallback scraping methods
|
| 199 |
+
- [ ] Add URL validation and normalization
|
| 200 |
+
- [ ] Create job content parsing logic
|
| 201 |
+
|
| 202 |
+
**Days 18-19: LLM Integration**
|
| 203 |
+
- [ ] Set up OpenAI and Anthropic clients
|
| 204 |
+
- [ ] Implement resume-job matching algorithm
|
| 205 |
+
- [ ] Build interview question generation
|
| 206 |
+
- [ ] Add comprehensive analysis logic
|
| 207 |
+
|
| 208 |
+
**Days 20-21: Frontend Analysis Interface**
|
| 209 |
+
- [ ] Build job URL input component
|
| 210 |
+
- [ ] Create real-time scraping status
|
| 211 |
+
- [ ] Design analysis results layout
|
| 212 |
+
- [ ] Add interactive elements and animations
|
| 213 |
+
|
| 214 |
+
### Week 4: Advanced Features & Database
|
| 215 |
+
**Days 22-24: Database Integration**
|
| 216 |
+
- [ ] Set up Neon PostgreSQL database
|
| 217 |
+
- [ ] Create schema for users and analyses
|
| 218 |
+
- [ ] Implement Vercel KV for caching
|
| 219 |
+
- [ ] Build data persistence layer
|
| 220 |
+
|
| 221 |
+
**Days 25-26: User Authentication**
|
| 222 |
+
- [ ] Implement NextAuth.js or Clerk
|
| 223 |
+
- [ ] Add user registration/login
|
| 224 |
+
- [ ] Create user dashboard
|
| 225 |
+
- [ ] Implement session management
|
| 226 |
+
|
| 227 |
+
**Days 27-28: Analysis History & Sharing**
|
| 228 |
+
- [ ] Build analysis history page
|
| 229 |
+
- [ ] Implement shareable result links
|
| 230 |
+
- [ ] Add export functionality (PDF, JSON)
|
| 231 |
+
- [ ] Create analytics tracking
|
| 232 |
+
|
| 233 |
+
### Week 5: Performance & Polish
|
| 234 |
+
**Days 29-31: Performance Optimization**
|
| 235 |
+
- [ ] Optimize Vercel function cold starts
|
| 236 |
+
- [ ] Implement edge caching strategies
|
| 237 |
+
- [ ] Compress and optimize images/assets
|
| 238 |
+
- [ ] Add service worker for offline support
|
| 239 |
+
|
| 240 |
+
**Days 32-33: UI/UX Polish**
|
| 241 |
+
- [ ] Refine animations and transitions
|
| 242 |
+
- [ ] Add dark mode support
|
| 243 |
+
- [ ] Implement responsive design improvements
|
| 244 |
+
- [ ] Add accessibility features (ARIA, keyboard nav)
|
| 245 |
+
|
| 246 |
+
**Days 34-35: Error Handling & Monitoring**
|
| 247 |
+
- [ ] Set up Sentry for error tracking
|
| 248 |
+
- [ ] Implement comprehensive error boundaries
|
| 249 |
+
- [ ] Add rate limiting and abuse prevention
|
| 250 |
+
- [ ] Create status page and health checks
|
| 251 |
+
|
| 252 |
+
### Week 6: Deployment & Launch
|
| 253 |
+
**Days 36-38: Production Deployment**
|
| 254 |
+
- [ ] Configure production environment variables
|
| 255 |
+
- [ ] Set up custom domain and SSL
|
| 256 |
+
- [ ] Implement CI/CD pipeline
|
| 257 |
+
- [ ] Configure monitoring and alerts
|
| 258 |
+
|
| 259 |
+
**Days 39-40: Testing & Bug Fixes**
|
| 260 |
+
- [ ] Conduct comprehensive end-to-end testing
|
| 261 |
+
- [ ] Performance testing under load
|
| 262 |
+
- [ ] Security testing and vulnerability scanning
|
| 263 |
+
- [ ] User acceptance testing
|
| 264 |
+
|
| 265 |
+
**Days 41-42: Launch Preparation**
|
| 266 |
+
- [ ] Create documentation and help guides
|
| 267 |
+
- [ ] Set up analytics and tracking
|
| 268 |
+
- [ ] Prepare launch announcement
|
| 269 |
+
- [ ] Go live! π
|
| 270 |
+
|
| 271 |
+
## π° Cost Analysis
|
| 272 |
+
|
| 273 |
+
### Vercel Costs
|
| 274 |
+
```yaml
|
| 275 |
+
Plan: Pro ($20/month)
|
| 276 |
+
- 100GB bandwidth
|
| 277 |
+
- 1000 serverless function executions
|
| 278 |
+
- 100GB Vercel KV storage
|
| 279 |
+
- Custom domains
|
| 280 |
+
- Advanced analytics
|
| 281 |
+
|
| 282 |
+
Additional Costs:
|
| 283 |
+
- Extra function executions: $0.40/1000
|
| 284 |
+
- Extra bandwidth: $0.40/GB
|
| 285 |
+
- Vercel Blob storage: $0.15/GB
|
| 286 |
+
```
|
| 287 |
+
|
| 288 |
+
### Third-Party Services
|
| 289 |
+
```yaml
|
| 290 |
+
Database (Neon): $0-19/month
|
| 291 |
+
File Storage: $0.15/GB (Vercel Blob)
|
| 292 |
+
LLM APIs:
|
| 293 |
+
- OpenAI: $0.0015/1K tokens (GPT-4o-mini)
|
| 294 |
+
- Anthropic: $0.003/1K tokens (Claude-3.5-Sonnet)
|
| 295 |
+
Firecrawl: $20/month (5000 pages)
|
| 296 |
+
Monitoring (Sentry): $26/month (team plan)
|
| 297 |
+
|
| 298 |
+
Total Estimated: $66-85/month
|
| 299 |
+
```
|
| 300 |
+
|
| 301 |
+
## π§ Vercel Configuration
|
| 302 |
+
|
| 303 |
+
### vercel.json
|
| 304 |
+
```json
|
| 305 |
+
{
|
| 306 |
+
"framework": "nextjs",
|
| 307 |
+
"buildCommand": "npm run build",
|
| 308 |
+
"devCommand": "npm run dev",
|
| 309 |
+
"installCommand": "npm install",
|
| 310 |
+
"functions": {
|
| 311 |
+
"api/pdf/extract.py": {
|
| 312 |
+
"runtime": "python3.9",
|
| 313 |
+
"maxDuration": 30
|
| 314 |
+
},
|
| 315 |
+
"api/analysis/match.py": {
|
| 316 |
+
"runtime": "python3.9",
|
| 317 |
+
"maxDuration": 45
|
| 318 |
+
}
|
| 319 |
+
},
|
| 320 |
+
"env": {
|
| 321 |
+
"OPENAI_API_KEY": "@openai-api-key",
|
| 322 |
+
"ANTHROPIC_API_KEY": "@anthropic-api-key",
|
| 323 |
+
"FIRECRAWL_API_KEY": "@firecrawl-api-key",
|
| 324 |
+
"DATABASE_URL": "@database-url"
|
| 325 |
+
},
|
| 326 |
+
"regions": ["iad1", "sfo1"],
|
| 327 |
+
"rewrites": [
|
| 328 |
+
{
|
| 329 |
+
"source": "/api/pdf/:path*",
|
| 330 |
+
"destination": "/api/pdf/:path*"
|
| 331 |
+
}
|
| 332 |
+
]
|
| 333 |
+
}
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
### Environment Variables Setup
|
| 337 |
+
```bash
|
| 338 |
+
# Development
|
| 339 |
+
cp .env.example .env.local
|
| 340 |
+
|
| 341 |
+
# Production (Vercel CLI)
|
| 342 |
+
vercel env add OPENAI_API_KEY
|
| 343 |
+
vercel env add ANTHROPIC_API_KEY
|
| 344 |
+
vercel env add FIRECRAWL_API_KEY
|
| 345 |
+
vercel env add DATABASE_URL
|
| 346 |
+
vercel env add NEXTAUTH_SECRET
|
| 347 |
+
vercel env add NEXTAUTH_URL
|
| 348 |
+
```
|
| 349 |
+
|
| 350 |
+
## π Deployment Strategy
|
| 351 |
+
|
| 352 |
+
### Phase 1: MVP Deployment (Week 4)
|
| 353 |
+
- Basic PDF upload and processing
|
| 354 |
+
- Simple job analysis
|
| 355 |
+
- Core UI components
|
| 356 |
+
- No authentication
|
| 357 |
+
|
| 358 |
+
### Phase 2: Beta Release (Week 5)
|
| 359 |
+
- User authentication
|
| 360 |
+
- Analysis history
|
| 361 |
+
- Enhanced UI/UX
|
| 362 |
+
- Performance optimizations
|
| 363 |
+
|
| 364 |
+
### Phase 3: Production Launch (Week 6)
|
| 365 |
+
- Full feature set
|
| 366 |
+
- Monitoring and analytics
|
| 367 |
+
- Custom domain
|
| 368 |
+
- Marketing and announcements
|
| 369 |
+
|
| 370 |
+
## π Alternative Deployment Options
|
| 371 |
+
|
| 372 |
+
If Vercel limitations become challenging:
|
| 373 |
+
|
| 374 |
+
### Option 1: Railway
|
| 375 |
+
- Better for Python applications
|
| 376 |
+
- PostgreSQL included
|
| 377 |
+
- $5/month starter plan
|
| 378 |
+
- Persistent storage support
|
| 379 |
+
|
| 380 |
+
### Option 2: Render
|
| 381 |
+
- Native Python support
|
| 382 |
+
- PostgreSQL included
|
| 383 |
+
- $7/month starter plan
|
| 384 |
+
- Background jobs support
|
| 385 |
+
|
| 386 |
+
### Option 3: AWS Lambda + CloudFront
|
| 387 |
+
- More control over infrastructure
|
| 388 |
+
- Pay-per-use pricing
|
| 389 |
+
- Requires more DevOps knowledge
|
| 390 |
+
- Better for high-scale applications
|
| 391 |
+
|
| 392 |
+
## π Success Metrics
|
| 393 |
+
|
| 394 |
+
### Technical KPIs
|
| 395 |
+
- Page load time: <2 seconds
|
| 396 |
+
- Function cold start: <1 second
|
| 397 |
+
- PDF processing: <10 seconds
|
| 398 |
+
- Analysis generation: <30 seconds
|
| 399 |
+
- Uptime: >99.9%
|
| 400 |
+
|
| 401 |
+
### User Experience KPIs
|
| 402 |
+
- Time to first analysis: <60 seconds
|
| 403 |
+
- User retention rate: >40%
|
| 404 |
+
- Analysis accuracy: >90%
|
| 405 |
+
- User satisfaction: >4.5/5
|
| 406 |
+
|
| 407 |
+
## π― Next Steps
|
| 408 |
+
|
| 409 |
+
1. **Immediate**: Set up Next.js project and basic structure
|
| 410 |
+
2. **Week 1**: Focus on core PDF upload functionality
|
| 411 |
+
3. **Week 2**: Implement LLM integration for resume enhancement
|
| 412 |
+
4. **Week 3**: Build job scraping and analysis engine
|
| 413 |
+
5. **Week 4**: Add user management and persistence
|
| 414 |
+
6. **Week 5**: Polish and optimize for production
|
| 415 |
+
7. **Week 6**: Deploy and launch!
|
| 416 |
+
|
| 417 |
+
Ready to start building? Let's begin with the Next.js foundation! π
|
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π IQKiller Vercel Deployment Roadmap - Updated
|
| 2 |
+
|
| 3 |
+
> **β¨ Updated with insights from [Vercel AI SDK PDF Support](https://github.com/vercel-labs/ai-sdk-preview-pdf-support)**
|
| 4 |
+
|
| 5 |
+
## π Enhanced Architecture (Vercel AI SDK Powered)
|
| 6 |
+
|
| 7 |
+
### Target Architecture (Optimized)
|
| 8 |
+
- **Frontend**: Next.js 14 + React + TypeScript + Tailwind CSS
|
| 9 |
+
- **PDF Processing**: Vercel AI SDK with native PDF support
|
| 10 |
+
- **Backend**: Next.js API routes (instead of Python serverless)
|
| 11 |
+
- **AI Integration**: Vercel AI SDK with OpenAI + Anthropic
|
| 12 |
+
- **Database**: Vercel KV (Redis) + Neon PostgreSQL
|
| 13 |
+
- **File Storage**: Native browser file handling + AI SDK
|
| 14 |
+
- **Deployment**: Vercel Edge Network
|
| 15 |
+
|
| 16 |
+
## π οΈ Updated Technology Stack
|
| 17 |
+
|
| 18 |
+
### Frontend Stack
|
| 19 |
+
```javascript
|
| 20 |
+
// Core Framework (from Vercel example)
|
| 21 |
+
Next.js 14 (App Router)
|
| 22 |
+
React 18
|
| 23 |
+
TypeScript
|
| 24 |
+
Tailwind CSS
|
| 25 |
+
|
| 26 |
+
// AI Integration
|
| 27 |
+
Vercel AI SDK (@ai-sdk/openai, @ai-sdk/anthropic)
|
| 28 |
+
useObject hook for PDF processing
|
| 29 |
+
useChat for interview interactions
|
| 30 |
+
|
| 31 |
+
// UI Components
|
| 32 |
+
shadcn/ui components
|
| 33 |
+
Framer Motion (animations)
|
| 34 |
+
React Hook Form (forms)
|
| 35 |
+
Zod (validation)
|
| 36 |
+
|
| 37 |
+
// File Upload (Native AI SDK approach)
|
| 38 |
+
Native File API + AI SDK PDF support
|
| 39 |
+
No external PDF processing libraries needed
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
### Backend Stack (Simplified)
|
| 43 |
+
```javascript
|
| 44 |
+
// API Framework (Next.js native)
|
| 45 |
+
Next.js API Routes
|
| 46 |
+
Vercel AI SDK
|
| 47 |
+
TypeScript throughout
|
| 48 |
+
|
| 49 |
+
// AI Providers
|
| 50 |
+
@ai-sdk/openai (GPT-4o-mini)
|
| 51 |
+
@ai-sdk/anthropic (Claude-3.5-Sonnet)
|
| 52 |
+
|
| 53 |
+
// Web Scraping (Keep our approach)
|
| 54 |
+
Firecrawl SDK
|
| 55 |
+
Puppeteer (Vercel-compatible)
|
| 56 |
+
|
| 57 |
+
// Database
|
| 58 |
+
Vercel KV (Redis)
|
| 59 |
+
@vercel/postgres (Neon)
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
## π Updated Project Structure (Based on Vercel Example)
|
| 63 |
+
|
| 64 |
+
```
|
| 65 |
+
iqkiller-vercel/
|
| 66 |
+
βββ π± Frontend (Next.js 14)
|
| 67 |
+
β βββ app/
|
| 68 |
+
β β βββ (dashboard)/
|
| 69 |
+
β β β βββ analyze/
|
| 70 |
+
β β β β βββ page.tsx # Main analysis page
|
| 71 |
+
β β β β βββ loading.tsx
|
| 72 |
+
β β β βββ results/[id]/
|
| 73 |
+
β β β β βββ page.tsx
|
| 74 |
+
β β β βββ layout.tsx
|
| 75 |
+
β β βββ api/
|
| 76 |
+
β β β βββ analyze/
|
| 77 |
+
β β β β βββ route.ts # PDF + Job analysis API
|
| 78 |
+
β β β βββ scrape/
|
| 79 |
+
β β β β βββ route.ts # Job URL scraping API
|
| 80 |
+
β β β βββ chat/
|
| 81 |
+
β β β βββ route.ts # Interview chat API
|
| 82 |
+
β β βββ globals.css
|
| 83 |
+
β β βββ layout.tsx
|
| 84 |
+
β β βββ page.tsx
|
| 85 |
+
β βββ components/
|
| 86 |
+
β β βββ ui/ # shadcn/ui components
|
| 87 |
+
β β βββ pdf-upload.tsx # Based on Vercel example
|
| 88 |
+
β β βββ job-analysis.tsx # Our interview analysis
|
| 89 |
+
β β βββ chat-interface.tsx # Interview chat
|
| 90 |
+
β β βββ results-display.tsx # Analysis results
|
| 91 |
+
β βββ lib/
|
| 92 |
+
β β βββ ai.ts # AI SDK configuration
|
| 93 |
+
β β βββ utils.ts
|
| 94 |
+
β β βββ validations.ts
|
| 95 |
+
β β βββ database.ts # Vercel KV + Postgres
|
| 96 |
+
β βββ hooks/
|
| 97 |
+
β β βββ use-pdf-analysis.ts # Custom hook for PDF + AI
|
| 98 |
+
β β βββ use-job-scraping.ts
|
| 99 |
+
β β βββ use-interview-chat.ts
|
| 100 |
+
β βββ types/
|
| 101 |
+
β βββ analysis.ts
|
| 102 |
+
β βββ interview.ts
|
| 103 |
+
βββ ποΈ Database
|
| 104 |
+
β βββ schema.sql
|
| 105 |
+
β βββ migrations/
|
| 106 |
+
βββ π Deployment
|
| 107 |
+
β βββ vercel.json
|
| 108 |
+
β βββ .env.example
|
| 109 |
+
β βββ next.config.js
|
| 110 |
+
βββ π Documentation
|
| 111 |
+
βββ README.md
|
| 112 |
+
βββ API.md
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
## π― Updated Development Approach
|
| 116 |
+
|
| 117 |
+
### Week 1: Foundation with AI SDK (Days 1-7)
|
| 118 |
+
**Days 1-2: Setup Based on Vercel Example**
|
| 119 |
+
- [ ] Clone and study the Vercel AI SDK PDF example
|
| 120 |
+
- [ ] Create IQKiller project using their structure as template
|
| 121 |
+
- [ ] Set up Vercel AI SDK with OpenAI and Anthropic
|
| 122 |
+
- [ ] Configure shadcn/ui and Tailwind CSS
|
| 123 |
+
|
| 124 |
+
**Days 3-4: PDF Processing with AI SDK**
|
| 125 |
+
- [ ] Implement PDF upload using Vercel AI SDK approach
|
| 126 |
+
- [ ] Create `useObject` hook for resume analysis
|
| 127 |
+
- [ ] Build PDF analysis component based on their example
|
| 128 |
+
- [ ] Add real-time processing feedback
|
| 129 |
+
|
| 130 |
+
**Days 5-7: Job Analysis Integration**
|
| 131 |
+
- [ ] Extend AI SDK usage for job description analysis
|
| 132 |
+
- [ ] Implement job URL scraping API route
|
| 133 |
+
- [ ] Create comprehensive analysis combining resume + job
|
| 134 |
+
- [ ] Add interview question generation
|
| 135 |
+
|
| 136 |
+
### Week 2: Enhanced Features (Days 8-14)
|
| 137 |
+
- [ ] Interview chat interface using `useChat` hook
|
| 138 |
+
- [ ] Salary negotiation scenarios
|
| 139 |
+
- [ ] Analysis history with Vercel KV
|
| 140 |
+
- [ ] Real-time collaboration features
|
| 141 |
+
|
| 142 |
+
### Week 3: Polish & Deploy (Days 15-21)
|
| 143 |
+
- [ ] Performance optimization
|
| 144 |
+
- [ ] Error handling and loading states
|
| 145 |
+
- [ ] Vercel deployment configuration
|
| 146 |
+
- [ ] Production testing and launch
|
| 147 |
+
|
| 148 |
+
## π» **Let's Start Building Now!**
|
| 149 |
+
|
| 150 |
+
Based on the Vercel AI SDK example, here's our immediate action plan:
|
| 151 |
+
|
| 152 |
+
### Option 1: Clone and Modify Vercel Example
|
| 153 |
+
```bash
|
| 154 |
+
# Start with proven foundation
|
| 155 |
+
npx create-next-app --example https://github.com/vercel-labs/ai-sdk-preview-pdf-support iqkiller-vercel
|
| 156 |
+
cd iqkiller-vercel
|
| 157 |
+
|
| 158 |
+
# Customize for IQKiller features
|
| 159 |
+
# Add job scraping, interview analysis, etc.
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### Option 2: Use Our Setup Script with AI SDK Integration
|
| 163 |
+
```bash
|
| 164 |
+
# Use our comprehensive setup
|
| 165 |
+
./setup-vercel-project.sh
|
| 166 |
+
|
| 167 |
+
# Then integrate Vercel AI SDK approach
|
| 168 |
+
cd iqkiller-vercel
|
| 169 |
+
npm install ai @ai-sdk/openai @ai-sdk/anthropic
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
## π **Vercel Deployment Configuration**
|
| 173 |
+
|
| 174 |
+
### package.json (AI SDK Integration)
|
| 175 |
+
```json
|
| 176 |
+
{
|
| 177 |
+
"dependencies": {
|
| 178 |
+
"ai": "^3.4.7",
|
| 179 |
+
"@ai-sdk/openai": "^1.0.0",
|
| 180 |
+
"@ai-sdk/anthropic": "^1.0.0",
|
| 181 |
+
"next": "14.2.0",
|
| 182 |
+
"react": "^18.3.0",
|
| 183 |
+
"typescript": "^5.0.0",
|
| 184 |
+
"tailwindcss": "^3.4.0",
|
| 185 |
+
"@radix-ui/react-slot": "^1.0.2",
|
| 186 |
+
"class-variance-authority": "^0.7.0",
|
| 187 |
+
"clsx": "^2.0.0",
|
| 188 |
+
"lucide-react": "^0.263.1",
|
| 189 |
+
"tailwind-merge": "^1.14.0"
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
### Environment Variables (.env.local)
|
| 195 |
+
```bash
|
| 196 |
+
# AI Providers (matching Vercel example format)
|
| 197 |
+
OPENAI_API_KEY=sk-proj-your-openai-key
|
| 198 |
+
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key
|
| 199 |
+
|
| 200 |
+
# Additional IQKiller services
|
| 201 |
+
FIRECRAWL_API_KEY=fc-your-firecrawl-key
|
| 202 |
+
SERPAPI_KEY=your-serpapi-key
|
| 203 |
+
|
| 204 |
+
# Vercel Storage
|
| 205 |
+
KV_URL=your-vercel-kv-url
|
| 206 |
+
KV_REST_API_URL=your-kv-rest-url
|
| 207 |
+
KV_REST_API_TOKEN=your-kv-token
|
| 208 |
+
KV_REST_API_READ_ONLY_TOKEN=your-kv-readonly-token
|
| 209 |
+
|
| 210 |
+
POSTGRES_URL=your-neon-postgres-url
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
## π― **Which Approach Do You Prefer?**
|
| 214 |
+
|
| 215 |
+
1. **Fast Start**: Clone Vercel example and customize for IQKiller
|
| 216 |
+
2. **Full Control**: Use our setup script and integrate AI SDK step by step
|
| 217 |
+
3. **Hybrid**: Start with Vercel example structure but add our comprehensive features
|
| 218 |
+
|
| 219 |
+
The Vercel AI SDK approach will give us:
|
| 220 |
+
- β
**Native PDF support** - no custom parsing needed
|
| 221 |
+
- β
**Proven deployment** - optimized for Vercel from day 1
|
| 222 |
+
- β
**Type safety** - full TypeScript integration
|
| 223 |
+
- β
**Real-time features** - streaming responses and chat
|
| 224 |
+
- β
**Edge runtime** - faster global performance
|
| 225 |
+
|
| 226 |
+
**Ready to start building? Which approach should we take first?** π
|
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Vercel Environment Variables Setup
|
| 2 |
+
|
| 3 |
+
## π Environment Variables Required
|
| 4 |
+
|
| 5 |
+
For IQKiller to work properly on Vercel, you need to configure the following environment variables in your Vercel dashboard:
|
| 6 |
+
|
| 7 |
+
### Required API Keys
|
| 8 |
+
|
| 9 |
+
| Variable Name | Description | Where to Get |
|
| 10 |
+
|---------------|-------------|--------------|
|
| 11 |
+
| `SERPAPI_KEY` | Search API for job posting data | [SerpAPI](https://serpapi.com/) |
|
| 12 |
+
| `OPENAI_API_KEY` | OpenAI API for LLM processing (primary) | [OpenAI Platform](https://platform.openai.com/) |
|
| 13 |
+
| `ANTHROPIC_API_KEY` | Anthropic Claude API (fallback) | [Anthropic Console](https://console.anthropic.com/) |
|
| 14 |
+
| `FIRECRAWL_API_KEY` | Firecrawl API for web scraping | [Firecrawl](https://firecrawl.dev/) |
|
| 15 |
+
|
| 16 |
+
## π Setting Up Environment Variables on Vercel
|
| 17 |
+
|
| 18 |
+
### Method 1: Vercel Dashboard (Recommended)
|
| 19 |
+
|
| 20 |
+
1. Go to your [Vercel Dashboard](https://vercel.com/dashboard)
|
| 21 |
+
2. Select your IQKiller project
|
| 22 |
+
3. Go to **Settings** β **Environment Variables**
|
| 23 |
+
4. Add each variable:
|
| 24 |
+
- **Name**: `SERPAPI_KEY`
|
| 25 |
+
- **Value**: `860035cdbc22f1452e9a5313bc595ff0a41781b922dce50e0f93a83869f08319`
|
| 26 |
+
- **Environment**: Select all (Production, Preview, Development)
|
| 27 |
+
- Click **Save**
|
| 28 |
+
|
| 29 |
+
5. Repeat for all variables:
|
| 30 |
+
```
|
| 31 |
+
SERPAPI_KEY=860035cdbc22f1452e9a5313bc595ff0a41781b922dce50e0f93a83869f08319
|
| 32 |
+
OPENAI_API_KEY=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
|
| 33 |
+
ANTHROPIC_API_KEY=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
|
| 34 |
+
FIRECRAWL_API_KEY=fc-08e46542bfcc4ca7a953fac4dea4237e
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Method 2: Vercel CLI
|
| 38 |
+
|
| 39 |
+
If you have Vercel CLI installed:
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
cd iqkiller-vercel
|
| 43 |
+
vercel env add SERPAPI_KEY
|
| 44 |
+
# Paste: 860035cdbc22f1452e9a5313bc595ff0a41781b922dce50e0f93a83869f08319
|
| 45 |
+
|
| 46 |
+
vercel env add OPENAI_API_KEY
|
| 47 |
+
# Paste: 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
|
| 48 |
+
|
| 49 |
+
vercel env add ANTHROPIC_API_KEY
|
| 50 |
+
# Paste: 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
|
| 51 |
+
|
| 52 |
+
vercel env add FIRECRAWL_API_KEY
|
| 53 |
+
# Paste: fc-08e46542bfcc4ca7a953fac4dea4237e
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## π Local Development Setup
|
| 57 |
+
|
| 58 |
+
For local development, run the setup script:
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
chmod +x setup-env.sh
|
| 62 |
+
./setup-env.sh
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
This will create:
|
| 66 |
+
- `.env` file for the Python project
|
| 67 |
+
- `.env.local` file for the Next.js project
|
| 68 |
+
|
| 69 |
+
## π Security Notes
|
| 70 |
+
|
| 71 |
+
- β
`.env` files are in `.gitignore` - they won't be committed
|
| 72 |
+
- β
Never share API keys in public repositories
|
| 73 |
+
- β
Use different API keys for development/production if needed
|
| 74 |
+
- β
Regularly rotate API keys for security
|
| 75 |
+
|
| 76 |
+
## β
Verification
|
| 77 |
+
|
| 78 |
+
After setting up environment variables:
|
| 79 |
+
|
| 80 |
+
1. **Local**: Run `./setup-env.sh` and test with `npm run dev`
|
| 81 |
+
2. **Vercel**: Deploy and check the application logs for any missing environment variable errors
|
| 82 |
+
|
| 83 |
+
## π Troubleshooting
|
| 84 |
+
|
| 85 |
+
**Error: Missing API Key**
|
| 86 |
+
- Check that all 4 environment variables are set in Vercel dashboard
|
| 87 |
+
- Ensure variable names match exactly (case-sensitive)
|
| 88 |
+
- Redeploy after adding variables
|
| 89 |
+
|
| 90 |
+
**Error: Invalid API Key**
|
| 91 |
+
- Verify API keys are active and have sufficient credits
|
| 92 |
+
- Check for any extra spaces or characters when copying
|
| 93 |
+
|
| 94 |
+
**Local development not working**
|
| 95 |
+
- Run `./setup-env.sh` to regenerate `.env` files
|
| 96 |
+
- Check that `.env` file exists and contains all keys
|
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client'
|
| 2 |
+
|
| 3 |
+
import React, { useState } from 'react'
|
| 4 |
+
import { Button } from '@/components/ui/button'
|
| 5 |
+
import { Input } from '@/components/ui/input'
|
| 6 |
+
import { Label } from '@/components/ui/label'
|
| 7 |
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
| 8 |
+
import { Loader2, Link as LinkIcon, FileText } from 'lucide-react'
|
| 9 |
+
|
| 10 |
+
interface JobAnalysisProps {
|
| 11 |
+
onJobData: (data: any) => void
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export function JobAnalysis({ onJobData }: JobAnalysisProps) {
|
| 15 |
+
const [jobUrl, setJobUrl] = useState('')
|
| 16 |
+
const [jobText, setJobText] = useState('')
|
| 17 |
+
const [isLoading, setIsLoading] = useState(false)
|
| 18 |
+
const [mode, setMode] = useState<'url' | 'text'>('url')
|
| 19 |
+
|
| 20 |
+
const handleUrlScraping = async () => {
|
| 21 |
+
if (!jobUrl.trim()) return
|
| 22 |
+
|
| 23 |
+
setIsLoading(true)
|
| 24 |
+
try {
|
| 25 |
+
const response = await fetch('/api/scrape', {
|
| 26 |
+
method: 'POST',
|
| 27 |
+
headers: { 'Content-Type': 'application/json' },
|
| 28 |
+
body: JSON.stringify({ url: jobUrl })
|
| 29 |
+
})
|
| 30 |
+
|
| 31 |
+
const data = await response.json()
|
| 32 |
+
if (data.success) {
|
| 33 |
+
onJobData(data.jobData)
|
| 34 |
+
}
|
| 35 |
+
} catch (error) {
|
| 36 |
+
console.error('Error scraping job:', error)
|
| 37 |
+
} finally {
|
| 38 |
+
setIsLoading(false)
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
const handleTextSubmit = () => {
|
| 43 |
+
if (!jobText.trim()) return
|
| 44 |
+
|
| 45 |
+
onJobData({
|
| 46 |
+
description: jobText,
|
| 47 |
+
source: 'manual_text'
|
| 48 |
+
})
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
return (
|
| 52 |
+
<Card>
|
| 53 |
+
<CardHeader>
|
| 54 |
+
<CardTitle>Job Information</CardTitle>
|
| 55 |
+
<CardDescription>
|
| 56 |
+
Add job posting via URL or paste the description directly
|
| 57 |
+
</CardDescription>
|
| 58 |
+
</CardHeader>
|
| 59 |
+
<CardContent className="space-y-4">
|
| 60 |
+
<div className="flex space-x-2">
|
| 61 |
+
<Button
|
| 62 |
+
variant={mode === 'url' ? 'default' : 'outline'}
|
| 63 |
+
onClick={() => setMode('url')}
|
| 64 |
+
className="flex-1"
|
| 65 |
+
>
|
| 66 |
+
<LinkIcon className="w-4 h-4 mr-2" />
|
| 67 |
+
URL
|
| 68 |
+
</Button>
|
| 69 |
+
<Button
|
| 70 |
+
variant={mode === 'text' ? 'default' : 'outline'}
|
| 71 |
+
onClick={() => setMode('text')}
|
| 72 |
+
className="flex-1"
|
| 73 |
+
>
|
| 74 |
+
<FileText className="w-4 h-4 mr-2" />
|
| 75 |
+
Text
|
| 76 |
+
</Button>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
{mode === 'url' ? (
|
| 80 |
+
<div className="space-y-3">
|
| 81 |
+
<Label htmlFor="job-url">Job Posting URL</Label>
|
| 82 |
+
<Input
|
| 83 |
+
id="job-url"
|
| 84 |
+
type="url"
|
| 85 |
+
placeholder="https://linkedin.com/jobs/view/123456"
|
| 86 |
+
value={jobUrl}
|
| 87 |
+
onChange={(e) => setJobUrl(e.target.value)}
|
| 88 |
+
/>
|
| 89 |
+
<Button
|
| 90 |
+
onClick={handleUrlScraping}
|
| 91 |
+
disabled={isLoading || !jobUrl.trim()}
|
| 92 |
+
className="w-full"
|
| 93 |
+
>
|
| 94 |
+
{isLoading && <Loader2 className="w-4 h-4 mr-2 animate-spin" />}
|
| 95 |
+
Scrape Job Details
|
| 96 |
+
</Button>
|
| 97 |
+
</div>
|
| 98 |
+
) : (
|
| 99 |
+
<div className="space-y-3">
|
| 100 |
+
<Label htmlFor="job-text">Job Description</Label>
|
| 101 |
+
<textarea
|
| 102 |
+
id="job-text"
|
| 103 |
+
className="w-full min-h-[200px] p-3 border rounded-md resize-y"
|
| 104 |
+
placeholder="Paste the complete job description here..."
|
| 105 |
+
value={jobText}
|
| 106 |
+
onChange={(e) => setJobText(e.target.value)}
|
| 107 |
+
/>
|
| 108 |
+
<Button
|
| 109 |
+
onClick={handleTextSubmit}
|
| 110 |
+
disabled={!jobText.trim()}
|
| 111 |
+
className="w-full"
|
| 112 |
+
>
|
| 113 |
+
Use This Job Description
|
| 114 |
+
</Button>
|
| 115 |
+
</div>
|
| 116 |
+
)}
|
| 117 |
+
</CardContent>
|
| 118 |
+
</Card>
|
| 119 |
+
)
|
| 120 |
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IQKiller API Keys - Template for Local Development
|
| 2 |
+
# Copy this file to .env and fill in your actual API keys
|
| 3 |
+
|
| 4 |
+
# Search API for job posting data
|
| 5 |
+
SERPAPI_KEY=your_serpapi_key_here
|
| 6 |
+
|
| 7 |
+
# OpenAI API for LLM processing (primary)
|
| 8 |
+
OPENAI_API_KEY=your_openai_api_key_here
|
| 9 |
+
|
| 10 |
+
# Anthropic Claude API for LLM processing (fallback)
|
| 11 |
+
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
| 12 |
+
|
| 13 |
+
# Firecrawl API for enhanced web scraping
|
| 14 |
+
FIRECRAWL_API_KEY=your_firecrawl_api_key_here
|
| 15 |
+
|
| 16 |
+
# Optional: Set to development for local testing
|
| 17 |
+
NODE_ENV=development
|
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "π PDF Resume Text Extractor for IQKiller"
|
| 4 |
+
echo "=========================================="
|
| 5 |
+
echo ""
|
| 6 |
+
echo "This tool extracts text from your PDF resume so you can copy-paste it into IQKiller."
|
| 7 |
+
echo ""
|
| 8 |
+
|
| 9 |
+
# Check if PDF file is provided as argument
|
| 10 |
+
if [ "$1" != "" ]; then
|
| 11 |
+
echo "π Processing: $1"
|
| 12 |
+
python3 pdf_upload_tool.py "$1"
|
| 13 |
+
else
|
| 14 |
+
echo "π Drag and drop your PDF file here, or provide the path:"
|
| 15 |
+
echo ""
|
| 16 |
+
echo "Usage:"
|
| 17 |
+
echo " ./extract_pdf_resume.sh /path/to/your/resume.pdf"
|
| 18 |
+
echo ""
|
| 19 |
+
echo "Or run the interactive version:"
|
| 20 |
+
python3 pdf_upload_tool.py
|
| 21 |
+
fi
|
| 22 |
+
|
| 23 |
+
echo ""
|
| 24 |
+
echo "π‘ Copy the extracted text above and paste it into IQKiller at:"
|
| 25 |
+
echo " http://localhost:7860"
|
| 26 |
+
echo ""
|
|
@@ -1,197 +1,153 @@
|
|
|
|
|
| 1 |
"""
|
| 2 |
-
|
| 3 |
-
Generates
|
| 4 |
"""
|
| 5 |
|
| 6 |
import re
|
| 7 |
-
import
|
| 8 |
-
from typing import Dict, List,
|
| 9 |
from dataclasses import dataclass
|
| 10 |
|
| 11 |
@dataclass
|
| 12 |
-
class
|
| 13 |
-
"""Structured interview guide
|
| 14 |
-
|
|
|
|
| 15 |
match_score: float
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
|
| 26 |
-
class
|
| 27 |
-
"""
|
| 28 |
|
| 29 |
def __init__(self):
|
| 30 |
self.tech_skills = [
|
| 31 |
"Python", "JavaScript", "Java", "SQL", "React", "Node.js",
|
| 32 |
"AWS", "Docker", "Git", "Machine Learning", "Data Science",
|
| 33 |
"Analytics", "R", "Tableau", "Pandas", "NumPy", "TensorFlow",
|
| 34 |
-
"Kubernetes", "MongoDB", "PostgreSQL", "Redis", "Apache Spark"
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
self.soft_skills = [
|
| 38 |
-
"Leadership", "Communication", "Project Management", "Team Work",
|
| 39 |
-
"Problem Solving", "Critical Thinking", "Adaptability", "Creativity"
|
| 40 |
]
|
| 41 |
|
| 42 |
-
self.
|
| 43 |
-
"spotify":
|
| 44 |
-
"google":
|
| 45 |
-
"amazon":
|
| 46 |
-
"microsoft":
|
| 47 |
-
"meta":
|
| 48 |
-
"apple":
|
| 49 |
-
"netflix":
|
| 50 |
}
|
| 51 |
|
| 52 |
-
def
|
| 53 |
-
"""
|
| 54 |
-
if not resume_text.strip():
|
| 55 |
-
return {
|
| 56 |
-
"skills": [],
|
| 57 |
-
"experience": 0,
|
| 58 |
-
"roles": [],
|
| 59 |
-
"projects": [],
|
| 60 |
-
"education": "Unknown",
|
| 61 |
-
"achievements": []
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
# Extract skills
|
| 65 |
-
found_skills = []
|
| 66 |
-
for skill in self.tech_skills + self.soft_skills:
|
| 67 |
-
if skill.lower() in resume_text.lower():
|
| 68 |
-
found_skills.append(skill)
|
| 69 |
-
|
| 70 |
-
# Extract experience
|
| 71 |
-
experience_patterns = [
|
| 72 |
-
r'(\d+)[\s\+]*years?\s+(?:of\s+)?experience',
|
| 73 |
-
r'(\d+)\+?\s*years?\s+(?:in|with)',
|
| 74 |
-
r'(\d+)\s*years?\s+(?:working|professional)'
|
| 75 |
-
]
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
if match:
|
| 81 |
-
experience_years = int(match.group(1))
|
| 82 |
-
break
|
| 83 |
|
| 84 |
-
# Extract
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
-
#
|
| 97 |
education_patterns = [
|
| 98 |
-
r'(master|
|
| 99 |
-
r'(
|
| 100 |
-
r'(m\.s\.|b\.s\.|ph\.d\.)\s+([a-zA-Z\s]+)'
|
| 101 |
]
|
| 102 |
|
| 103 |
-
education = "
|
| 104 |
for pattern in education_patterns:
|
| 105 |
match = re.search(pattern, resume_text, re.IGNORECASE)
|
| 106 |
if match:
|
| 107 |
-
|
| 108 |
-
if degree.lower() in ['ms', 'm.s.', 'master']:
|
| 109 |
-
education = "Master of Science"
|
| 110 |
-
elif degree.lower() in ['phd', 'ph.d.', 'doctorate']:
|
| 111 |
-
education = "PhD"
|
| 112 |
break
|
| 113 |
|
| 114 |
-
|
| 115 |
-
project_patterns = [
|
| 116 |
-
r'(built|developed|created|designed)\s+([a-zA-Z\s]+?)(?:\s+using|\s+with|\s+for)',
|
| 117 |
-
r'project[:\s]+([a-zA-Z\s]+)',
|
| 118 |
-
r'([a-zA-Z\s]+?)\s+(?:project|application|system|platform)'
|
| 119 |
-
]
|
| 120 |
-
|
| 121 |
-
projects = []
|
| 122 |
-
for pattern in project_patterns:
|
| 123 |
-
matches = re.findall(pattern, resume_text, re.IGNORECASE)
|
| 124 |
-
for match in matches[:3]: # limit to 3 projects
|
| 125 |
-
if isinstance(match, tuple) and len(match) > 0:
|
| 126 |
-
projects.append(match[1] if len(match) > 1 else match[0])
|
| 127 |
-
elif isinstance(match, str) and match.strip():
|
| 128 |
-
projects.append(match)
|
| 129 |
-
|
| 130 |
-
return {
|
| 131 |
-
"skills": found_skills,
|
| 132 |
-
"experience": experience_years,
|
| 133 |
-
"roles": found_roles or ["Professional"],
|
| 134 |
-
"projects": projects,
|
| 135 |
-
"education": education,
|
| 136 |
-
"achievements": []
|
| 137 |
-
}
|
| 138 |
|
| 139 |
-
def
|
| 140 |
-
"""
|
| 141 |
-
if not job_text.strip():
|
| 142 |
-
return {
|
| 143 |
-
"company": "Unknown Company",
|
| 144 |
-
"role": "Unknown Role",
|
| 145 |
-
"required_skills": [],
|
| 146 |
-
"location": "Remote",
|
| 147 |
-
"industry": "Technology",
|
| 148 |
-
"seniority": "Mid-level"
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
# Extract company
|
| 152 |
company_patterns = [
|
| 153 |
-
r'at\s+([A-Z][a-zA-Z\s
|
| 154 |
-
r'([A-Z][a-zA-Z\s
|
| 155 |
-
r'join\s+([A-Z][a-zA-Z\s
|
| 156 |
-
r'company:\s*([A-Z][a-zA-Z\s&]+?)(?:\s|$|,|\n)'
|
| 157 |
]
|
| 158 |
|
| 159 |
-
company = "
|
| 160 |
for pattern in company_patterns:
|
| 161 |
match = re.search(pattern, job_text, re.IGNORECASE)
|
| 162 |
if match:
|
| 163 |
company = match.group(1).strip()
|
| 164 |
break
|
| 165 |
|
| 166 |
-
# Detect specific companies
|
| 167 |
-
detected_company = None
|
| 168 |
-
for comp_name, keywords in self.company_patterns.items():
|
| 169 |
-
if any(keyword in job_text.lower() for keyword in keywords):
|
| 170 |
-
detected_company = comp_name
|
| 171 |
-
break
|
| 172 |
-
|
| 173 |
-
if detected_company:
|
| 174 |
-
company_names = {
|
| 175 |
-
"spotify": "Spotify",
|
| 176 |
-
"google": "Google",
|
| 177 |
-
"amazon": "Amazon",
|
| 178 |
-
"microsoft": "Microsoft",
|
| 179 |
-
"meta": "Meta",
|
| 180 |
-
"apple": "Apple",
|
| 181 |
-
"netflix": "Netflix"
|
| 182 |
-
}
|
| 183 |
-
company = company_names.get(detected_company, company)
|
| 184 |
-
|
| 185 |
# Extract role
|
| 186 |
role_patterns = [
|
| 187 |
-
r'(senior\s+)?(data\s+scientist|software\s+engineer|product\s+manager|
|
| 188 |
r'position[:\s]+(senior\s+)?([a-zA-Z\s]+)',
|
| 189 |
r'role[:\s]+(senior\s+)?([a-zA-Z\s]+)',
|
| 190 |
-
r'we\'re\s+looking\s+for\s+(?:a\s+)?(senior\s+)?([a-zA-Z\s]+)'
|
| 191 |
]
|
| 192 |
|
| 193 |
-
role = "
|
| 194 |
-
seniority = "Mid-level"
|
| 195 |
for pattern in role_patterns:
|
| 196 |
match = re.search(pattern, job_text, re.IGNORECASE)
|
| 197 |
if match:
|
|
@@ -199,513 +155,395 @@ class ComprehensiveAnalyzer:
|
|
| 199 |
if len(groups) >= 2:
|
| 200 |
senior_part = groups[0] or ""
|
| 201 |
role_part = groups[1] or groups[-1]
|
| 202 |
-
if "senior" in senior_part.lower():
|
| 203 |
-
seniority = "Senior"
|
| 204 |
role = (senior_part + role_part).strip().title()
|
| 205 |
break
|
| 206 |
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
location_patterns = [
|
| 215 |
-
r'location[:\s]+([a-zA-Z\s,]+)',
|
| 216 |
-
r'([a-zA-Z\s]+),\s*([A-Z]{2})',
|
| 217 |
-
r'(remote|hybrid|on-site)',
|
| 218 |
-
r'(san francisco|new york|seattle|austin|boston|chicago)'
|
| 219 |
]
|
| 220 |
|
| 221 |
-
|
| 222 |
-
for
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
location = match.group(1).strip().title()
|
| 226 |
-
break
|
| 227 |
|
| 228 |
-
|
| 229 |
-
industry = "Technology"
|
| 230 |
-
if "spotify" in company.lower() or "music" in job_text.lower():
|
| 231 |
-
industry = "Music & Entertainment"
|
| 232 |
-
elif "finance" in job_text.lower() or "bank" in job_text.lower():
|
| 233 |
-
industry = "Finance"
|
| 234 |
-
elif "healthcare" in job_text.lower() or "medical" in job_text.lower():
|
| 235 |
-
industry = "Healthcare"
|
| 236 |
-
|
| 237 |
-
return {
|
| 238 |
-
"company": company,
|
| 239 |
-
"role": role,
|
| 240 |
-
"required_skills": required_skills,
|
| 241 |
-
"location": location,
|
| 242 |
-
"industry": industry,
|
| 243 |
-
"seniority": seniority
|
| 244 |
-
}
|
| 245 |
|
| 246 |
-
def
|
| 247 |
-
"""Calculate
|
| 248 |
-
|
| 249 |
-
|
| 250 |
|
| 251 |
-
|
| 252 |
-
|
|
|
|
| 253 |
|
| 254 |
-
#
|
| 255 |
-
|
| 256 |
-
|
|
|
|
| 257 |
|
| 258 |
-
#
|
| 259 |
-
|
| 260 |
|
| 261 |
-
#
|
| 262 |
-
|
|
|
|
| 263 |
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
# Calculate final score
|
| 268 |
-
final_score = (
|
| 269 |
-
skill_score * 0.5 +
|
| 270 |
-
experience_score * 0.3 +
|
| 271 |
-
education_boost * 0.1 +
|
| 272 |
-
role_relevance * 0.1
|
| 273 |
-
)
|
| 274 |
-
|
| 275 |
-
return min(max(final_score, 40), 97)
|
| 276 |
|
| 277 |
-
def
|
| 278 |
-
"""
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
{
|
| 286 |
-
"question": f"How would you design a system to handle {job_data['role'].lower()} requirements at scale?",
|
| 287 |
-
"why": f"This tests your system design skills and understanding of {job_data['role']} challenges at {job_data['company']}.",
|
| 288 |
-
"approach": "Start with requirements gathering, then discuss architecture, data flow, and scalability considerations.",
|
| 289 |
-
"key_points": [
|
| 290 |
-
"System architecture understanding",
|
| 291 |
-
"Scalability considerations",
|
| 292 |
-
"Technology trade-offs"
|
| 293 |
-
]
|
| 294 |
-
},
|
| 295 |
-
{
|
| 296 |
-
"question": f"Given your experience with {skills[0] if skills else 'your main technology'}, how would you approach solving a complex data problem?",
|
| 297 |
-
"why": f"This question assesses your problem-solving approach and technical depth in {skills[0] if skills else 'your core technology'}.",
|
| 298 |
-
"approach": "Break down the problem, discuss your methodology, mention specific tools and techniques you'd use.",
|
| 299 |
-
"key_points": [
|
| 300 |
-
f"Deep knowledge of {skills[0] if skills else 'core technology'}",
|
| 301 |
-
"Problem decomposition skills",
|
| 302 |
-
"Practical application experience"
|
| 303 |
-
]
|
| 304 |
-
},
|
| 305 |
-
{
|
| 306 |
-
"question": f"Tell me about a time you had to optimize performance in a {job_data['industry'].lower()} context.",
|
| 307 |
-
"why": f"Performance optimization is crucial in {job_data['industry']} and shows your ability to work under constraints.",
|
| 308 |
-
"approach": "Use the STAR method: describe the situation, task, actions taken, and measurable results.",
|
| 309 |
-
"key_points": [
|
| 310 |
-
"Performance optimization techniques",
|
| 311 |
-
"Measurement and monitoring",
|
| 312 |
-
"Industry-specific challenges"
|
| 313 |
-
]
|
| 314 |
-
}
|
| 315 |
-
]
|
| 316 |
-
|
| 317 |
-
return base_questions
|
| 318 |
|
| 319 |
-
def
|
| 320 |
-
"""Generate
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
"key_points": [
|
| 327 |
-
"Rapid learning ability",
|
| 328 |
-
"Practical application skills",
|
| 329 |
-
"Project impact measurement"
|
| 330 |
-
]
|
| 331 |
-
},
|
| 332 |
-
{
|
| 333 |
-
"question": f"Can you describe a challenging project where you had to collaborate with cross-functional teams?",
|
| 334 |
-
"why": f"Collaboration is essential in {job_data['industry']} environments and shows your teamwork skills.",
|
| 335 |
-
"approach": "Highlight your communication skills, conflict resolution, and ability to work with diverse stakeholders.",
|
| 336 |
-
"key_points": [
|
| 337 |
-
"Cross-functional collaboration",
|
| 338 |
-
"Communication effectiveness",
|
| 339 |
-
"Stakeholder management"
|
| 340 |
-
]
|
| 341 |
-
},
|
| 342 |
-
{
|
| 343 |
-
"question": f"Tell me about a time when you had to handle a significant technical challenge or failure.",
|
| 344 |
-
"why": f"This shows your problem-solving skills and resilience, important for {job_data['role']} responsibilities.",
|
| 345 |
-
"approach": "Focus on your analytical approach, the steps you took to resolve the issue, and lessons learned.",
|
| 346 |
-
"key_points": [
|
| 347 |
-
"Problem-solving methodology",
|
| 348 |
-
"Resilience and adaptability",
|
| 349 |
-
"Learning from failures"
|
| 350 |
-
]
|
| 351 |
-
}
|
| 352 |
]
|
| 353 |
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
-
def
|
| 357 |
-
"""
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
f"Knowledge of {job_data['company']}'s mission",
|
| 365 |
-
f"Understanding of {job_data['industry']} trends",
|
| 366 |
-
"Personal alignment with company values"
|
| 367 |
-
]
|
| 368 |
-
},
|
| 369 |
-
{
|
| 370 |
-
"question": f"How would you approach the unique challenges of {job_data['role']} in a {job_data['industry'].lower()} environment?",
|
| 371 |
-
"why": f"This tests your understanding of industry-specific challenges and your strategic thinking.",
|
| 372 |
-
"approach": "Discuss industry trends, specific challenges, and how your background prepares you to address them.",
|
| 373 |
-
"key_points": [
|
| 374 |
-
f"Industry knowledge ({job_data['industry']})",
|
| 375 |
-
"Strategic thinking",
|
| 376 |
-
"Role-specific expertise"
|
| 377 |
-
]
|
| 378 |
-
}
|
| 379 |
]
|
| 380 |
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
-
def
|
| 384 |
-
"""
|
| 385 |
-
|
| 386 |
|
| 387 |
-
#
|
| 388 |
-
|
| 389 |
|
| 390 |
-
|
|
|
|
|
|
|
| 391 |
|
| 392 |
-
|
| 393 |
-
|
| 394 |
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
{job_data['role']} interview at {job_data['company']} is an excellent opportunity for you, given your {resume_data['education']} and {resume_data['experience']} years of experience.
|
| 398 |
-
With your background in {', '.join(resume_data['skills'][:3]) if resume_data['skills'] else 'technical skills'}, you are well-positioned to contribute to {job_data['company']}'s mission.
|
| 399 |
-
Your {resume_data['experience']} years of experience and proven track record make you a strong candidate.
|
| 400 |
-
Approach this interview with confidenceβyour skills align well with what they're looking for.
|
| 401 |
-
"""
|
| 402 |
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
-
#
|
| 415 |
-
|
| 416 |
-
"typical_rounds": "3 to 5 rounds",
|
| 417 |
-
"interview_types": [
|
| 418 |
-
"Phone Screen: Initial HR screening and basic qualifications",
|
| 419 |
-
"Technical Interview: Focus on technical skills and problem-solving",
|
| 420 |
-
"Behavioral Interview: Past experiences and cultural fit",
|
| 421 |
-
"Final Interview: Senior leadership and strategic alignment"
|
| 422 |
-
],
|
| 423 |
-
"stakeholders": [
|
| 424 |
-
"HR Recruiter: Initial screening",
|
| 425 |
-
"Hiring Manager: Direct supervisor assessment",
|
| 426 |
-
"Team Members: Technical and collaboration evaluation",
|
| 427 |
-
"Senior Leadership: Strategic fit evaluation"
|
| 428 |
-
],
|
| 429 |
-
"timeline": "3 to 4 weeks typically",
|
| 430 |
-
"company_insights": f"{job_data['company']} values innovation and data-driven decision making."
|
| 431 |
-
}
|
| 432 |
|
| 433 |
-
#
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
company_questions = self.generate_company_questions(job_data)
|
| 437 |
-
|
| 438 |
-
# Preparation strategy
|
| 439 |
-
preparation_strategy = {
|
| 440 |
-
"immediate_priorities": [
|
| 441 |
-
"Review core technical concepts",
|
| 442 |
-
"Prepare STAR examples",
|
| 443 |
-
"Research company background"
|
| 444 |
-
],
|
| 445 |
-
"study_schedule": {
|
| 446 |
-
"technical_prep": "60% of time",
|
| 447 |
-
"behavioral_prep": "25% of time",
|
| 448 |
-
"company_research": "15% of time"
|
| 449 |
-
},
|
| 450 |
-
"time_allocation": "5-7 hours over 3-5 days"
|
| 451 |
-
}
|
| 452 |
|
| 453 |
-
#
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
f"{resume_data['experience']} years of experience",
|
| 457 |
-
f"Skills in {', '.join(skill_matches[:3]) if skill_matches else 'core technologies'}",
|
| 458 |
-
f"Background in {', '.join(resume_data['roles'][:2]) if resume_data['roles'] else 'technical roles'}"
|
| 459 |
-
]
|
| 460 |
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
]
|
| 469 |
|
| 470 |
-
return
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
company_questions=company_questions,
|
| 479 |
-
preparation_strategy=preparation_strategy,
|
| 480 |
-
talking_points=talking_points,
|
| 481 |
-
smart_questions=smart_questions
|
| 482 |
-
)
|
| 483 |
|
| 484 |
-
|
| 485 |
-
|
|
|
|
| 486 |
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
score_status = "π’ Excellent Match" if guide.match_score >= 85 else "π‘ Good Match" if guide.match_score >= 70 else "π΄ Developing Match"
|
| 490 |
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
|
| 496 |
-
|
| 497 |
-
<div style="margin: 20px 0;">
|
| 498 |
-
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
| 499 |
-
<span style="color: var(--apple-green);">Strong Matches</span>
|
| 500 |
-
<span style="margin-left: 10px; color: var(--apple-green);">{'β' * min(strong_count, 20)}</span>
|
| 501 |
-
<span style="margin-left: 10px; color: rgba(255,255,255,0.8);">{strong_count}</span>
|
| 502 |
-
</div>
|
| 503 |
-
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
| 504 |
-
<span style="color: var(--apple-orange);">Partial Matches</span>
|
| 505 |
-
<span style="margin-left: 10px; color: var(--apple-orange);">{'β' * min(partial_count, 20)}</span>
|
| 506 |
-
<span style="margin-left: 10px; color: rgba(255,255,255,0.8);">{partial_count}</span>
|
| 507 |
-
</div>
|
| 508 |
-
<div style="display: flex; align-items: center;">
|
| 509 |
-
<span style="color: var(--apple-red);">Skill Gaps</span>
|
| 510 |
-
<span style="margin-left: 10px; color: var(--apple-red);">{'β' * min(gap_count, 20)}</span>
|
| 511 |
-
<span style="margin-left: 10px; color: rgba(255,255,255,0.8);">{gap_count}</span>
|
| 512 |
-
</div>
|
| 513 |
-
</div>
|
| 514 |
-
"""
|
| 515 |
|
| 516 |
-
#
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
tech_questions_html += f"""
|
| 520 |
-
<div style="margin-bottom: 30px; padding: 20px; background: var(--glass-bg); border-radius: 12px; border-left: 4px solid var(--apple-blue);">
|
| 521 |
-
<h4 style="color: var(--apple-orange); margin-bottom: 15px;">π‘ Question {i}: {q['question']}</h4>
|
| 522 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;"><strong>Why they ask this:</strong> {q['why']}</p>
|
| 523 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;"><strong>How to approach:</strong> {q['approach']}</p>
|
| 524 |
-
<p style="color: rgba(255,255,255,0.9);"><strong>Key points to mention:</strong> {', '.join(q['key_points'])}</p>
|
| 525 |
-
</div>
|
| 526 |
-
"""
|
| 527 |
|
| 528 |
-
#
|
| 529 |
-
|
| 530 |
-
for i, q in enumerate(guide.behavioral_questions, 1):
|
| 531 |
-
behavioral_questions_html += f"""
|
| 532 |
-
<div style="margin-bottom: 30px; padding: 20px; background: var(--glass-bg); border-radius: 12px; border-left: 4px solid var(--apple-green);">
|
| 533 |
-
<h4 style="color: var(--apple-orange); margin-bottom: 15px;">π‘ Question {i}: {q['question']}</h4>
|
| 534 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;"><strong>Why they ask this:</strong> {q['why']}</p>
|
| 535 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;"><strong>How to approach:</strong> {q['approach']}</p>
|
| 536 |
-
<p style="color: rgba(255,255,255,0.9);"><strong>Key points to mention:</strong> {', '.join(q['key_points'])}</p>
|
| 537 |
-
</div>
|
| 538 |
-
"""
|
| 539 |
|
| 540 |
-
#
|
| 541 |
-
|
| 542 |
-
for i, q in enumerate(guide.company_questions, 1):
|
| 543 |
-
company_questions_html += f"""
|
| 544 |
-
<div style="margin-bottom: 30px; padding: 20px; background: var(--glass-bg); border-radius: 12px; border-left: 4px solid var(--apple-orange);">
|
| 545 |
-
<h4 style="color: var(--apple-orange); margin-bottom: 15px;">π‘ Question {i}: {q['question']}</h4>
|
| 546 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;"><strong>Why they ask this:</strong> {q['why']}</p>
|
| 547 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;"><strong>How to approach:</strong> {q['approach']}</p>
|
| 548 |
-
<p style="color: rgba(255,255,255,0.9);"><strong>Key points to mention:</strong> {', '.join(q['key_points'])}</p>
|
| 549 |
-
</div>
|
| 550 |
-
"""
|
| 551 |
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
<p style="color: rgba(255,255,255,0.9); line-height: 1.6; margin-bottom: 30px;">
|
| 566 |
-
{guide.introduction.strip()}
|
| 567 |
-
</p>
|
| 568 |
-
|
| 569 |
-
<h2 style="color: white; margin-bottom: 20px;">π Skills Match Analysis</h2>
|
| 570 |
-
<div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;">
|
| 571 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 572 |
-
<strong>Overall Assessment:</strong> {guide.skills_analysis['overall_assessment']}
|
| 573 |
-
</p>
|
| 574 |
-
|
| 575 |
-
<h4 style="color: white; margin-bottom: 15px;">Skills Breakdown</h4>
|
| 576 |
-
{skills_viz}
|
| 577 |
-
|
| 578 |
-
<div style="margin-top: 20px;">
|
| 579 |
-
<p style="color: rgba(255,255,255,0.9);">
|
| 580 |
-
<strong>β
Your Strengths:</strong> {', '.join(guide.skills_analysis['strong_matches'][:5]) if guide.skills_analysis['strong_matches'] else 'Technical foundation, analytical thinking'}
|
| 581 |
-
</p>
|
| 582 |
-
</div>
|
| 583 |
-
</div>
|
| 584 |
-
|
| 585 |
-
<h2 style="color: white; margin-bottom: 20px;">π― What Is the Interview Process Like?</h2>
|
| 586 |
-
<div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;">
|
| 587 |
-
<h4 style="color: white; margin-bottom: 15px;">1. Typical Number of Rounds</h4>
|
| 588 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">Expect {guide.interview_process['typical_rounds']} of interviews.</p>
|
| 589 |
-
|
| 590 |
-
<h4 style="color: white; margin-bottom: 15px;">2. Types of Interviews Expected</h4>
|
| 591 |
-
<ul style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 592 |
-
{"".join([f"<li>{interview_type}</li>" for interview_type in guide.interview_process['interview_types']])}
|
| 593 |
-
</ul>
|
| 594 |
-
|
| 595 |
-
<h4 style="color: white; margin-bottom: 15px;">3. Key Stakeholders They'll Meet</h4>
|
| 596 |
-
<ul style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 597 |
-
{"".join([f"<li>{stakeholder}</li>" for stakeholder in guide.interview_process['stakeholders']])}
|
| 598 |
-
</ul>
|
| 599 |
-
|
| 600 |
-
<h4 style="color: white; margin-bottom: 15px;">4. Timeline and Logistics</h4>
|
| 601 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">{guide.interview_process['timeline']}</p>
|
| 602 |
-
|
| 603 |
-
<h4 style="color: white; margin-bottom: 15px;">5. Company-Specific Insights</h4>
|
| 604 |
-
<p style="color: rgba(255,255,255,0.9);">{guide.interview_process['company_insights']}</p>
|
| 605 |
-
</div>
|
| 606 |
-
|
| 607 |
-
<h2 style="color: white; margin-bottom: 20px;">π§ Technical & Problem-Solving Questions</h2>
|
| 608 |
-
<p style="color: rgba(255,255,255,0.8); margin-bottom: 30px;">
|
| 609 |
-
These questions test your technical knowledge. Focus on demonstrating both your understanding and problem-solving approach.
|
| 610 |
-
</p>
|
| 611 |
-
{tech_questions_html}
|
| 612 |
-
|
| 613 |
-
<h2 style="color: white; margin-bottom: 20px;">π― Behavioral & Experience Questions</h2>
|
| 614 |
-
<p style="color: rgba(255,255,255,0.8); margin-bottom: 30px;">
|
| 615 |
-
Use the STAR method (Situation, Task, Action, Result) to structure your responses.
|
| 616 |
-
</p>
|
| 617 |
-
{behavioral_questions_html}
|
| 618 |
-
|
| 619 |
-
<h2 style="color: white; margin-bottom: 20px;">π’ Company & Culture Questions</h2>
|
| 620 |
-
<p style="color: rgba(255,255,255,0.8); margin-bottom: 30px;">
|
| 621 |
-
These questions assess your interest in the company and cultural fit.
|
| 622 |
</p>
|
| 623 |
-
{company_questions_html}
|
| 624 |
-
|
| 625 |
-
<h2 style="color: white; margin-bottom: 20px;">π― Preparation Strategy</h2>
|
| 626 |
-
<div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;">
|
| 627 |
-
<h4 style="color: white; margin-bottom: 15px;">Your Preparation Roadmap</h4>
|
| 628 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 629 |
-
Based on your {guide.match_score:.1f}% match score, here's your personalized preparation strategy:
|
| 630 |
-
</p>
|
| 631 |
-
|
| 632 |
-
<h5 style="color: var(--apple-blue); margin-bottom: 10px;">Immediate Priorities</h5>
|
| 633 |
-
<ul style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 634 |
-
{"".join([f"<li>{priority}</li>" for priority in guide.preparation_strategy['immediate_priorities']])}
|
| 635 |
-
</ul>
|
| 636 |
-
|
| 637 |
-
<h5 style="color: var(--apple-blue); margin-bottom: 10px;">Study Schedule</h5>
|
| 638 |
-
<ul style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 639 |
-
<li>Technical prep: {guide.preparation_strategy['study_schedule']['technical_prep']} of time</li>
|
| 640 |
-
<li>Behavioral prep: {guide.preparation_strategy['study_schedule']['behavioral_prep']} of time</li>
|
| 641 |
-
<li>Company research: {guide.preparation_strategy['study_schedule']['company_research']} of time</li>
|
| 642 |
-
</ul>
|
| 643 |
-
|
| 644 |
-
<p style="color: rgba(255,255,255,0.8);">
|
| 645 |
-
<strong>Time Allocation:</strong> {guide.preparation_strategy['time_allocation']}
|
| 646 |
-
</p>
|
| 647 |
-
</div>
|
| 648 |
-
|
| 649 |
-
<h2 style="color: white; margin-bottom: 20px;">π¬ Key Talking Points</h2>
|
| 650 |
-
<div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;">
|
| 651 |
-
<h4 style="color: white; margin-bottom: 15px;">Lead with Your Strengths</h4>
|
| 652 |
-
<ul style="color: rgba(255,255,255,0.9);">
|
| 653 |
-
{"".join([f"<li>{point}</li>" for point in guide.talking_points])}
|
| 654 |
-
</ul>
|
| 655 |
-
</div>
|
| 656 |
-
|
| 657 |
-
<h2 style="color: white; margin-bottom: 20px;">β Smart Questions to Ask</h2>
|
| 658 |
-
<div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;">
|
| 659 |
-
<p style="color: rgba(255,255,255,0.8); margin-bottom: 15px;">
|
| 660 |
-
Show your engagement and strategic thinking with these questions:
|
| 661 |
-
</p>
|
| 662 |
-
<ol style="color: rgba(255,255,255,0.9);">
|
| 663 |
-
{"".join([f"<li>{question}</li>" for question in guide.smart_questions])}
|
| 664 |
-
</ol>
|
| 665 |
-
</div>
|
| 666 |
-
|
| 667 |
-
<h2 style="color: white; margin-bottom: 20px;">π
Day-of-Interview Preparation</h2>
|
| 668 |
-
<div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;">
|
| 669 |
-
<h4 style="color: white; margin-bottom: 15px;">Morning Review (30 minutes)</h4>
|
| 670 |
-
<ul style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 671 |
-
<li>Review your top strengths: {', '.join(guide.skills_analysis['strong_matches'][:3]) if guide.skills_analysis['strong_matches'] else 'technical skills, experience'}</li>
|
| 672 |
-
<li>Practice your 2-minute elevator pitch</li>
|
| 673 |
-
<li>Review company's recent news/updates</li>
|
| 674 |
-
<li>Check logistics (time, location, interviewer names)</li>
|
| 675 |
-
</ul>
|
| 676 |
-
|
| 677 |
-
<h4 style="color: white; margin-bottom: 15px;">Mental Preparation</h4>
|
| 678 |
-
<ul style="color: rgba(255,255,255,0.9); margin-bottom: 20px;">
|
| 679 |
-
<li>Confidence booster: You have a {guide.match_score:.1f}% match score</li>
|
| 680 |
-
<li>Remember your competitive advantages</li>
|
| 681 |
-
<li>Focus on learning and growth mindset</li>
|
| 682 |
-
</ul>
|
| 683 |
-
</div>
|
| 684 |
-
|
| 685 |
-
<h2 style="color: white; margin-bottom: 20px;">β
Success Metrics</h2>
|
| 686 |
-
<div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;">
|
| 687 |
-
<p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;">You'll know the interview went well if:</p>
|
| 688 |
-
<ul style="color: rgba(255,255,255,0.9);">
|
| 689 |
-
<li>Successfully demonstrate your core strengths</li>
|
| 690 |
-
<li>Ask 3-4 thoughtful questions about the role/team</li>
|
| 691 |
-
<li>Share specific examples from your background</li>
|
| 692 |
-
<li>Show enthusiasm for learning and growth</li>
|
| 693 |
-
<li>Position yourself as ready to contribute immediately</li>
|
| 694 |
-
</ul>
|
| 695 |
-
</div>
|
| 696 |
-
|
| 697 |
-
<h2 style="color: white; margin-bottom: 20px;">π Conclusion</h2>
|
| 698 |
-
<div style="background: linear-gradient(135deg, var(--apple-green), var(--apple-blue)); padding: 20px; border-radius: 12px; text-align: center;">
|
| 699 |
-
<p style="color: white; font-size: 1.1rem; margin-bottom: 15px;">
|
| 700 |
-
You're well-prepared for this interview! Your {guide.match_score:.1f}% match score indicates strong alignment.
|
| 701 |
-
</p>
|
| 702 |
-
<p style="color: white; font-weight: 600;">
|
| 703 |
-
Remember: Be authentic, ask thoughtful questions, and show enthusiasm. Good luck! π
|
| 704 |
-
</p>
|
| 705 |
-
</div>
|
| 706 |
-
|
| 707 |
-
<div style="text-align: center; margin-top: 30px; color: rgba(255,255,255,0.6); font-size: 0.9rem;">
|
| 708 |
-
<p><em>This personalized guide was generated based on your specific background and role requirements.</em></p>
|
| 709 |
-
</div>
|
| 710 |
</div>
|
| 711 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
+
Enhanced Interview Guide Generator - InterviewGuideGPT Format
|
| 4 |
+
Generates polished, role-specific interview guides following exact structure requirements
|
| 5 |
"""
|
| 6 |
|
| 7 |
import re
|
| 8 |
+
import json
|
| 9 |
+
from typing import Dict, List, Any
|
| 10 |
from dataclasses import dataclass
|
| 11 |
|
| 12 |
@dataclass
|
| 13 |
+
class GuideData:
|
| 14 |
+
"""Structured data for interview guide generation"""
|
| 15 |
+
role_title: str
|
| 16 |
+
company: str
|
| 17 |
match_score: float
|
| 18 |
+
user_overview: str
|
| 19 |
+
user_skills: List[str]
|
| 20 |
+
role_skills: List[str]
|
| 21 |
+
team_context: str
|
| 22 |
+
interview_rounds: int
|
| 23 |
+
process_notes: List[str]
|
| 24 |
+
key_projects: List[str] = None
|
| 25 |
+
candidate_strengths: List[str] = None
|
| 26 |
+
skill_gaps: List[str] = None
|
| 27 |
|
| 28 |
+
class InterviewGuideGPT:
|
| 29 |
+
"""Elite career-coach AI for generating polished interview guides"""
|
| 30 |
|
| 31 |
def __init__(self):
|
| 32 |
self.tech_skills = [
|
| 33 |
"Python", "JavaScript", "Java", "SQL", "React", "Node.js",
|
| 34 |
"AWS", "Docker", "Git", "Machine Learning", "Data Science",
|
| 35 |
"Analytics", "R", "Tableau", "Pandas", "NumPy", "TensorFlow",
|
| 36 |
+
"Kubernetes", "MongoDB", "PostgreSQL", "Redis", "Apache Spark",
|
| 37 |
+
"Scala", "Hadoop", "Spark", "Kafka", "Elasticsearch"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
]
|
| 39 |
|
| 40 |
+
self.company_insights = {
|
| 41 |
+
"spotify": "Spotify prizes data-driven creativity in music.",
|
| 42 |
+
"google": "Google values innovation and technical excellence at scale.",
|
| 43 |
+
"amazon": "Amazon focuses on customer obsession and operational excellence.",
|
| 44 |
+
"microsoft": "Microsoft emphasizes collaboration and empowering others.",
|
| 45 |
+
"meta": "Meta drives connection and community through technology.",
|
| 46 |
+
"apple": "Apple pursues perfection in user experience and design.",
|
| 47 |
+
"netflix": "Netflix champions freedom, responsibility, and context over control."
|
| 48 |
}
|
| 49 |
|
| 50 |
+
def analyze_resume_and_job(self, resume_text: str, job_text: str) -> GuideData:
|
| 51 |
+
"""Analyze resume and job to extract structured data"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
# Extract user data
|
| 54 |
+
user_skills = self._extract_skills(resume_text)
|
| 55 |
+
user_overview = self._extract_user_overview(resume_text)
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
# Extract job data
|
| 58 |
+
role_title, company = self._extract_role_and_company(job_text)
|
| 59 |
+
role_skills = self._extract_skills(job_text)
|
| 60 |
+
team_context = self._extract_team_context(job_text)
|
| 61 |
+
|
| 62 |
+
# Calculate match score
|
| 63 |
+
match_score = self._calculate_match_score(user_skills, role_skills, resume_text, job_text)
|
| 64 |
+
|
| 65 |
+
# Generate process info
|
| 66 |
+
interview_rounds = self._estimate_interview_rounds(company, role_title)
|
| 67 |
+
process_notes = self._generate_process_notes(company, role_title)
|
| 68 |
+
|
| 69 |
+
# Extract additional data
|
| 70 |
+
key_projects = self._extract_key_projects(resume_text)
|
| 71 |
+
candidate_strengths = self._extract_strengths(resume_text, user_skills)
|
| 72 |
+
skill_gaps = list(set(role_skills) - set(user_skills))
|
| 73 |
+
|
| 74 |
+
return GuideData(
|
| 75 |
+
role_title=role_title,
|
| 76 |
+
company=company,
|
| 77 |
+
match_score=match_score,
|
| 78 |
+
user_overview=user_overview,
|
| 79 |
+
user_skills=user_skills,
|
| 80 |
+
role_skills=role_skills,
|
| 81 |
+
team_context=team_context,
|
| 82 |
+
interview_rounds=interview_rounds,
|
| 83 |
+
process_notes=process_notes,
|
| 84 |
+
key_projects=key_projects,
|
| 85 |
+
candidate_strengths=candidate_strengths,
|
| 86 |
+
skill_gaps=skill_gaps
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
def _extract_skills(self, text: str) -> List[str]:
|
| 90 |
+
"""Extract technical skills from text"""
|
| 91 |
+
skills = []
|
| 92 |
+
text_lower = text.lower()
|
| 93 |
+
|
| 94 |
+
for skill in self.tech_skills:
|
| 95 |
+
if skill.lower() in text_lower:
|
| 96 |
+
skills.append(skill)
|
| 97 |
+
|
| 98 |
+
# Add soft skills
|
| 99 |
+
soft_skills = ["Leadership", "Communication", "Project Management", "Team Work", "Problem Solving"]
|
| 100 |
+
for skill in soft_skills:
|
| 101 |
+
if skill.lower() in text_lower or any(word in text_lower for word in skill.lower().split()):
|
| 102 |
+
skills.append(skill)
|
| 103 |
|
| 104 |
+
return list(set(skills))
|
| 105 |
+
|
| 106 |
+
def _extract_user_overview(self, resume_text: str) -> str:
|
| 107 |
+
"""Extract user overview from resume"""
|
| 108 |
+
# Look for experience years
|
| 109 |
+
experience_match = re.search(r'(\d+)[\s\+]*years?\s+(?:of\s+)?experience', resume_text, re.IGNORECASE)
|
| 110 |
+
years = experience_match.group(1) if experience_match else "several"
|
| 111 |
|
| 112 |
+
# Look for degree/education
|
| 113 |
education_patterns = [
|
| 114 |
+
r'(bachelor|master|phd|doctorate|degree)',
|
| 115 |
+
r'(computer science|data science|engineering|mathematics|statistics)'
|
|
|
|
| 116 |
]
|
| 117 |
|
| 118 |
+
education = "degree"
|
| 119 |
for pattern in education_patterns:
|
| 120 |
match = re.search(pattern, resume_text, re.IGNORECASE)
|
| 121 |
if match:
|
| 122 |
+
education = match.group(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
break
|
| 124 |
|
| 125 |
+
return f"Professional with {years} years of experience and {education} background"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
def _extract_role_and_company(self, job_text: str) -> tuple:
|
| 128 |
+
"""Extract role title and company from job text"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
# Extract company
|
| 130 |
company_patterns = [
|
| 131 |
+
r'at\s+([A-Z][a-zA-Z\s&\.]+?)(?:\s|$|,|\n)',
|
| 132 |
+
r'([A-Z][a-zA-Z\s&\.]+?)\s+is\s+(?:hiring|looking)',
|
| 133 |
+
r'join\s+([A-Z][a-zA-Z\s&\.]+?)(?:\s|$|,|\n)',
|
|
|
|
| 134 |
]
|
| 135 |
|
| 136 |
+
company = "Company"
|
| 137 |
for pattern in company_patterns:
|
| 138 |
match = re.search(pattern, job_text, re.IGNORECASE)
|
| 139 |
if match:
|
| 140 |
company = match.group(1).strip()
|
| 141 |
break
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
# Extract role
|
| 144 |
role_patterns = [
|
| 145 |
+
r'(senior\s+)?(data\s+scientist|software\s+engineer|product\s+manager|analyst|developer)',
|
| 146 |
r'position[:\s]+(senior\s+)?([a-zA-Z\s]+)',
|
| 147 |
r'role[:\s]+(senior\s+)?([a-zA-Z\s]+)',
|
|
|
|
| 148 |
]
|
| 149 |
|
| 150 |
+
role = "Role"
|
|
|
|
| 151 |
for pattern in role_patterns:
|
| 152 |
match = re.search(pattern, job_text, re.IGNORECASE)
|
| 153 |
if match:
|
|
|
|
| 155 |
if len(groups) >= 2:
|
| 156 |
senior_part = groups[0] or ""
|
| 157 |
role_part = groups[1] or groups[-1]
|
|
|
|
|
|
|
| 158 |
role = (senior_part + role_part).strip().title()
|
| 159 |
break
|
| 160 |
|
| 161 |
+
return role, company
|
| 162 |
+
|
| 163 |
+
def _extract_team_context(self, job_text: str) -> str:
|
| 164 |
+
"""Extract team context from job description"""
|
| 165 |
+
context_keywords = [
|
| 166 |
+
"team", "collaborate", "cross-functional", "stakeholder",
|
| 167 |
+
"partner", "work with", "engineering", "product", "data"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
]
|
| 169 |
|
| 170 |
+
sentences = job_text.split('.')
|
| 171 |
+
for sentence in sentences:
|
| 172 |
+
if any(keyword in sentence.lower() for keyword in context_keywords):
|
| 173 |
+
return sentence.strip()
|
|
|
|
|
|
|
| 174 |
|
| 175 |
+
return "Collaborative team environment focused on innovation and results"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
+
def _calculate_match_score(self, user_skills: List[str], role_skills: List[str], resume_text: str, job_text: str) -> float:
|
| 178 |
+
"""Calculate match score between user and role"""
|
| 179 |
+
if not role_skills:
|
| 180 |
+
return 0.75
|
| 181 |
|
| 182 |
+
# Skill overlap
|
| 183 |
+
skill_overlap = len(set(user_skills) & set(role_skills))
|
| 184 |
+
skill_score = skill_overlap / len(role_skills) if role_skills else 0.5
|
| 185 |
|
| 186 |
+
# Experience factor
|
| 187 |
+
experience_match = re.search(r'(\d+)[\s\+]*years?\s+(?:of\s+)?experience', resume_text, re.IGNORECASE)
|
| 188 |
+
experience_years = int(experience_match.group(1)) if experience_match else 3
|
| 189 |
+
experience_score = min(experience_years * 0.15, 1.0)
|
| 190 |
|
| 191 |
+
# Education factor
|
| 192 |
+
education_score = 0.2 if any(word in resume_text.lower() for word in ['degree', 'bachelor', 'master']) else 0.1
|
| 193 |
|
| 194 |
+
# Role relevance
|
| 195 |
+
role_keywords = ['engineer', 'scientist', 'analyst', 'manager', 'developer']
|
| 196 |
+
role_relevance = 0.2 if any(keyword in resume_text.lower() for keyword in role_keywords) else 0.1
|
| 197 |
|
| 198 |
+
final_score = (skill_score * 0.5 + experience_score * 0.3 + education_score * 0.1 + role_relevance * 0.1)
|
| 199 |
+
return min(max(final_score, 0.4), 0.97)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
+
def _estimate_interview_rounds(self, company: str, role: str) -> int:
|
| 202 |
+
"""Estimate number of interview rounds"""
|
| 203 |
+
if any(term in company.lower() for term in ['startup', 'small']):
|
| 204 |
+
return 3
|
| 205 |
+
elif any(term in company.lower() for term in ['google', 'amazon', 'microsoft', 'apple', 'meta']):
|
| 206 |
+
return 5
|
| 207 |
+
else:
|
| 208 |
+
return 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
|
| 210 |
+
def _generate_process_notes(self, company: str, role: str) -> List[str]:
|
| 211 |
+
"""Generate interview process notes"""
|
| 212 |
+
base_process = [
|
| 213 |
+
"Phone/Video Screen",
|
| 214 |
+
"Technical Assessment",
|
| 215 |
+
"Behavioral Interview",
|
| 216 |
+
"Final Round"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
]
|
| 218 |
|
| 219 |
+
if any(term in role.lower() for term in ['senior', 'lead', 'principal']):
|
| 220 |
+
base_process.insert(-1, "Leadership Interview")
|
| 221 |
+
|
| 222 |
+
return base_process
|
| 223 |
|
| 224 |
+
def _extract_key_projects(self, resume_text: str) -> List[str]:
|
| 225 |
+
"""Extract key projects from resume"""
|
| 226 |
+
project_indicators = [
|
| 227 |
+
r'built\s+([^\.]+)',
|
| 228 |
+
r'developed\s+([^\.]+)',
|
| 229 |
+
r'created\s+([^\.]+)',
|
| 230 |
+
r'led\s+([^\.]+)',
|
| 231 |
+
r'implemented\s+([^\.]+)'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
]
|
| 233 |
|
| 234 |
+
projects = []
|
| 235 |
+
for pattern in project_indicators:
|
| 236 |
+
matches = re.findall(pattern, resume_text, re.IGNORECASE)
|
| 237 |
+
projects.extend([match.strip() for match in matches[:2]]) # Limit to 2 per pattern
|
| 238 |
+
|
| 239 |
+
return projects[:6] # Max 6 projects
|
| 240 |
|
| 241 |
+
def _extract_strengths(self, resume_text: str, skills: List[str]) -> List[str]:
|
| 242 |
+
"""Extract candidate strengths"""
|
| 243 |
+
strengths = []
|
| 244 |
|
| 245 |
+
# Add top skills as strengths
|
| 246 |
+
strengths.extend(skills[:3])
|
| 247 |
|
| 248 |
+
# Add experience-based strengths
|
| 249 |
+
if re.search(r'(\d+)[\s\+]*years', resume_text, re.IGNORECASE):
|
| 250 |
+
strengths.append("Extensive experience")
|
| 251 |
|
| 252 |
+
if any(word in resume_text.lower() for word in ['led', 'managed', 'supervised']):
|
| 253 |
+
strengths.append("Leadership experience")
|
| 254 |
|
| 255 |
+
if any(word in resume_text.lower() for word in ['scaled', 'optimized', 'improved']):
|
| 256 |
+
strengths.append("Performance optimization")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
+
return strengths[:6]
|
| 259 |
+
|
| 260 |
+
def generate_interview_guide(self, guide_data: GuideData) -> str:
|
| 261 |
+
"""Generate interview guide following exact InterviewGuideGPT format"""
|
| 262 |
+
|
| 263 |
+
# Calculate derived helpers
|
| 264 |
+
match_bucket, emoji = self._get_match_bucket(guide_data.match_score)
|
| 265 |
+
percent = round(guide_data.match_score * 100, 1)
|
| 266 |
+
|
| 267 |
+
user_skills_set = set(guide_data.user_skills)
|
| 268 |
+
role_skills_set = set(guide_data.role_skills)
|
| 269 |
+
|
| 270 |
+
strong = len(user_skills_set & role_skills_set)
|
| 271 |
+
partial = len(user_skills_set) - strong
|
| 272 |
+
gaps = len(role_skills_set) - strong
|
| 273 |
+
|
| 274 |
+
# Get company insight
|
| 275 |
+
company_insight = self._get_company_insight(guide_data.company)
|
| 276 |
+
|
| 277 |
+
# Generate sections
|
| 278 |
+
intro = self._generate_introduction(guide_data)
|
| 279 |
+
tech_questions = self._generate_technical_questions(guide_data)
|
| 280 |
+
behavioral_questions = self._generate_behavioral_questions(guide_data)
|
| 281 |
+
culture_questions = self._generate_culture_questions(guide_data)
|
| 282 |
+
talking_points = self._generate_talking_points(guide_data)
|
| 283 |
+
smart_questions = self._generate_smart_questions(guide_data)
|
| 284 |
+
|
| 285 |
+
# Format the complete guide
|
| 286 |
+
guide = f"""# Personalized Interview Guide: {guide_data.role_title} at {guide_data.company}
|
| 287 |
+
**Match Score: {emoji} {match_bucket} Match ({percent}%)**
|
| 288 |
+
|
| 289 |
+
---
|
| 290 |
+
|
| 291 |
+
## Introduction
|
| 292 |
+
{intro}
|
| 293 |
+
|
| 294 |
+
## π Skills Match Analysis
|
| 295 |
+
**Overall Assessment:** Strong technical foundation with {strong} direct skill matches and proven experience in {guide_data.user_overview.split()[-2]} {guide_data.user_overview.split()[-1]}.
|
| 296 |
+
|
| 297 |
+
```text
|
| 298 |
+
Skills Breakdown
|
| 299 |
+
Strong Matches {'β' * min(20, strong * 2)} {strong}
|
| 300 |
+
Partial Matches {partial}
|
| 301 |
+
Skill Gaps {gaps}
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
β
**Your Strengths:** {', '.join(guide_data.user_skills[:6])}
|
| 305 |
+
|
| 306 |
+
## π― Interview Process at {guide_data.company}
|
| 307 |
+
|
| 308 |
+
1. **Typical rounds:** {guide_data.interview_rounds}
|
| 309 |
+
2. **Stages:** {', '.join(guide_data.process_notes)}
|
| 310 |
+
3. **Timeline:** 3-4 weeks (typical)
|
| 311 |
+
4. **Company insight:** {company_insight}
|
| 312 |
+
|
| 313 |
+
## π§ Technical & Problem-Solving Questions
|
| 314 |
+
|
| 315 |
+
{tech_questions}
|
| 316 |
+
|
| 317 |
+
## π€ Behavioral & Experience Questions
|
| 318 |
+
|
| 319 |
+
{behavioral_questions}
|
| 320 |
+
|
| 321 |
+
## π’ Company & Culture Questions
|
| 322 |
+
|
| 323 |
+
{culture_questions}
|
| 324 |
+
|
| 325 |
+
## π
Preparation Strategy
|
| 326 |
+
|
| 327 |
+
**Immediate priorities:** Review core technical concepts β’ Prepare STAR examples β’ Research company background
|
| 328 |
+
|
| 329 |
+
**Study schedule:** Technical 60% β’ Behavioral 25% β’ Company 15%
|
| 330 |
+
|
| 331 |
+
**Time allocation:** 5β7 hours over 3β5 days
|
| 332 |
+
|
| 333 |
+
## π¬ Key Talking Points
|
| 334 |
+
|
| 335 |
+
{talking_points}
|
| 336 |
+
|
| 337 |
+
## β Smart Questions to Ask
|
| 338 |
+
|
| 339 |
+
{smart_questions}
|
| 340 |
+
|
| 341 |
+
## ποΈ Day-of-Interview Checklist
|
| 342 |
+
|
| 343 |
+
β Morning review of key concepts
|
| 344 |
+
β Confirm logistics and timing
|
| 345 |
+
β Mental preparation and confidence building
|
| 346 |
+
β Arrive 10 minutes early
|
| 347 |
+
|
| 348 |
+
## β
Success Metrics
|
| 349 |
+
|
| 350 |
+
β Demonstrated {len(guide_data.user_skills)} core strengths
|
| 351 |
+
β Asked β₯4 thoughtful questions
|
| 352 |
+
β Showed enthusiasm & growth mindset
|
| 353 |
+
|
| 354 |
+
## π Conclusion
|
| 355 |
+
|
| 356 |
+
You're an excellent fitβbe confident. Good luck! π
|
| 357 |
+
|
| 358 |
+
---
|
| 359 |
+
*Generated with IQKiller v2.0 β no data retained.*"""
|
| 360 |
+
|
| 361 |
+
return guide
|
| 362 |
+
|
| 363 |
+
def _get_match_bucket(self, score: float) -> tuple:
|
| 364 |
+
"""Get match bucket and emoji"""
|
| 365 |
+
if score >= 0.90:
|
| 366 |
+
return "Excellent", "π’"
|
| 367 |
+
elif score >= 0.80:
|
| 368 |
+
return "Strong", "π‘"
|
| 369 |
+
else:
|
| 370 |
+
return "Developing", "π΄"
|
| 371 |
+
|
| 372 |
+
def _get_company_insight(self, company: str) -> str:
|
| 373 |
+
"""Get company-specific insight"""
|
| 374 |
+
company_lower = company.lower()
|
| 375 |
+
for key, insight in self.company_insights.items():
|
| 376 |
+
if key in company_lower:
|
| 377 |
+
return insight
|
| 378 |
+
return f"{company} values innovation and excellence in their field."
|
| 379 |
+
|
| 380 |
+
def _generate_introduction(self, guide_data: GuideData) -> str:
|
| 381 |
+
"""Generate introduction section"""
|
| 382 |
+
return f"This {guide_data.role_title} position at {guide_data.company} represents an excellent opportunity for someone with your background. Your {guide_data.user_overview} aligns well with {guide_data.team_context.lower()}. With your technical skills and experience, you're well-positioned to contribute meaningfully to their mission."
|
| 383 |
+
|
| 384 |
+
def _generate_technical_questions(self, guide_data: GuideData) -> str:
|
| 385 |
+
"""Generate exactly 3 technical questions"""
|
| 386 |
+
questions = []
|
| 387 |
|
| 388 |
+
# Question 1: System design
|
| 389 |
+
q1 = f"""**1. How would you design a system to handle {guide_data.role_title.lower()} requirements at scale?**
|
| 390 |
+
|
| 391 |
+
*Why they ask:* Tests your system design skills and understanding of scalability challenges.
|
| 392 |
+
|
| 393 |
+
*How to answer:* Start with requirements gathering, discuss architecture, data flow, and scaling considerations.
|
| 394 |
+
|
| 395 |
+
*Key points:* System architecture understanding, scalability considerations, technology trade-offs"""
|
| 396 |
+
|
| 397 |
+
# Question 2: Technical depth
|
| 398 |
+
main_skill = guide_data.user_skills[0] if guide_data.user_skills else "your main technology"
|
| 399 |
+
q2 = f"""**2. Given your experience with {main_skill}, how would you approach solving a complex data problem?**
|
| 400 |
+
|
| 401 |
+
*Why they ask:* Assesses your problem-solving approach and technical depth in {main_skill}.
|
| 402 |
+
|
| 403 |
+
*How to answer:* Break down the problem, discuss methodology, mention specific tools and techniques.
|
| 404 |
+
|
| 405 |
+
*Key points:* Deep knowledge of {main_skill}, problem decomposition skills, practical application"""
|
| 406 |
+
|
| 407 |
+
# Question 3: Role-specific
|
| 408 |
+
q3 = f"""**3. Describe how you would optimize performance in a {guide_data.role_title.lower()} context.**
|
| 409 |
+
|
| 410 |
+
*Why they ask:* Evaluates your understanding of performance optimization specific to this role.
|
| 411 |
+
|
| 412 |
+
*How to answer:* Discuss monitoring, bottleneck identification, and optimization strategies.
|
| 413 |
+
|
| 414 |
+
*Key points:* Performance metrics understanding, optimization techniques, real-world experience"""
|
| 415 |
+
|
| 416 |
+
return f"{q1}\n\n{q2}\n\n{q3}"
|
| 417 |
+
|
| 418 |
+
def _generate_behavioral_questions(self, guide_data: GuideData) -> str:
|
| 419 |
+
"""Generate exactly 3 behavioral questions"""
|
| 420 |
+
q1 = """**1. Tell me about a time when you had to learn a new technology quickly for a project.**
|
| 421 |
+
|
| 422 |
+
*STAR Framework:* Situation - Task - Action - Result
|
| 423 |
+
|
| 424 |
+
*Focus on:* Learning agility, problem-solving approach, impact of quick learning"""
|
| 425 |
+
|
| 426 |
+
q2 = """**2. Describe a situation where you had to work with a difficult team member or stakeholder.**
|
| 427 |
+
|
| 428 |
+
*STAR Framework:* Situation - Task - Action - Result
|
| 429 |
+
|
| 430 |
+
*Focus on:* Communication skills, conflict resolution, collaboration approach"""
|
| 431 |
+
|
| 432 |
+
q3 = """**3. Give me an example of a project where you had to make trade-offs between competing priorities.**
|
| 433 |
+
|
| 434 |
+
*STAR Framework:* Situation - Task - Action - Result
|
| 435 |
+
|
| 436 |
+
*Focus on:* Decision-making process, stakeholder management, outcome evaluation"""
|
| 437 |
+
|
| 438 |
+
return f"{q1}\n\n{q2}\n\n{q3}"
|
| 439 |
+
|
| 440 |
+
def _generate_culture_questions(self, guide_data: GuideData) -> str:
|
| 441 |
+
"""Generate exactly 3 culture questions"""
|
| 442 |
+
q1 = f"""**1. Why are you interested in working at {guide_data.company} specifically?**
|
| 443 |
+
|
| 444 |
+
*Purpose:* Tests genuine interest and company research.
|
| 445 |
+
|
| 446 |
+
*Approach:* Connect company mission to your values and career goals."""
|
| 447 |
+
|
| 448 |
+
q2 = f"""**2. How do you stay current with industry trends and continue learning in your field?**
|
| 449 |
+
|
| 450 |
+
*Purpose:* Assesses growth mindset and continuous learning.
|
| 451 |
+
|
| 452 |
+
*Approach:* Share specific resources, communities, and learning practices."""
|
| 453 |
+
|
| 454 |
+
q3 = f"""**3. Describe your ideal work environment and team dynamics.**
|
| 455 |
+
|
| 456 |
+
*Purpose:* Evaluates cultural fit and team compatibility.
|
| 457 |
+
|
| 458 |
+
*Approach:* Align your preferences with {guide_data.company}'s known culture."""
|
| 459 |
+
|
| 460 |
+
return f"{q1}\n\n{q2}\n\n{q3}"
|
| 461 |
+
|
| 462 |
+
def _generate_talking_points(self, guide_data: GuideData) -> str:
|
| 463 |
+
"""Generate key talking points"""
|
| 464 |
+
points = []
|
| 465 |
|
| 466 |
+
# Add background
|
| 467 |
+
points.append(f"β {guide_data.user_overview}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
|
| 469 |
+
# Add key projects
|
| 470 |
+
if guide_data.key_projects:
|
| 471 |
+
points.append(f"β {len(guide_data.key_projects)} key projects including {', '.join(guide_data.key_projects[:2])}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
|
| 473 |
+
# Add skills highlights
|
| 474 |
+
top_skills = guide_data.user_skills[:3]
|
| 475 |
+
points.append(f"β Technical expertise: {' + '.join(top_skills)} highlights")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
|
| 477 |
+
return '\n'.join(points)
|
| 478 |
+
|
| 479 |
+
def _generate_smart_questions(self, guide_data: GuideData) -> str:
|
| 480 |
+
"""Generate smart questions to ask"""
|
| 481 |
+
questions = [
|
| 482 |
+
"β What does success look like in the first 90 days?",
|
| 483 |
+
"β What's the biggest technical challenge facing the team?",
|
| 484 |
+
f"β How does {guide_data.company} support professional development and career growth?",
|
| 485 |
+
f"β What do you enjoy most about working at {guide_data.company}?",
|
| 486 |
+
"β How do you measure the impact of this role on company objectives?",
|
| 487 |
+
f"β What opportunities exist for innovation within the {guide_data.role_title} position?"
|
| 488 |
]
|
| 489 |
|
| 490 |
+
return '\n'.join(questions)
|
| 491 |
+
|
| 492 |
+
# Main function to generate guide from resume and job text
|
| 493 |
+
def generate_interviewgpt_guide(resume_text: str, job_text: str) -> str:
|
| 494 |
+
"""Generate interview guide using InterviewGuideGPT format"""
|
| 495 |
+
generator = InterviewGuideGPT()
|
| 496 |
+
guide_data = generator.analyze_resume_and_job(resume_text, job_text)
|
| 497 |
+
return generator.generate_interview_guide(guide_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
|
| 499 |
+
# Legacy compatibility
|
| 500 |
+
class ComprehensiveAnalyzer:
|
| 501 |
+
"""Legacy wrapper for backward compatibility"""
|
| 502 |
|
| 503 |
+
def __init__(self):
|
| 504 |
+
self.generator = InterviewGuideGPT()
|
|
|
|
| 505 |
|
| 506 |
+
def generate_comprehensive_guide(self, resume_text: str, job_input: str):
|
| 507 |
+
"""Legacy method for backward compatibility"""
|
| 508 |
+
guide_data = self.generator.analyze_resume_and_job(resume_text, job_input)
|
| 509 |
+
return MockGuide(self.generator.generate_interview_guide(guide_data))
|
| 510 |
+
|
| 511 |
+
class MockGuide:
|
| 512 |
+
"""Mock guide object for legacy compatibility"""
|
| 513 |
+
def __init__(self, content):
|
| 514 |
+
self.content = content
|
| 515 |
+
|
| 516 |
+
def format_interview_guide_html(guide) -> str:
|
| 517 |
+
"""Convert markdown guide to HTML for display"""
|
| 518 |
+
import re
|
| 519 |
|
| 520 |
+
html_content = guide.content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 521 |
|
| 522 |
+
# Convert markdown headers to HTML
|
| 523 |
+
html_content = re.sub(r'^# (.*)', r'<h1 style="color: white; text-align: center; margin-bottom: 20px;">\1</h1>', html_content, flags=re.MULTILINE)
|
| 524 |
+
html_content = re.sub(r'^## (.*)', r'<h2 style="color: white; margin: 30px 0 20px 0;">\1</h2>', html_content, flags=re.MULTILINE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
|
| 526 |
+
# Convert markdown bold to HTML
|
| 527 |
+
html_content = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', html_content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
|
| 529 |
+
# Convert markdown code blocks
|
| 530 |
+
html_content = re.sub(r'```text\n(.*?)\n```', r'<pre style="background: rgba(0,0,0,0.3); padding: 15px; border-radius: 8px; color: white; font-family: monospace;">\1</pre>', html_content, flags=re.DOTALL)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
|
| 532 |
+
# Convert lists
|
| 533 |
+
html_content = re.sub(r'^β (.*)', r'<li style="color: rgba(255,255,255,0.9); margin: 5px 0;">\1</li>', html_content, flags=re.MULTILINE)
|
| 534 |
+
html_content = re.sub(r'^β
(.*)', r'<p style="color: var(--apple-green); margin: 15px 0;"><strong>β
\1</strong></p>', html_content, flags=re.MULTILINE)
|
| 535 |
+
|
| 536 |
+
# Convert line breaks to HTML
|
| 537 |
+
html_content = html_content.replace('\n\n', '</p><p style="color: rgba(255,255,255,0.9); line-height: 1.6; margin: 15px 0;">')
|
| 538 |
+
html_content = html_content.replace('\n', '<br>')
|
| 539 |
+
|
| 540 |
+
# Wrap in container
|
| 541 |
+
html_content = f'''
|
| 542 |
+
<div class="result-card slide-in" style="max-width: 1200px; margin: 0 auto; background: var(--glass-bg); border-radius: 16px; padding: 30px; backdrop-filter: blur(15px); box-shadow: var(--shadow-soft);">
|
| 543 |
+
<p style="color: rgba(255,255,255,0.9); line-height: 1.6; margin: 15px 0;">
|
| 544 |
+
{html_content}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
</div>
|
| 547 |
+
'''
|
| 548 |
+
|
| 549 |
+
return html_content
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"extends": "next/core-web-vitals"
|
| 3 |
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# dependencies
|
| 4 |
+
/node_modules
|
| 5 |
+
/.pnp
|
| 6 |
+
.pnp.js
|
| 7 |
+
.yarn/install-state.gz
|
| 8 |
+
|
| 9 |
+
# testing
|
| 10 |
+
/coverage
|
| 11 |
+
|
| 12 |
+
# next.js
|
| 13 |
+
/.next/
|
| 14 |
+
/out/
|
| 15 |
+
|
| 16 |
+
# production
|
| 17 |
+
/build
|
| 18 |
+
|
| 19 |
+
# misc
|
| 20 |
+
.DS_Store
|
| 21 |
+
*.pem
|
| 22 |
+
|
| 23 |
+
# debug
|
| 24 |
+
npm-debug.log*
|
| 25 |
+
yarn-debug.log*
|
| 26 |
+
yarn-error.log*
|
| 27 |
+
|
| 28 |
+
# local env files
|
| 29 |
+
.env*
|
| 30 |
+
|
| 31 |
+
# vercel
|
| 32 |
+
.vercel
|
| 33 |
+
|
| 34 |
+
# typescript
|
| 35 |
+
*.tsbuildinfo
|
| 36 |
+
next-env.d.ts
|
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright 2024 Vercel, Inc.
|
| 2 |
+
|
| 3 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
you may not use this file except in compliance with the License.
|
| 5 |
+
You may obtain a copy of the License at
|
| 6 |
+
|
| 7 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
|
| 9 |
+
Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
See the License for the specific language governing permissions and
|
| 13 |
+
limitations under the License.
|
|
Binary file (7.71 kB). View file
|
|
|
|
Binary file (15.1 kB). View file
|
|
|
|
Binary file (10.3 kB). View file
|
|
|
|
Binary file (13.2 kB). View file
|
|
|
|
Binary file (10.1 kB). View file
|
|
|
|
Binary file (8.86 kB). View file
|
|
|
|
Binary file (14.8 kB). View file
|
|
|
|
Binary file (7.92 kB). View file
|
|
|
|
Binary file (11 kB). View file
|
|
|
|
Binary file (11.3 kB). View file
|
|
|
|
Binary file (14.1 kB). View file
|
|
|
|
Binary file (19.1 kB). View file
|
|
|
|
Binary file (11.1 kB). View file
|
|
|
|
The diff for this file is too large to render.
See raw diff
|
|
|
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI SDK PDF Support Example
|
| 2 |
+
|
| 3 |
+
This example demonstrates how to use the [AI SDK](https://sdk.vercel.ai/docs) with [Next.js](https://nextjs.org/) with the `useObject` hook to submit PDF messages to the AI provider of your choice (Google or Anthropic).
|
| 4 |
+
|
| 5 |
+
## Deploy your own
|
| 6 |
+
|
| 7 |
+
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-sdk-preview-pdf-support&env=GOOGLE_API_KEY&envDescription=API%20keys%20needed%20for%20application&envLink=google.com)
|
| 8 |
+
|
| 9 |
+
## How to use
|
| 10 |
+
|
| 11 |
+
Run [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
npx create-next-app --example https://github.com/vercel-labs/ai-sdk-preview-pdf-support ai-sdk-preview-pdf-support-example
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
yarn create next-app --example https://github.com/vercel-labs/ai-sdk-preview-pdf-support ai-sdk-preview-pdf-support-example
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
pnpm create next-app --example https://github.com/vercel-labs/ai-sdk-preview-pdf-support ai-sdk-preview-pdf-support-example
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
To run the example locally you need to:
|
| 26 |
+
|
| 27 |
+
1. Sign up for accounts with the AI providers you want to use (e.g., Google).
|
| 28 |
+
2. Obtain API keys for Google provider.
|
| 29 |
+
3. Set the required environment variables as shown in the `.env.example` file, but in a new file called `.env`.
|
| 30 |
+
4. `npm install` to install the required dependencies.
|
| 31 |
+
5. `npm run dev` to launch the development server.
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
## Learn More
|
| 35 |
+
|
| 36 |
+
To learn more about Vercel AI SDK or Next.js take a look at the following resources:
|
| 37 |
+
|
| 38 |
+
- [AI SDK docs](https://sdk.vercel.ai/docs)
|
| 39 |
+
- [Vercel AI Playground](https://play.vercel.ai)
|
| 40 |
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
| 41 |
+
|
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use server";
|
| 2 |
+
|
| 3 |
+
import { google } from "@ai-sdk/google";
|
| 4 |
+
import { generateObject } from "ai";
|
| 5 |
+
import { z } from "zod";
|
| 6 |
+
|
| 7 |
+
export const generateQuizTitle = async (file: string) => {
|
| 8 |
+
const result = await generateObject({
|
| 9 |
+
model: google("gemini-1.5-flash-latest"),
|
| 10 |
+
schema: z.object({
|
| 11 |
+
title: z
|
| 12 |
+
.string()
|
| 13 |
+
.describe(
|
| 14 |
+
"A max three word title for the quiz based on the file provided as context",
|
| 15 |
+
),
|
| 16 |
+
}),
|
| 17 |
+
prompt:
|
| 18 |
+
"Generate a title for a quiz based on the following (PDF) file name. Try and extract as much info from the file name as possible. If the file name is just numbers or incoherent, just return quiz.\n\n " + file,
|
| 19 |
+
});
|
| 20 |
+
return result.object.title;
|
| 21 |
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
| 4 |
+
|
| 5 |
+
@layer base {
|
| 6 |
+
:root {
|
| 7 |
+
--background: 0 0% 100%;
|
| 8 |
+
--foreground: 240 10% 3.9%;
|
| 9 |
+
--card: 0 0% 100%;
|
| 10 |
+
--card-foreground: 240 10% 3.9%;
|
| 11 |
+
--popover: 0 0% 100%;
|
| 12 |
+
--popover-foreground: 240 10% 3.9%;
|
| 13 |
+
--primary: 240 5.9% 10%;
|
| 14 |
+
--primary-foreground: 0 0% 98%;
|
| 15 |
+
--secondary: 240 4.8% 95.9%;
|
| 16 |
+
--secondary-foreground: 240 5.9% 10%;
|
| 17 |
+
--muted: 240 4.8% 95.9%;
|
| 18 |
+
--muted-foreground: 240 3.8% 46.1%;
|
| 19 |
+
--accent: 240 4.8% 95.9%;
|
| 20 |
+
--accent-foreground: 240 5.9% 10%;
|
| 21 |
+
--destructive: 0 84.2% 60.2%;
|
| 22 |
+
--destructive-foreground: 0 0% 98%;
|
| 23 |
+
--border: 240 5.9% 90%;
|
| 24 |
+
--input: 240 5.9% 90%;
|
| 25 |
+
--ring: 240 10% 3.9%;
|
| 26 |
+
--chart-1: 12 76% 61%;
|
| 27 |
+
--chart-2: 173 58% 39%;
|
| 28 |
+
--chart-3: 197 37% 24%;
|
| 29 |
+
--chart-4: 43 74% 66%;
|
| 30 |
+
--chart-5: 27 87% 67%;
|
| 31 |
+
--radius: 0.5rem;
|
| 32 |
+
}
|
| 33 |
+
.dark {
|
| 34 |
+
--background: 240 10% 3.9%;
|
| 35 |
+
--foreground: 0 0% 98%;
|
| 36 |
+
--card: 240 10% 3.9%;
|
| 37 |
+
--card-foreground: 0 0% 98%;
|
| 38 |
+
--popover: 240 10% 3.9%;
|
| 39 |
+
--popover-foreground: 0 0% 98%;
|
| 40 |
+
--primary: 0 0% 98%;
|
| 41 |
+
--primary-foreground: 240 5.9% 10%;
|
| 42 |
+
--secondary: 240 3.7% 15.9%;
|
| 43 |
+
--secondary-foreground: 0 0% 98%;
|
| 44 |
+
--muted: 240 3.7% 15.9%;
|
| 45 |
+
--muted-foreground: 240 5% 64.9%;
|
| 46 |
+
--accent: 240 3.7% 15.9%;
|
| 47 |
+
--accent-foreground: 0 0% 98%;
|
| 48 |
+
--destructive: 0 62.8% 30.6%;
|
| 49 |
+
--destructive-foreground: 0 0% 98%;
|
| 50 |
+
--border: 240 3.7% 15.9%;
|
| 51 |
+
--input: 240 3.7% 15.9%;
|
| 52 |
+
--ring: 240 4.9% 83.9%;
|
| 53 |
+
--chart-1: 220 70% 50%;
|
| 54 |
+
--chart-2: 160 60% 45%;
|
| 55 |
+
--chart-3: 30 80% 55%;
|
| 56 |
+
--chart-4: 280 65% 60%;
|
| 57 |
+
--chart-5: 340 75% 55%;
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
@layer base {
|
| 62 |
+
* {
|
| 63 |
+
@apply border-border;
|
| 64 |
+
}
|
| 65 |
+
body {
|
| 66 |
+
@apply bg-background text-foreground;
|
| 67 |
+
}
|
| 68 |
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import "./globals.css";
|
| 2 |
+
import { Metadata } from "next";
|
| 3 |
+
import { Toaster } from "sonner";
|
| 4 |
+
import { ThemeProvider } from "next-themes";
|
| 5 |
+
import { Geist } from "next/font/google";
|
| 6 |
+
|
| 7 |
+
const geist = Geist({ subsets: ["latin"] });
|
| 8 |
+
|
| 9 |
+
export const metadata: Metadata = {
|
| 10 |
+
metadataBase: new URL("https://ai-sdk-preview-pdf-support.vercel.app"),
|
| 11 |
+
title: "PDF Support Preview",
|
| 12 |
+
description: "Experimental preview of PDF support with the AI SDK",
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
export default function RootLayout({
|
| 16 |
+
children,
|
| 17 |
+
}: Readonly<{
|
| 18 |
+
children: React.ReactNode;
|
| 19 |
+
}>) {
|
| 20 |
+
return (
|
| 21 |
+
<html lang="en" suppressHydrationWarning className={`${geist.className}`}>
|
| 22 |
+
<body>
|
| 23 |
+
<ThemeProvider attribute="class" enableSystem forcedTheme="dark">
|
| 24 |
+
<Toaster position="top-center" richColors />
|
| 25 |
+
{children}
|
| 26 |
+
</ThemeProvider>
|
| 27 |
+
</body>
|
| 28 |
+
</html>
|
| 29 |
+
);
|
| 30 |
+
}
|
|
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import { experimental_useObject } from "ai/react";
|
| 5 |
+
import { questionsSchema } from "@/lib/schemas";
|
| 6 |
+
import { z } from "zod";
|
| 7 |
+
import { toast } from "sonner";
|
| 8 |
+
import { FileUp, Plus, Loader2 } from "lucide-react";
|
| 9 |
+
import { Button } from "@/components/ui/button";
|
| 10 |
+
import {
|
| 11 |
+
Card,
|
| 12 |
+
CardContent,
|
| 13 |
+
CardFooter,
|
| 14 |
+
CardHeader,
|
| 15 |
+
CardTitle,
|
| 16 |
+
CardDescription,
|
| 17 |
+
} from "@/components/ui/card";
|
| 18 |
+
import { Progress } from "@/components/ui/progress";
|
| 19 |
+
import Quiz from "@/components/quiz";
|
| 20 |
+
import { Link } from "@/components/ui/link";
|
| 21 |
+
import NextLink from "next/link";
|
| 22 |
+
import { generateQuizTitle } from "./actions";
|
| 23 |
+
import { AnimatePresence, motion } from "framer-motion";
|
| 24 |
+
import { VercelIcon, GitIcon } from "@/components/icons";
|
| 25 |
+
|
| 26 |
+
export default function ChatWithFiles() {
|
| 27 |
+
const [files, setFiles] = useState<File[]>([]);
|
| 28 |
+
const [questions, setQuestions] = useState<z.infer<typeof questionsSchema>>(
|
| 29 |
+
[],
|
| 30 |
+
);
|
| 31 |
+
const [isDragging, setIsDragging] = useState(false);
|
| 32 |
+
const [title, setTitle] = useState<string>();
|
| 33 |
+
|
| 34 |
+
const {
|
| 35 |
+
submit,
|
| 36 |
+
object: partialQuestions,
|
| 37 |
+
isLoading,
|
| 38 |
+
} = experimental_useObject({
|
| 39 |
+
api: "/api/generate-quiz",
|
| 40 |
+
schema: questionsSchema,
|
| 41 |
+
initialValue: undefined,
|
| 42 |
+
onError: (error) => {
|
| 43 |
+
toast.error("Failed to generate quiz. Please try again.");
|
| 44 |
+
setFiles([]);
|
| 45 |
+
},
|
| 46 |
+
onFinish: ({ object }) => {
|
| 47 |
+
setQuestions(object ?? []);
|
| 48 |
+
},
|
| 49 |
+
});
|
| 50 |
+
|
| 51 |
+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
| 52 |
+
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
| 53 |
+
|
| 54 |
+
if (isSafari && isDragging) {
|
| 55 |
+
toast.error(
|
| 56 |
+
"Safari does not support drag & drop. Please use the file picker.",
|
| 57 |
+
);
|
| 58 |
+
return;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
const selectedFiles = Array.from(e.target.files || []);
|
| 62 |
+
const validFiles = selectedFiles.filter(
|
| 63 |
+
(file) => file.type === "application/pdf" && file.size <= 5 * 1024 * 1024,
|
| 64 |
+
);
|
| 65 |
+
console.log(validFiles);
|
| 66 |
+
|
| 67 |
+
if (validFiles.length !== selectedFiles.length) {
|
| 68 |
+
toast.error("Only PDF files under 5MB are allowed.");
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
setFiles(validFiles);
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
const encodeFileAsBase64 = (file: File): Promise<string> => {
|
| 75 |
+
return new Promise((resolve, reject) => {
|
| 76 |
+
const reader = new FileReader();
|
| 77 |
+
reader.readAsDataURL(file);
|
| 78 |
+
reader.onload = () => resolve(reader.result as string);
|
| 79 |
+
reader.onerror = (error) => reject(error);
|
| 80 |
+
});
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
const handleSubmitWithFiles = async (e: React.FormEvent<HTMLFormElement>) => {
|
| 84 |
+
e.preventDefault();
|
| 85 |
+
const encodedFiles = await Promise.all(
|
| 86 |
+
files.map(async (file) => ({
|
| 87 |
+
name: file.name,
|
| 88 |
+
type: file.type,
|
| 89 |
+
data: await encodeFileAsBase64(file),
|
| 90 |
+
})),
|
| 91 |
+
);
|
| 92 |
+
submit({ files: encodedFiles });
|
| 93 |
+
const generatedTitle = await generateQuizTitle(encodedFiles[0].name);
|
| 94 |
+
setTitle(generatedTitle);
|
| 95 |
+
};
|
| 96 |
+
|
| 97 |
+
const clearPDF = () => {
|
| 98 |
+
setFiles([]);
|
| 99 |
+
setQuestions([]);
|
| 100 |
+
};
|
| 101 |
+
|
| 102 |
+
const progress = partialQuestions ? (partialQuestions.length / 4) * 100 : 0;
|
| 103 |
+
|
| 104 |
+
if (questions.length === 4) {
|
| 105 |
+
return (
|
| 106 |
+
<Quiz title={title ?? "Quiz"} questions={questions} clearPDF={clearPDF} />
|
| 107 |
+
);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
return (
|
| 111 |
+
<div
|
| 112 |
+
className="min-h-[100dvh] w-full flex justify-center"
|
| 113 |
+
onDragOver={(e) => {
|
| 114 |
+
e.preventDefault();
|
| 115 |
+
setIsDragging(true);
|
| 116 |
+
}}
|
| 117 |
+
onDragExit={() => setIsDragging(false)}
|
| 118 |
+
onDragEnd={() => setIsDragging(false)}
|
| 119 |
+
onDragLeave={() => setIsDragging(false)}
|
| 120 |
+
onDrop={(e) => {
|
| 121 |
+
e.preventDefault();
|
| 122 |
+
setIsDragging(false);
|
| 123 |
+
console.log(e.dataTransfer.files);
|
| 124 |
+
handleFileChange({
|
| 125 |
+
target: { files: e.dataTransfer.files },
|
| 126 |
+
} as React.ChangeEvent<HTMLInputElement>);
|
| 127 |
+
}}
|
| 128 |
+
>
|
| 129 |
+
<AnimatePresence>
|
| 130 |
+
{isDragging && (
|
| 131 |
+
<motion.div
|
| 132 |
+
className="fixed pointer-events-none dark:bg-zinc-900/90 h-dvh w-dvw z-10 justify-center items-center flex flex-col gap-1 bg-zinc-100/90"
|
| 133 |
+
initial={{ opacity: 0 }}
|
| 134 |
+
animate={{ opacity: 1 }}
|
| 135 |
+
exit={{ opacity: 0 }}
|
| 136 |
+
>
|
| 137 |
+
<div>Drag and drop files here</div>
|
| 138 |
+
<div className="text-sm dark:text-zinc-400 text-zinc-500">
|
| 139 |
+
{"(PDFs only)"}
|
| 140 |
+
</div>
|
| 141 |
+
</motion.div>
|
| 142 |
+
)}
|
| 143 |
+
</AnimatePresence>
|
| 144 |
+
<Card className="w-full max-w-md h-full border-0 sm:border sm:h-fit mt-12">
|
| 145 |
+
<CardHeader className="text-center space-y-6">
|
| 146 |
+
<div className="mx-auto flex items-center justify-center space-x-2 text-muted-foreground">
|
| 147 |
+
<div className="rounded-full bg-primary/10 p-2">
|
| 148 |
+
<FileUp className="h-6 w-6" />
|
| 149 |
+
</div>
|
| 150 |
+
<Plus className="h-4 w-4" />
|
| 151 |
+
<div className="rounded-full bg-primary/10 p-2">
|
| 152 |
+
<Loader2 className="h-6 w-6" />
|
| 153 |
+
</div>
|
| 154 |
+
</div>
|
| 155 |
+
<div className="space-y-2">
|
| 156 |
+
<CardTitle className="text-2xl font-bold">
|
| 157 |
+
PDF Quiz Generator
|
| 158 |
+
</CardTitle>
|
| 159 |
+
<CardDescription className="text-base">
|
| 160 |
+
Upload a PDF to generate an interactive quiz based on its content
|
| 161 |
+
using the <Link href="https://sdk.vercel.ai">AI SDK</Link> and{" "}
|
| 162 |
+
<Link href="https://sdk.vercel.ai/providers/ai-sdk-providers/google-generative-ai">
|
| 163 |
+
Google's Gemini Pro
|
| 164 |
+
</Link>
|
| 165 |
+
.
|
| 166 |
+
</CardDescription>
|
| 167 |
+
</div>
|
| 168 |
+
</CardHeader>
|
| 169 |
+
<CardContent>
|
| 170 |
+
<form onSubmit={handleSubmitWithFiles} className="space-y-4">
|
| 171 |
+
<div
|
| 172 |
+
className={`relative flex flex-col items-center justify-center border-2 border-dashed border-muted-foreground/25 rounded-lg p-6 transition-colors hover:border-muted-foreground/50`}
|
| 173 |
+
>
|
| 174 |
+
<input
|
| 175 |
+
type="file"
|
| 176 |
+
onChange={handleFileChange}
|
| 177 |
+
accept="application/pdf"
|
| 178 |
+
className="absolute inset-0 opacity-0 cursor-pointer"
|
| 179 |
+
/>
|
| 180 |
+
<FileUp className="h-8 w-8 mb-2 text-muted-foreground" />
|
| 181 |
+
<p className="text-sm text-muted-foreground text-center">
|
| 182 |
+
{files.length > 0 ? (
|
| 183 |
+
<span className="font-medium text-foreground">
|
| 184 |
+
{files[0].name}
|
| 185 |
+
</span>
|
| 186 |
+
) : (
|
| 187 |
+
<span>Drop your PDF here or click to browse.</span>
|
| 188 |
+
)}
|
| 189 |
+
</p>
|
| 190 |
+
</div>
|
| 191 |
+
<Button
|
| 192 |
+
type="submit"
|
| 193 |
+
className="w-full"
|
| 194 |
+
disabled={files.length === 0}
|
| 195 |
+
>
|
| 196 |
+
{isLoading ? (
|
| 197 |
+
<span className="flex items-center space-x-2">
|
| 198 |
+
<Loader2 className="h-4 w-4 animate-spin" />
|
| 199 |
+
<span>Generating Quiz...</span>
|
| 200 |
+
</span>
|
| 201 |
+
) : (
|
| 202 |
+
"Generate Quiz"
|
| 203 |
+
)}
|
| 204 |
+
</Button>
|
| 205 |
+
</form>
|
| 206 |
+
</CardContent>
|
| 207 |
+
{isLoading && (
|
| 208 |
+
<CardFooter className="flex flex-col space-y-4">
|
| 209 |
+
<div className="w-full space-y-1">
|
| 210 |
+
<div className="flex justify-between text-sm text-muted-foreground">
|
| 211 |
+
<span>Progress</span>
|
| 212 |
+
<span>{Math.round(progress)}%</span>
|
| 213 |
+
</div>
|
| 214 |
+
<Progress value={progress} className="h-2" />
|
| 215 |
+
</div>
|
| 216 |
+
<div className="w-full space-y-2">
|
| 217 |
+
<div className="grid grid-cols-6 sm:grid-cols-4 items-center space-x-2 text-sm">
|
| 218 |
+
<div
|
| 219 |
+
className={`h-2 w-2 rounded-full ${
|
| 220 |
+
isLoading ? "bg-yellow-500/50 animate-pulse" : "bg-muted"
|
| 221 |
+
}`}
|
| 222 |
+
/>
|
| 223 |
+
<span className="text-muted-foreground text-center col-span-4 sm:col-span-2">
|
| 224 |
+
{partialQuestions
|
| 225 |
+
? `Generating question ${partialQuestions.length + 1} of 4`
|
| 226 |
+
: "Analyzing PDF content"}
|
| 227 |
+
</span>
|
| 228 |
+
</div>
|
| 229 |
+
</div>
|
| 230 |
+
</CardFooter>
|
| 231 |
+
)}
|
| 232 |
+
</Card>
|
| 233 |
+
<motion.div
|
| 234 |
+
className="flex flex-row gap-4 items-center justify-between fixed bottom-6 text-xs "
|
| 235 |
+
initial={{ y: 20, opacity: 0 }}
|
| 236 |
+
animate={{ y: 0, opacity: 1 }}
|
| 237 |
+
>
|
| 238 |
+
<NextLink
|
| 239 |
+
target="_blank"
|
| 240 |
+
href="https://github.com/vercel-labs/ai-sdk-preview-pdf-support"
|
| 241 |
+
className="flex flex-row gap-2 items-center border px-2 py-1.5 rounded-md hover:bg-zinc-100 dark:border-zinc-800 dark:hover:bg-zinc-800"
|
| 242 |
+
>
|
| 243 |
+
<GitIcon />
|
| 244 |
+
View Source Code
|
| 245 |
+
</NextLink>
|
| 246 |
+
|
| 247 |
+
<NextLink
|
| 248 |
+
target="_blank"
|
| 249 |
+
href="https://vercel.com/templates/next.js/ai-quiz-generator"
|
| 250 |
+
className="flex flex-row gap-2 items-center bg-zinc-900 px-2 py-1.5 rounded-md text-zinc-50 hover:bg-zinc-950 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-50"
|
| 251 |
+
>
|
| 252 |
+
<VercelIcon size={14} />
|
| 253 |
+
Deploy with Vercel
|
| 254 |
+
</NextLink>
|
| 255 |
+
</motion.div>
|
| 256 |
+
</div>
|
| 257 |
+
);
|
| 258 |
+
}
|
|
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { openai } from '@ai-sdk/openai'
|
| 2 |
+
import { streamText, generateObject } from 'ai'
|
| 3 |
+
import { NextRequest } from 'next/server'
|
| 4 |
+
import { z } from 'zod'
|
| 5 |
+
import { processQuestionBank, selectQuestionsByRole, type Question } from '@/lib/questions-processor'
|
| 6 |
+
import { researchCompanyInsights, generateSalaryInsights, type CompanyInsights } from '@/lib/company-research'
|
| 7 |
+
import fs from 'fs'
|
| 8 |
+
import path from 'path'
|
| 9 |
+
import { NextResponse } from 'next/server'
|
| 10 |
+
|
| 11 |
+
// Simulate your existing analysis functions
|
| 12 |
+
async function analyzeResume(resumeText: string) {
|
| 13 |
+
// Simulate resume analysis (5-10 seconds)
|
| 14 |
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
| 15 |
+
return {
|
| 16 |
+
skills: ['Python', 'Machine Learning', 'SQL', 'AWS', 'React'],
|
| 17 |
+
experience: '5+ years',
|
| 18 |
+
education: 'Computer Science',
|
| 19 |
+
strengths: ['Technical Leadership', 'Problem Solving', 'Communication']
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
async function analyzeJobMatch(resumeData: any, jobDescription: string) {
|
| 24 |
+
// Simulate job matching analysis (10-15 seconds)
|
| 25 |
+
await new Promise(resolve => setTimeout(resolve, 5000))
|
| 26 |
+
return {
|
| 27 |
+
overallMatch: 85,
|
| 28 |
+
skillsMatch: 90,
|
| 29 |
+
experienceMatch: 80,
|
| 30 |
+
missingSkills: ['Kubernetes', 'GraphQL'],
|
| 31 |
+
strongMatches: ['Python', 'Machine Learning', 'AWS']
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
async function generateInterviewQuestion(skillArea: string, difficulty: string) {
|
| 36 |
+
// Generate one interview question (3-5 seconds each)
|
| 37 |
+
const result = await streamText({
|
| 38 |
+
model: openai('gpt-4o-mini'),
|
| 39 |
+
prompt: `Generate a ${difficulty} interview question about ${skillArea}.
|
| 40 |
+
Format: Question, Expected Answer, Follow-up Questions.
|
| 41 |
+
Make it practical and specific.`,
|
| 42 |
+
maxTokens: 200,
|
| 43 |
+
})
|
| 44 |
+
|
| 45 |
+
return result.textStream
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
async function loadQuestionBank(): Promise<Question[]> {
|
| 49 |
+
try {
|
| 50 |
+
// Try to load the real CSV file
|
| 51 |
+
const csvPath = path.join(process.cwd(), 'Question_bank_IQ_categorized/summary (1).csv')
|
| 52 |
+
|
| 53 |
+
let csvData = ''
|
| 54 |
+
try {
|
| 55 |
+
csvData = fs.readFileSync(csvPath, 'utf-8')
|
| 56 |
+
} catch {
|
| 57 |
+
// Fallback to sample data if CSV not found
|
| 58 |
+
csvData = `idx,title,type,summaries,link
|
| 59 |
+
0,Weekly Aggregation,python,"Summary: Group a list of sequential timestamps into weekly lists starting from the first timestamp.",interviewquery.com/questions/weekly-aggregation
|
| 60 |
+
1,Decreasing Comments,product metrics,"Summary: Identify reasons and metrics for decreasing average comments per user despite user growth in a new city.",interviewquery.com/questions/decreasing-comments
|
| 61 |
+
2,Employee Salaries,sql,"Summary: Select the top 3 departments with at least ten employees, ranked by the percentage of employees earning over 100K.",interviewquery.com/questions/employee-salaries
|
| 62 |
+
3,500 Cards,probability,"Summary: Determine the probability of drawing three cards in increasing order from a shuffled deck of 500 numbered cards.",interviewquery.com/questions/500-cards
|
| 63 |
+
4,Random Number,algorithms,"Summary: Select a random number from a stream with equal probability using O(1) space.",interviewquery.com/questions/random-number`
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
return processQuestionBank(csvData)
|
| 67 |
+
} catch (error) {
|
| 68 |
+
console.error('Error loading question bank:', error)
|
| 69 |
+
return []
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
async function personalizeQuestions(
|
| 74 |
+
questions: Question[],
|
| 75 |
+
resumeData: any,
|
| 76 |
+
jobData: any
|
| 77 |
+
): Promise<Question[]> {
|
| 78 |
+
const personalizedQuestions = []
|
| 79 |
+
|
| 80 |
+
for (const question of questions) {
|
| 81 |
+
try {
|
| 82 |
+
const personalization = await generateObject({
|
| 83 |
+
model: openai('gpt-4o-mini'),
|
| 84 |
+
prompt: `
|
| 85 |
+
Personalize this interview question for a candidate:
|
| 86 |
+
|
| 87 |
+
ORIGINAL QUESTION: "${question.summaries}"
|
| 88 |
+
|
| 89 |
+
CANDIDATE BACKGROUND:
|
| 90 |
+
- Role: ${jobData.role} at ${jobData.company}
|
| 91 |
+
- Experience: ${resumeData.experience_years} years
|
| 92 |
+
- Skills: ${resumeData.skills.slice(0, 5).join(', ')}
|
| 93 |
+
- Previous roles: ${resumeData.previous_roles.join(', ')}
|
| 94 |
+
|
| 95 |
+
Create:
|
| 96 |
+
1. A personalized approach explanation (2-3 sentences)
|
| 97 |
+
2. Why this question is relevant for this candidate
|
| 98 |
+
3. 2-3 follow-up questions based on their background
|
| 99 |
+
`,
|
| 100 |
+
schema: z.object({
|
| 101 |
+
approach: z.string(),
|
| 102 |
+
relevance: z.string(),
|
| 103 |
+
followUps: z.array(z.string())
|
| 104 |
+
})
|
| 105 |
+
})
|
| 106 |
+
|
| 107 |
+
personalizedQuestions.push({
|
| 108 |
+
...question,
|
| 109 |
+
approach: personalization.object.approach,
|
| 110 |
+
relevance: personalization.object.relevance,
|
| 111 |
+
followUps: personalization.object.followUps
|
| 112 |
+
})
|
| 113 |
+
} catch (error) {
|
| 114 |
+
// Fallback if personalization fails
|
| 115 |
+
personalizedQuestions.push({
|
| 116 |
+
...question,
|
| 117 |
+
approach: `This question tests your understanding of ${question.type}. Focus on demonstrating your practical experience and problem-solving approach.`,
|
| 118 |
+
relevance: `Relevant for ${jobData.role} positions requiring ${question.type} skills.`,
|
| 119 |
+
followUps: ['Can you walk through your thought process?', 'How would you optimize this solution?']
|
| 120 |
+
})
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
return personalizedQuestions
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
async function generateComprehensiveGuide(finalAnalysis: any, jobInfo: any) {
|
| 128 |
+
try {
|
| 129 |
+
// Load question bank and research company insights
|
| 130 |
+
const [questionBank, companyInsights] = await Promise.all([
|
| 131 |
+
loadQuestionBank(),
|
| 132 |
+
researchCompanyInsights(jobInfo.company, jobInfo.role || jobInfo.title, jobInfo.location)
|
| 133 |
+
])
|
| 134 |
+
|
| 135 |
+
// Select and personalize questions
|
| 136 |
+
const resumeData = {
|
| 137 |
+
name: 'Candidate',
|
| 138 |
+
experience_years: finalAnalysis.experienceYears || 3,
|
| 139 |
+
skills: finalAnalysis.matchingSkills || [],
|
| 140 |
+
previous_roles: finalAnalysis.previousRoles || [],
|
| 141 |
+
key_achievements: finalAnalysis.achievements || [],
|
| 142 |
+
education: finalAnalysis.education || ''
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
const jobData = {
|
| 146 |
+
role: jobInfo.role || jobInfo.title,
|
| 147 |
+
company: jobInfo.company,
|
| 148 |
+
location: jobInfo.location,
|
| 149 |
+
description: jobInfo.description || jobInfo.fullContent || '',
|
| 150 |
+
required_skills: finalAnalysis.missingSkills || []
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
const technicalQuestions = selectQuestionsByRole(questionBank, jobData.role, 'technical').slice(0, 6)
|
| 154 |
+
const behavioralQuestions = selectQuestionsByRole(questionBank, jobData.role, 'behavioral').slice(0, 4)
|
| 155 |
+
const caseStudyQuestions = selectQuestionsByRole(questionBank, jobData.role, 'caseStudy').slice(0, 4)
|
| 156 |
+
|
| 157 |
+
// Personalize questions (limit to avoid timeout)
|
| 158 |
+
const [personalizedTechnical, personalizedBehavioral, personalizedCaseStudy] = await Promise.all([
|
| 159 |
+
personalizeQuestions(technicalQuestions.slice(0, 3), resumeData, jobData), // Limit for demo
|
| 160 |
+
personalizeQuestions(behavioralQuestions.slice(0, 2), resumeData, jobData),
|
| 161 |
+
personalizeQuestions(caseStudyQuestions.slice(0, 2), resumeData, jobData)
|
| 162 |
+
])
|
| 163 |
+
|
| 164 |
+
// Generate comprehensive guide structure
|
| 165 |
+
const guide = {
|
| 166 |
+
title: `${jobData.role} Interview Guide - ${jobData.company}`,
|
| 167 |
+
introduction: {
|
| 168 |
+
roleOverview: `The ${jobData.role} role at ${jobData.company} combines ${companyInsights.candidateProfile.keySkills.slice(0, 3).join(', ')} with strong business impact. ${companyInsights.businessModel.overview}`,
|
| 169 |
+
culture: `${jobData.company}'s culture emphasizes ${companyInsights.culture.values.slice(0, 3).join(', ')}. ${companyInsights.culture.workEnvironment}`,
|
| 170 |
+
whyThisRole: `This role offers unique opportunities to ${companyInsights.businessModel.keyMetrics.slice(0, 2).join(' and ')} while working with cutting-edge technology and talented teams.`
|
| 171 |
+
},
|
| 172 |
+
interviewProcess: {
|
| 173 |
+
diagram: `Interview Process:\n${companyInsights.interviewProcess.stages.map((stage, idx) => `${idx + 1}. **${stage.name}** (${stage.duration}) - ${stage.description}`).join('\n')}`,
|
| 174 |
+
stages: companyInsights.interviewProcess.stages,
|
| 175 |
+
levelDifferences: `**Junior ${jobData.role}** candidates can expect more foundational questions and hands-on problem solving.\n\n**Senior ${jobData.role}** candidates will be evaluated on system design, stakeholder communication, and technical leadership.`
|
| 176 |
+
},
|
| 177 |
+
questions: {
|
| 178 |
+
technical: personalizedTechnical,
|
| 179 |
+
behavioral: personalizedBehavioral,
|
| 180 |
+
caseStudy: personalizedCaseStudy
|
| 181 |
+
},
|
| 182 |
+
preparation: {
|
| 183 |
+
tips: [
|
| 184 |
+
{
|
| 185 |
+
title: 'Study the Business Model',
|
| 186 |
+
description: `Understand ${jobData.company}'s ${companyInsights.businessModel.overview} and key metrics.`
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
title: 'Technical Practice',
|
| 190 |
+
description: `Focus on ${companyInsights.candidateProfile.keySkills.slice(0, 3).join(', ')} - use platforms like Interview Query for hands-on practice.`
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
title: 'Behavioral Preparation',
|
| 194 |
+
description: 'Prepare STAR method examples showcasing your experience with problem solving, leadership, and collaboration.'
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
title: 'Company Research',
|
| 198 |
+
description: `Research ${jobData.company}'s recent product launches and company values to show genuine interest.`
|
| 199 |
+
}
|
| 200 |
+
],
|
| 201 |
+
studyPlan: `**Week 1:** Technical fundamentals and coding practice\n**Week 2:** System design and case study preparation\n**Week 3:** Behavioral questions and company research\n**Final Days:** Mock interviews and confidence building`,
|
| 202 |
+
mockInterviews: 'Practice with peers, use Interview Query\'s mock interview service, or record yourself answering questions to improve communication.'
|
| 203 |
+
},
|
| 204 |
+
faqs: {
|
| 205 |
+
salary: `**Salary Range:** ${companyInsights.salaryInsights.range}\n\n**Negotiation:** ${companyInsights.salaryInsights.negotiation}`,
|
| 206 |
+
experiences: `Browse [${jobData.company} Interview Experiences](https://www.interviewquery.com/interview-experiences) to read first-hand candidate stories and success tips.`,
|
| 207 |
+
jobPostings: `Check out the latest [${jobData.company} job openings](https://www.interviewquery.com/jobs) and practice tailored questions before applying.`
|
| 208 |
+
},
|
| 209 |
+
conclusion: {
|
| 210 |
+
summary: `${jobData.company}'s ${jobData.role} interviews emphasize technical excellence, problem-solving ability, and cultural alignment. With thorough preparation and clear communication, you'll demonstrate the skills and mindset they're seeking.`,
|
| 211 |
+
resources: {
|
| 212 |
+
successStory: {
|
| 213 |
+
title: `${jobData.role} Success Stories`,
|
| 214 |
+
link: 'https://www.interviewquery.com/success-stories'
|
| 215 |
+
},
|
| 216 |
+
questionList: {
|
| 217 |
+
title: `Top ${jobData.role} Interview Questions`,
|
| 218 |
+
link: 'https://www.interviewquery.com/questions'
|
| 219 |
+
},
|
| 220 |
+
learningPath: {
|
| 221 |
+
title: `${jobData.role} Learning Path`,
|
| 222 |
+
link: 'https://www.interviewquery.com/learning-paths'
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
},
|
| 226 |
+
metadata: {
|
| 227 |
+
generatedAt: new Date().toISOString(),
|
| 228 |
+
personalizedFor: 'Candidate',
|
| 229 |
+
targetRole: jobData.role,
|
| 230 |
+
targetCompany: jobData.company,
|
| 231 |
+
sourceData: jobInfo.source || 'manual'
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
return guide
|
| 236 |
+
} catch (error) {
|
| 237 |
+
console.error('Guide generation error:', error)
|
| 238 |
+
throw error
|
| 239 |
+
}
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
async function generateFinalAnalysis(resumeData: any, matchData: any) {
|
| 243 |
+
// Simulate final analysis generation
|
| 244 |
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
| 245 |
+
|
| 246 |
+
return {
|
| 247 |
+
overallMatch: matchData.overallMatch,
|
| 248 |
+
skillsBreakdown: {
|
| 249 |
+
technical: matchData.skillsMatch,
|
| 250 |
+
experience: matchData.experienceMatch,
|
| 251 |
+
culture: 78
|
| 252 |
+
},
|
| 253 |
+
salaryInsights: {
|
| 254 |
+
range: { min: '95k', max: '135k' },
|
| 255 |
+
median: '115k',
|
| 256 |
+
percentile: '75th',
|
| 257 |
+
tip: 'Consider negotiating 10-15% above base offer'
|
| 258 |
+
},
|
| 259 |
+
matchingSkills: matchData.strongMatches,
|
| 260 |
+
missingSkills: matchData.missingSkills,
|
| 261 |
+
recommendations: [
|
| 262 |
+
'Practice system design questions for senior-level roles',
|
| 263 |
+
'Prepare STAR method examples for behavioral questions',
|
| 264 |
+
'Research company culture and recent product launches',
|
| 265 |
+
'Prepare questions about team structure and growth opportunities'
|
| 266 |
+
],
|
| 267 |
+
summary: {
|
| 268 |
+
matchScore: matchData.overallMatch,
|
| 269 |
+
preparationTime: '2-3 hours recommended',
|
| 270 |
+
focusAreas: matchData.missingSkills
|
| 271 |
+
},
|
| 272 |
+
// Add additional data for guide generation
|
| 273 |
+
experienceYears: 3,
|
| 274 |
+
previousRoles: ['Software Developer', 'Data Analyst'],
|
| 275 |
+
achievements: ['Led team of 5 developers', 'Improved system performance by 40%'],
|
| 276 |
+
education: 'Computer Science',
|
| 277 |
+
requiredSkills: matchData.missingSkills
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
function extractJobInfoFromDescription(jobDescription: string) {
|
| 282 |
+
// Extract job info from description string (fallback method)
|
| 283 |
+
const lines = jobDescription.split('\n')
|
| 284 |
+
let role = 'Software Engineer'
|
| 285 |
+
let company = 'Target Company'
|
| 286 |
+
let location = 'United States'
|
| 287 |
+
|
| 288 |
+
// Try to extract role and company from job description
|
| 289 |
+
for (const line of lines.slice(0, 10)) {
|
| 290 |
+
const rolePatterns = [
|
| 291 |
+
/(?:position|role|job title|title):\s*(.+)/i,
|
| 292 |
+
/(?:hiring|seeking|looking for)(?:\s+a)?\s+(.+?)(?:\s+at|\s+to|\s*$)/i,
|
| 293 |
+
/^(.+?)\s+(?:position|role|job)/i
|
| 294 |
+
]
|
| 295 |
+
|
| 296 |
+
const companyPatterns = [
|
| 297 |
+
/(?:company|organization|employer):\s*(.+)/i,
|
| 298 |
+
/at\s+(.+?)(?:\s+we|\s+is|\s+has|\s*$)/i,
|
| 299 |
+
/(.+?)(?:\s+is\s+(?:hiring|seeking|looking))/i
|
| 300 |
+
]
|
| 301 |
+
|
| 302 |
+
for (const pattern of rolePatterns) {
|
| 303 |
+
const match = line.match(pattern)
|
| 304 |
+
if (match) {
|
| 305 |
+
role = match[1].trim()
|
| 306 |
+
break
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
for (const pattern of companyPatterns) {
|
| 311 |
+
const match = line.match(pattern)
|
| 312 |
+
if (match) {
|
| 313 |
+
company = match[1].trim()
|
| 314 |
+
break
|
| 315 |
+
}
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
return {
|
| 320 |
+
role,
|
| 321 |
+
company,
|
| 322 |
+
location,
|
| 323 |
+
title: role,
|
| 324 |
+
description: jobDescription.substring(0, 500) + '...',
|
| 325 |
+
requirements: ['Skills and qualifications as listed in job posting'],
|
| 326 |
+
source: 'job_description'
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
export async function POST(req: NextRequest) {
|
| 331 |
+
try {
|
| 332 |
+
const { resumeText, jobDescription, jobData } = await req.json()
|
| 333 |
+
|
| 334 |
+
const stream = new ReadableStream({
|
| 335 |
+
async start(controller) {
|
| 336 |
+
const encoder = new TextEncoder()
|
| 337 |
+
|
| 338 |
+
// Helper function to send updates
|
| 339 |
+
const sendUpdate = (data: any) => {
|
| 340 |
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
try {
|
| 344 |
+
// Step 1: Resume Analysis
|
| 345 |
+
sendUpdate({
|
| 346 |
+
step: 'resume_analysis',
|
| 347 |
+
status: 'processing',
|
| 348 |
+
message: 'π Analyzing your resume...',
|
| 349 |
+
progress: 10
|
| 350 |
+
})
|
| 351 |
+
|
| 352 |
+
const resumeData = await analyzeResume(resumeText)
|
| 353 |
+
|
| 354 |
+
sendUpdate({
|
| 355 |
+
step: 'resume_analysis',
|
| 356 |
+
status: 'completed',
|
| 357 |
+
message: 'β
Resume analyzed successfully!',
|
| 358 |
+
data: resumeData,
|
| 359 |
+
progress: 25
|
| 360 |
+
})
|
| 361 |
+
|
| 362 |
+
// Step 2: Job Matching
|
| 363 |
+
sendUpdate({
|
| 364 |
+
step: 'job_matching',
|
| 365 |
+
status: 'processing',
|
| 366 |
+
message: 'π― Analyzing job compatibility...',
|
| 367 |
+
progress: 30
|
| 368 |
+
})
|
| 369 |
+
|
| 370 |
+
const matchData = await analyzeJobMatch(resumeData, jobDescription)
|
| 371 |
+
|
| 372 |
+
sendUpdate({
|
| 373 |
+
step: 'job_matching',
|
| 374 |
+
status: 'completed',
|
| 375 |
+
message: `β
${matchData.overallMatch}% compatibility found!`,
|
| 376 |
+
data: matchData,
|
| 377 |
+
progress: 50
|
| 378 |
+
})
|
| 379 |
+
|
| 380 |
+
// Step 3: Interview Questions Generation
|
| 381 |
+
const skillAreas = [
|
| 382 |
+
{ skill: 'Technical Skills', difficulty: 'intermediate' },
|
| 383 |
+
{ skill: 'System Design', difficulty: 'advanced' },
|
| 384 |
+
{ skill: 'Behavioral', difficulty: 'intermediate' },
|
| 385 |
+
{ skill: 'Problem Solving', difficulty: 'advanced' },
|
| 386 |
+
{ skill: 'Leadership', difficulty: 'intermediate' }
|
| 387 |
+
]
|
| 388 |
+
|
| 389 |
+
for (let i = 0; i < skillAreas.length; i++) {
|
| 390 |
+
const area = skillAreas[i]
|
| 391 |
+
sendUpdate({
|
| 392 |
+
step: 'questions_generation',
|
| 393 |
+
status: 'processing',
|
| 394 |
+
message: `π§ Generating ${area.skill} questions...`,
|
| 395 |
+
progress: 52 + (i * 6)
|
| 396 |
+
})
|
| 397 |
+
|
| 398 |
+
// Simulate question generation
|
| 399 |
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
| 400 |
+
|
| 401 |
+
sendUpdate({
|
| 402 |
+
step: 'questions_generation',
|
| 403 |
+
status: 'completed',
|
| 404 |
+
message: `β
${area.skill} questions generated!`,
|
| 405 |
+
data: { area: area.skill, difficulty: area.difficulty },
|
| 406 |
+
progress: 55 + (i * 6)
|
| 407 |
+
})
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
// Step 4: Questions Ready
|
| 411 |
+
sendUpdate({
|
| 412 |
+
step: 'questions_ready',
|
| 413 |
+
status: 'completed',
|
| 414 |
+
message: 'π― All questions personalized for your background!',
|
| 415 |
+
progress: 85
|
| 416 |
+
})
|
| 417 |
+
|
| 418 |
+
// Step 5: Final Analysis
|
| 419 |
+
sendUpdate({
|
| 420 |
+
step: 'final_analysis',
|
| 421 |
+
status: 'processing',
|
| 422 |
+
message: 'π Creating final analysis...',
|
| 423 |
+
progress: 87
|
| 424 |
+
})
|
| 425 |
+
|
| 426 |
+
const finalAnalysis = await generateFinalAnalysis(resumeData, matchData)
|
| 427 |
+
|
| 428 |
+
sendUpdate({
|
| 429 |
+
step: 'final_analysis',
|
| 430 |
+
status: 'completed',
|
| 431 |
+
message: 'β
Analysis complete!',
|
| 432 |
+
data: finalAnalysis,
|
| 433 |
+
progress: 92
|
| 434 |
+
})
|
| 435 |
+
|
| 436 |
+
// Step 6: Professional Guide Generation
|
| 437 |
+
sendUpdate({
|
| 438 |
+
step: 'professional_guide',
|
| 439 |
+
status: 'processing',
|
| 440 |
+
message: 'π Generating comprehensive interview guide...',
|
| 441 |
+
progress: 94
|
| 442 |
+
})
|
| 443 |
+
|
| 444 |
+
// Use pre-scraped job data if available, otherwise extract from description
|
| 445 |
+
const jobInfo = jobData || extractJobInfoFromDescription(jobDescription)
|
| 446 |
+
|
| 447 |
+
const guide = await generateComprehensiveGuide(finalAnalysis, jobInfo)
|
| 448 |
+
|
| 449 |
+
sendUpdate({
|
| 450 |
+
step: 'professional_guide',
|
| 451 |
+
status: 'completed',
|
| 452 |
+
message: 'β
Professional guide generated!',
|
| 453 |
+
data: guide,
|
| 454 |
+
progress: 98
|
| 455 |
+
})
|
| 456 |
+
|
| 457 |
+
// Step 7: Complete
|
| 458 |
+
sendUpdate({
|
| 459 |
+
step: 'completed',
|
| 460 |
+
status: 'completed',
|
| 461 |
+
message: 'π Analysis complete! Your interview guide is ready.',
|
| 462 |
+
progress: 100,
|
| 463 |
+
results: {
|
| 464 |
+
overallMatch: finalAnalysis.overallMatch,
|
| 465 |
+
skillsBreakdown: finalAnalysis.skillsBreakdown,
|
| 466 |
+
matchingSkills: finalAnalysis.matchingSkills,
|
| 467 |
+
missingSkills: finalAnalysis.missingSkills,
|
| 468 |
+
recommendations: finalAnalysis.recommendations,
|
| 469 |
+
interviewQuestions: [
|
| 470 |
+
{ category: 'technical', question: 'How would you design a scalable machine learning pipeline?' },
|
| 471 |
+
{ category: 'behavioral', question: 'Tell me about a time you had to learn a new technology quickly' },
|
| 472 |
+
{ category: 'culture', question: 'How do you handle competing priorities in a fast-paced environment?' }
|
| 473 |
+
],
|
| 474 |
+
comprehensiveGuide: guide,
|
| 475 |
+
summary: finalAnalysis.summary
|
| 476 |
+
}
|
| 477 |
+
})
|
| 478 |
+
|
| 479 |
+
// Send final completion signal
|
| 480 |
+
controller.enqueue(encoder.encode(`data: [DONE]\n\n`))
|
| 481 |
+
|
| 482 |
+
} catch (error) {
|
| 483 |
+
console.error('Analysis error:', error)
|
| 484 |
+
sendUpdate({
|
| 485 |
+
step: 'error',
|
| 486 |
+
status: 'error',
|
| 487 |
+
message: 'Analysis failed. Please try again.',
|
| 488 |
+
progress: 0,
|
| 489 |
+
error: error instanceof Error ? error.message : 'Unknown error'
|
| 490 |
+
})
|
| 491 |
+
} finally {
|
| 492 |
+
controller.close()
|
| 493 |
+
}
|
| 494 |
+
}
|
| 495 |
+
})
|
| 496 |
+
|
| 497 |
+
return new Response(stream, {
|
| 498 |
+
headers: {
|
| 499 |
+
'Content-Type': 'text/event-stream',
|
| 500 |
+
'Cache-Control': 'no-cache',
|
| 501 |
+
'Connection': 'keep-alive',
|
| 502 |
+
}
|
| 503 |
+
})
|
| 504 |
+
} catch (error) {
|
| 505 |
+
console.error('Stream setup error:', error)
|
| 506 |
+
return NextResponse.json({ error: 'Failed to start analysis' }, { status: 500 })
|
| 507 |
+
}
|
| 508 |
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { openai } from '@ai-sdk/openai'
|
| 2 |
+
import { generateObject } from 'ai'
|
| 3 |
+
import { NextRequest, NextResponse } from 'next/server'
|
| 4 |
+
import { z } from 'zod'
|
| 5 |
+
|
| 6 |
+
const analysisSchema = z.object({
|
| 7 |
+
matchScore: z.number().min(0).max(100),
|
| 8 |
+
technicalSkills: z.array(z.string()),
|
| 9 |
+
softSkills: z.array(z.string()),
|
| 10 |
+
matchingSkills: z.array(z.string()),
|
| 11 |
+
interviewQuestions: z.array(z.object({
|
| 12 |
+
category: z.enum(['technical', 'behavioral', 'culture']),
|
| 13 |
+
question: z.string()
|
| 14 |
+
})),
|
| 15 |
+
recommendations: z.array(z.string())
|
| 16 |
+
})
|
| 17 |
+
|
| 18 |
+
export async function POST(req: NextRequest) {
|
| 19 |
+
try {
|
| 20 |
+
const { resumeText, jobDescription } = await req.json()
|
| 21 |
+
|
| 22 |
+
if (!resumeText || !jobDescription) {
|
| 23 |
+
return NextResponse.json(
|
| 24 |
+
{ error: 'Resume text and job description are required' },
|
| 25 |
+
{ status: 400 }
|
| 26 |
+
)
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Handle PDF file format
|
| 30 |
+
let processedResumeText = resumeText
|
| 31 |
+
if (resumeText.startsWith('PDF_FILE:')) {
|
| 32 |
+
// For now, use a placeholder. In production, extract text from PDF using AI SDK
|
| 33 |
+
processedResumeText = `[PDF Resume content would be extracted here]
|
| 34 |
+
|
| 35 |
+
Based on the filename and context, this appears to be a professional resume containing:
|
| 36 |
+
- Work experience in software development
|
| 37 |
+
- Education background
|
| 38 |
+
- Technical skills including programming languages
|
| 39 |
+
- Project experience
|
| 40 |
+
- Professional achievements`
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const { object } = await generateObject({
|
| 44 |
+
model: openai('gpt-4o-mini'),
|
| 45 |
+
prompt: `Analyze this resume against the job posting and provide a comprehensive interview preparation analysis.
|
| 46 |
+
|
| 47 |
+
Resume:
|
| 48 |
+
${processedResumeText}
|
| 49 |
+
|
| 50 |
+
Job Posting:
|
| 51 |
+
${typeof jobDescription === 'string' ? jobDescription : JSON.stringify(jobDescription)}
|
| 52 |
+
|
| 53 |
+
Provide:
|
| 54 |
+
1. Match score (0-100) based on skills and experience alignment
|
| 55 |
+
2. Technical skills found in resume
|
| 56 |
+
3. Soft skills identified
|
| 57 |
+
4. Skills that match between resume and job requirements
|
| 58 |
+
5. 6 interview questions (2 technical, 2 behavioral, 2 culture-fit) specific to this role
|
| 59 |
+
6. 4 specific recommendations for interview preparation
|
| 60 |
+
|
| 61 |
+
Be accurate and helpful in your analysis. Focus on actionable insights.`,
|
| 62 |
+
schema: analysisSchema,
|
| 63 |
+
})
|
| 64 |
+
|
| 65 |
+
return NextResponse.json({
|
| 66 |
+
success: true,
|
| 67 |
+
analysis: object
|
| 68 |
+
})
|
| 69 |
+
} catch (error) {
|
| 70 |
+
console.error('Analysis error:', error)
|
| 71 |
+
return NextResponse.json(
|
| 72 |
+
{ error: 'Failed to analyze resume and job posting' },
|
| 73 |
+
{ status: 500 }
|
| 74 |
+
)
|
| 75 |
+
}
|
| 76 |
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { openai } from '@ai-sdk/openai'
|
| 2 |
+
import { generateObject } from 'ai'
|
| 3 |
+
import { NextRequest, NextResponse } from 'next/server'
|
| 4 |
+
import { z } from 'zod'
|
| 5 |
+
import { researchCompanyInsights, generateProcessDiagram, generateSalaryInsights, type CompanyInsights } from '@/lib/company-research'
|
| 6 |
+
import { processQuestionBank, selectQuestionsByRole, type Question } from '@/lib/questions-processor'
|
| 7 |
+
import fs from 'fs'
|
| 8 |
+
import path from 'path'
|
| 9 |
+
|
| 10 |
+
interface ResumeData {
|
| 11 |
+
name?: string
|
| 12 |
+
experience_years: number
|
| 13 |
+
skills: string[]
|
| 14 |
+
previous_roles: string[]
|
| 15 |
+
key_achievements: string[]
|
| 16 |
+
education?: string
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
interface JobData {
|
| 20 |
+
role: string
|
| 21 |
+
company: string
|
| 22 |
+
location?: string
|
| 23 |
+
description: string
|
| 24 |
+
required_skills: string[]
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
interface ComprehensiveGuide {
|
| 28 |
+
title: string
|
| 29 |
+
introduction: {
|
| 30 |
+
roleOverview: string
|
| 31 |
+
culture: string
|
| 32 |
+
whyThisRole: string
|
| 33 |
+
}
|
| 34 |
+
interviewProcess: {
|
| 35 |
+
diagram: string
|
| 36 |
+
stages: Array<{
|
| 37 |
+
name: string
|
| 38 |
+
description: string
|
| 39 |
+
duration: string
|
| 40 |
+
}>
|
| 41 |
+
levelDifferences: string
|
| 42 |
+
}
|
| 43 |
+
questions: {
|
| 44 |
+
technical: Question[]
|
| 45 |
+
behavioral: Question[]
|
| 46 |
+
caseStudy: Question[]
|
| 47 |
+
}
|
| 48 |
+
preparation: {
|
| 49 |
+
tips: Array<{
|
| 50 |
+
title: string
|
| 51 |
+
description: string
|
| 52 |
+
}>
|
| 53 |
+
studyPlan: string
|
| 54 |
+
mockInterviews: string
|
| 55 |
+
}
|
| 56 |
+
faqs: {
|
| 57 |
+
salary: string
|
| 58 |
+
experiences: string
|
| 59 |
+
jobPostings: string
|
| 60 |
+
}
|
| 61 |
+
conclusion: {
|
| 62 |
+
summary: string
|
| 63 |
+
resources: {
|
| 64 |
+
successStory: { title: string; link: string }
|
| 65 |
+
questionList: { title: string; link: string }
|
| 66 |
+
learningPath: { title: string; link: string }
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
metadata: {
|
| 70 |
+
generatedAt: string
|
| 71 |
+
personalizedFor: string
|
| 72 |
+
targetRole: string
|
| 73 |
+
targetCompany: string
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
export async function POST(req: NextRequest) {
|
| 78 |
+
try {
|
| 79 |
+
const { resumeData, jobData } = await req.json()
|
| 80 |
+
|
| 81 |
+
if (!resumeData || !jobData) {
|
| 82 |
+
return NextResponse.json(
|
| 83 |
+
{ error: 'Resume data and job data are required' },
|
| 84 |
+
{ status: 400 }
|
| 85 |
+
)
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// Load question bank
|
| 89 |
+
const questionBank = await loadQuestionBank()
|
| 90 |
+
|
| 91 |
+
// Generate comprehensive guide
|
| 92 |
+
const guide = await generateComprehensiveGuide({
|
| 93 |
+
resumeData,
|
| 94 |
+
jobData,
|
| 95 |
+
questionBank
|
| 96 |
+
})
|
| 97 |
+
|
| 98 |
+
return NextResponse.json({ guide })
|
| 99 |
+
} catch (error) {
|
| 100 |
+
console.error('Error generating comprehensive guide:', error)
|
| 101 |
+
return NextResponse.json(
|
| 102 |
+
{ error: 'Failed to generate interview guide' },
|
| 103 |
+
{ status: 500 }
|
| 104 |
+
)
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
async function loadQuestionBank(): Promise<Question[]> {
|
| 109 |
+
try {
|
| 110 |
+
// In a real implementation, you'd load from your CSV file
|
| 111 |
+
// For now, we'll use a placeholder that returns sample questions
|
| 112 |
+
const csvPath = path.join(process.cwd(), '../Question_bank_IQ_categorized/summary (1).csv')
|
| 113 |
+
|
| 114 |
+
// Try to read the file, fallback to sample data if not available
|
| 115 |
+
let csvData = ''
|
| 116 |
+
try {
|
| 117 |
+
csvData = fs.readFileSync(csvPath, 'utf-8')
|
| 118 |
+
} catch {
|
| 119 |
+
// Fallback to sample questions if CSV not found
|
| 120 |
+
csvData = getSampleQuestionData()
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
return processQuestionBank(csvData)
|
| 124 |
+
} catch (error) {
|
| 125 |
+
console.error('Error loading question bank:', error)
|
| 126 |
+
return getSampleQuestions()
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
function getSampleQuestionData(): string {
|
| 131 |
+
return `idx,title,type,summaries,link
|
| 132 |
+
0,Weekly Aggregation,python,"Summary: Group a list of sequential timestamps into weekly lists starting from the first timestamp.",interviewquery.com/questions/weekly-aggregation
|
| 133 |
+
1,Decreasing Comments,product metrics,"Summary: Identify reasons and metrics for decreasing average comments per user despite user growth in a new city.",interviewquery.com/questions/decreasing-comments
|
| 134 |
+
2,Employee Salaries,sql,"Summary: Select the top 3 departments with at least ten employees, ranked by the percentage of employees earning over 100K.",interviewquery.com/questions/employee-salaries
|
| 135 |
+
3,500 Cards,probability,"Summary: Determine the probability of drawing three cards in increasing order from a shuffled deck of 500 numbered cards.",interviewquery.com/questions/500-cards
|
| 136 |
+
4,Random Number,algorithms,"Summary: Select a random number from a stream with equal probability using O(1) space.",interviewquery.com/questions/random-number`
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
function getSampleQuestions(): Question[] {
|
| 140 |
+
return [
|
| 141 |
+
{
|
| 142 |
+
idx: 0,
|
| 143 |
+
title: 'Weekly Aggregation',
|
| 144 |
+
type: 'python',
|
| 145 |
+
summaries: 'Group a list of sequential timestamps into weekly lists starting from the first timestamp.',
|
| 146 |
+
link: 'interviewquery.com/questions/weekly-aggregation',
|
| 147 |
+
category: 'Technical/Programming',
|
| 148 |
+
difficulty: 'mid'
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
idx: 1,
|
| 152 |
+
title: 'Decreasing Comments',
|
| 153 |
+
type: 'product metrics',
|
| 154 |
+
summaries: 'Identify reasons and metrics for decreasing average comments per user despite user growth in a new city.',
|
| 155 |
+
link: 'interviewquery.com/questions/decreasing-comments',
|
| 156 |
+
category: 'Business/Analytics',
|
| 157 |
+
difficulty: 'mid'
|
| 158 |
+
}
|
| 159 |
+
]
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
async function generateComprehensiveGuide(data: {
|
| 163 |
+
resumeData: ResumeData
|
| 164 |
+
jobData: JobData
|
| 165 |
+
questionBank: Question[]
|
| 166 |
+
}): Promise<ComprehensiveGuide> {
|
| 167 |
+
const { resumeData, jobData, questionBank } = data
|
| 168 |
+
|
| 169 |
+
// Research company and role
|
| 170 |
+
const companyInsights = await researchCompanyInsights(
|
| 171 |
+
jobData.company,
|
| 172 |
+
jobData.role,
|
| 173 |
+
jobData.location
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
// Select and personalize questions
|
| 177 |
+
const categorizedQuestions = await selectAndPersonalizeQuestions(
|
| 178 |
+
questionBank,
|
| 179 |
+
resumeData,
|
| 180 |
+
jobData
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
// Generate salary insights
|
| 184 |
+
const salaryData = await generateSalaryInsights(
|
| 185 |
+
jobData.company,
|
| 186 |
+
jobData.role,
|
| 187 |
+
jobData.location
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
// Create comprehensive guide
|
| 191 |
+
return {
|
| 192 |
+
title: `${jobData.role} Interview Guide - ${jobData.company}`,
|
| 193 |
+
introduction: await generateIntroduction(companyInsights, jobData),
|
| 194 |
+
interviewProcess: {
|
| 195 |
+
diagram: generateProcessDiagram(companyInsights.interviewProcess.stages),
|
| 196 |
+
stages: companyInsights.interviewProcess.stages,
|
| 197 |
+
levelDifferences: await generateLevelDifferences(jobData.role)
|
| 198 |
+
},
|
| 199 |
+
questions: categorizedQuestions,
|
| 200 |
+
preparation: await generatePreparationPlan(resumeData, jobData, companyInsights),
|
| 201 |
+
faqs: {
|
| 202 |
+
salary: generateSalaryFAQ(salaryData, jobData),
|
| 203 |
+
experiences: `Browse [${jobData.company} Interview Experiences](https://www.interviewquery.com/interview-experiences) to read first-hand candidate stories and success tips.`,
|
| 204 |
+
jobPostings: `Check out the latest [${jobData.company} job openings](https://www.interviewquery.com/jobs) and practice tailored questions before applying.`
|
| 205 |
+
},
|
| 206 |
+
conclusion: generateConclusion(jobData),
|
| 207 |
+
metadata: {
|
| 208 |
+
generatedAt: new Date().toISOString(),
|
| 209 |
+
personalizedFor: resumeData.name || 'Candidate',
|
| 210 |
+
targetRole: jobData.role,
|
| 211 |
+
targetCompany: jobData.company
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
async function selectAndPersonalizeQuestions(
|
| 217 |
+
questionBank: Question[],
|
| 218 |
+
resumeData: ResumeData,
|
| 219 |
+
jobData: JobData
|
| 220 |
+
) {
|
| 221 |
+
const technicalQuestions = selectQuestionsByRole(questionBank, jobData.role, 'technical').slice(0, 6)
|
| 222 |
+
const behavioralQuestions = selectQuestionsByRole(questionBank, jobData.role, 'behavioral').slice(0, 4)
|
| 223 |
+
const caseStudyQuestions = selectQuestionsByRole(questionBank, jobData.role, 'caseStudy').slice(0, 4)
|
| 224 |
+
|
| 225 |
+
// Personalize questions with AI
|
| 226 |
+
const personalizedTechnical = await personalizeQuestions(technicalQuestions, resumeData, jobData)
|
| 227 |
+
const personalizedBehavioral = await personalizeQuestions(behavioralQuestions, resumeData, jobData)
|
| 228 |
+
const personalizedCaseStudy = await personalizeQuestions(caseStudyQuestions, resumeData, jobData)
|
| 229 |
+
|
| 230 |
+
return {
|
| 231 |
+
technical: personalizedTechnical,
|
| 232 |
+
behavioral: personalizedBehavioral,
|
| 233 |
+
caseStudy: personalizedCaseStudy
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
async function personalizeQuestions(
|
| 238 |
+
questions: Question[],
|
| 239 |
+
resumeData: ResumeData,
|
| 240 |
+
jobData: JobData
|
| 241 |
+
): Promise<Question[]> {
|
| 242 |
+
const personalizedQuestions = []
|
| 243 |
+
|
| 244 |
+
for (const question of questions) {
|
| 245 |
+
try {
|
| 246 |
+
const personalization = await generateObject({
|
| 247 |
+
model: openai('gpt-4o-mini'),
|
| 248 |
+
prompt: `
|
| 249 |
+
Personalize this interview question for a candidate:
|
| 250 |
+
|
| 251 |
+
ORIGINAL QUESTION: "${question.summaries}"
|
| 252 |
+
|
| 253 |
+
CANDIDATE BACKGROUND:
|
| 254 |
+
- Role: ${jobData.role} at ${jobData.company}
|
| 255 |
+
- Experience: ${resumeData.experience_years} years
|
| 256 |
+
- Skills: ${resumeData.skills.slice(0, 5).join(', ')}
|
| 257 |
+
- Previous roles: ${resumeData.previous_roles.join(', ')}
|
| 258 |
+
|
| 259 |
+
Create:
|
| 260 |
+
1. A personalized approach explanation (2-3 sentences)
|
| 261 |
+
2. Why this question is relevant for this candidate
|
| 262 |
+
3. 2-3 follow-up questions based on their background
|
| 263 |
+
`,
|
| 264 |
+
schema: z.object({
|
| 265 |
+
approach: z.string(),
|
| 266 |
+
relevance: z.string(),
|
| 267 |
+
followUps: z.array(z.string())
|
| 268 |
+
})
|
| 269 |
+
})
|
| 270 |
+
|
| 271 |
+
personalizedQuestions.push({
|
| 272 |
+
...question,
|
| 273 |
+
approach: personalization.object.approach,
|
| 274 |
+
relevance: personalization.object.relevance,
|
| 275 |
+
followUps: personalization.object.followUps
|
| 276 |
+
})
|
| 277 |
+
} catch (error) {
|
| 278 |
+
// Fallback if personalization fails
|
| 279 |
+
personalizedQuestions.push({
|
| 280 |
+
...question,
|
| 281 |
+
approach: `This question tests your understanding of ${question.type}. Focus on demonstrating your practical experience and problem-solving approach.`,
|
| 282 |
+
relevance: `Relevant for ${jobData.role} positions requiring ${question.type} skills.`,
|
| 283 |
+
followUps: ['Can you walk through your thought process?', 'How would you optimize this solution?']
|
| 284 |
+
})
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
return personalizedQuestions
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
async function generateIntroduction(companyInsights: CompanyInsights, jobData: JobData) {
|
| 292 |
+
return {
|
| 293 |
+
roleOverview: `The ${jobData.role} role at ${jobData.company} combines ${companyInsights.candidateProfile.keySkills.slice(0, 3).join(', ')} with strong business impact. ${companyInsights.businessModel.overview}`,
|
| 294 |
+
culture: `${jobData.company}'s culture emphasizes ${companyInsights.culture.values.slice(0, 3).join(', ')}. ${companyInsights.culture.workEnvironment}`,
|
| 295 |
+
whyThisRole: `This role offers unique opportunities to ${companyInsights.businessModel.keyMetrics.slice(0, 2).join(' and ')} while working with cutting-edge technology and talented teams.`
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
async function generateLevelDifferences(role: string): Promise<string> {
|
| 300 |
+
return `
|
| 301 |
+
**Junior ${role}** candidates can expect more foundational questions and hands-on problem solving.
|
| 302 |
+
|
| 303 |
+
**Senior ${role}** candidates will be evaluated on system design, stakeholder communication, and technical leadership.
|
| 304 |
+
|
| 305 |
+
**Principal/Staff** level roles focus on architecture decisions, cross-team collaboration, and strategic thinking.
|
| 306 |
+
`
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
async function generatePreparationPlan(
|
| 310 |
+
resumeData: ResumeData,
|
| 311 |
+
jobData: JobData,
|
| 312 |
+
companyInsights: CompanyInsights
|
| 313 |
+
) {
|
| 314 |
+
return {
|
| 315 |
+
tips: [
|
| 316 |
+
{
|
| 317 |
+
title: 'Study the Business Model',
|
| 318 |
+
description: `Understand ${jobData.company}'s ${companyInsights.businessModel.overview} and key metrics like ${companyInsights.businessModel.keyMetrics.slice(0, 2).join(' and ')}.`
|
| 319 |
+
},
|
| 320 |
+
{
|
| 321 |
+
title: 'Technical Practice',
|
| 322 |
+
description: `Focus on ${companyInsights.candidateProfile.keySkills.slice(0, 3).join(', ')} - use platforms like Interview Query and LeetCode for hands-on practice.`
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
title: 'Behavioral Preparation',
|
| 326 |
+
description: 'Prepare STAR method examples showcasing your experience with problem solving, leadership, and collaboration.'
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
title: 'Company Research',
|
| 330 |
+
description: `Research ${jobData.company}'s recent product launches, engineering blog posts, and company values to show genuine interest.`
|
| 331 |
+
}
|
| 332 |
+
],
|
| 333 |
+
studyPlan: `
|
| 334 |
+
**Week 1:** Technical fundamentals and coding practice
|
| 335 |
+
**Week 2:** System design and case study preparation
|
| 336 |
+
**Week 3:** Behavioral questions and company research
|
| 337 |
+
**Final Days:** Mock interviews and confidence building
|
| 338 |
+
`,
|
| 339 |
+
mockInterviews: 'Practice with peers, use Interview Query\'s mock interview service, or record yourself answering questions to improve communication.'
|
| 340 |
+
}
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
function generateSalaryFAQ(salaryData: any, jobData: JobData): string {
|
| 344 |
+
return `
|
| 345 |
+
**Salary Range:** ${salaryData.range}
|
| 346 |
+
|
| 347 |
+
**Total Compensation:** ${salaryData.breakdown}
|
| 348 |
+
|
| 349 |
+
**Negotiation Tips:**
|
| 350 |
+
${salaryData.negotiationTips.map((tip: string) => `- ${tip}`).join('\n')}
|
| 351 |
+
`
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
function generateConclusion(jobData: JobData) {
|
| 355 |
+
return {
|
| 356 |
+
summary: `${jobData.company}'s ${jobData.role} interviews emphasize technical excellence, problem-solving ability, and cultural alignment. With thorough preparation and clear communication, you'll demonstrate the skills and mindset they're seeking.`,
|
| 357 |
+
resources: {
|
| 358 |
+
successStory: {
|
| 359 |
+
title: `${jobData.role} Success Stories`,
|
| 360 |
+
link: 'https://www.interviewquery.com/success-stories'
|
| 361 |
+
},
|
| 362 |
+
questionList: {
|
| 363 |
+
title: `Top ${jobData.role} Interview Questions`,
|
| 364 |
+
link: 'https://www.interviewquery.com/questions'
|
| 365 |
+
},
|
| 366 |
+
learningPath: {
|
| 367 |
+
title: `${jobData.role} Learning Path`,
|
| 368 |
+
link: 'https://www.interviewquery.com/learning-paths'
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
}
|