Chand11 commited on
Commit
aac9af8
Β·
verified Β·
1 Parent(s): a9bcebe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -147
app.py CHANGED
@@ -1,171 +1,162 @@
 
 
1
  import gradio as gr
2
- import io
 
3
  import google.generativeai as genai
4
- import fitz # Import PyMuPDF
5
- from google.colab import userdata
6
-
7
- # Access your API key from the Secrets Manager
8
- # In a Hugging Face Space, you would typically set this as a Space Secret
9
- # For local testing, you can keep this, but remember to remove it before deploying
10
- # or use the Hugging Face Secrets management.
11
- try:
12
- GOOGLE_API_KEY = userdata.get('GOOGLE_API_KEY')
13
  genai.configure(api_key=GOOGLE_API_KEY)
14
- except Exception as e:
15
- print(f"Could not retrieve API key from Colab userdata. Make sure 'GOOGLE_API_KEY' is set in Colab secrets. Error: {e}")
16
- print("For Hugging Face Spaces deployment, set this as a Space Secret.")
17
- # You might want to handle this more robustly in a production app
18
-
19
-
20
- def analyze_resume_gradio(job_description, resume_file):
21
- """
22
- Analyzes a resume against a job description using Google Generative AI.
23
-
24
- Args:
25
- job_description (str): The text of the job description.
26
- resume_file (gr.File): The uploaded resume file object from Gradio.
27
-
28
- Returns:
29
- tuple: A tuple containing three strings:
30
- - Analysis of missing items.
31
- - ATS-optimized resume text.
32
- - Tailored cover letter text.
33
- """
34
- print("--- analyze_resume_gradio function started ---")
35
-
36
- analysis_text = ""
37
- resume_output_text = ""
38
- cover_letter_text = ""
39
-
40
- if not job_description:
41
- return "Please provide a job description.", "", ""
42
- if not resume_file:
43
- return "Please upload a resume file.", "", ""
44
-
45
- resume_text = ""
46
- try:
47
- # Gradio's File component provides the file path in the 'name' attribute
48
- file_path = resume_file.name
49
- print(f"Processing file: {file_path}")
50
-
51
- # Determine file type based on extension or mime type (Gradio might provide mime_type)
52
- # For simplicity, let's infer from extension for now
53
- if file_path.lower().endswith('.pdf'):
54
- print("Attempting to read PDF file.")
55
- # Read PDF content from the file path provided by Gradio
56
- pdf_document = fitz.open(file_path)
57
- for page_num in range(pdf_document.page_count):
58
- page = pdf_document.load_page(page_num)
59
- resume_text += page.get_text()
60
- pdf_document.close()
61
- print("Successfully read text from PDF.")
62
-
63
- elif file_path.lower().endswith(('.txt', '.doc', '.docx')):
64
- # For .txt, .doc, .docx, we'll attempt to read as text.
65
- # For .doc/.docx, a more robust solution might need libraries like python-docx or textract
66
- # but for a basic example, reading as text might work for some cases.
67
- print("Attempting to read text/doc/docx file.")
68
- with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
69
- resume_text = f.read()
70
- print("File read successfully as text.")
71
- else:
72
- print(f"Unsupported file type for Gradio: {file_path}")
73
- return f"Unsupported file type: {file_path}. Please upload a PDF, .txt, .doc or .docx file.", "", ""
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- if not resume_text:
77
- return "Could not extract text from the uploaded file.", "", ""
 
 
78
 
79
- # print(f"\nResume Content (partial):")
80
- # print(resume_text[:500] + "...")
 
 
 
 
81
 
82
- # Use the generative model to analyze the resume
83
- # Ensure API key is configured. If not, the genai.GenerativeModel call might fail.
84
- if 'genai' not in globals() or genai.api_key is None:
85
- return "Google API Key is not configured. Please set it up.", "", ""
86
 
 
 
87
 
88
- model = genai.GenerativeModel('gemini-1.5-flash-latest') # Use an appropriate model
89
- print("Generative model initialized.")
 
 
90
 
91
- # Prompt for analysis of missing items
92
- analysis_prompt = f"""Analyze the following resume based on the provided job description.
93
- Identify any missing keywords, skills, or experience mentioned in the job description that are not present in the resume.
94
 
95
- Job Description:
96
- {job_description}
 
 
 
 
 
97
 
98
- Resume:
99
- {resume_text}
100
 
101
- Provide a clear list of what is missing from the resume compared to the job description.
102
- """
103
- print("Sending analysis prompt to model.")
104
- analysis_response = model.generate_content(analysis_prompt)
105
- analysis_text = analysis_response.text
106
- print("Analysis response received.")
107
 
