File size: 5,500 Bytes
aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 0ed08ae aac9af8 a9bcebe aac9af8 a9bcebe 0ed08ae aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 3611b81 aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 a9bcebe aac9af8 0ed08ae aac9af8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | 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()
|