Ame-Mark commited on
Commit
2cc6fdb
Β·
verified Β·
1 Parent(s): bca1d31

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +361 -0
app.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import streamlit as st
3
+ from groq import Groq
4
+ import re
5
+ from fpdf import FPDF
6
+ import datetime
7
+ import os
8
+
9
+ # ── Page Config ───────────────────────────────────
10
+ st.set_page_config(
11
+ page_title = "InterviewGen AI",
12
+ page_icon = "🎯",
13
+ layout = "wide"
14
+ )
15
+
16
+ # ── Custom CSS ────────────────────────────────────
17
+ st.markdown("""
18
+ <style>
19
+ .main-header {
20
+ font-size: 2.8rem;
21
+ font-weight: 900;
22
+ background: linear-gradient(90deg, #667eea, #764ba2);
23
+ -webkit-background-clip: text;
24
+ -webkit-text-fill-color: transparent;
25
+ text-align: center;
26
+ padding: 1rem 0;
27
+ }
28
+ .question-card {
29
+ background: #f8f9fa;
30
+ border-left: 5px solid #667eea;
31
+ padding: 1.2rem;
32
+ margin: 0.8rem 0;
33
+ border-radius: 10px;
34
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
35
+ }
36
+ .answer-card {
37
+ background: linear-gradient(135deg, #e8f4f8, #f0fff4);
38
+ border-left: 5px solid #2ecc71;
39
+ padding: 1.2rem;
40
+ margin: 0.8rem 0;
41
+ border-radius: 10px;
42
+ }
43
+ .score-card {
44
+ background: linear-gradient(135deg, #fff3cd, #ffeaa7);
45
+ border-left: 5px solid #f39c12;
46
+ padding: 1.2rem;
47
+ margin: 0.8rem 0;
48
+ border-radius: 10px;
49
+ }
50
+ .metric-card {
51
+ background: linear-gradient(135deg, #667eea, #764ba2);
52
+ color: white;
53
+ padding: 1rem;
54
+ border-radius: 10px;
55
+ text-align: center;
56
+ }
57
+ </style>
58
+ """, unsafe_allow_html=True)
59
+
60
+ # ── Groq Client ───────────────────────────────────
61
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
62
+ client = Groq(api_key=GROQ_API_KEY)
63
+
64
+ # ── Helper Functions ──────────────────────────────
65
+ def generate_questions(role, difficulty, q_type, num, job_desc=""):
66
+ job_context = f"Job Description: {job_desc[:500]}" if job_desc else ""
67
+ prompt = f"""You are a senior technical interviewer at a top tech company.
68
+ {job_context}
69
+ Generate exactly {num} {difficulty} level {q_type} interview questions for a {role}.
70
+ Format EXACTLY like this:
71
+ Q1: [question]
72
+ A1: [detailed answer]
73
+
74
+ Q2: [question]
75
+ A2: [detailed answer]
76
+
77
+ Only output questions and answers. Nothing else."""
78
+
79
+ response = client.chat.completions.create(
80
+ model = "llama-3.3-70b-versatile",
81
+ messages = [{"role": "user", "content": prompt}],
82
+ temperature = 0.7,
83
+ max_tokens = 2000
84
+ )
85
+ return response.choices[0].message.content
86
+
87
+ def evaluate_answer(question, user_answer, correct_answer):
88
+ prompt = f"""You are a technical interviewer evaluating a candidate answer.
89
+ Question: {question}
90
+ Candidate Answer: {user_answer}
91
+ Expected Answer: {correct_answer}
92
+
93
+ Evaluate the candidate answer and provide:
94
+ 1. Score: X/10
95
+ 2. Strengths: what they got right
96
+ 3. Improvements: what they missed
97
+ 4. Verdict: Pass/Fail
98
+
99
+ Be concise and professional."""
100
+
101
+ response = client.chat.completions.create(
102
+ model = "llama-3.3-70b-versatile",
103
+ messages = [{"role": "user", "content": prompt}],
104
+ temperature = 0.3,
105
+ max_tokens = 500
106
+ )
107
+ return response.choices[0].message.content
108
+
109
+ def parse_questions(text):
110
+ qa_pairs = []
111
+ blocks = re.split(r"Q\d+:", text)
112
+ blocks = [b.strip() for b in blocks if b.strip()]
113
+ for block in blocks:
114
+ if re.search(r"A\d+:", block):
115
+ parts = re.split(r"A\d+:", block, maxsplit=1)
116
+ question = parts[0].strip()
117
+ answer = parts[1].strip() if len(parts) > 1 else "N/A"
118
+ else:
119
+ question = block.strip()
120
+ answer = "N/A"
121
+ qa_pairs.append({"question": question, "answer": answer})
122
+ return qa_pairs
123
+
124
+ # ── Session State Init ────────────────────────────
125
+ if "history" not in st.session_state: st.session_state.history = []
126
+ if "total_generated" not in st.session_state: st.session_state.total_generated = 0
127
+ if "parsed_qa" not in st.session_state: st.session_state.parsed_qa = []
128
+ if "mock_index" not in st.session_state: st.session_state.mock_index = 0
129
+ if "mock_scores" not in st.session_state: st.session_state.mock_scores = []
130
+ if "mock_active" not in st.session_state: st.session_state.mock_active = False
131
+
132
+ # ── Header ────────────────────────────────────────
133
+ st.markdown("<p class=\'main-header\'>🎯 InterviewGen AI</p>", unsafe_allow_html=True)
134
+ st.markdown("<p style=\'text-align:center;color:gray;font-size:1.1rem;\'>Professional Interview Preparation Powered by LLaMA-3.3 & Groq</p>", unsafe_allow_html=True)
135
+ st.divider()
136
+
137
+ # ── Top Metrics ───────────────────────────────────
138
+ col1, col2, col3, col4 = st.columns(4)
139
+ with col1: st.metric("Questions Generated", st.session_state.total_generated)
140
+ with col2: st.metric("Sessions", len(st.session_state.history))
141
+ with col3: st.metric("Mock Interviews", len(st.session_state.mock_scores))
142
+ with col4:
143
+ avg = sum(st.session_state.mock_scores) / len(st.session_state.mock_scores) if st.session_state.mock_scores else 0
144
+ st.metric("Avg Mock Score", f"{avg:.1f}/10")
145
+
146
+ st.divider()
147
+
148
+ # ── Sidebar ───────────────────────────────────────
149
+ with st.sidebar:
150
+ st.markdown("## Settings")
151
+
152
+ role = st.selectbox(
153
+ "Select Role",
154
+ ["Python Developer", "Data Scientist",
155
+ "Software Engineer", "ML Engineer",
156
+ "DevOps Engineer", "Full Stack Developer",
157
+ "Data Analyst", "Backend Developer",
158
+ "Frontend Developer", "AI Engineer"]
159
+ )
160
+ difficulty = st.select_slider(
161
+ "Difficulty Level",
162
+ options=["Junior", "Mid-Level", "Senior"]
163
+ )
164
+ num_questions = st.slider(
165
+ "Number of Questions",
166
+ min_value=1, max_value=10, value=5
167
+ )
168
+ show_answers = st.toggle("Show Answers", value=True)
169
+
170
+ st.divider()
171
+ st.markdown("### Paste Job Description (Optional)")
172
+ job_desc = st.text_area(
173
+ "Job Description",
174
+ placeholder="Paste job description here for targeted questions...",
175
+ height=150
176
+ )
177
+
178
+ # ── Tabs ──────────────────────────────────────────
179
+ tab1, tab2, tab3 = st.tabs([
180
+ "πŸ“‹ Generate Questions",
181
+ "🎯 Mock Interview Mode",
182
+ "πŸ“š History"
183
+ ])
184
+
185
+ # ════════════════════════════════════════════════
186
+ # TAB 1 β€” Generate Questions
187
+ # ════════════════════════════════════════════════
188
+ with tab1:
189
+ q_type = st.radio(
190
+ "Question Type",
191
+ ["Technical", "Behavioral", "Mixed"],
192
+ horizontal=True
193
+ )
194
+
195
+ generate_btn = st.button(
196
+ "πŸš€ Generate Interview Questions",
197
+ use_container_width=True
198
+ )
199
+
200
+ if generate_btn:
201
+ with st.spinner("LLaMA-3.3 is generating questions..."):
202
+ raw = generate_questions(role, difficulty, q_type, num_questions, job_desc)
203
+ parsed = parse_questions(raw)
204
+ st.session_state.parsed_qa = parsed
205
+
206
+ st.markdown(f"### {role} | {difficulty} | {q_type}")
207
+ st.divider()
208
+
209
+ questions = []
210
+ answers = []
211
+
212
+ for i, qa in enumerate(parsed):
213
+ q = qa["question"]
214
+ a = qa["answer"]
215
+ questions.append(q)
216
+ answers.append(a)
217
+
218
+ st.info(f"**Q{i+1}.** {q}")
219
+ if show_answers:
220
+ st.success(f"**Answer:** {a}")
221
+ st.write("")
222
+
223
+ st.session_state.total_generated += len(parsed)
224
+ st.session_state.history.append({
225
+ "time" : datetime.datetime.now().strftime("%H:%M:%S"),
226
+ "role" : role,
227
+ "difficulty": difficulty,
228
+ "type" : q_type,
229
+ "questions" : questions,
230
+ "answers" : answers
231
+ })
232
+
233
+ # ── PDF Export ────────────────────────────
234
+ try:
235
+ pdf = FPDF()
236
+ pdf.add_page()
237
+ pdf.set_font("Arial", "B", 14)
238
+ pdf.cell(190, 10, f"Interview Questions - {role}", ln=True, align="C")
239
+ pdf.set_font("Arial", "", 9)
240
+ pdf.cell(190, 8, f"Type: {q_type} | Difficulty: {difficulty}", ln=True, align="C")
241
+ pdf.ln(4)
242
+ for i, (q, a) in enumerate(zip(questions, answers)):
243
+ q_c = q.encode("latin-1", "replace").decode("latin-1")
244
+ a_c = a.encode("latin-1", "replace").decode("latin-1")
245
+ pdf.set_font("Arial", "B", 10)
246
+ pdf.multi_cell(190, 7, f"Q{i+1}. {q_c}")
247
+ if show_answers:
248
+ pdf.set_font("Arial", "", 9)
249
+ pdf.multi_cell(190, 6, f"Answer: {a_c}")
250
+ pdf.ln(2)
251
+ pdf_path = "/tmp/interview_questions.pdf"
252
+ pdf.output(pdf_path)
253
+ with open(pdf_path, "rb") as f:
254
+ st.download_button(
255
+ label = "πŸ“₯ Download as PDF",
256
+ data = f,
257
+ file_name = f"interview_{role.replace(' ','_')}.pdf",
258
+ mime = "application/pdf"
259
+ )
260
+ except Exception as e:
261
+ st.warning(f"PDF error: {e}")
262
+
263
+ # ═══════════════════════════════════��════════════
264
+ # TAB 2 β€” Mock Interview Mode
265
+ # ════════════════════════════════════════════════
266
+ with tab2:
267
+ st.markdown("### 🎯 Mock Interview Mode")
268
+ st.markdown("Answer questions one by one β€” AI will evaluate your answers!")
269
+ st.divider()
270
+
271
+ if not st.session_state.parsed_qa:
272
+ st.info("First generate questions in Tab 1, then come back here!")
273
+ else:
274
+ total_q = len(st.session_state.parsed_qa)
275
+ idx = st.session_state.mock_index
276
+
277
+ if idx < total_q:
278
+ current_qa = st.session_state.parsed_qa[idx]
279
+
280
+ st.markdown(f"**Question {idx+1} of {total_q}**")
281
+ st.progress((idx) / total_q)
282
+
283
+ st.markdown(f"""
284
+ <div class="question-card">
285
+ <strong>Q{idx+1}. {current_qa["question"]}</strong>
286
+ </div>
287
+ """, unsafe_allow_html=True)
288
+
289
+ user_answer = st.text_area(
290
+ "Your Answer",
291
+ placeholder="Type your answer here...",
292
+ height=150,
293
+ key=f"answer_{idx}"
294
+ )
295
+
296
+ col1, col2 = st.columns(2)
297
+ with col1:
298
+ submit_btn = st.button("Submit Answer", use_container_width=True)
299
+ with col2:
300
+ skip_btn = st.button("Skip Question", use_container_width=True)
301
+
302
+ if submit_btn and user_answer:
303
+ with st.spinner("AI is evaluating your answer..."):
304
+ evaluation = evaluate_answer(
305
+ current_qa["question"],
306
+ user_answer,
307
+ current_qa["answer"]
308
+ )
309
+
310
+ st.markdown(f"""
311
+ <div class="score-card">
312
+ <strong>AI Evaluation:</strong><br>{evaluation}
313
+ </div>
314
+ """, unsafe_allow_html=True)
315
+
316
+ # Extract score
317
+ score_match = re.search(r"(\d+)/10", evaluation)
318
+ if score_match:
319
+ score = int(score_match.group(1))
320
+ st.session_state.mock_scores.append(score)
321
+
322
+ st.session_state.mock_index += 1
323
+ st.rerun()
324
+
325
+ if skip_btn:
326
+ st.session_state.mock_index += 1
327
+ st.rerun()
328
+
329
+ else:
330
+ st.success("Mock Interview Complete!")
331
+ if st.session_state.mock_scores:
332
+ avg = sum(st.session_state.mock_scores) / len(st.session_state.mock_scores)
333
+ st.markdown(f"### Your Final Score: {avg:.1f}/10")
334
+ if avg >= 8:
335
+ st.balloons()
336
+ st.success("Excellent! You are ready for the interview!")
337
+ elif avg >= 6:
338
+ st.warning("Good performance! A little more practice needed.")
339
+ else:
340
+ st.error("Keep practicing! Review the answers carefully.")
341
+
342
+ if st.button("Restart Mock Interview"):
343
+ st.session_state.mock_index = 0
344
+ st.session_state.mock_scores = []
345
+ st.rerun()
346
+
347
+ # ════════════════════════════════════════════════
348
+ # TAB 3 β€” History
349
+ # ════════════════════════════════════════════════
350
+ with tab3:
351
+ st.markdown("### Question History")
352
+ if not st.session_state.history:
353
+ st.info("No history yet! Generate some questions first.")
354
+ else:
355
+ for session in reversed(st.session_state.history):
356
+ with st.expander(f"{session['time']} - {session['role']} | {session['difficulty']} | {session['type']}"):
357
+ for i, (q, a) in enumerate(zip(session["questions"], session["answers"])):
358
+ st.markdown(f"**Q{i+1}.** {q}")
359
+ if show_answers:
360
+ st.markdown(f"*A: {a[:200]}...*")
361
+ st.write("")