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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +170 -520
app.py CHANGED
@@ -1,521 +1,171 @@
1
- import json
2
- import PyPDF2
3
- import docx
4
- import streamlit as st
5
- import pandas as pd
6
- import plotly.express as px
7
- from io import BytesIO
8
- from reportlab.lib import colors
9
- from reportlab.lib.pagesizes import letter
10
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
11
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
12
-
13
- import google.generativeai as genai # Replaced openai import
14
-
15
- # Utility Functions
16
- def read_pdf(file):
17
- pdf_reader = PyPDF2.PdfReader(file)
18
- text = ""
19
- for page in pdf_reader.pages:
20
- text += page.extract_text()
21
- return text
22
-
23
-
24
- def read_docx(file):
25
- doc = docx.Document(file)
26
- text = ""
27
- for paragraph in doc.paragraphs:
28
- text += paragraph.text + "\n"
29
- return text
30
-
31
-
32
- def load_resume(uploaded_file):
33
- if uploaded_file.name.endswith('.pdf'):
34
- return read_pdf(uploaded_file)
35
- elif uploaded_file.name.endswith('.docx'):
36
- return read_docx(uploaded_file)
37
- else:
38
- st.error("Unsupported file format")
39
- return None
40
-
41
- def generate_updated_resume(resume_text, match_analysis):
42
- buffer = BytesIO()
43
- doc = SimpleDocTemplate(buffer, pagesize=letter,
44
- rightMargin=40, leftMargin=40,
45
- topMargin=60, bottomMargin=40)
46
- styles = getSampleStyleSheet()
47
-
48
- # Custom styles
49
- header_style = styles['Heading1']
50
- header_style.fontSize = 16
51
- header_style.spaceAfter = 18
52
- header_style.textColor = colors.HexColor('#1a1a1a')
53
-
54
- section_header_style = ParagraphStyle(
55
- name='SectionHeader',
56
- parent=styles['Heading2'],
57
- fontSize=13,
58
- spaceAfter=12,
59
- textColor=colors.HexColor('#0d47a1'),
60
- underlineWidth=1,
61
- underlineOffset=-3
62
- )
63
-
64
- normal_style = ParagraphStyle(
65
- name='NormalText',
66
- parent=styles['Normal'],
67
- fontSize=10,
68
- leading=14,
69
- spaceAfter=6,
70
- )
71
-
72
- bullet_style = ParagraphStyle(
73
- name='BulletStyle',
74
- parent=normal_style,
75
- bulletFontName='Helvetica',
76
- bulletFontSize=8,
77
- bulletIndent=10,
78
- leftIndent=20
79
- )
80
-
81
- recommendation_style = ParagraphStyle(
82
- name='RecommendationStyle',
83
- parent=styles['Normal'],
84
- fontSize=9,
85
- textColor=colors.HexColor('#00695c'),
86
- leftIndent=25,
87
- spaceAfter=4
88
- )
89
-
90
- content = []
91
- content.append(Paragraph("Updated Resume", header_style))
92
- content.append(Spacer(1, 12))
93
-
94
- # Resume Content Parsing
95
- resume_parts = resume_text.split("\n")
96
- current_section = ""
97
- bullets = []
98
-
99
- def flush_bullets():
100
- for bullet in bullets:
101
- content.append(Paragraph(f"• {bullet.strip()}", bullet_style))
102
- bullets.clear()
103
-
104
- common_sections = ['EXPERIENCE', 'EDUCATION', 'SKILLS', 'PROJECTS', 'CERTIFICATIONS', 'SUMMARY', 'OBJECTIVE']
105
-
106
- for line in resume_parts:
107
- line = line.strip()
108
- if not line:
109
- continue
110
-
111
- is_section = line.isupper() or any(section in line.upper() for section in common_sections)
112
-
113
- if is_section:
114
- flush_bullets()
115
- current_section = line
116
- content.append(Spacer(1, 12))
117
- content.append(Paragraph(current_section, section_header_style))
118
  else:
