""" interview_coach.py — Version Alpha ──────────────────────────────────────────────────────────────────────────── Pure Gradio UI layer. Imports all logic from engine.py and config.py. All business logic lives in engine.py and agents/. UI Structure (4 Tabs): Tab 1 — 🎯 Practice : JD input + mode selector + Q&A + feedback Tab 2 — 📈 History : Session history + PDF download Tab 3 — 💡 Prep Sheet : AI-generated role-specific preparation guide Tab 4 — ℹ️ About : How scoring works, STAR guide """ import gradio as gr import os from config import CUSTOM_CSS, INTERVIEW_MODES from engine import ( render_history, generate_all_questions, score_answer, next_question, generate_pdf_report, ) #Custom UI Theme custom_theme = gr.themes.Soft( primary_hue=gr.themes.colors.purple, secondary_hue=gr.themes.colors.red, neutral_hue=gr.themes.colors.slate, font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "sans-serif"], ) # ── Animated background HTML ────────────────────────────────────────────────── _BLOBS_HTML = """
""" _HEADER_HTML = """

AI Interview Coach

Practice · Get Feedback · Improve

Powered by Qwen/Qwen2.5-7B-Instruct

""" def _score_badge_html(score_result: dict) -> str: """Render keyword coverage badges cleanly as inline HTML without any percentage bubbles.""" if not score_result: return "" hit = score_result.get("hit_keywords", []) missed = score_result.get("missed_keywords", []) badge_style = "display:inline-block;padding:3px 9px;border-radius:20px;font-size:0.78rem;font-weight:600;margin:3px 3px;" hit_badges = "".join(f'✅ {k}' for k in hit) missed_badges = "".join(f'❌ {k}' for k in missed) # ── FIXED: Completely stripped out {color} and {cov:.0f}% to prevent any NameErrors ── return f"""
🔑 KEYWORD SUMMARY
{hit_badges}{missed_badges}
""" def _progress_bar_html(current: int, total: int) -> str: """Render an HTML progress bar for question progress.""" if total == 0: return "" pct = int((current / total) * 100) return f"""
Progress Q{current} of {total}
""" _ABOUT_MD = """ ## 🎙️ About AI Interview Coach **AI Interview Coach** is an intelligent interview preparation tool powered by **Mistral 7B** via the HuggingFace Inference API. --- ### 🔄 How It Works 1. **Paste** your target job description 2. **Choose** your interview depth (Quick / Standard / Deep Dive) 3. **Answer** AI-generated questions tailored to that specific role 4. **Get feedback** with scores, strengths, weaknesses, and keyword analysis 5. **Download** a PDF report of your session --- ### 📊 How Scoring Works | Score | Meaning | |-------|---------| | 8–10 | Excellent — strong STAR structure, good keyword coverage | | 5–7 | Good — solid effort, but missing some key terms or depth | | 1–4 | Needs work — expand your answers and use role-specific language | | NIL | Irrelevant — answer must be a genuine attempt at the question | > **Keyword Coverage Rule:** The AI extracts 5–7 key terms from the job description. If your answer uses fewer than 40% of them, your score is capped at **5/10**. Use the Prep Sheet to learn which terms to include. --- ### ⭐ The STAR Format Use this structure for behavioral and situational answers: | Part | What to Say | Example | |------|-------------|---------| | **S**ituation | Set the context | "At my previous company, we had a critical deadline..." | | **T**ask | Your role/responsibility | "I was responsible for..." | | **A**ction | What YOU did | "I specifically implemented..." | | **R**esult | Outcome (quantified if possible) | "This reduced load time by 40%..." | --- ### 🚀 Interview Modes | Mode | Questions | Best For | |------|-----------|---------| | ⚡ Quick | 3 | Final-hour revision, confidence check | | 📋 Standard | 5 | Regular practice sessions | | 🔬 Deep Dive | 7 | Thorough preparation for important interviews | --- *Built for the HuggingFace Hackathon 2026 · Model: Qwen/Qwen2.5-7B-Instruct (8B params)* """ """ import gradio as gr with gr.Blocks(theme=gr.Theme.from_hub("theme-repo/STONE_Theme")) as demo: ... """ # ── Build UI ────────────────────────────────────────────────────────────────── with gr.Blocks(title="AI Interview Coach", fill_width=True) as demo: # ── Shared State ────────────────────────────────────────────────────────── history_state = gr.State([]) q_index = gr.State("0") job_profile_state = gr.State({ "valid": False, "industry": "General", "role_level": "Mid-Level", "keywords": [], "tips": "", "interview_style": "Mixed", }) score_result_state = gr.State({}) # Latest ScorerAgent result for badge rendering n_questions_state = gr.State(3) # Resolved question count for progress bar # ── Background + Header ─────────────────────────────────────────────────── gr.HTML(_BLOBS_HTML, elem_id="bg-blobs") gr.HTML(_HEADER_HTML, elem_id="app-header") with gr.Tabs(): # ══════════════════════════════════════════════════════════════════════ # TAB 1: PRACTICE # ══════════════════════════════════════════════════════════════════════ with gr.Tab("🎯 Practice"): with gr.Row(equal_height=False): # ── LEFT COLUMN: Job Input + Mode ───────────────────────────── with gr.Column(scale=1, min_width=360): with gr.Group(elem_id="step-1-group"): gr.HTML('''

