bodhak commited on
Commit
22dc5f7
·
verified ·
1 Parent(s): a18bc8d

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +400 -0
  2. requirements.txt +7 -0
  3. static/style.css +0 -0
  4. templates/index.html +411 -0
app.py ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # from flask import Flask, request, jsonify, render_template
2
+ # import pdfplumber
3
+ # import io
4
+ # from transformers import T5ForConditionalGeneration, T5Tokenizer
5
+ # import torch
6
+
7
+ # app = Flask(__name__)
8
+
9
+ # # Load the T5 model and tokenizer
10
+ # model_name = "t5-large"
11
+ # tokenizer = T5Tokenizer.from_pretrained(model_name)
12
+ # model = T5ForConditionalGeneration.from_pretrained(model_name)
13
+
14
+ # @app.route('/')
15
+ # def index():
16
+ # return render_template('index.html') # Ensure index.html exists in templates/
17
+
18
+ # @app.route('/upload-resume', methods=['POST'])
19
+ # def upload_resume():
20
+ # try:
21
+ # file = request.files['resume']
22
+ # if not file.filename.endswith('.pdf'):
23
+ # return jsonify({"error": "Only PDF files are supported"}), 400
24
+
25
+ # # Extract text from PDF
26
+ # text = ""
27
+ # with pdfplumber.open(io.BytesIO(file.read())) as pdf:
28
+ # for page in pdf.pages:
29
+ # page_text = page.extract_text()
30
+ # if page_text:
31
+ # text += page_text + "\n"
32
+
33
+ # # Create a prompt dynamically based on resume content
34
+ # prompt = f"Generate 5 technical and 3 behavioral interview questions based on this resume:\n{text.strip()}"
35
+
36
+ # # Tokenize and generate questions
37
+ # inputs = tokenizer(prompt, return_tensors="pt", max_length=1024, truncation=True)
38
+ # output = model.generate(**inputs, max_length=512, num_beams=4, early_stopping=True)
39
+
40
+ # questions = tokenizer.decode(output[0], skip_special_tokens=True)
41
+ # questions_list = [q.strip() for q in questions.split("\n") if q.strip()]
42
+
43
+ # return jsonify(questions_list)
44
+
45
+ # except Exception as e:
46
+ # print("Error:", e)
47
+ # return jsonify({"error": str(e)}), 500
48
+
49
+ # if __name__ == '__main__':
50
+ # app.run(debug=True)
51
+
52
+
53
+
54
+
55
+ from flask import Flask, request, jsonify, render_template, send_from_directory
56
+ import pdfplumber
57
+ import io
58
+ import re
59
+ import nltk
60
+ from nltk.corpus import stopwords
61
+ from nltk.tokenize import word_tokenize
62
+ import spacy
63
+ from transformers import GPT2Tokenizer, GPT2LMHeadModel
64
+ import torch
65
+ import logging
66
+ from collections import Counter
67
+ import os
68
+
69
+
70
+ os.environ["TRANSFORMERS_CACHE"] = os.path.join(os.getcwd(), "models")
71
+
72
+ # Configure logging
73
+ logging.basicConfig(level=logging.INFO,
74
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
75
+ logger = logging.getLogger(__name__)
76
+
77
+ app = Flask(__name__, static_folder='static')
78
+
79
+ # Download required NLTK data
80
+ try:
81
+ nltk.download('punkt', quiet=True)
82
+ nltk.download('stopwords', quiet=True)
83
+ stop_words = set(stopwords.words('english'))
84
+ except Exception as e:
85
+ logger.warning(f"NLTK download error: {str(e)}")
86
+ stop_words = set()
87
+
88
+ # Load spaCy model for entity recognition
89
+ try:
90
+ nlp = spacy.load("en_core_web_sm")
91
+ except Exception as e:
92
+ logger.warning(f"SpaCy model loading error: {str(e)}")
93
+
94
+ # Optional fallback if spaCy model isn't installed
95
+ def download_spacy_model():
96
+ import subprocess
97
+ subprocess.call(["python", "-m", "spacy", "download", "en_core_web_sm"])
98
+
99
+ try:
100
+ download_spacy_model()
101
+ nlp = spacy.load("en_core_web_sm")
102
+ except:
103
+ logger.error("Failed to load spaCy model")
104
+ nlp = None
105
+
106
+ # Load GPT-2 model and tokenizer for better text generation
107
+ try:
108
+ model_name = "distilgpt2"
109
+ tokenizer = GPT2Tokenizer.from_pretrained(model_name)
110
+ model = GPT2LMHeadModel.from_pretrained(model_name)
111
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
112
+ model.to(device)
113
+ except Exception as e:
114
+ logger.error(f"Model loading error: {str(e)}")
115
+ model = None
116
+ tokenizer = None
117
+
118
+ def extract_skills(text):
119
+ """Extract technical skills from resume text"""
120
+ # Common technical skills to look for
121
+ common_skills = {
122
+ 'programming': ['python', 'java', 'javascript', 'c++', 'c#', 'ruby', 'php', 'swift', 'kotlin', 'go', 'rust', 'typescript', 'scala', 'perl', 'shell', 'bash', 'sql', 'html', 'css'],
123
+ 'frameworks': ['react', 'angular', 'vue', 'django', 'flask', 'spring', 'express', 'rails', 'asp.net', 'laravel', 'node.js', 'bootstrap', 'jquery', 'tensorflow', 'pytorch', 'numpy', 'pandas'],
124
+ 'databases': ['mysql', 'postgresql', 'mongodb', 'oracle', 'sql server', 'sqlite', 'redis', 'cassandra', 'dynamodb', 'firebase', 'elasticsearch'],
125
+ 'tools': ['git', 'docker', 'kubernetes', 'jenkins', 'aws', 'azure', 'gcp', 'terraform', 'ansible', 'jira', 'confluence', 'notion', 'figma', 'photoshop', 'illustrator'],
126
+ 'methodologies': ['agile', 'scrum', 'kanban', 'devops', 'ci/cd', 'test driven development', 'tdd', 'behavior driven development', 'bdd', 'rest', 'soap', 'microservices', 'serverless']
127
+ }
128
+
129
+ # Flatten the list
130
+ all_skills = [skill for category in common_skills.values() for skill in category]
131
+
132
+ # Find matches in the text
133
+ found_skills = []
134
+ text_lower = text.lower()
135
+
136
+ for skill in all_skills:
137
+ # Check for whole word matches
138
+ pattern = r'\b' + re.escape(skill) + r'\b'
139
+ if re.search(pattern, text_lower):
140
+ found_skills.append(skill)
141
+
142
+ # If spaCy is available, also look for named entities that might be technologies
143
+ if nlp:
144
+ doc = nlp(text)
145
+ for ent in doc.ents:
146
+ if ent.label_ in ["ORG", "PRODUCT"] and len(ent.text) > 2:
147
+ entity = ent.text.lower()
148
+ # Check if entity might be a technology
149
+ if any(tech_word in entity for tech_word in ["tech", "software", "platform", "system", "framework", "api", "cloud"]):
150
+ found_skills.append(ent.text)
151
+
152
+ # Count occurrences to identify most important skills
153
+ skill_counter = Counter(found_skills)
154
+ top_skills = [skill for skill, _ in skill_counter.most_common(10)]
155
+
156
+ return top_skills
157
+
158
+ def extract_experience(text):
159
+ """Extract work experience information from resume"""
160
+ experience_data = []
161
+
162
+ # Look for common experience section headers
163
+ experience_headers = ["experience", "work experience", "employment history", "professional experience"]
164
+
165
+ # Simple pattern matching for job titles and dates
166
+ job_title_pattern = r"(?:^|\n)(?:Senior |Lead |Junior |Staff |Principal )?\b(?:Developer|Engineer|Designer|Manager|Director|Analyst|Consultant|Administrator|Architect|Specialist)\b"
167
+ date_pattern = r"\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]* \d{4}\s*(?:-|–|to)\s*(?:(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]* \d{4}|Present|Current|Now)"
168
+
169
+ # Find experience section
170
+ text_lower = text.lower()
171
+ section_start = None
172
+ for header in experience_headers:
173
+ if header in text_lower:
174
+ section_start = text_lower.find(header)
175
+ break
176
+
177
+ if section_start is not None:
178
+ # Extract the section (assume it ends at the next major section)
179
+ next_section_start = float('inf')
180
+ for next_header in ["education", "skills", "projects", "certifications", "references"]:
181
+ pos = text_lower.find(next_header, section_start + 1)
182
+ if pos > section_start and pos < next_section_start:
183
+ next_section_start = pos
184
+
185
+ experience_section = text[section_start:next_section_start] if next_section_start < float('inf') else text[section_start:]
186
+
187
+ # Extract job titles
188
+ job_titles = re.findall(job_title_pattern, experience_section, re.IGNORECASE)
189
+
190
+ # Extract date ranges
191
+ date_ranges = re.findall(date_pattern, experience_section)
192
+
193
+ # Combine the information
194
+ for i, title in enumerate(job_titles[:3]): # Limit to top 3 positions
195
+ date = date_ranges[i] if i < len(date_ranges) else "Unknown date"
196
+ experience_data.append({"title": title.strip(), "date": date})
197
+
198
+ return experience_data
199
+
200
+ def extract_education(text):
201
+ """Extract education information from resume"""
202
+ education_data = []
203
+
204
+ # Look for degrees and institutions
205
+ degree_pattern = r"\b(?:Bachelor|Master|PhD|Doctorate|BSc|MSc|BA|MA|MBA|MD|JD|BS|MS|B\.S\.|M\.S\.|B\.A\.|M\.A\.)['\s\w]*\b"
206
+ institution_pattern = r"\b(?:University|College|Institute|School) of [\w\s]+\b"
207
+
208
+ # Find matches
209
+ degrees = re.findall(degree_pattern, text)
210
+ institutions = re.findall(institution_pattern, text)
211
+
212
+ # Combine the information
213
+ for i, degree in enumerate(degrees[:2]): # Limit to top 2 degrees
214
+ institution = institutions[i] if i < len(institutions) else "Unknown institution"
215
+ education_data.append({"degree": degree.strip(), "institution": institution})
216
+
217
+ return education_data
218
+
219
+ def preprocess_resume(text):
220
+ """Extract structured information from resume text"""
221
+ # Basic text cleaning
222
+ text = text.replace('\n\n', ' [BREAK] ')
223
+ text = re.sub(r'\s+', ' ', text)
224
+ text = text.replace(' [BREAK] ', '\n\n')
225
+
226
+ # Extract key information
227
+ skills = extract_skills(text)
228
+ experience = extract_experience(text)
229
+ education = extract_education(text)
230
+
231
+ # Create structured resume data
232
+ resume_data = {
233
+ "skills": skills,
234
+ "experience": experience,
235
+ "education": education,
236
+ "full_text": text
237
+ }
238
+
239
+ return resume_data
240
+
241
+ def generate_interview_questions(resume_data):
242
+ """Generate interview questions based on resume data"""
243
+ # Default set of questions if model fails
244
+ default_questions = [
245
+ # Technical questions
246
+ "What challenges have you faced when working with databases, and how did you overcome them?",
247
+ "Describe a project where you had to optimize code for performance. What approach did you take?",
248
+ "How do you ensure your code is maintainable and follows best practices?",
249
+ "What software development methodologies are you familiar with, and which do you prefer?",
250
+ "How do you approach testing your code?",
251
+
252
+ # Behavioral questions
253
+ "Tell me about a challenging project you worked on and how you approached it.",
254
+ "Describe a situation where you had to learn a new technology quickly.",
255
+ "How do you handle tight deadlines and pressure?"
256
+ ]
257
+
258
+ # If no model is loaded, return default questions
259
+ if model is None or tokenizer is None:
260
+ return default_questions
261
+
262
+ # Extract key resume information
263
+ skills_str = ", ".join(resume_data["skills"])
264
+
265
+ experience_str = ""
266
+ for exp in resume_data["experience"]:
267
+ experience_str += f"{exp['title']} ({exp['date']}), "
268
+
269
+ # Build a prompt for the model
270
+ prompt = f"""Generate 8 interview questions based on this resume information:
271
+ Skills: {skills_str}
272
+ Experience: {experience_str}
273
+
274
+ Include 5 technical questions specific to the candidate's skills and 3 behavioral questions.
275
+ Format each question on a new line and make them realistic interview questions.
276
+ """
277
+
278
+ try:
279
+ # Generate text
280
+ input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)
281
+ attention_mask = torch.ones(input_ids.shape, dtype=torch.long, device=device)
282
+
283
+ # Generate with better parameters for coherent questions
284
+ output = model.generate(
285
+ input_ids,
286
+ attention_mask=attention_mask,
287
+ max_length=1024,
288
+ num_return_sequences=1,
289
+ no_repeat_ngram_size=2,
290
+ do_sample=True,
291
+ top_p=0.92,
292
+ top_k=50,
293
+ temperature=0.85
294
+ )
295
+
296
+ generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
297
+
298
+ # Process the output to extract questions
299
+ text_split = generated_text.replace(prompt, "").strip().split("\n")
300
+
301
+ # Clean up and format questions
302
+ questions = []
303
+ for line in text_split:
304
+ # Remove question numbers and clean
305
+ line = re.sub(r'^\d+[\.\)]\s*', '', line.strip())
306
+
307
+ # Only keep lines that look like questions
308
+ if line and ('?' in line or any(q_word in line.lower() for q_word in ["how", "what", "why", "when", "where", "describe", "tell", "explain"])):
309
+ # Ensure questions end with question mark
310
+ if not line.endswith('?') and any(q_word in line.lower() for q_word in ["how", "what", "why", "when", "where"]):
311
+ line += '?'
312
+ questions.append(line)
313
+
314
+ # Check if we got enough questions
315
+ if len(questions) >= 5:
316
+ return questions[:8] # Return up to 8 questions
317
+ else:
318
+ # Fallback to default questions
319
+ logger.warning("Generated questions insufficient, using defaults")
320
+ return default_questions
321
+
322
+ except Exception as e:
323
+ logger.error(f"Question generation error: {str(e)}")
324
+ return default_questions
325
+
326
+ @app.route('/')
327
+ def index():
328
+ return render_template('index.html')
329
+
330
+ @app.route('/static/<path:path>')
331
+ def serve_static(path):
332
+ return send_from_directory('static', path)
333
+
334
+ @app.route('/upload-resume', methods=['POST'])
335
+ def upload_resume():
336
+ try:
337
+ if 'resume' not in request.files:
338
+ return jsonify({"error": "No file part"}), 400
339
+
340
+ file = request.files['resume']
341
+
342
+ if file.filename == '':
343
+ return jsonify({"error": "No selected file"}), 400
344
+
345
+ if not file.filename.endswith('.pdf'):
346
+ return jsonify({"error": "Only PDF files are supported"}), 400
347
+
348
+ # Extract text from PDF
349
+ text = ""
350
+ try:
351
+ with pdfplumber.open(io.BytesIO(file.read())) as pdf:
352
+ for page in pdf.pages:
353
+ page_text = page.extract_text()
354
+ if page_text:
355
+ text += page_text + "\n"
356
+ except Exception as e:
357
+ logger.error(f"PDF extraction error: {str(e)}")
358
+ return jsonify({"error": "Unable to extract text from PDF. Is the file corrupt?"}), 500
359
+
360
+ if not text.strip():
361
+ return jsonify({"error": "No text could be extracted from the PDF"}), 400
362
+
363
+ # Process resume and generate questions
364
+ resume_data = preprocess_resume(text)
365
+ questions = generate_interview_questions(resume_data)
366
+
367
+ # Add tailored question based on skills
368
+ if resume_data["skills"]:
369
+ top_skill = resume_data["skills"][0]
370
+ skill_question = f"Tell me about your experience with {top_skill} and how you've applied it in your projects."
371
+ questions.append(skill_question)
372
+
373
+ return jsonify(questions)
374
+
375
+ except Exception as e:
376
+ logger.error(f"General error: {str(e)}")
377
+ return jsonify({"error": "An error occurred processing your request. Please try again."}), 500
378
+
379
+ if __name__ == '__main__':
380
+ # Make sure templates and static directories exist
381
+ for directory in ['templates', 'static']:
382
+ os.makedirs(directory, exist_ok=True)
383
+
384
+ # Create index.html in templates if needed
385
+ templates_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
386
+ if not os.path.exists(os.path.join(templates_dir, 'index.html')):
387
+ with open(os.path.join(templates_dir, 'index.html'), 'w') as f:
388
+ f.write('''<!DOCTYPE html>
389
+ <html>
390
+ <head>
391
+ <title>Resume Question Generator</title>
392
+ <meta http-equiv="refresh" content="0;url=/" />
393
+ </head>
394
+ <body>
395
+ <p>Redirecting...</p>
396
+ </body>
397
+ </html>''')
398
+
399
+ # Start the Flask app
400
+ app.run(debug=True)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Flask==2.2.3
2
+ gunicorn==20.1.0
3
+ pdfplumber==0.7.6
4
+ nltk==3.8.1
5
+ spacy==3.5.0
6
+ torch==2.0.0
7
+ transformers==4.27.1
static/style.css ADDED
File without changes
templates/index.html ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>AI Interview Simulator</title>
6
+ </head>
7
+ <body>
8
+ <h1>Upload Your Resume</h1>
9
+ <form id="upload-form" enctype="multipart/form-data">
10
+ <input type="file" name="resume" accept=".pdf" required />
11
+ <button type="submit">Generate Questions</button>
12
+ </form>
13
+
14
+ <h2>Generated Questions:</h2>
15
+ <ul id="question-list"></ul>
16
+
17
+ <script>
18
+ const form = document.getElementById("upload-form");
19
+ const questionList = document.getElementById("question-list");
20
+
21
+ form.addEventListener("submit", async (e) => {
22
+ e.preventDefault();
23
+ const formData = new FormData(form);
24
+ const response = await fetch("/upload-resume", {
25
+ method: "POST",
26
+ body: formData
27
+ });
28
+ const questions = await response.json();
29
+ questionList.innerHTML = "";
30
+ questions.forEach(q => {
31
+ const li = document.createElement("li");
32
+ li.textContent = q;
33
+ questionList.appendChild(li);
34
+ });
35
+ });
36
+ </script>
37
+ </body>
38
+ </html> -->
39
+
40
+
41
+ <!DOCTYPE html>
42
+ <html lang="en">
43
+ <head>
44
+ <meta charset="UTF-8">
45
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
46
+ <title>AI Interview Simulator</title>
47
+ <style>
48
+ :root {
49
+ --primary: #4f46e5;
50
+ --primary-dark: #4338ca;
51
+ --secondary: #f3f4f6;
52
+ --text: #1f2937;
53
+ --light-text: #6b7280;
54
+ --accent: #fef3c7;
55
+ --border: #e5e7eb;
56
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
57
+ }
58
+
59
+ * {
60
+ margin: 0;
61
+ padding: 0;
62
+ box-sizing: border-box;
63
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
64
+ }
65
+
66
+ body {
67
+ background-color: #f9fafb;
68
+ color: var(--text);
69
+ line-height: 1.5;
70
+ }
71
+
72
+ .container {
73
+ max-width: 800px;
74
+ margin: 0 auto;
75
+ padding: 2rem 1rem;
76
+ }
77
+
78
+ header {
79
+ text-align: center;
80
+ margin-bottom: 3rem;
81
+ }
82
+
83
+ h1 {
84
+ color: var(--primary);
85
+ font-size: 2.5rem;
86
+ margin-bottom: 1rem;
87
+ }
88
+
89
+ .tagline {
90
+ color: var(--light-text);
91
+ font-size: 1.2rem;
92
+ max-width: 600px;
93
+ margin: 0 auto;
94
+ }
95
+
96
+ .card {
97
+ background-color: white;
98
+ border-radius: 12px;
99
+ box-shadow: var(--shadow);
100
+ padding: 2rem;
101
+ margin-bottom: 2rem;
102
+ }
103
+
104
+ .card-title {
105
+ color: var(--text);
106
+ font-size: 1.5rem;
107
+ margin-bottom: 1.5rem;
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 10px;
111
+ }
112
+
113
+ .icon {
114
+ background-color: var(--accent);
115
+ width: 40px;
116
+ height: 40px;
117
+ border-radius: 50%;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ font-weight: bold;
122
+ color: var(--primary);
123
+ }
124
+
125
+ .upload-area {
126
+ border: 2px dashed var(--border);
127
+ border-radius: 8px;
128
+ padding: 2rem;
129
+ text-align: center;
130
+ transition: all 0.3s;
131
+ cursor: pointer;
132
+ position: relative;
133
+ margin-bottom: 1.5rem;
134
+ }
135
+
136
+ .upload-area:hover {
137
+ border-color: var(--primary);
138
+ background-color: rgba(79, 70, 229, 0.03);
139
+ }
140
+
141
+ .upload-area p {
142
+ color: var(--light-text);
143
+ margin: 1rem 0;
144
+ }
145
+
146
+ .file-input {
147
+ position: absolute;
148
+ width: 100%;
149
+ height: 100%;
150
+ top: 0;
151
+ left: 0;
152
+ opacity: 0;
153
+ cursor: pointer;
154
+ }
155
+
156
+ .button {
157
+ background-color: var(--primary);
158
+ color: white;
159
+ border: none;
160
+ border-radius: 8px;
161
+ padding: 12px 24px;
162
+ font-size: 1rem;
163
+ font-weight: 600;
164
+ cursor: pointer;
165
+ transition: all 0.2s;
166
+ display: inline-flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ gap: 8px;
170
+ }
171
+
172
+ .button:hover {
173
+ background-color: var(--primary-dark);
174
+ transform: translateY(-1px);
175
+ }
176
+
177
+ .file-name {
178
+ background-color: var(--secondary);
179
+ padding: 8px 16px;
180
+ border-radius: 6px;
181
+ display: none;
182
+ align-items: center;
183
+ justify-content: space-between;
184
+ margin-bottom: 1.5rem;
185
+ }
186
+
187
+ .file-name.active {
188
+ display: flex;
189
+ }
190
+
191
+ .remove-file {
192
+ background: none;
193
+ border: none;
194
+ color: var(--light-text);
195
+ cursor: pointer;
196
+ font-size: 1.2rem;
197
+ }
198
+
199
+ .question-list {
200
+ list-style-type: none;
201
+ }
202
+
203
+ .question-item {
204
+ padding: 16px;
205
+ border-radius: 8px;
206
+ background-color: var(--secondary);
207
+ margin-bottom: 12px;
208
+ animation: fadeIn 0.5s ease-in-out;
209
+ }
210
+
211
+ .question-number {
212
+ font-weight: 600;
213
+ color: var(--primary);
214
+ margin-right: 8px;
215
+ }
216
+
217
+ .loading {
218
+ display: none;
219
+ align-items: center;
220
+ justify-content: center;
221
+ gap: 12px;
222
+ margin: 2rem 0;
223
+ }
224
+
225
+ .loading.active {
226
+ display: flex;
227
+ }
228
+
229
+ .spinner {
230
+ width: 24px;
231
+ height: 24px;
232
+ border: 3px solid rgba(79, 70, 229, 0.3);
233
+ border-radius: 50%;
234
+ border-top-color: var(--primary);
235
+ animation: spin 1s linear infinite;
236
+ }
237
+
238
+ .no-questions {
239
+ text-align: center;
240
+ color: var(--light-text);
241
+ padding: 2rem;
242
+ }
243
+
244
+ @keyframes fadeIn {
245
+ from { opacity: 0; transform: translateY(10px); }
246
+ to { opacity: 1; transform: translateY(0); }
247
+ }
248
+
249
+ @keyframes spin {
250
+ to { transform: rotate(360deg); }
251
+ }
252
+
253
+ @media (max-width: 640px) {
254
+ .container {
255
+ padding: 1rem;
256
+ }
257
+
258
+ h1 {
259
+ font-size: 2rem;
260
+ }
261
+
262
+ .card {
263
+ padding: 1.5rem;
264
+ }
265
+ }
266
+ </style>
267
+ </head>
268
+ <body>
269
+ <div class="container">
270
+ <header>
271
+ <h1>AI Interview Simulator</h1>
272
+ <p class="tagline">Upload your resume and receive tailored interview questions to help you prepare for your next job interview</p>
273
+ </header>
274
+
275
+ <div class="card">
276
+ <h2 class="card-title">
277
+ <span class="icon">1</span>
278
+ Upload Your Resume
279
+ </h2>
280
+
281
+ <form id="upload-form" enctype="multipart/form-data">
282
+ <div class="upload-area" id="drop-area">
283
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-upload" style="color: var(--primary);">
284
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
285
+ <polyline points="17 8 12 3 7 8"></polyline>
286
+ <line x1="12" y1="3" x2="12" y2="15"></line>
287
+ </svg>
288
+
289
+ <p>Drag and drop your resume, or click to browse</p>
290
+ <p style="font-size: 0.9rem;">Supported format: PDF</p>
291
+
292
+ <input type="file" name="resume" accept=".pdf" required class="file-input" id="file-input" />
293
+ </div>
294
+
295
+ <div class="file-name" id="file-display">
296
+ <span id="file-name">document.pdf</span>
297
+ <button type="button" class="remove-file" id="remove-file">×</button>
298
+ </div>
299
+
300
+ <button type="submit" class="button" id="generate-btn">
301
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
302
+ <circle cx="12" cy="12" r="10"></circle>
303
+ <polygon points="10 8 16 12 10 16 10 8"></polygon>
304
+ </svg>
305
+ Generate Interview Questions
306
+ </button>
307
+ </form>
308
+ </div>
309
+
310
+ <div class="loading" id="loading">
311
+ <div class="spinner"></div>
312
+ <span>Analyzing your resume...</span>
313
+ </div>
314
+
315
+ <div class="card" id="questions-card" style="display: none;">
316
+ <h2 class="card-title">
317
+ <span class="icon">2</span>
318
+ Your Interview Questions
319
+ </h2>
320
+
321
+ <div id="questions-container">
322
+ <ul class="question-list" id="question-list"></ul>
323
+ <div class="no-questions" id="no-questions">
324
+ Upload your resume to see personalized interview questions
325
+ </div>
326
+ </div>
327
+ </div>
328
+ </div>
329
+
330
+ <script>
331
+ const form = document.getElementById("upload-form");
332
+ const fileInput = document.getElementById("file-input");
333
+ const fileDisplay = document.getElementById("file-display");
334
+ const fileName = document.getElementById("file-name");
335
+ const removeFile = document.getElementById("remove-file");
336
+ const questionList = document.getElementById("question-list");
337
+ const questionsCard = document.getElementById("questions-card");
338
+ const noQuestions = document.getElementById("no-questions");
339
+ const loading = document.getElementById("loading");
340
+
341
+ // File input handling
342
+ fileInput.addEventListener("change", (e) => {
343
+ if (fileInput.files.length > 0) {
344
+ fileName.textContent = fileInput.files[0].name;
345
+ fileDisplay.classList.add("active");
346
+ }
347
+ });
348
+
349
+ removeFile.addEventListener("click", () => {
350
+ fileInput.value = "";
351
+ fileDisplay.classList.remove("active");
352
+ });
353
+
354
+ // Form submission
355
+ form.addEventListener("submit", async (e) => {
356
+ e.preventDefault();
357
+
358
+ if (fileInput.files.length === 0) {
359
+ alert("Please select a resume file first");
360
+ return;
361
+ }
362
+
363
+ loading.classList.add("active");
364
+
365
+ try {
366
+ const formData = new FormData(form);
367
+
368
+ // Simulate API call with timeout
369
+ setTimeout(async () => {
370
+ // In a real application, this would be an actual API call
371
+ // const response = await fetch("/upload-resume", {
372
+ // method: "POST",
373
+ // body: formData
374
+ // });
375
+ // const questions = await response.json();
376
+
377
+ // Sample questions for demonstration
378
+ const questions = [
379
+ "Tell me about your experience with front-end development frameworks mentioned in your resume.",
380
+ "Can you explain a challenging project you worked on and how you approached it?",
381
+ "How do you stay updated with the latest technologies in your field?",
382
+ "What made you interested in applying for this position?",
383
+ "Describe a situation where you had to learn a new technology quickly. How did you approach it?",
384
+ "How do you handle tight deadlines and pressure?",
385
+ "Can you elaborate on your experience with team collaboration tools?",
386
+ "What is your process for debugging complex issues in your code?"
387
+ ];
388
+
389
+ // Display questions
390
+ questionList.innerHTML = "";
391
+ questions.forEach((q, index) => {
392
+ const li = document.createElement("li");
393
+ li.className = "question-item";
394
+ li.innerHTML = `<span class="question-number">Q${index + 1}:</span> ${q}`;
395
+ questionList.appendChild(li);
396
+ });
397
+
398
+ loading.classList.remove("active");
399
+ questionsCard.style.display = "block";
400
+ noQuestions.style.display = "none";
401
+ }, 2000); // Simulate loading delay
402
+
403
+ } catch (error) {
404
+ console.error("Error:", error);
405
+ loading.classList.remove("active");
406
+ alert("There was an error processing your request. Please try again.");
407
+ }
408
+ });
409
+ </script>
410
+ </body>
411
+ </html>