AdithyaSNair commited on
Commit
0b0fa7c
·
verified ·
1 Parent(s): 4d52091

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +371 -37
app.py CHANGED
@@ -1,20 +1,30 @@
 
 
1
  import streamlit as st
2
  from streamlit_option_menu import option_menu
3
  from langchain_groq import ChatGroq
4
  from langchain_core.prompts import PromptTemplate
5
- import fitz
6
  import requests
7
  from bs4 import BeautifulSoup
8
  import uuid
 
 
 
 
9
 
 
10
  llm = ChatGroq(
11
  temperature=0,
12
- groq_api_key='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', # Replace with your Groq API key
13
  model_name="llama-3.1-70b-versatile"
14
  )
15
 
16
 
17
  def extract_text_from_pdf(pdf_file):
 
 
 
18
  text = ""
19
  try:
20
  with fitz.open(stream=pdf_file.read(), filetype="pdf") as doc:
@@ -26,11 +36,17 @@ def extract_text_from_pdf(pdf_file):
26
  return ""
27
 
28
  def extract_job_description(job_link):
 
 
 
29
  try:
30
- response = requests.get(job_link)
 
 
 
31
  response.raise_for_status()
32
  soup = BeautifulSoup(response.text, 'html.parser')
33
- # You might need to adjust the selectors based on the website's structure
34
  job_description = soup.get_text(separator='\n')
35
  return job_description.strip()
36
  except Exception as e:
@@ -38,6 +54,9 @@ def extract_job_description(job_link):
38
  return ""
39
 
40
  def extract_requirements(job_description):
 
 
 
41
  prompt_text = f"""
42
  The following is a job description:
43
 
@@ -56,22 +75,28 @@ def extract_requirements(job_description):
56
  return requirements
57
 
58
  def generate_email(job_description, requirements, resume_text):
 
 
 
59
  prompt_text = f"""
60
- Given the following job description:
61
- {job_description}
62
 
63
- And the following extracted requirements:
64
- {requirements}
 
 
 
65
 
66
- And the following resume text:
67
- {resume_text}
68
 
69
- Write a cold email as Adithya S Nair, a recent graduate in Computer Science with a focus on Artificial Intelligence and Machine Learning.
70
- Highlight your relevant skills and experiences from the resume, emphasizing how you, as a fresher, can bring value to the client’s company.
71
- Mention key projects, internships, and any leadership experiences that align with the job description and requirements.
72
- Ensure the email is concise and professional.
 
73
 
74
- Email:
75
  """
76
 
77
  prompt = PromptTemplate.from_template(prompt_text)
@@ -81,6 +106,207 @@ Ensure the email is concise and professional.
81
  email_text = response.content.strip()
82
  return email_text
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  # -------------------------------
85
  # Page Functions
86
  # -------------------------------
@@ -89,7 +315,7 @@ def email_generator_page():
89
  st.header("Automated Email Generator")
90
 
91
  st.write("""