Step 1:
📋 Paste the Job Description

''') job_desc_box = gr.Textbox( label="Job Description", show_label=False, lines=9, placeholder="Paste from LinkedIn, a company's careers page, etc.\n\nTip: A longer, more detailed JD = better tailored questions.", ) with gr.Group(elem_id="step-2-group"): gr.HTML('''

Step 2
🎚️ Choose Interview Depth

How many questions do you want to answer?

''') mode_selector = gr.Radio( choices=list(INTERVIEW_MODES.keys()), value="⚡ Quick (3 Questions)", label="Interview Mode", show_label=False, ) start_btn = gr.Button( "🚀 Start Interview", variant="primary", size="lg", ) gr.HTML("""

💡 Tips for a better session:
• Paste the full job posting (350+ characters)
• Answer in complete sentences
• Use STAR format for behavioral questions

""") # ── RIGHT COLUMN: Q&A ───────────────────────────────────────── with gr.Column(scale=1, min_width=360): with gr.Group(elem_id="step-3-group"): gr.HTML('''

Step 3:
💬 Answer the Questions

''') progress_bar_display = gr.HTML('
Session Progress-->
') """gr.HTML('''
Interview Question
''') """ question_box = gr.Textbox( label="Interview Question", show_label=True, lines=3, interactive=False, placeholder="Your question will appear here after you click Start Interview...", ) answer_box = gr.Textbox( label="Your Answer", show_label=True, lines=5, placeholder=( "Answer in complete sentences.\n\n" "💡 STAR Format: Situation → Task → Action → Result" ), ) with gr.Row(): feedback_btn = gr.Button("📊 Get Feedback", variant="primary", size="lg") next_btn = gr.Button("➡️ Next Question", variant="secondary", size="lg") # ── Keyword Coverage Badge Area ─────────────────────────────────── keyword_badges_display = gr.HTML('
Keyword Coverage Badges-->
') # ── Feedback ────────────────────────────────────────────────────── with gr.Group(elem_id="step-4-group"): gr.HTML('''

Step 4:
📋 Get feedback

