interviewgen-ai / app.py
Ame-Mark's picture
Upload app.py with huggingface_hub
2cc6fdb verified
Raw
History Blame Contribute Delete
14.1 kB
import streamlit as st
from groq import Groq
import re
from fpdf import FPDF
import datetime
import os
# ── Page Config ───────────────────────────────────
st.set_page_config(
page_title = "InterviewGen AI",
page_icon = "🎯",
layout = "wide"
)
# ── Custom CSS ────────────────────────────────────
st.markdown("""
<style>
.main-header {
font-size: 2.8rem;
font-weight: 900;
background: linear-gradient(90deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-align: center;
padding: 1rem 0;
}
.question-card {
background: #f8f9fa;
border-left: 5px solid #667eea;
padding: 1.2rem;
margin: 0.8rem 0;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.answer-card {
background: linear-gradient(135deg, #e8f4f8, #f0fff4);
border-left: 5px solid #2ecc71;
padding: 1.2rem;
margin: 0.8rem 0;
border-radius: 10px;
}
.score-card {
background: linear-gradient(135deg, #fff3cd, #ffeaa7);
border-left: 5px solid #f39c12;
padding: 1.2rem;
margin: 0.8rem 0;
border-radius: 10px;
}
.metric-card {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 1rem;
border-radius: 10px;
text-align: center;
}
</style>
""", unsafe_allow_html=True)
# ── Groq Client ───────────────────────────────────
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
client = Groq(api_key=GROQ_API_KEY)
# ── Helper Functions ──────────────────────────────
def generate_questions(role, difficulty, q_type, num, job_desc=""):
job_context = f"Job Description: {job_desc[:500]}" if job_desc else ""
prompt = f"""You are a senior technical interviewer at a top tech company.
{job_context}
Generate exactly {num} {difficulty} level {q_type} interview questions for a {role}.
Format EXACTLY like this:
Q1: [question]
A1: [detailed answer]
Q2: [question]
A2: [detailed answer]
Only output questions and answers. Nothing else."""
response = client.chat.completions.create(
model = "llama-3.3-70b-versatile",
messages = [{"role": "user", "content": prompt}],
temperature = 0.7,
max_tokens = 2000
)
return response.choices[0].message.content
def evaluate_answer(question, user_answer, correct_answer):
prompt = f"""You are a technical interviewer evaluating a candidate answer.
Question: {question}
Candidate Answer: {user_answer}
Expected Answer: {correct_answer}
Evaluate the candidate answer and provide:
1. Score: X/10
2. Strengths: what they got right
3. Improvements: what they missed
4. Verdict: Pass/Fail
Be concise and professional."""
response = client.chat.completions.create(
model = "llama-3.3-70b-versatile",
messages = [{"role": "user", "content": prompt}],
temperature = 0.3,
max_tokens = 500
)
return response.choices[0].message.content
def parse_questions(text):
qa_pairs = []
blocks = re.split(r"Q\d+:", text)
blocks = [b.strip() for b in blocks if b.strip()]
for block in blocks:
if re.search(r"A\d+:", block):
parts = re.split(r"A\d+:", block, maxsplit=1)
question = parts[0].strip()
answer = parts[1].strip() if len(parts) > 1 else "N/A"
else:
question = block.strip()
answer = "N/A"
qa_pairs.append({"question": question, "answer": answer})
return qa_pairs
# ── Session State Init ────────────────────────────
if "history" not in st.session_state: st.session_state.history = []
if "total_generated" not in st.session_state: st.session_state.total_generated = 0
if "parsed_qa" not in st.session_state: st.session_state.parsed_qa = []
if "mock_index" not in st.session_state: st.session_state.mock_index = 0
if "mock_scores" not in st.session_state: st.session_state.mock_scores = []
if "mock_active" not in st.session_state: st.session_state.mock_active = False
# ── Header ────────────────────────────────────────
st.markdown("<p class=\'main-header\'>🎯 InterviewGen AI</p>", unsafe_allow_html=True)
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)
st.divider()
# ── Top Metrics ───────────────────────────────────
col1, col2, col3, col4 = st.columns(4)
with col1: st.metric("Questions Generated", st.session_state.total_generated)
with col2: st.metric("Sessions", len(st.session_state.history))
with col3: st.metric("Mock Interviews", len(st.session_state.mock_scores))
with col4:
avg = sum(st.session_state.mock_scores) / len(st.session_state.mock_scores) if st.session_state.mock_scores else 0
st.metric("Avg Mock Score", f"{avg:.1f}/10")
st.divider()
# ── Sidebar ───────────────────────────────────────
with st.sidebar:
st.markdown("## Settings")
role = st.selectbox(
"Select Role",
["Python Developer", "Data Scientist",
"Software Engineer", "ML Engineer",
"DevOps Engineer", "Full Stack Developer",
"Data Analyst", "Backend Developer",
"Frontend Developer", "AI Engineer"]
)
difficulty = st.select_slider(
"Difficulty Level",
options=["Junior", "Mid-Level", "Senior"]
)
num_questions = st.slider(
"Number of Questions",
min_value=1, max_value=10, value=5
)
show_answers = st.toggle("Show Answers", value=True)
st.divider()
st.markdown("### Paste Job Description (Optional)")
job_desc = st.text_area(
"Job Description",
placeholder="Paste job description here for targeted questions...",
height=150
)
# ── Tabs ──────────────────────────────────────────
tab1, tab2, tab3 = st.tabs([
"πŸ“‹ Generate Questions",
"🎯 Mock Interview Mode",
"πŸ“š History"
])
# ════════════════════════════════════════════════
# TAB 1 β€” Generate Questions
# ════════════════════════════════════════════════
with tab1:
q_type = st.radio(
"Question Type",
["Technical", "Behavioral", "Mixed"],
horizontal=True
)
generate_btn = st.button(
"πŸš€ Generate Interview Questions",
use_container_width=True
)
if generate_btn:
with st.spinner("LLaMA-3.3 is generating questions..."):
raw = generate_questions(role, difficulty, q_type, num_questions, job_desc)
parsed = parse_questions(raw)
st.session_state.parsed_qa = parsed
st.markdown(f"### {role} | {difficulty} | {q_type}")
st.divider()
questions = []
answers = []
for i, qa in enumerate(parsed):
q = qa["question"]
a = qa["answer"]
questions.append(q)
answers.append(a)
st.info(f"**Q{i+1}.** {q}")
if show_answers:
st.success(f"**Answer:** {a}")
st.write("")
st.session_state.total_generated += len(parsed)
st.session_state.history.append({
"time" : datetime.datetime.now().strftime("%H:%M:%S"),
"role" : role,
"difficulty": difficulty,
"type" : q_type,
"questions" : questions,
"answers" : answers
})
# ── PDF Export ────────────────────────────
try:
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", "B", 14)
pdf.cell(190, 10, f"Interview Questions - {role}", ln=True, align="C")
pdf.set_font("Arial", "", 9)
pdf.cell(190, 8, f"Type: {q_type} | Difficulty: {difficulty}", ln=True, align="C")
pdf.ln(4)
for i, (q, a) in enumerate(zip(questions, answers)):
q_c = q.encode("latin-1", "replace").decode("latin-1")
a_c = a.encode("latin-1", "replace").decode("latin-1")
pdf.set_font("Arial", "B", 10)
pdf.multi_cell(190, 7, f"Q{i+1}. {q_c}")
if show_answers:
pdf.set_font("Arial", "", 9)
pdf.multi_cell(190, 6, f"Answer: {a_c}")
pdf.ln(2)
pdf_path = "/tmp/interview_questions.pdf"
pdf.output(pdf_path)
with open(pdf_path, "rb") as f:
st.download_button(
label = "πŸ“₯ Download as PDF",
data = f,
file_name = f"interview_{role.replace(' ','_')}.pdf",
mime = "application/pdf"
)
except Exception as e:
st.warning(f"PDF error: {e}")
# ════════════════════════════════════════════════
# TAB 2 β€” Mock Interview Mode
# ════════════════════════════════════════════════
with tab2:
st.markdown("### 🎯 Mock Interview Mode")
st.markdown("Answer questions one by one β€” AI will evaluate your answers!")
st.divider()
if not st.session_state.parsed_qa:
st.info("First generate questions in Tab 1, then come back here!")
else:
total_q = len(st.session_state.parsed_qa)
idx = st.session_state.mock_index
if idx < total_q:
current_qa = st.session_state.parsed_qa[idx]
st.markdown(f"**Question {idx+1} of {total_q}**")
st.progress((idx) / total_q)
st.markdown(f"""
<div class="question-card">
<strong>Q{idx+1}. {current_qa["question"]}</strong>
</div>
""", unsafe_allow_html=True)
user_answer = st.text_area(
"Your Answer",
placeholder="Type your answer here...",
height=150,
key=f"answer_{idx}"
)
col1, col2 = st.columns(2)
with col1:
submit_btn = st.button("Submit Answer", use_container_width=True)
with col2:
skip_btn = st.button("Skip Question", use_container_width=True)
if submit_btn and user_answer:
with st.spinner("AI is evaluating your answer..."):
evaluation = evaluate_answer(
current_qa["question"],
user_answer,
current_qa["answer"]
)
st.markdown(f"""
<div class="score-card">
<strong>AI Evaluation:</strong><br>{evaluation}
</div>
""", unsafe_allow_html=True)
# Extract score
score_match = re.search(r"(\d+)/10", evaluation)
if score_match:
score = int(score_match.group(1))
st.session_state.mock_scores.append(score)
st.session_state.mock_index += 1
st.rerun()
if skip_btn:
st.session_state.mock_index += 1
st.rerun()
else:
st.success("Mock Interview Complete!")
if st.session_state.mock_scores:
avg = sum(st.session_state.mock_scores) / len(st.session_state.mock_scores)
st.markdown(f"### Your Final Score: {avg:.1f}/10")
if avg >= 8:
st.balloons()
st.success("Excellent! You are ready for the interview!")
elif avg >= 6:
st.warning("Good performance! A little more practice needed.")
else:
st.error("Keep practicing! Review the answers carefully.")
if st.button("Restart Mock Interview"):
st.session_state.mock_index = 0
st.session_state.mock_scores = []
st.rerun()
# ════════════════════════════════════════════════
# TAB 3 β€” History
# ════════════════════════════════════════════════
with tab3:
st.markdown("### Question History")
if not st.session_state.history:
st.info("No history yet! Generate some questions first.")
else:
for session in reversed(st.session_state.history):
with st.expander(f"{session['time']} - {session['role']} | {session['difficulty']} | {session['type']}"):
for i, (q, a) in enumerate(zip(session["questions"], session["answers"])):
st.markdown(f"**Q{i+1}.** {q}")
if show_answers:
st.markdown(f"*A: {a[:200]}...*")
st.write("")