108
- # Prompt to generate an ATS-optimized resume
109
- resume_prompt = f"""Based on the following original resume, job description, and the analysis of missing items,
110
- generate a new ATS-optimized resume. Focus on incorporating the missing keywords and skills in a natural way.
 
 
111
 
112
- Original Resume:
113
- {resume_text}
114
 
115
- Job Description:
116
- {job_description}
117
 
118
- Missing Items Analysis:
119
- {analysis_text}
120
 
121
- Generate the new ATS-optimized resume:
122
- """
123
- print("Sending resume prompt to model.")
124
- resume_response = model.generate_content(resume_prompt)
125
- resume_output_text = resume_response.text
126
- print("Resume response received.")
127
 
128
- # Prompt to generate a cover letter
129
- cover_letter_prompt = f"""Based on the following job description and the generated ATS-optimized resume,
130
- write a tailored cover letter. Highlight how the candidate's skills and experience match the job requirements.
 
131
 
132
- Job Description:
133
- {job_description}
134
 
135
- ATS-Optimized Resume:
136
- {resume_output_text}
 
 
 
137
 
138
- Write the cover letter:
139
- """
140
- print("Sending cover letter prompt to model.")
141
- cover_letter_response = model.generate_content(cover_letter_prompt)
142
- cover_letter_text = cover_letter_response.text
143
- print("Cover letter response received.")
144
 
145
  except Exception as e:
146
- print(f"\nAn error occurred during file processing or analysis: {e}")
147
- return f"An error occurred: {e}", "", ""
148
-
149
- return analysis_text, resume_output_text, cover_letter_text
150
-
151
-
152
- # Create the Gradio interface
153
- iface = gr.Interface(
154
- fn=analyze_resume_gradio,
155
- inputs=[
156
- gr.Textbox(lines=10, label="Job Description"),
157
- gr.File(label="Upload Resume (PDF, TXT, DOC, DOCX)") # Allow multiple file types
158
- ],
159
- outputs=[
160
- gr.Textbox(label="Analysis of Missing Items"),
161
- gr.Textbox(label="ATS-Optimized Resume"),
162
- gr.Textbox(label="Tailored Cover Letter")
163
- ],
164
- title="Resume Analyzer",
165
- description="Upload your resume and paste a job description to get an analysis, an ATS-optimized resume, and a cover letter."
166
- )
167
-
168
- # Launch the Gradio app
169
- # In a Hugging Face Space, this will be handled automatically.
170
- # For local testing, you can use iface.launch()
171
- # iface.launch()
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from io import BytesIO
3
  import gradio as gr
4
+ import fitz # PyMuPDF
5
+ import docx # python-docx
6
  import google.generativeai as genai
7
+
8
+ # --- Configure Google Gemini API key from Space Secret ---
9
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "").strip()
10
+ if GOOGLE_API_KEY:
 
 
 
 
 