92
- This application generates a personalized email based on a job posting and your resume.
93
  """)
94
 
95
  # Input fields
@@ -128,10 +354,72 @@ def email_generator_page():
128
  if email_text:
129
  st.subheader("Generated Email:")
130
  st.write(email_text)
 
 
 
 
 
 
 
131
  else:
132
  st.error("Failed to generate email.")
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  def resume_analysis_page():
 
 
135
  st.header("Resume Analysis and Optimization")
136
 
137
  uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
@@ -142,14 +430,33 @@ def resume_analysis_page():
142
  st.success("Resume uploaded successfully!")
143
  # Perform analysis
144
  st.subheader("Extracted Information")
145
- # Example: Extracted skills
146
  skills = extract_skills(resume_text)
147
  st.write("**Skills:**", ', '.join(skills))
 
 
 
148
  # Provide optimization suggestions
149
  st.subheader("Optimization Suggestions")
150
- st.write("- **Keyword Optimization:** Consider adding more industry-specific keywords relevant to your desired roles.")
151
  st.write("- **Formatting:** Ensure consistent formatting for headings and bullet points to enhance readability.")
152
  st.write("- **Experience Details:** Provide specific achievements and quantify your accomplishments where possible.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  else:
154
  st.error("Failed to extract text from resume.")
155
 
@@ -165,27 +472,50 @@ def job_recommendations_page():
165
  # Fetch job recommendations
166
  st.subheader("Recommended Jobs")
167
  jobs = get_job_recommendations(resume_text)
168
- for job in jobs:
169
- st.write(f"**{job['title']}** at {job['company']}")
170
- st.markdown(f"[Apply Here]({job['link']})")
 
 
 
171
  else:
172
  st.error("Failed to extract text from resume.")
173
 
174
- # Placeholder for job recommendations - Replace with actual implementation
175
- def get_job_recommendations(resume_text):
176
- # Implement job fetching logic, possibly integrating with job APIs
177
- # This is a placeholder example
178
- return [
179
- {"title": "Data Scientist", "company": "TechCorp", "link": "https://example.com/job1"},
180
- {"title": "Machine Learning Engineer", "company": "InnovateX", "link": "https://example.com/job2"},
181
- ]
182
 
183
- def extract_skills(text):
184
- # Implement advanced skill extraction logic
185
- # For demonstration, using a predefined skill list
186
- skills_list = ["Python", "Machine Learning", "Data Analysis", "SQL", "Communication", "Leadership"]
187
- extracted_skills = [skill for skill in skills_list if skill.lower() in text.lower()]
188
- return extracted_skills
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
  # -------------------------------
191
  # Main App with Sidebar Navigation
@@ -197,18 +527,22 @@ def main():
197
  with st.sidebar:
198
  selected = option_menu(
199
  "Main Menu",
200
- ["Email Generator", "Resume Analysis", "Job Recommendations"],
201
- icons=["envelope", "file-person", "briefcase"],
202
  menu_icon="cast",
203
  default_index=0,
204
  )
205
 
206
  if selected == "Email Generator":
207
  email_generator_page()
 
 
208
  elif selected == "Resume Analysis":
209
  resume_analysis_page()
210
  elif selected == "Job Recommendations":
211
  job_recommendations_page()
 
 
212
 
213
  if __name__ == "__main__":
214
  main()
 
1
+ # app.py
2
+
3
  import streamlit as st
4
  from streamlit_option_menu import option_menu
5
  from langchain_groq import ChatGroq
6
  from langchain_core.prompts import PromptTemplate
7
+ import fitz # PyMuPDF
8
  import requests
9
  from bs4 import BeautifulSoup
10
  import uuid
11
+ import plotly.express as px
12
+ import re
13
+ import pandas as pd
14
+ import json
15
 
16
+ # Initialize the LLM with your Groq API key from Streamlit secrets
17
  llm = ChatGroq(
18
  temperature=0,
19
+ groq_api_key=st.secrets["groq_api_key"],
20
  model_name="llama-3.1-70b-versatile"
21
  )
22
 
23
 
24
  def extract_text_from_pdf(pdf_file):
25
+ """
26
+ Extracts text from an uploaded PDF file.
27
+ """
28
  text = ""
29
  try:
30
  with fitz.open(stream=pdf_file.read(), filetype="pdf") as doc:
 
36
  return ""
37
 
38
  def extract_job_description(job_link):
39
+ """
40
+ Fetches and extracts job description text from a given URL.
41
+ """
42
  try:
43
+ headers = {
44
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
45
+ }
46
+ response = requests.get(job_link, headers=headers)
47
  response.raise_for_status()
48
  soup = BeautifulSoup(response.text, 'html.parser')
49
+ # Adjust selectors based on the website's structure for better extraction
50
  job_description = soup.get_text(separator='\n')
51
  return job_description.strip()
52
  except Exception as e:
 
54
  return ""
55
 
56
  def extract_requirements(job_description):
57
+ """
58
+ Uses Groq to extract job requirements from the job description.
59
+ """
60
  prompt_text = f"""
61
  The following is a job description:
62
 
 
75
  return requirements
76
 
77
  def generate_email(job_description, requirements, resume_text):
