Download app.py from Chand11/Job2: direct link, hf CLI and curl.
- Browser
- Download file 5.5 kB
-
https://huggingface.co/spaces/Chand11/Job2/resolve/main/app.py
- Command line
-
hf download hf://spaces/Chand11/Job2/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Chand11/Job2/resolve/main/app.py
5.5 kB
| import os | |
| from io import BytesIO | |
| import gradio as gr | |
| import fitz # PyMuPDF | |
| import docx # python-docx | |
| import google.generativeai as genai | |
| # --- Configure Google Gemini API key from Space Secret --- | |
| GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "").strip() | |
| if GOOGLE_API_KEY: | |
| genai.configure(api_key=GOOGLE_API_KEY) | |
| # Create the model lazily so we can show a friendly error if the key is missing | |
| def get_model(): | |
| if not GOOGLE_API_KEY: | |
| raise RuntimeError( | |
| "Missing GOOGLE_API_KEY secret. Go to Settings β Variables and secrets and add it." | |
| ) | |
| # List available models and print them | |
| available_models = genai.list_models() | |
| print(available_models) # List the models to check what's available | |
| # Use a valid model if gemini-1.5-flash is not available | |
| return genai.GenerativeModel("gemini-2.5-flash") # Or another valid model from available models | |
| # --- Helpers to read resume files --- | |
| 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"): | |
| # python-docx can open a path directly | |
| 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}") | |
| # --- Main analysis function --- | |
| 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.", "", "") | |
| # ---------- Gemini prompts (your Colab logic, adapted) ---------- | |
| try: | |
| # 1) Analysis of missing items | |
| 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() | |
| # 2) ATS-optimized resume | |
| 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() | |
| # 3) Tailored cover letter | |
| 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}", "", "") | |
| # --- Gradio UI --- | |
| 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" # returns a temp file path (string) | |
| ) | |
| 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] | |
| ) | |
| # For Spaces | |
| if __name__ == "__main__": | |
| demo.launch() | |