import os import json import pandas as pd import gradio as gr from langgraph_agent import LangGraphResumeAnalyzer import utils # Instantiate LangGraph Agent analyzer = LangGraphResumeAnalyzer() # FIXED CSS preventing vibrating / trembling layout bug by locking vertical scrollbar gutter permanently 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" # AI Tailored Resume Rewriter Bullets 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 Info 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(): # TAB 1: LangGraph ATS Audit & Matching 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.") # TAB 2: NVIDIA Nemotron OCR Scanner View 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) # TAB 3: LangGraph RAG Candidate Chatbot 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) # TAB 4: Full Audit Report Export 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") # Event Bindings 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)