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