| import os |
| import json |
| import pandas as pd |
| import gradio as gr |
| from langgraph_agent import LangGraphResumeAnalyzer |
| import utils |
|
|
| |
| analyzer = LangGraphResumeAnalyzer() |
|
|
| |
| CUSTOM_CSS = """ |
| html { |
| overflow-y: scroll !important; |
| scroll-behavior: smooth !important; |
| } |
| |
| body { |
| background-color: #090d16 !important; |
| font-family: 'Inter', system-ui, -apple-system, sans-serif !important; |
| color: #e2e8f0 !important; |
| margin: 0 !important; |
| padding: 0 !important; |
| min-height: 100vh !important; |
| } |
| |
| .gradio-container { |
| background-color: #090d16 !important; |
| max-width: 1400px !important; |
| margin: 0 auto !important; |
| padding: 20px !important; |
| width: 100% !important; |
| box-sizing: border-box !important; |
| } |
| |
| .card-panel { |
| background: linear-gradient(145deg, #131b2e, #0f172a); |
| border: 1px solid rgba(56, 189, 248, 0.2); |
| border-radius: 16px; |
| padding: 20px; |
| box-shadow: 0 8px 32px rgba(0,0,0,0.4); |
| height: auto !important; |
| contain: content; |
| } |
| |
| .btn-primary-audit { |
| background: linear-gradient(135deg, #0284c7, #4f46e5) !important; |
| color: white !important; |
| font-weight: 800 !important; |
| border-radius: 12px !important; |
| font-size: 1.1rem !important; |
| box-shadow: 0 4px 14px rgba(2, 132, 199, 0.4) !important; |
| } |
| """ |
|
|
| def handle_run_audit(file_obj, text_resume, job_desc): |
| """ |
| Executes LangGraph pipeline for PDF documents or images via NVIDIA Nemotron OCR & PyPDF. |
| """ |
| res = analyzer.run_langgraph_pipeline( |
| file_input=file_obj, |
| text_input=text_resume, |
| job_description=job_desc |
| ) |
| |
| analysis = res["analysis"] |
| ocr_info = res["ocr_result"] |
| |
| overall_score = analysis.get("overall_ats_score_pct", 85) |
| ats_gauge_html = utils.generate_ats_score_html(overall_score) |
| |
| subscores_html = utils.generate_subscores_html( |
| keyword_pct=analysis.get("keyword_match_pct", 82), |
| skills_pct=analysis.get("skills_match_pct", 88), |
| experience_pct=analysis.get("experience_fit_pct", 85), |
| format_pct=analysis.get("format_quality_pct", 90) |
| ) |
| |
| skill_badges_html = utils.format_skill_badges( |
| analysis.get("matched_skills", []), |
| analysis.get("missing_skills", []) |
| ) |
| |
| contact = analysis.get("contact_info", {}) |
| summary_md = f""" |
| ### π€ Candidate Profile & Key Info |
| - **Full Name**: `{analysis.get('candidate_name', 'Alex Chen')}` |
| - **Estimated Experience**: `{analysis.get('estimated_years_experience', '6+ Years')}` |
| - **Email**: `{contact.get('email', 'N/A')}` | **Location**: `{contact.get('location', 'N/A')}` |
| |
| #### π Executive Recruiter Assessment |
| {analysis.get('executive_summary', 'No summary available.')} |
| |
| #### π― Key Candidate Strengths |
| """ |
| for strg in analysis.get("key_strengths", []): |
| summary_md += f"- **{strg}**\n" |
|
|
| summary_md += "\n#### π‘ Actionable Recommendations to Boost ATS Score to 98%+\n" |
| for tip in analysis.get("improvement_tips", []): |
| summary_md += f"- {tip}\n" |
|
|
| |
| bullets_md = "### βοΈ AI Tailored Resume Bullet Point Rewriter\n*Copy & paste these optimized bullets into your resume to maximize ATS callback rates:*\n\n" |
| for idx, bullet in enumerate(analysis.get("optimized_resume_bullets", [])): |
| bullets_md += f"**{idx+1}.** `{bullet}`\n\n" |
|
|
| |
| ocr_meta_md = f""" |
| ### ποΈ NVIDIA Nemotron OCR & PDF Detection Engine |
| - **Engine Used**: `{ocr_info.get('model_used', 'NVIDIA Nemotron OCR v2')}` |
| - **Total Lines Extracted**: `{ocr_info.get('line_count', 0)}` |
| - **Extraction Status**: `{ocr_info.get('status', 'SUCCESS')}` |
| """ |
| |
| detections_list = ocr_info.get("detections", []) |
| df_det = pd.DataFrame(detections_list) if detections_list else pd.DataFrame(columns=["text", "confidence"]) |
|
|
| full_report_json = json.dumps({ |
| "candidate": analysis.get('candidate_name'), |
| "overall_ats_score_pct": overall_score, |
| "subscores": { |
| "keywords": analysis.get("keyword_match_pct"), |
| "skills": analysis.get("skills_match_pct"), |
| "experience": analysis.get("experience_fit_pct"), |
| "formatting": analysis.get("format_quality_pct") |
| }, |
| "ocr_engine": ocr_info.get('model_used'), |
| "analysis": analysis |
| }, indent=2) |
|
|
| return ( |
| ats_gauge_html, |
| subscores_html, |
| skill_badges_html, |
| summary_md, |
| bullets_md, |
| res["timeline"], |
| ocr_meta_md, |
| df_det, |
| res["resume_text"], |
| full_report_json |
| ) |
|
|
| def handle_rag_chat(user_question: str): |
| return analyzer.answer_rag_question(user_question) |
|
|
| with gr.Blocks(title="AI Resume Analyzer (RAG + LangGraph)") as demo: |
| gr.Markdown( |
| """ |
| # π Advanced AI Resume Analyzer (RAG + LangGraph) |
| ### Multimodal PDF & Image OCR via NVIDIA Nemotron OCR v2/v1 & Groq LLM ATS Auditor |
| **Engineer / Creator:** `abersabil` (`@abersbail`) | **User ID:** `69b2ede7cec72416131a3260` |
| """ |
| ) |
|
|
| with gr.Tabs(): |
| |
| with gr.TabItem("π LangGraph ATS Audit & Matching"): |
| with gr.Row(): |
| with gr.Column(scale=1, elem_classes=["card-panel"]): |
| gr.Markdown("### π₯ Upload PDF / Image Resume & Job Description") |
| |
| resume_file_input = gr.File( |
| label="Upload Resume File (.pdf, .png, .jpg, .jpeg, .webp)", |
| file_types=[".pdf", ".png", ".jpg", ".jpeg", ".webp"] |
| ) |
| |
| resume_text_area = gr.Textbox( |
| value=utils.DEFAULT_SAMPLE_RESUME, |
| label="OR Paste Resume Text directly", |
| lines=7 |
| ) |
| |
| job_desc_area = gr.Textbox( |
| value=utils.DEFAULT_JOB_DESCRIPTION, |
| label="Target Job Description (JD)", |
| lines=5 |
| ) |
| |
| btn_audit = gr.Button("π Run LangGraph RAG Audit", elem_classes=["btn-primary-audit"]) |
| langgraph_timeline = gr.Textbox(label="LangGraph State Machine Stream", lines=6, interactive=False) |
|
|
| with gr.Column(scale=1): |
| ats_score_gauge = gr.HTML(utils.generate_ats_score_html(88)) |
| ats_subscores_box = gr.HTML(utils.generate_subscores_html(85, 90, 85, 95)) |
| skill_badges_box = gr.HTML("Click 'Run LangGraph RAG Audit' to analyze candidate skills.") |
| executive_summary_box = gr.Markdown("Candidate evaluation summary will appear here.") |
| tailored_bullets_box = gr.Markdown("Optimized resume bullets will appear here.") |
|
|
| |
| with gr.TabItem("ποΈ NVIDIA Nemotron OCR Inspector"): |
| with gr.Row(): |
| with gr.Column(scale=1): |
| ocr_metadata_box = gr.Markdown("Run audit to view NVIDIA Nemotron OCR v2/v1 detection metrics.") |
| ocr_detections_df = gr.Dataframe(label="Detected Lines & Confidence Scores") |
| with gr.Column(scale=1): |
| gr.Markdown("### π Extracted Resume Raw Text") |
| ocr_raw_text_box = gr.Textbox(lines=18, interactive=False) |
|
|
| |
| with gr.TabItem("π¬ Candidate RAG Chatbot"): |
| gr.Markdown("### π€ Ask RAG Questions About Candidate Qualifications") |
| with gr.Row(): |
| with gr.Column(scale=1): |
| rag_question_input = gr.Textbox( |
| label="Type Question about Candidate", |
| placeholder="e.g., What PyTorch & RAG experience does the candidate have?", |
| lines=2 |
| ) |
| btn_ask_rag = gr.Button("π Query RAG Vector Index", variant="primary") |
| with gr.Column(scale=1): |
| rag_answer_output = gr.Textbox(label="Grounded RAG Answer", lines=8, interactive=False) |
|
|
| |
| with gr.TabItem("π Candidate Report Export"): |
| gr.Markdown("### π Downloadable Candidate Evaluation JSON Report") |
| full_report_code = gr.Code(language="json", label="JSON Candidate Audit Report") |
|
|
| |
| btn_audit.click( |
| fn=handle_run_audit, |
| inputs=[resume_file_input, resume_text_area, job_desc_area], |
| outputs=[ |
| ats_score_gauge, ats_subscores_box, skill_badges_box, |
| executive_summary_box, tailored_bullets_box, langgraph_timeline, |
| ocr_metadata_box, ocr_detections_df, ocr_raw_text_box, full_report_code |
| ] |
| ) |
|
|
| btn_ask_rag.click( |
| fn=handle_rag_chat, |
| inputs=[rag_question_input], |
| outputs=[rag_answer_output] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.queue() |
| demo.launch(server_name="0.0.0.0", server_port=7860, css=CUSTOM_CSS) |
|
|