78
+ """
79
+ Generates a personalized cold email using Groq based on the job description, requirements, and resume.
80
+ """
81
  prompt_text = f"""
82
+ You are Adithya S Nair, a recent Computer Science graduate specializing in Artificial Intelligence and Machine Learning. Craft a concise and professional cold email to a potential employer based on the following information:
 
83
 
84
+ **Job Description:**
85
+ {job_description}
86
+
87
+ **Extracted Requirements:**
88
+ {requirements}
89
 
90
+ **Your Resume:**
91
+ {resume_text}
92
 
93
+ **Email Requirements:**
94
+ - **Introduction:** Briefly introduce yourself and mention the specific job you are applying for.
95
+ - **Body:** Highlight your relevant skills, projects, internships, and leadership experiences that align with the job requirements.
96
+ - **Value Proposition:** Explain how your fresh perspective and recent academic knowledge can add value to the company.
97
+ - **Closing:** Express enthusiasm for the opportunity, mention your willingness for an interview, and thank the recipient for their time.
98
 
99
+ **Email:**
100
  """
101
 
102
  prompt = PromptTemplate.from_template(prompt_text)
 
106
  email_text = response.content.strip()
107
  return email_text
108
 
109
+ def generate_cover_letter(job_description, requirements, resume_text):
110
+ """
111
+ Generates a personalized cover letter using Groq based on the job description, requirements, and resume.
112
+ """
113
+ prompt_text = f"""
114
+ You are Adithya S Nair, a recent Computer Science graduate specializing in Artificial Intelligence and Machine Learning. Compose a personalized and professional cover letter based on the following information:
115
+
116
+ **Job Description:**
117
+ {job_description}
118
+
119
+ **Extracted Requirements:**
120
+ {requirements}
121
+
122
+ **Your Resume:**
123
+ {resume_text}
124
+
125
+ **Cover Letter Requirements:**
126
+ 1. **Greeting:** Address the hiring manager by name if available; otherwise, use a generic greeting such as "Dear Hiring Manager."
127
+ 2. **Introduction:** Begin with an engaging opening that mentions the specific position you are applying for and conveys your enthusiasm.
128
+ 3. **Body:**
129
+ - **Skills and Experiences:** Highlight relevant technical skills, projects, internships, and leadership roles that align with the job requirements.
130
+ - **Alignment:** Demonstrate how your academic background and hands-on experiences make you a suitable candidate for the role.
131
+ 4. **Value Proposition:** Explain how your fresh perspective, recent academic knowledge, and eagerness to learn can contribute to the company's success.
132
+ 5. **Conclusion:** End with a strong closing statement expressing your interest in an interview, your availability, and gratitude for the hiring manager’s time and consideration.
133
+ 6. **Professional Tone:** Maintain a respectful and professional tone throughout the letter.
134
+
135
+ **Cover Letter:**
136
+ """
137
+
138
+ prompt = PromptTemplate.from_template(prompt_text)
139
+ chain = prompt | llm
140
+ response = chain.invoke({})
141
+
142
+ cover_letter = response.content.strip()
143
+ return cover_letter
144
+
145
+ def extract_skills(text):
146
+ """
147
+ Extracts a list of skills from the resume text using Groq.
148
+ """
149
+ prompt_text = f"""
150
+ Extract a comprehensive list of technical and soft skills from the following resume text. Provide the skills as a comma-separated list.
151
+
152
+ Resume Text:
153
+ {text}
154
+
155
+ Skills:
156
+ """
157
+
158
+ prompt = PromptTemplate.from_template(prompt_text)
159
+ chain = prompt | llm
160
+ response = chain.invoke({})
161
+
162
+ skills = response.content.strip()
163
+ # Clean and split the skills
164
+ skills_list = [skill.strip() for skill in re.split(',|\\n', skills) if skill.strip()]
165
+ return skills_list
166
+
167
+ def suggest_keywords(resume_text, job_description=None):
168
+ """
169
+ Suggests additional relevant keywords to enhance resume compatibility with ATS.
170
+ """
171
+ prompt_text = f"""
172
+ Analyze the following resume text and suggest additional relevant keywords that can enhance its compatibility with Applicant Tracking Systems (ATS). If a job description is provided, tailor the keywords to align with the job requirements.
173
+
174
+ Resume Text:
175
+ {resume_text}
176
+
177
+ Job Description:
178
+ {job_description if job_description else "N/A"}
179
+
180
+ Suggested Keywords:
181
+ """
182
+
183
+ prompt = PromptTemplate.from_template(prompt_text)
184
+ chain = prompt | llm
185
+ response = chain.invoke({})
186
+
187
+ keywords = response.content.strip()
188
+ keywords_list = [keyword.strip() for keyword in re.split(',|\\n', keywords) if keyword.strip()]
189
+ return keywords_list
190
+
191
+ def get_job_recommendations(resume_text, location="India"):
192
+ """
193
+ Fetches job recommendations using the JSearch API based on the user's skills.
194
+ """
195
+ # Extract skills from resume
196
+ skills = extract_skills(resume_text)
197
+ query = " ".join(skills) if skills else "Software Engineer"
198
+
199
+ url = "https://jsearch.p.rapidapi.com/search"
200
+ headers = {
201
+ "X-RapidAPI-Key": st.secrets["rapidapi_key"], # Accessing RapidAPI key securely
202
+ "X-RapidAPI-Host": "jsearch.p.rapidapi.com"
203
+ }
204
+ params = {
205
+ "query": query,
206
+ "page": "1",
207
+ "num_pages": "1",
208
+ "size": "20",
209
+ "remote_filter": "false",
210
+ "location": location,
211
+ "sort": "relevance",
212
+ "salary_min": "0",
213
+ "salary_max": "0",
214
+ "salary_currency": "INR",
215
+ "radius": "0",
216
+ "company_type": "",
217
+ "job_type": "",
218
+ "degree_level": "",
219
+ "career_level": "",
220
+ "include_remote": "false"
221
+ }
222
+
223
+ try:
224
+ response = requests.get(url, headers=headers, params=params)
225
+ response.raise_for_status()
226
+ data = response.json()
227
+ jobs = data.get("data", [])
228
+ job_list = []
229
+ for job in jobs:
230
+ job_info = {
231
+ "title": job.get("job_title"),
232
+ "company": job.get("employer", {}).get("name"),
233
+ "link": job.get("job_apply_link") or job.get("job_listing_url")
234
+ }
235
+ job_list.append(job_info)
236
+ return job_list
237
+ except Exception as e:
238
+ st.error(f"Error fetching job recommendations: {e}")
239
+ return []
240
+
241
+ def create_skill_distribution_chart(skills):
242
+ """
243
+ Creates a bar chart showing the distribution of skills.
244
+ """
245
+ skill_counts = {}
246
+ for skill in skills:
247
+ skill_counts[skill] = skill_counts.get(skill, 0) + 1
248
+ df = pd.DataFrame(list(skill_counts.items()), columns=['Skill', 'Count'])
249
+ fig = px.bar(df, x='Skill', y='Count', title='Skill Distribution')
250
+ return fig
251
+
252
+ def create_experience_timeline(resume_text):
253
+ """
254
+ Creates an experience timeline from the resume text.
255
+ """
256
+ # Extract work experience details using Groq
257
+ prompt_text = f"""
258
+ From the following resume text, extract the job titles, companies, and durations of employment. Provide the information in a table format with columns: Job Title, Company, Duration (in years).
259
+
260
+ Resume Text:
261
+ {resume_text}
262
+
263
+ Table:
264
+ """
265
+
266
+ prompt = PromptTemplate.from_template(prompt_text)
267
+ chain = prompt | llm
268
+ response = chain.invoke({})
269
+
270
+ table_text = response.content.strip()
271
+ # Parse the table_text to create a DataFrame
272
+ data = []
273
+ for line in table_text.split('\n'):
274
+ if line.strip() and not line.lower().startswith("job title"):
275
+ parts = line.split('|')
276
+ if len(parts) == 3:
277
+ job_title = parts[0].strip()
278
+ company = parts[1].strip()
279
+ duration = parts[2].strip()
280
+ # Convert duration to a float representing years
281
+ duration_years = parse_duration(duration)
282
+ data.append({"Job Title": job_title, "Company": company, "Duration (years)": duration_years})
283
+ df = pd.DataFrame(data)
284
+ if not df.empty:
285
+ # Create a cumulative duration for timeline
286
+ df['Start Year'] = df['Duration (years)'].cumsum() - df['Duration (years)']
287
+ df['End Year'] = df['Duration (years)'].cumsum()
288
+ fig = px.timeline(df, x_start="Start Year", x_end="End Year", y="Job Title", color="Company", title="Experience Timeline")
289
+ fig.update_yaxes(categoryorder="total ascending")
290
+ return fig
291
+ else:
292
+ return None
293
+
294
+ def parse_duration(duration_str):
295
+ """
296
+ Parses duration strings like '2 years' or '6 months' into float years.
297
+ """
298
+ try:
299
+ if 'year' in duration_str.lower():
300
+ years = float(re.findall(r'\d+\.?\d*', duration_str)[0])
301
+ return years
302
+ elif 'month' in duration_str.lower():
303
+ months = float(re.findall(r'\d+\.?\d*', duration_str)[0])
304
+ return months / 12
305
+ else:
306
+ return 0
307
+ except:
308
+ return 0
309
+
310
  # -------------------------------
