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()