Job2 / app.py
Chand11's picture
Update app.py
3611b81 verified
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()