311
  # Page Functions
312
  # -------------------------------
 
315
  st.header("Automated Email Generator")
316
 
317
  st.write("""
318
+ This application generates a personalized cold email based on a job posting and your resume.
319
  """)
320
 
321
  # Input fields
 
354
  if email_text:
355
  st.subheader("Generated Email:")
356
  st.write(email_text)
357
+ # Provide download option
358
+ st.download_button(
359
+ label="Download Email",
360
+ data=email_text,
361
+ file_name="generated_email.txt",
362
+ mime="text/plain"
363
+ )
364
  else:
365
  st.error("Failed to generate email.")
366
 
367
+ def cover_letter_generator_page():
368
+ st.header("Automated Cover Letter Generator")
369
+
370
+ st.write("""
371
+ This application generates a personalized cover letter based on a job posting and your resume.
372
+ """)
373
+
374
+ # Input fields
375
+ job_link = st.text_input("Enter the job link:")
376
+ uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
377
+
378
+ if st.button("Generate Cover Letter"):
379
+ if not job_link:
380
+ st.error("Please enter a job link.")
381
+ return
382
+ if not uploaded_file:
383
+ st.error("Please upload your resume.")
384
+ return
385
+
386
+ with st.spinner("Processing..."):
387
+ # Extract job description
388
+ job_description = extract_job_description(job_link)
389
+ if not job_description:
390
+ st.error("Failed to extract job description.")
391
+ return
392
+
393
+ # Extract requirements
394
+ requirements = extract_requirements(job_description)
395
+ if not requirements:
396
+ st.error("Failed to extract requirements.")
397
+ return
398
+
399
+ # Extract resume text
400
+ resume_text = extract_text_from_pdf(uploaded_file)
401
+ if not resume_text:
402
+ st.error("Failed to extract text from resume.")
403
+ return
404
+
405
+ # Generate cover letter
406
+ cover_letter = generate_cover_letter(job_description, requirements, resume_text)
407
+ if cover_letter:
408
+ st.subheader("Generated Cover Letter:")
409
+ st.write(cover_letter)
410
+ # Provide download option
411
+ st.download_button(
412
+ label="Download Cover Letter",
413
+ data=cover_letter,
414
+ file_name="generated_cover_letter.txt",
415
+ mime="text/plain"
416
+ )
417
+ else:
418
+ st.error("Failed to generate cover letter.")
419
+
420
  def resume_analysis_page():