119
- bullets.append(line)
120
-
121
- flush_bullets()
122
-
123
- # ATS Recommendations
124
- if match_analysis.get('ats_optimization_suggestions'):
125
- content.append(Spacer(1, 20))
126
- content.append(Paragraph("ATS Optimization Recommendations", section_header_style))
127
- content.append(Spacer(1, 10))
128
-
129
- for suggestion in match_analysis['ats_optimization_suggestions']:
130
- section = suggestion.get('section', '')
131
- current = suggestion.get('current_content', '')
132
- suggested = suggestion.get('suggested_change', '')
133
- keywords = ', '.join(suggestion.get('keywords_to_add', []))
134
- formatting = suggestion.get('formatting_suggestion', '')
135
- reason = suggestion.get('reason', '')
136
- content.append(Paragraph(f" Section: {section}", recommendation_style))
137
- if current:
138
- content.append(Paragraph(f" Current: {current}", recommendation_style))
139
- content.append(Paragraph(f" Suggestion: {suggested}", recommendation_style))
140
- if keywords:
141
- content.append(Paragraph(f" Keywords to Add: {keywords}", recommendation_style))
142
- if formatting:
143
- content.append(Paragraph(f" Formatting: {formatting}", recommendation_style))
144
- if reason:
145
- content.append(Paragraph(f" Reason: {reason}", recommendation_style))
146
- content.append(Spacer(1, 6))
147
-
148
- doc.build(content)
149
- buffer.seek(0)
150
- return buffer
151
-
152
- def generate_updated_resume1(resume_text, match_analysis):
153
- buffer = BytesIO()
154
- doc = SimpleDocTemplate(buffer, pagesize=letter)
155
- styles = getSampleStyleSheet()
156
-
157
- # Modify existing styles
158
- styles['Heading1'].fontSize = 14
159
- styles['Heading1'].spaceAfter = 16
160
- styles['Heading1'].textColor = colors.HexColor('#2c3e50')
161
- styles['Heading2'].fontSize = 12
162
- styles['Heading2'].spaceAfter = 12
163
- styles['Heading2'].textColor = colors.HexColor('#34495e')
164
- styles['Normal'].fontSize = 10
165
- styles['Normal'].spaceAfter = 8
166
- styles['Normal'].leading = 14
167
-
168
- # Add a custom style for recommendations
169
- styles.add(ParagraphStyle(
170
- name='RecommendationStyle',
171
- parent=styles['Normal'],
172
- fontSize=10,
173
- spaceAfter=8,
174
- leading=14,
175
- leftIndent=20,
176
- textColor=colors.HexColor('#2980b9')
177
- ))
178
-
179
- # Create content
180
- content = []
181
-
182
- # Add header
183
- content.append(Paragraph("Updated Resume", styles['Heading1']))
184
- content.append(Spacer(1, 12))
185
-
186
- # Add existing resume content with proper formatting
187
- resume_parts = resume_text.split("\n")
188
- current_section = None
189
- for part in resume_parts:
190
- if part.strip(): # Skip empty lines
191
- # Detect section headers (uppercase or common section names)
192
- common_sections = ['EXPERIENCE', 'EDUCATION', 'SKILLS', 'PROJECTS', 'CERTIFICATIONS']
193
- is_section = part.isupper() or any(section in part.upper() for section in common_sections)
194
-
195
- if is_section:
196
- current_section = part
197
- content.append(Paragraph(part, styles['Heading2']))
198
- else:
199
- content.append(Paragraph(part, styles['Normal']))
200
- content.append(Spacer(1, 6))
201
-
202
- # Add ATS optimization recommendations
203
- if match_analysis.get('ats_optimization_suggestions'):
204
- content.append(Spacer(1, 12))
205
- content.append(Paragraph("ATS Optimization Recommendations", styles['Heading2']))
206
- content.append(Spacer(1, 8))
207
-
208
- for suggestion in match_analysis['ats_optimization_suggestions']:
209
- content.append(Paragraph(f" Section: {suggestion['section']}", styles['RecommendationStyle']))
210
- if suggestion.get('current_content'):
211
- content.append(Paragraph(f" Current: {suggestion['current_content']}", styles['RecommendationStyle']))
212
- content.append(Paragraph(f" Suggestion: {suggestion['suggested_change']}", styles['RecommendationStyle']))
213
- if suggestion.get('keywords_to_add'):
214
- content.append(Paragraph(f" Keywords to Add: {', '.join(suggestion['keywords_to_add'])}", styles['RecommendationStyle']))
215
- if suggestion.get('formatting_suggestion'):
216
- content.append(
217
- Paragraph(f" Formatting: {suggestion['formatting_suggestion']}", styles['RecommendationStyle']))
218
- content.append(Spacer(1, 6))
219
-
220
- # Build PDF
221
- doc.build(content)
222
- buffer.seek(0)
223
- return buffer
224
-
225
-
226
- class JobAnalyzer:
227
- def __init__(self, api_key: str):
228
- # Configure Google Generative AI
229
- genai.configure(api_key=api_key)
230
- self.model = genai.GenerativeModel("gemini-1.5-flash") # You can choose a different model
231
-
232
- def analyze_job(self, job_description: str) -> dict:
233
- prompt = """
234
- Analyze this job description and provide a detailed JSON with:
235
- 1. Key technical skills required
236
- 2. Soft skills required
237
- 3. Years of experience required
238
- 4. Education requirements
239
- 5. Key responsibilities
240
- 6. Company culture indicators
241
- 7. Required certifications
242
- 8. Industry type
243
- 9. Job level (entry, mid, senior)
244
- 10. Key technologies mentioned
245
-
246
- Format the response as a JSON object with these categories.
247
- Job Description: {description}
248
- """
249
- try:
250
- response = self.model.generate_content(prompt.format(description=job_description))
251
- # Assuming the response text is a valid JSON string
252
- parsed_response = json.loads(response.text)
253
- return parsed_response
254
- except Exception as e:
255
- st.error(f"Error analyzing job description: {str(e)}")
256
- return {}
257
-
258
- def analyze_resume(self, resume_text: str) -> dict:
259
- prompt = """
260
- Analyze this resume and provide a detailed JSON with:
261
- 1. Technical skills
262
- 2. Soft skills
263
- 3. Years of experience
264
- 4. Education details
265
- 5. Key achievements
266
- 6. Core competencies
267
- 7. Industry experience
268
- 8. Leadership experience
269
- 9. Technologies used
270
- 10. Projects completed
271
-
272
- Format the response as a JSON object with these categories.
273
- Resume: {resume}
274
- """
275
- try:
276
- response = self.model.generate_content(prompt.format(resume=resume_text))
277
- # Assuming the response text is a valid JSON string
278
- parsed_response = json.loads(response.text)
279
- return parsed_response
280
- except json.JSONDecodeError as e:
281
- st.error(
282
- f"Error parsing resume analysis response: {str(e)}. Please check the resume text for any formatting issues.")
283
- return {}
284
- except Exception as e:
285
- st.error(f"Error analyzing resume: {str(e)}")
286
- return {}
287
-
288
- def analyze_match(self, job_analysis: dict, resume_analysis: dict) -> dict:
289
- prompt = """You are a professional resume analyzer. Compare the provided job requirements and resume to generate a detailed analysis in valid JSON format. IMPORTANT: Respond ONLY with a valid JSON object and NO additional text or formatting.
290
- Job Requirements: {job}
291
- Resume Details: {resume}
292
-
293
- Generate a response following this EXACT structure:
294
- {{
295
- "overall_match_percentage":"85%",
296
- "matching_skills":[{{"skill_name":"Python","is_match":true}},{{"skill_name":"AWS","is_match":true}}],
297
- "missing_skills":[{{"skill_name":"Docker","is_match":false,"suggestion":"Consider obtaining Docker certification"}}],
298
- "skills_gap_analysis":{{"technical_skills":"Specific technical gap analysis","soft_skills":"Specific soft skills gap analysis"}},
299
- "experience_match_analysis":"Detailed experience match analysis",
300
- "education_match_analysis":"Detailed education match analysis",
301
- "recommendations_for_improvement":[{{"recommendation":"Add metrics","section":"Experience","guidance":"Quantify achievements with specific numbers"}}],
302
- "ats_optimization_suggestions":[{{"section":"Skills","current_content":"Current format","suggested_change":"Specific change needed","keywords_to_add":["keyword1","keyword2"],"formatting_suggestion":"Specific format change","reason":"Detailed reason"}}],
303
- "key_strengths":"Specific key strengths",
304
- "areas_of_improvement":"Specific areas to improve"
305
- }}
306
-
307
- Focus on providing detailed, actionable insights for each field. Keep the JSON structure exact but replace the example content with detailed analysis based on the provided job and resume."""
308
- try:
309
- response = self.model.generate_content(
310
- prompt.format(
311
- job=json.dumps(job_analysis, indent=2),
312
- resume=json.dumps(resume_analysis, indent=2)
313
- )
314
- )
315
- try:
316
- # Clean up the response content
317
- response_content = response.text.strip()
318
- # Remove any leading/trailing whitespace or quotes
319
- response_content = response_content.strip('"\'')
320
- # Parse the JSON
321
- parsed_response = json.loads(response_content)
322
- return parsed_response
323
- except json.JSONDecodeError as e:
324
- st.error(f"Error parsing match analysis response. Please try again.")
325
- print(f"Debug - Response content: {response.text}")
326
- print(f"Debug - Error details: {str(e)}")
327
- return {}
328
- return parsed_response
329
- except Exception as e:
330
- st.error(f"Error analyzing match: {str(e)}")
331
- return {}
332
-
333
-
334
- class CoverLetterGenerator:
335
- def __init__(self, api_key: str):
336
- # Configure Google Generative AI
337
- genai.configure(api_key=api_key)
338
- self.model = genai.GenerativeModel("gemini-1.5-flash") # You can choose a different model
339
-
340
- def generate_cover_letter(self, job_analysis: dict, resume_analysis: dict, match_analysis: dict,
341
- tone: str = "professional") -> str:
342
- prompt = """
343
- Generate a compelling cover letter using this information:
344
- Job Details: {job}
345
- Candidate Details: {resume}
346
- Match Analysis: {match}
347
- Tone: {tone}
348
-
349
- Requirements:
350
- 1. Make it personal and specific
351
- 2. Highlight the strongest matches
352
- 3. Address potential gaps professionally
353
- 4. Keep it concise but impactful
354
- 5. Use the specified tone: {tone}
355
- 6. Include specific examples from the resume
356
- 7. Make it ATS-friendly
357
- 8. Add a strong call to action
358
- """
359
- try:
360
- response = self.model.generate_content(
361
- prompt.format(
362
- job=json.dumps(job_analysis, indent=2),
363
- resume=json.dumps(resume_analysis, indent=2),
364
- match=json.dumps(match_analysis, indent=2),
365
- tone=tone
366
- )
367
- )
368
- return response.text
369
- except Exception as e:
370
- st.error(f"Error generating cover letter: {str(e)}")
371
- return ""
372
-
373
-
374
- def main():
375
- st.set_page_config(page_title="LinkedIn Job Application Assistant - HireReady 📝", layout="wide")
376
-
377
- # API key input
378
- api_key = st.sidebar.text_input("Enter Google AI Studio API Key 🗝️", type="password") # Changed label
379
- if not api_key:
380
- st.warning("🔑 Please enter your Google AI Studio API key to continue.")
381
- return
382
-
383
- st.title("LinkedIn Job Application Assistant - HireReady 🚀")
384
- st.markdown("""
385
- Optimize your job application by analyzing job requirements 📋, matching your resume 📜, and generating tailored cover letters 💌.
386
- """)
387
-
388
- # Initialize analyzers
389
- # Pass the API key during initialization
390
- job_analyzer = JobAnalyzer(api_key)
391
- cover_letter_gen = CoverLetterGenerator(api_key)
392
-
393
- # File Upload Section
394
- col1, col2 = st.columns(2)
395
- with col1:
396
- st.subheader("Job Description 📋")
397
- job_desc = st.text_area("Paste the job description here", height=300)
398
- with col2:
399
- st.subheader("Your Resume 📜")
400
- resume_file = st.file_uploader("Upload your resume", type=['pdf', 'docx'])
401
-
402
- if job_desc and resume_file:
403
- with st.spinner("🔍 Analyzing your application..."):
404
- # Load and analyze resume
405
- resume_text = load_resume(resume_file)
406
- if resume_text:
407
- # Perform analysis
408
- job_analysis = job_analyzer.analyze_job(job_desc)
409
- resume_analysis = job_analyzer.analyze_resume(resume_text)
410
- match_analysis = job_analyzer.analyze_match(job_analysis, resume_analysis)
411
-
412
- if not job_analysis or not resume_analysis or not match_analysis:
413
- st.error("Insufficient data returned from the API. Please try again.")
414
- return
415
-
416
- # Display Results
417
- st.header("Analysis Results 📊")
418
-
419
- # Match Overview
420
- col1, col2, col3 = st.columns(3)
421
- with col1:
422
- st.metric(
423
- "Overall Match 🎯",
424
- f"{match_analysis.get('overall_match_percentage', '0%')}"
425
- )
426
- with col2:
427
- st.metric(
428
- "Skills Match 🧠",
429
- f"{len(match_analysis.get('matching_skills', []))} skills"
430
- )
431
- with col3:
432
- st.metric(
433
- "Skills to Develop 📈",
434
- f"{len(match_analysis.get('missing_skills', []))} skills"
435
- )
436
-
437
- # Detailed Analysis Tabs
438
- tab1, tab2, tab3, tab4, tab5 = st.tabs([
439
- "Skills Analysis 📊", "Experience Match 🗂️", "Recommendations 💡", "Cover Letter 💌", "Updated Resume 📝"
440
- ])
441
-
442
- with tab1:
443
- st.subheader("Matching Skills")
444
- for skill in match_analysis.get('matching_skills', []):
445
- st.success(f"✅ {skill['skill_name']}")
446
-
447
- st.subheader("Missing Skills")
448
- for skill in match_analysis.get('missing_skills', []):
449
- st.warning(f"⚠️ {skill['skill_name']}")
450
- st.info(f"Suggestion: {skill['suggestion']}")
451
-
452
- # Skills analysis graph
453
- matching_skills_count = len(match_analysis.get('matching_skills', []))
454
- missing_skills_count = len(match_analysis.get('missing_skills', []))
455
- skills_data = pd.DataFrame({
456
- 'Status': ['Matching', 'Missing'],
457
- 'Count': [matching_skills_count, missing_skills_count]
458
- })
459
- fig = px.bar(skills_data, x='Status', y='Count', color='Status',
460
- color_discrete_sequence=['#5cb85c', '#d9534f'], title='Skills Analysis')
461
- fig.update_layout(xaxis_title='Status', yaxis_title='Count')
462
- st.plotly_chart(fig)
463
-
464
- with tab2:
465
- st.write("### Experience Match Analysis 🗂️")
466
- st.write(match_analysis.get('experience_match_analysis', ''))
467
- st.write("### Education Match Analysis 🎓")
468
- st.write(match_analysis.get('education_match_analysis', ''))
469
-
470
- with tab3:
471
- st.write("### Key Recommendations 🔑")
472
- for rec in match_analysis.get('recommendations_for_improvement', []):
473
- st.info(f"**{rec['recommendation']}**")
474
- st.write(f"**Section:** {rec['section']}")
475
- st.write(f"**Guidance:** {rec['guidance']}")
476
-
477
- st.write("### ATS Optimization Suggestions 🤖")
478
- for suggestion in match_analysis.get('ats_optimization_suggestions', []):
479
- st.write("---")
480
- st.warning(f"**Section to Modify:** {suggestion['section']}")
481
- if suggestion.get('current_content'):
482
- st.write(f"**Current Content:** {suggestion['current_content']}")
483
- st.write(f"**Suggested Change:** {suggestion['suggested_change']}")
484
- if suggestion.get('keywords_to_add'):
485
- st.write(f"**Keywords to Add:** {', '.join(suggestion['keywords_to_add'])}")
486
- if suggestion.get('formatting_suggestion'):
487
- st.write(f"**Formatting Changes:** {suggestion['formatting_suggestion']}")
488
- if suggestion.get('reason'):
489
- st.info(f"**Reason for Change:** {suggestion['reason']}")
490
-
491
- with tab4:
492
- st.write("### Cover Letter Generator 🖊️")
493
- tone = st.selectbox("Select tone 🎭", ["Professional 👔", "Enthusiastic 😃", "Confident 😎", "Friendly 👋"])
494
-
495
- if st.button("Generate Cover Letter ✍️"):
496
- with st.spinner("✍️ Crafting your cover letter..."):
497
- cover_letter = cover_letter_gen.generate_cover_letter(
498
- job_analysis, resume_analysis, match_analysis, tone.lower().split()[0])
499
- st.markdown("### Your Custom Cover Letter 💌")
500
- st.text_area("", cover_letter, height=400)
501
- st.download_button(
502
- "Download Cover Letter 📥",
503
- cover_letter,
504
- "cover_letter.txt",
505
- "text/plain"
506
- )
507
-
508
- with tab5:
509
- st.write("### Updated Resume 📝")
510
- updated_resume = generate_updated_resume(resume_text, match_analysis)
511
- # Provide a download button for the updated resume
512
- st.download_button(
513
- "Download Updated Resume 📥",
514
- updated_resume,
515
- "updated_resume.pdf",
516
- mime="application/pdf"
517
- )
518
-
519
-
520
- if __name__ == "__main__":
521
- main()
 
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()