''') feedback_box = gr.Textbox( label="AI Coach Feedback", show_label=False, interactive=False, lines=8, elem_id="feedback_box", placeholder="Feedback will appear here after you click Get Feedback...", ) # ── Review Panels ───────────────────────────────────────────────── with gr.Accordion("🔁 Previous Question Review", open=False): with gr.Row(): prev_question_box = gr.Textbox( label="Previous Question", interactive=False, lines=2, placeholder="Appears after clicking Next Question...", ) prev_answer_box = gr.Textbox( label="Your Previous Answer", interactive=False, lines=3, placeholder="Your last answer...", ) with gr.Accordion("📋 This Session's Log", open=False): session_log_display = gr.Markdown("Complete questions to see your session log here.") # ══════════════════════════════════════════════════════════════════════ # TAB 2: HISTORY & PDF # ══════════════════════════════════════════════════════════════════════ with gr.Tab("📈 History & Report") as history_tab: gr.HTML("""

⬇️ Download Your Interview Report

Generate a professionally formatted PDF containing your questions, answers, AI feedback, keyword analysis, and coaching recommendations.

""") with gr.Row(elem_id="report-actions"): download_report_btn = gr.DownloadButton("📥 Download Report", variant="primary", size="lg") refresh_btn = gr.Button("🔄 Refresh Preview", variant="secondary") clear_btn = gr.Button("🗑️ Clear Session", variant="secondary") history_display = gr.Markdown("### 📝 Session Summary\n\n" + render_history([]), elem_classes=["padded-markdown"]) # ══════════════════════════════════════════════════════════════════════ # TAB 3: PREP SHEET # ══════════════════════════════════════════════════════════════════════ with gr.Tab("💡 Prep Sheet"): gr.HTML("""

💡 AI-Generated Preparation Guide

This guide is generated specifically for your target role after you click Start Interview. It includes expected keywords, interview style tips, and curated resources.

""") tips_display = gr.Markdown( "*Start an interview on the Practice tab to generate your personalised prep sheet.*", elem_classes=["padded-markdown"] ) # ══════════════════════════════════════════════════════════════════════ # TAB 4: ABOUT # ══════════════════════════════════════════════════════════════════════ with gr.Tab("ℹ️ About"): gr.Markdown(_ABOUT_MD, elem_classes=["padded-markdown"]) # ── Event wiring ────────────────────────────────────────────────────────── def _handle_start(job_desc, mode_label, history_state, job_profile_state): """Wrapper: calls engine, then updates progress bar HTML.""" result = generate_all_questions(job_desc, mode_label, history_state, job_profile_state) # result = (question, "0", progress_str, history_state, tips_md, job_profile, score_result) first_q, idx_str, _prog_str, new_hist, tips_md, new_profile, new_score_res = result n = INTERVIEW_MODES.get(mode_label, 3) prog_html = _progress_bar_html(1, n) if "Please" not in first_q else '
' empty_html = '
' return first_q, idx_str, new_hist, tips_md, new_profile, new_score_res, n, prog_html, empty_html start_btn.click( fn=_handle_start, inputs=[job_desc_box, mode_selector, history_state, job_profile_state], outputs=[ question_box, q_index, history_state, tips_display, job_profile_state, score_result_state, n_questions_state, progress_bar_display, keyword_badges_display, ], ).then( fn=render_history, inputs=[history_state], outputs=[history_display], ) def _handle_feedback(answer, q_index_str, history_state, job_profile_state, n_total): """Wrapper: calls engine scorer, then renders keyword badge HTML.""" feedback_text, new_hist, result = score_answer( answer, q_index_str, history_state, job_profile_state ) badges_html = _score_badge_html(result) idx = int(q_index_str) if q_index_str else 0 prog_html = _progress_bar_html(idx + 1, n_total) return feedback_text, new_hist, result, badges_html, prog_html feedback_btn.click( fn=_handle_feedback, inputs=[answer_box, q_index, history_state, job_profile_state, n_questions_state], outputs=[feedback_box, history_state, score_result_state, keyword_badges_display, progress_bar_display], ).then( fn=render_history, inputs=[history_state], outputs=[history_display], ) def _handle_next(q_index_str, answer, history_state, n_total): """Wrapper: calls engine next_question, updates progress bar.""" result = next_question(q_index_str, answer, history_state) # result = (question, answer, idx_str, progress_str, history, prev_q, prev_a, log) new_q, new_a, new_idx, prog_str, new_hist, prev_q, prev_a, log = result idx = int(new_idx) if new_idx else 0 prog_html = _progress_bar_html(idx + 1, n_total) if "Complete" not in prog_str else _progress_bar_html(n_total, n_total) empty_html = '
' # collapse badge area between questions return new_q, new_a, new_idx, new_hist, prev_q, prev_a, log, prog_html, empty_html next_btn.click( fn=_handle_next, inputs=[q_index, answer_box, history_state, n_questions_state], outputs=[ question_box, answer_box, q_index, history_state, prev_question_box, prev_answer_box, session_log_display, progress_bar_display, keyword_badges_display, ], ) history_tab.select( fn=generate_pdf_report, inputs=[history_state], outputs=[download_report_btn], ) refresh_btn.click( fn=render_history, inputs=[history_state], outputs=[history_display], ).then( fn=generate_pdf_report, inputs=[history_state], outputs=[download_report_btn], ) def clear_history_fn(history_state): return [], "Session cleared! Start a new interview above." clear_btn.click( fn=clear_history_fn, inputs=[history_state], outputs=[history_state, history_display], ) # ── Launch ──────────────────────────────────────────────────────────────────── if __name__ == "__main__": demo.launch(theme=custom_theme, css=CUSTOM_CSS, share=True)