| import os |
| from io import BytesIO |
| import gradio as gr |
| import fitz |
| import docx |
| import google.generativeai as genai |
|
|
| |
| GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "").strip() |
| if GOOGLE_API_KEY: |
| genai.configure(api_key=GOOGLE_API_KEY) |
|
|
| |
| def get_model(): |
| if not GOOGLE_API_KEY: |
| raise RuntimeError( |
| "Missing GOOGLE_API_KEY secret. Go to Settings β Variables and secrets and add it." |
| ) |
|
|
| |
| available_models = genai.list_models() |
| print(available_models) |
|
|
| |
| return genai.GenerativeModel("gemini-2.5-flash") |
|
|
|
|
| |
| def extract_text_from_resume(file_path: str) -> str: |
| if not file_path: |
| return "" |
|
|
| file_path_lower = file_path.lower() |
| try: |
| if file_path_lower.endswith(".pdf"): |
| text = "" |
| with open(file_path, "rb") as f: |
| pdf_bytes = f.read() |
| doc = fitz.open(stream=pdf_bytes, filetype="pdf") |
| for page in doc: |
| text += page.get_text() or "" |
| doc.close() |
| return text |
|
|
| elif file_path_lower.endswith(".docx"): |
| |
| d = docx.Document(file_path) |
| return "\n".join(p.text for p in d.paragraphs) |
|
|
| elif file_path_lower.endswith(".txt"): |
| with open(file_path, "r", encoding="utf-8", errors="ignore") as f: |
| return f.read() |
|
|
| else: |
| raise ValueError("Unsupported file type. Please upload .pdf, .docx, or .txt.") |
| except Exception as e: |
| raise RuntimeError(f"Failed to read resume: {e}") |
|
|
|
|
| |
| def analyze_resume(job_description: str, resume_file_path: str): |
| try: |
| model = get_model() |
| except Exception as e: |
| return (f"β {e}", "", "") |
|
|
| if not job_description.strip(): |
| return ("β Please paste a job description.", "", "") |
|
|
| if not resume_file_path: |
| return ("β Please upload a resume file.", "", "") |
|
|
| try: |
| resume_text = extract_text_from_resume(resume_file_path) |
| except Exception as e: |
| return (f"β οΈ {e}", "", "") |
|
|
| if not resume_text.strip(): |
| return ("β οΈ Could not extract text from the resume.", "", "") |
|
|
| |
| try: |
| |
| analysis_prompt = f""" |
| Analyze the following resume based on the provided job description. |
| Identify missing keywords, skills, or experience mentioned in the job description |
| that are not present in the resume. Return a clear, readable list. |
| Job Description: |
| {job_description} |
| Resume: |
| {resume_text} |
| """ |
| analysis_resp = model.generate_content(analysis_prompt) |
| analysis_text = (getattr(analysis_resp, "text", "") or "").strip() |
|
|
| |
| resume_prompt = f""" |
| Based on the original resume, job description, and the missing items analysis, |
| rewrite the resume to be ATS-optimized. Incorporate missing keywords/skills naturally. |
| Use concise bullet points and clear section headings. Do not use * and give ATS score too pelase |
| Original Resume: |
| {resume_text} |
| Job Description: |
| {job_description} |
| Missing Items Analysis: |
| {analysis_text} |
| Generate the new ATS-optimized resume: |
| """ |
| resume_resp = model.generate_content(resume_prompt) |
| optimized_resume = (getattr(resume_resp, "text", "") or "").strip() |
|
|
| |
| cover_letter_prompt = f""" |
| Write a tailored cover letter that aligns the candidate's experience to the job. |
| Keep it 200β350 words, professional, specific, and with a strong call to action. |
| Job Description: |
| {job_description} |
| ATS-Optimized Resume: |
| {optimized_resume} |
| """ |
| cover_letter_resp = model.generate_content(cover_letter_prompt) |
| cover_letter_text = (getattr(cover_letter_resp, "text", "") or "").strip() |
|
|
| return (analysis_text, optimized_resume, cover_letter_text) |
|
|
| except Exception as e: |
| return (f"β οΈ Error analyzing with Gemini: {e}", "", "") |
|
|
|
|
| |
| with gr.Blocks() as demo: |
| gr.Markdown("## π HireReady β Resume & Job Match Analyzer (Google Gemini)") |
|
|
| with gr.Row(): |
| job_desc = gr.Textbox( |
| label="Job Description", |
| placeholder="Paste the job description here...", |
| lines=10 |
| ) |
|
|
| with gr.Row(): |
| resume_file = gr.File( |
| label="Upload Resume (.pdf, .docx, .txt)", |
| file_count="single", |
| file_types=[".pdf", ".docx", ".txt"], |
| type="filepath" |
| ) |
|
|
| analyze_btn = gr.Button("π Analyze") |
| analysis_output = gr.Textbox(label="Analysis of Missing Items", lines=14) |
| resume_output = gr.Textbox(label="ATS-Optimized Resume", lines=18) |
| cover_letter_output = gr.Textbox(label="Tailored Cover Letter", lines=16) |
|
|
| analyze_btn.click( |
| fn=analyze_resume, |
| inputs=[job_desc, resume_file], |
| outputs=[analysis_output, resume_output, cover_letter_output] |
| ) |
|
|
| |
| if __name__ == "__main__": |
| demo.launch() |
|
|