421
+ import pandas as pd # Importing here to prevent unnecessary imports if not used
422
+
423
  st.header("Resume Analysis and Optimization")
424
 
425
  uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
 
430
  st.success("Resume uploaded successfully!")
431
  # Perform analysis
432
  st.subheader("Extracted Information")
433
+ # Extracted skills
434
  skills = extract_skills(resume_text)
435
  st.write("**Skills:**", ', '.join(skills))
436
+ # Extract keywords
437
+ keywords = suggest_keywords(resume_text)
438
+ st.write("**Suggested Keywords for ATS Optimization:**", ', '.join(keywords))
439
  # Provide optimization suggestions
440
  st.subheader("Optimization Suggestions")
441
+ st.write("- **Keyword Optimization:** Incorporate the suggested keywords to improve ATS compatibility.")
442
  st.write("- **Formatting:** Ensure consistent formatting for headings and bullet points to enhance readability.")
443
  st.write("- **Experience Details:** Provide specific achievements and quantify your accomplishments where possible.")
444
+
445
+ # Visual Resume Analytics
446
+ st.subheader("Visual Resume Analytics")
447
+ # Skill Distribution Chart
448
+ if skills:
449
+ st.write("**Skill Distribution:**")
450
+ fig_skills = create_skill_distribution_chart(skills)
451
+ st.plotly_chart(fig_skills)
452
+
453
+ # Experience Timeline (if applicable)
454
+ fig_experience = create_experience_timeline(resume_text)
455
+ if fig_experience:
456
+ st.write("**Experience Timeline:**")
457
+ st.plotly_chart(fig_experience)
458
+ else:
459
+ st.write("**Experience Timeline:** Not enough data to generate a timeline.")
460
  else:
461
  st.error("Failed to extract text from resume.")
462
 
 
472
  # Fetch job recommendations
473
  st.subheader("Recommended Jobs")
474
  jobs = get_job_recommendations(resume_text)
475
+ if jobs:
476
+ for job in jobs:
477
+ st.write(f"**{job['title']}** at {job['company']}")
478
+ st.markdown(f"[Apply Here]({job['link']})")
479
+ else:
480
+ st.write("No job recommendations found based on your skills.")
481
  else:
482
  st.error("Failed to extract text from resume.")
483
 
484
+ def skill_matching_page():
485
+ st.header("Skill Matching and Gap Analysis")
 
 
 
 
 
 
486
 
487
+ job_description_input = st.text_area("Paste the job description here:")
488
+ uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf")
489
+
490
+ if st.button("Analyze Skills"):
491
+ if not job_description_input:
492
+ st.error("Please paste the job description.")
493
+ return
494
+ if not uploaded_file:
495
+ st.error("Please upload your resume.")
496
+ return
497
+
498
+ with st.spinner("Analyzing..."):
499
+ # Extract resume text
500
+ resume_text = extract_text_from_pdf(uploaded_file)
501
+ if not resume_text:
502
+ st.error("Failed to extract text from resume.")
503
+ return
504
+
505
+ # Extract skills
506
+ resume_skills = extract_skills(resume_text)
507
+ job_skills = extract_skills(job_description_input)
508
+
509
+ # Find matches and gaps
510
+ matching_skills = set(resume_skills).intersection(set(job_skills))
511
+ missing_skills = set(job_skills) - set(resume_skills)
512
+
513
+ # Display results
514
+ st.subheader("Matching Skills")
515
+ st.write(', '.join(matching_skills) if matching_skills else "No matching skills found.")
516
+
517
+ st.subheader("Missing Skills")
518
+ st.write(', '.join(missing_skills) if missing_skills else "No missing skills.")
519
 
520
  # -------------------------------
521
  # Main App with Sidebar Navigation
 
527
  with st.sidebar:
528
  selected = option_menu(
529
  "Main Menu",
530
+ ["Email Generator", "Cover Letter Generator", "Resume Analysis", "Job Recommendations", "Skill Matching"],
531
+ icons=["envelope", "file-earmark-text", "file-person", "briefcase", "bar-chart"],
532
  menu_icon="cast",
533
  default_index=0,
534
  )
535
 
536
  if selected == "Email Generator":
537
  email_generator_page()
538
+ elif selected == "Cover Letter Generator":
539
+ cover_letter_generator_page()
540
  elif selected == "Resume Analysis":
541
  resume_analysis_page()
542
  elif selected == "Job Recommendations":
543
  job_recommendations_page()
544
+ elif selected == "Skill Matching":
545
+ skill_matching_page()
546
 
547
  if __name__ == "__main__":
548
  main()