11
  genai.configure(api_key=GOOGLE_API_KEY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # Create the model lazily so we can show a friendly error if the key is missing
14
+ def get_model():
15
+ if not GOOGLE_API_KEY:
16
+ raise RuntimeError(
17
+ "Missing GOOGLE_API_KEY secret. Go to Settings β†’ Variables and secrets and add it."
18
+ )
19
+ # Use a fast model. You can switch to "gemini-1.5-pro" if you prefer.
20
+ return genai.GenerativeModel("gemini-1.5-flash")
21
+
22
+ # --- Helpers to read resume files ---
23
+ def extract_text_from_resume(file_path: str) -> str:
24
+ if not file_path:
25
+ return ""
26
+
27
+ file_path_lower = file_path.lower()
28
+ try:
29
+ if file_path_lower.endswith(".pdf"):
30
+ text = ""
31
+ with open(file_path, "rb") as f:
32
+ pdf_bytes = f.read()
33
+ doc = fitz.open(stream=pdf_bytes, filetype="pdf")
34
+ for page in doc:
35
+ text += page.get_text() or ""
36
+ doc.close()
37
+ return text
38
+
39
+ elif file_path_lower.endswith(".docx"):
40
+ # python-docx can open a path directly
41
+ d = docx.Document(file_path)
42
+ return "\n".join(p.text for p in d.paragraphs)
43
+
44
+ elif file_path_lower.endswith(".txt"):
45
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
46
+ return f.read()
47
 
48
+ else:
49
+ raise ValueError("Unsupported file type. Please upload .pdf, .docx, or .txt.")
50
+ except Exception as e:
51
+ raise RuntimeError(f"Failed to read resume: {e}")
52
 
53
+ # --- Main analysis function ---
54
+ def analyze_resume(job_description: str, resume_file_path: str):
55
+ try:
56
+ model = get_model()
57
+ except Exception as e:
58
+ return (f"❌ {e}", "", "")
59
 
60
+ if not job_description.strip():
61
+ return ("❌ Please paste a job description.", "", "")
 
 
62
 
63
+ if not resume_file_path:
64
+ return ("❌ Please upload a resume file.", "", "")
65
 
66
+ try:
67
+ resume_text = extract_text_from_resume(resume_file_path)
68
+ except Exception as e:
69
+ return (f"⚠️ {e}", "", "")
70
 
71
+ if not resume_text.strip():
72
+ return ("⚠️ Could not extract text from the resume.", "", "")
 
73
 
74
+ # ---------- Gemini prompts (your Colab logic, adapted) ----------
75
+ try:
76
+ # 1) Analysis of missing items
77
+ analysis_prompt = f"""
78
+ Analyze the following resume based on the provided job description.
79
+ Identify missing keywords, skills, or experience mentioned in the job description
80
+ that are not present in the resume. Return a clear, readable list.
81
 
82
+ Job Description:
83
+ {job_description}
84
 
85
+ Resume:
86
+ {resume_text}
87
+ """
88
+ analysis_resp = model.generate_content(analysis_prompt)
89
+ analysis_text = (getattr(analysis_resp, "text", "") or "").strip()
 
90
 
91
+ # 2) ATS-optimized resume
92
+ resume_prompt = f"""
93
+ Based on the original resume, job description, and the missing items analysis,
94
+ rewrite the resume to be ATS-optimized. Incorporate missing keywords/skills naturally.
95
+ Use concise bullet points and clear section headings.
96
 
97
+ Original Resume:
98
+ {resume_text}
99
 
100
+ Job Description:
101
+ {job_description}
102
 
103
+ Missing Items Analysis:
104
+ {analysis_text}
105
 
106
+ Generate the new ATS-optimized resume:
107
+ """
108
+ resume_resp = model.generate_content(resume_prompt)
109
+ optimized_resume = (getattr(resume_resp, "text", "") or "").strip()
 
 
110
 
111
+ # 3) Tailored cover letter
112
+ cover_letter_prompt = f"""
113
+ Write a tailored cover letter that aligns the candidate's experience to the job.
114
+ Keep it 200–350 words, professional, specific, and with a strong call to action.
115
 
116
+ Job Description:
117
+ {job_description}
118
 
119
+ ATS-Optimized Resume:
120
+ {optimized_resume}
121
+ """
122
+ cover_letter_resp = model.generate_content(cover_letter_prompt)
123
+ cover_letter_text = (getattr(cover_letter_resp, "text", "") or "").strip()
124
 
125
+ return (analysis_text, optimized_resume, cover_letter_text)
 
 
 
 
 
126
 
127
  except Exception as e:
128
+ return (f"⚠️ Error analyzing with Gemini: {e}", "", "")
129
+
130
+ # --- Gradio UI ---
131
+ with gr.Blocks() as demo:
132
+ gr.Markdown("## πŸ“Š HireReady β€” Resume & Job Match Analyzer (Google Gemini)")
133
+
134
+ with gr.Row():
135
+ job_desc = gr.Textbox(
136
+ label="Job Description",
137
+ placeholder="Paste the job description here...",
138
+ lines=10
139
+ )
140
+
141
+ with gr.Row():
142
+ resume_file = gr.File(
143
+ label="Upload Resume (.pdf, .docx, .txt)",
144
+ file_count="single",
145
+ file_types=[".pdf", ".docx", ".txt"],
146
+ type="filepath" # returns a temp file path (string)
147
+ )
148
+
149
+ analyze_btn = gr.Button("πŸ” Analyze")
150
+ analysis_output = gr.Textbox(label="Analysis of Missing Items", lines=14)
151
+ resume_output = gr.Textbox(label="ATS-Optimized Resume", lines=18)
152
+ cover_letter_output = gr.Textbox(label="Tailored Cover Letter", lines=16)
153
+
154
+ analyze_btn.click(
155
+ fn=analyze_resume,
156
+ inputs=[job_desc, resume_file],
157
+ outputs=[analysis_output, resume_output, cover_letter_output]
158
+ )
159
+
160
+ # For Spaces
161
+ if __name__ == "__main__":
162
+ demo.launch()