AdithyaSNair commited on
Commit
df33714
·
verified ·
1 Parent(s): 384abd1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +131 -703
app.py CHANGED
@@ -8,26 +8,26 @@ import sqlite3
8
  from datetime import datetime, timedelta
9
  import re
10
  import os
11
- from streamlit_option_menu import option_menu
12
  import fitz # PyMuPDF
13
  from bs4 import BeautifulSoup
 
14
 
15
-
16
  GROQ_API_KEY = st.secrets["GROQ_API_KEY"]
17
  RAPIDAPI_KEY = st.secrets["RAPIDAPI_KEY"]
18
  YOUTUBE_API_KEY = st.secrets["YOUTUBE_API_KEY"]
19
  THE_MUSE_API_KEY = st.secrets.get("THE_MUSE_API_KEY", "")
20
  BLS_API_KEY = st.secrets.get("BLS_API_KEY", "")
21
 
22
-
23
  llm = ChatGroq(
24
  temperature=0,
25
  groq_api_key=GROQ_API_KEY,
26
  model_name="llama-3.1-70b-versatile"
27
  )
28
 
29
-
30
-
 
31
  @st.cache_data(ttl=3600)
32
  def extract_text_from_pdf(pdf_file):
33
  """
@@ -49,13 +49,10 @@ def extract_job_description(job_link):
49
  Fetches and extracts job description text from a given URL.
50
  """
51
  try:
52
- headers = {
53
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
54
- }
55
  response = requests.get(job_link, headers=headers)
56
  response.raise_for_status()
57
  soup = BeautifulSoup(response.text, 'html.parser')
58
- # You might need to adjust the selectors based on the website's structure
59
  job_description = soup.get_text(separator='\n')
60
  return job_description.strip()
61
  except Exception as e:
@@ -76,19 +73,20 @@ def extract_requirements(job_description):
76
 
77
  Requirements:
78
  """
79
-
80
  try:
81
  response = llm.invoke(prompt)
82
- requirements = response.content.strip()
83
- return requirements
84
  except Exception as e:
85
  st.error(f"Error extracting requirements: {e}")
86
  return ""
87
 
 
 
 
88
  @st.cache_data(ttl=3600)
89
  def generate_email(job_description, requirements, resume_text):
90
  """
91
- Generates a personalized cold email using Groq based on the job description, requirements, and resume.
92
  """
93
  prompt = f"""
94
  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:
@@ -103,16 +101,14 @@ def generate_email(job_description, requirements, resume_text):
103
  {resume_text}
104
 
105
  **Email Requirements:**
106
- - **Introduction:** Briefly introduce yourself and mention the specific job you are applying for.
107
- - **Body:** Highlight your relevant skills, projects, internships, and leadership experiences that align with the job requirements.
108
- - **Value Proposition:** Explain how your fresh perspective and recent academic knowledge can add value to the company.
109
- - **Closing:** Express enthusiasm for the opportunity, mention your willingness for an interview, and thank the recipient for their time.
110
  """
111
-
112
  try:
113
  response = llm.invoke(prompt)
114
- email_text = response.content.strip()
115
- return email_text
116
  except Exception as e:
117
  st.error(f"Error generating email: {e}")
118
  return ""
@@ -120,10 +116,10 @@ def generate_email(job_description, requirements, resume_text):
120
  @st.cache_data(ttl=3600)
121
  def generate_cover_letter(job_description, requirements, resume_text):
122
  """
123
- Generates a personalized cover letter using Groq based on the job description, requirements, and resume.
124
  """
125
  prompt = f"""
126
- 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:
127
 
128
  **Job Description:**
129
  {job_description}
@@ -135,24 +131,22 @@ def generate_cover_letter(job_description, requirements, resume_text):
135
  {resume_text}
136
 
137
  **Cover Letter Requirements:**
138
- 1. **Greeting:** Address the hiring manager by name if available; otherwise, use a generic greeting such as "Dear Hiring Manager."
139
- 2. **Introduction:** Begin with an engaging opening that mentions the specific position you are applying for and conveys your enthusiasm.
140
- 3. **Body:**
141
- - **Skills and Experiences:** Highlight relevant technical skills, projects, internships, and leadership roles that align with the job requirements.
142
- - **Alignment:** Demonstrate how your academic background and hands-on experiences make you a suitable candidate for the role.
143
- 4. **Value Proposition:** Explain how your fresh perspective, recent academic knowledge, and eagerness to learn can contribute to the company's success.
144
- 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.
145
- 6. **Professional Tone:** Maintain a respectful and professional tone throughout the letter.
146
  """
147
-
148
  try:
149
  response = llm.invoke(prompt)
150
- cover_letter = response.content.strip()
151
- return cover_letter
152
  except Exception as e:
153
  st.error(f"Error generating cover letter: {e}")
154
  return ""
155
 
 
 
 
156
  @st.cache_data(ttl=3600)
157
  def extract_skills(text):
158
  """
@@ -166,11 +160,9 @@ def extract_skills(text):
166
 
167
  Skills:
168
  """
169
-
170
  try:
171
  response = llm.invoke(prompt)
172
  skills = response.content.strip()
173
- # Clean and split the skills
174
  skills_list = [skill.strip() for skill in re.split(',|\n', skills) if skill.strip()]
175
  return skills_list
176
  except Exception as e:
@@ -180,10 +172,10 @@ def extract_skills(text):
180
  @st.cache_data(ttl=3600)
181
  def suggest_keywords(resume_text, job_description=None):
182
  """
183
- Suggests additional relevant keywords to enhance resume compatibility with ATS.
184
  """
185
  prompt = f"""
186
- 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.
187
 
188
  Resume Text:
189
  {resume_text}
@@ -193,7 +185,6 @@ def suggest_keywords(resume_text, job_description=None):
193
 
194
  Suggested Keywords:
195
  """
196
-
197
  try:
198
  response = llm.invoke(prompt)
199
  keywords = response.content.strip()
@@ -218,7 +209,6 @@ def create_experience_timeline(resume_text):
218
  """
219
  Creates an experience timeline from the resume text.
220
  """
221
- # Extract work experience details using Groq
222
  prompt = f"""
223
  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).
224
 
@@ -227,11 +217,9 @@ def create_experience_timeline(resume_text):
227
 
228
  Table:
229
  """
230
-
231
  try:
232
  response = llm.invoke(prompt)
233
  table_text = response.content.strip()
234
- # Parse the table_text to create a DataFrame
235
  data = []
236
  for line in table_text.split('\n'):
237
  if line.strip() and not line.lower().startswith("job title"):
@@ -240,12 +228,10 @@ def create_experience_timeline(resume_text):
240
  job_title = parts[0].strip()
241
  company = parts[1].strip()
242
  duration = parts[2].strip()
243
- # Convert duration to a float representing years
244
  duration_years = parse_duration(duration)
245
  data.append({"Job Title": job_title, "Company": company, "Duration (years)": duration_years})
246
  df = pd.DataFrame(data)
247
  if not df.empty:
248
- # Create a cumulative duration for timeline
249
  df['Start Year'] = df['Duration (years)'].cumsum() - df['Duration (years)']
250
  df['End Year'] = df['Duration (years)'].cumsum()
251
  fig = px.timeline(df, x_start="Start Year", x_end="End Year", y="Job Title", color="Company", title="Experience Timeline")
@@ -274,30 +260,15 @@ def parse_duration(duration_str):
274
  return 0
275
 
276
  # -------------------------------
277
- # API Integration Functions
278
  # -------------------------------
279
-
280
- # Remotive API Integration
281
- @st.cache_data(ttl=86400) # Cache results for 1 day
282
  def fetch_remotive_jobs_api(job_title, location=None, category=None, remote=True, max_results=50):
283
  """
284
- Fetches job listings from Remotive API based on user preferences.
285
-
286
- Args:
287
- job_title (str): The job title to search for.
288
- location (str, optional): The job location. Defaults to None.
289
- category (str, optional): The job category. Defaults to None.
290
- remote (bool, optional): Whether to fetch remote jobs. Defaults to True.
291
- max_results (int, optional): Maximum number of jobs to fetch. Defaults to 50.
292
-
293
- Returns:
294
- list: A list of job dictionaries.
295
  """
296
  base_url = "https://remotive.com/api/remote-jobs"
297
- params = {
298
- "search": job_title,
299
- "limit": max_results
300
- }
301
  if category:
302
  params["category"] = category
303
  try:
@@ -305,72 +276,36 @@ def fetch_remotive_jobs_api(job_title, location=None, category=None, remote=True
305
  response.raise_for_status()
306
  jobs = response.json().get("jobs", [])
307
  if remote:
308
- # Filter for remote jobs if not already
309
  jobs = [job for job in jobs if job.get("candidate_required_location") == "Worldwide" or job.get("remote") == True]
310
  return jobs
311
  except requests.exceptions.RequestException as e:
312
  st.error(f"Error fetching jobs from Remotive: {e}")
313
  return []
314
 
315
- # The Muse API Integration
316
- @st.cache_data(ttl=86400) # Cache results for 1 day
317
  def fetch_muse_jobs_api(job_title, location=None, category=None, max_results=50):
318
  """
319
- Fetches job listings from The Muse API based on user preferences.
320
-
321
- Args:
322
- job_title (str): The job title to search for.
323
- location (str, optional): The job location. Defaults to None.
324
- category (str, optional): The job category. Defaults to None.
325
- max_results (int, optional): Maximum number of jobs to fetch. Defaults to 50.
326
-
327
- Returns:
328
- list: A list of job dictionaries.
329
  """
330
  base_url = "https://www.themuse.com/api/public/jobs"
331
- headers = {
332
- "Content-Type": "application/json"
333
- }
334
- params = {
335
- "page": 1,
336
- "per_page": max_results,
337
- "category": category,
338
- "location": location,
339
- "company": None # Can be extended based on needs
340
- }
341
  try:
342
  response = requests.get(base_url, params=params, headers=headers)
343
  response.raise_for_status()
344
  jobs = response.json().get("results", [])
345
- # Filter based on job title
346
  filtered_jobs = [job for job in jobs if job_title.lower() in job.get("name", "").lower()]
347
  return filtered_jobs
348
  except requests.exceptions.RequestException as e:
349
  st.error(f"Error fetching jobs from The Muse: {e}")
350
  return []
351
 
352
- # Indeed API Integration using /list and /get
353
- @st.cache_data(ttl=86400) # Cache results for 1 day
354
  def fetch_indeed_jobs_list_api(job_title, location="United States", distance="1.0", language="en_GB", remoteOnly="false", datePosted="month", employmentTypes="fulltime;parttime;intern;contractor", index=0, page_size=10):
355
  """
356
- Fetches a list of job IDs from Indeed API based on user preferences.
357
-
358
- Args:
359
- job_title (str): The job title to search for.
360
- location (str, optional): The job location. Defaults to "United States".
361
- distance (str, optional): Search radius in miles. Defaults to "1.0".
362
- language (str, optional): Language code. Defaults to "en_GB".
363
- remoteOnly (str, optional): "true" or "false". Defaults to "false".
364
- datePosted (str, optional): e.g., "month". Defaults to "month".
365
- employmentTypes (str, optional): e.g., "fulltime;parttime;intern;contractor". Defaults to "fulltime;parttime;intern;contractor".
366
- index (int, optional): Pagination index. Defaults to 0.
367
- page_size (int, optional): Number of jobs to fetch. Defaults to 10.
368
-
369
- Returns:
370
- list: A list of job IDs.
371
  """
372
  url = "https://jobs-api14.p.rapidapi.com/list"
373
-
374
  querystring = {
375
  "query": job_title,
376
  "location": location,
@@ -382,106 +317,48 @@ def fetch_indeed_jobs_list_api(job_title, location="United States", distance="1.
382
  "index": str(index),
383
  "page_size": str(page_size)
384
  }
385
-
386
- headers = {
387
- "x-rapidapi-key": RAPIDAPI_KEY,
388
- "x-rapidapi-host": "jobs-api14.p.rapidapi.com"
389
- }
390
-
391
  try:
392
  response = requests.get(url, headers=headers, params=querystring)
393
  response.raise_for_status()
394
  data = response.json()
395
  job_ids = [job["id"] for job in data.get("jobs", [])]
396
  return job_ids
397
- except requests.exceptions.HTTPError as http_err:
398
- if response.status_code == 400:
399
- st.error("❌ Bad Request: Please check the parameters you're sending.")
400
- elif response.status_code == 403:
401
- st.error("❌ Access Forbidden: Check your API key and permissions.")
402
- elif response.status_code == 404:
403
- st.error("❌ Resource Not Found: Verify the endpoint and parameters.")
404
- else:
405
- st.error(f"❌ HTTP error occurred: {http_err}")
406
- return []
407
- except requests.exceptions.RequestException as req_err:
408
- st.error(f"❌ Request Exception: {req_err}")
409
- return []
410
- except Exception as e:
411
- st.error(f"❌ An unexpected error occurred: {e}")
412
  return []
413
 
414
- @st.cache_data(ttl=86400) # Cache results for 1 day
415
  def fetch_indeed_job_details_api(job_id, language="en_GB"):
416
  """
417
- Fetches job details from Indeed API based on job ID.
418
-
419
- Args:
420
- job_id (str): The job ID.
421
- language (str, optional): Language code. Defaults to "en_GB".
422
-
423
- Returns:
424
- dict: Job details.
425
  """
426
  url = "https://jobs-api14.p.rapidapi.com/get"
427
-
428
- querystring = {
429
- "id": job_id,
430
- "language": language
431
- }
432
-
433
- headers = {
434
- "x-rapidapi-key": RAPIDAPI_KEY,
435
- "x-rapidapi-host": "jobs-api14.p.rapidapi.com"
436
- }
437
-
438
  try:
439
  response = requests.get(url, headers=headers, params=querystring)
440
  response.raise_for_status()
441
- job_details = response.json()
442
- return job_details
443
- except requests.exceptions.HTTPError as http_err:
444
- if response.status_code == 400:
445
- st.error("❌ Bad Request: Please check the job ID and parameters.")
446
- elif response.status_code == 403:
447
- st.error("❌ Access Forbidden: Check your API key and permissions.")
448
- elif response.status_code == 404:
449
- st.error("❌ Job Not Found: Verify the job ID.")
450
- else:
451
- st.error(f"❌ HTTP error occurred: {http_err}")
452
- return {}
453
- except requests.exceptions.RequestException as req_err:
454
- st.error(f"❌ Request Exception: {req_err}")
455
- return {}
456
- except Exception as e:
457
- st.error(f"❌ An unexpected error occurred: {e}")
458
  return {}
459
 
460
  def recommend_indeed_jobs(user_skills, user_preferences):
461
  """
462
  Recommends jobs from Indeed API based on user skills and preferences.
463
-
464
- Args:
465
- user_skills (list): List of user's skills.
466
- user_preferences (dict): User preferences like job title, location, category.
467
-
468
- Returns:
469
- list: Recommended job listings.
470
  """
471
  job_title = user_preferences.get("job_title", "")
472
  location = user_preferences.get("location", "United States")
473
  category = user_preferences.get("category", "")
474
  language = "en_GB"
475
 
476
- # Fetch job IDs
477
- job_ids = fetch_indeed_jobs_list_api(job_title, location=location, category=category, page_size=5) # Limiting to 5 for API call count
478
-
479
  recommended_jobs = []
480
- api_calls_needed = len(job_ids) # Each /get call counts as one
481
 
482
- # Check if enough API calls are left
483
  if not can_make_api_calls(api_calls_needed):
484
- st.error("❌ You have reached your monthly API request limit. Please try again next month.")
485
  return []
486
 
487
  for job_id in job_ids:
@@ -493,117 +370,28 @@ def recommend_indeed_jobs(user_skills, user_preferences):
493
  recommended_jobs.append((match_score, job_details))
494
  decrement_api_calls(1)
495
 
496
- # Sort jobs based on match_score
497
  recommended_jobs.sort(reverse=True, key=lambda x: x[0])
498
-
499
- # Return only the job dictionaries
500
- return [job for score, job in recommended_jobs[:10]] # Top 10 recommendations
501
 
502
  def recommend_jobs(user_skills, user_preferences):
503
  """
504
- Recommends jobs based on user skills and preferences from Remotive, The Muse, and Indeed APIs.
505
-
506
- Args:
507
- user_skills (list): List of user's skills.
508
- user_preferences (dict): User preferences like job title, location, category.
509
-
510
- Returns:
511
- list: Recommended job listings.
512
  """
513
- # Fetch from Remotive
514
  remotive_jobs = fetch_remotive_jobs_api(user_preferences.get("job_title", ""), user_preferences.get("location"), user_preferences.get("category"))
515
-
516
- # Fetch from The Muse
517
  muse_jobs = fetch_muse_jobs_api(user_preferences.get("job_title", ""), user_preferences.get("location"), user_preferences.get("category"))
518
-
519
- # Fetch from Indeed
520
  indeed_jobs = recommend_indeed_jobs(user_skills, user_preferences)
521
 
522
- # Combine all job listings
523
  combined_jobs = remotive_jobs + muse_jobs + indeed_jobs
524
-
525
- # Remove duplicates based on job URL
526
  unique_jobs = {}
527
  for job in combined_jobs:
528
  url = job.get("url") or job.get("redirect_url") or job.get("url_standard")
529
  if url and url not in unique_jobs:
530
  unique_jobs[url] = job
531
-
532
  return list(unique_jobs.values())
533
 
534
- # -------------------------------
535
- # BLS API Integration and Display
536
- # -------------------------------
537
-
538
- @st.cache_data(ttl=86400) # Cache results for 1 day
539
- def fetch_bls_data(series_ids, start_year=2020, end_year=datetime.now().year):
540
- """
541
- Fetches labor market data from the BLS API.
542
-
543
- Args:
544
- series_ids (list): List of BLS series IDs.
545
- start_year (int, optional): Start year for data. Defaults to 2020.
546
- end_year (int, optional): End year for data. Defaults to current year.
547
-
548
- Returns:
549
- dict: BLS data response.
550
- """
551
- bls_url = "https://api.bls.gov/publicAPI/v2/timeseries/data/"
552
- headers = {
553
- "Content-Type": "application/json"
554
- }
555
- payload = {
556
- "seriesid": series_ids,
557
- "startyear": str(start_year),
558
- "endyear": str(end_year)
559
- }
560
- try:
561
- response = requests.post(bls_url, json=payload, headers=headers)
562
- response.raise_for_status()
563
- data = response.json()
564
- if data.get("status") == "REQUEST_SUCCEEDED":
565
- return data.get("Results", {})
566
- else:
567
- st.error("BLS API request failed.")
568
- return {}
569
- except requests.exceptions.RequestException as e:
570
- st.error(f"Error fetching data from BLS: {e}")
571
- return {}
572
-
573
- def display_bls_data(series_id, title):
574
- """
575
- Processes and displays BLS data with visualizations.
576
-
577
- Args:
578
- series_id (str): BLS series ID.
579
- title (str): Title for the visualization.
580
- """
581
- data = fetch_bls_data([series_id])
582
- if not data:
583
- st.info("No data available.")
584
- return
585
-
586
- series_data = data.get("series", [])[0]
587
- series_title = series_data.get("title", title)
588
- observations = series_data.get("data", [])
589
-
590
- # Extract year and value
591
- years = [int(obs["year"]) for obs in observations]
592
- values = [float(obs["value"].replace(',', '')) for obs in observations]
593
-
594
- df = pd.DataFrame({
595
- "Year": years,
596
- "Value": values
597
- }).sort_values("Year")
598
-
599
- st.markdown(f"### {series_title}")
600
- fig = px.line(df, x="Year", y="Value", title=series_title, markers=True)
601
- st.plotly_chart(fig, use_container_width=True)
602
-
603
  # -------------------------------
604
  # API Usage Counter Functions
605
  # -------------------------------
606
-
607
  def init_api_usage_db():
608
  """
609
  Initializes the SQLite database and creates the api_usage table if it doesn't exist.
@@ -617,10 +405,8 @@ def init_api_usage_db():
617
  last_reset DATE
618
  )
619
  ''')
620
- # Check if a row exists, if not, initialize
621
  c.execute('SELECT COUNT(*) FROM api_usage')
622
  if c.fetchone()[0] == 0:
623
- # Initialize with 25 requests and current date
624
  c.execute('INSERT INTO api_usage (count, last_reset) VALUES (?, ?)', (25, datetime.now().date()))
625
  conn.commit()
626
  conn.close()
@@ -628,9 +414,6 @@ def init_api_usage_db():
628
  def get_api_usage():
629
  """
630
  Retrieves the current API usage count and last reset date.
631
-
632
- Returns:
633
- tuple: (count, last_reset_date)
634
  """
635
  conn = sqlite3.connect('applications.db')
636
  c = conn.cursor()
@@ -644,7 +427,7 @@ def get_api_usage():
644
 
645
  def reset_api_usage():
646
  """
647
- Resets the API usage count to 25 and updates the last reset date.
648
  """
649
  conn = sqlite3.connect('applications.db')
650
  c = conn.cursor()
@@ -655,49 +438,34 @@ def reset_api_usage():
655
  def can_make_api_calls(requests_needed):
656
  """
657
  Checks if there are enough API calls remaining.
658
-
659
- Args:
660
- requests_needed (int): Number of API calls required.
661
-
662
- Returns:
663
- bool: True if allowed, False otherwise.
664
  """
665
  count, last_reset = get_api_usage()
666
  today = datetime.now().date()
667
  if today >= last_reset + timedelta(days=30):
668
  reset_api_usage()
669
  count, last_reset = get_api_usage()
670
- if count >= requests_needed:
671
- return True
672
- else:
673
- return False
674
 
675
  def decrement_api_calls(requests_used):
676
  """
677
- Decrements the API usage count by the number of requests used.
678
-
679
- Args:
680
- requests_used (int): Number of API calls used.
681
  """
682
  conn = sqlite3.connect('applications.db')
683
  c = conn.cursor()
684
  c.execute('SELECT count FROM api_usage WHERE id = 1')
685
  row = c.fetchone()
686
  if row:
687
- new_count = row[0] - requests_used
688
- if new_count < 0:
689
- new_count = 0
690
  c.execute('UPDATE api_usage SET count = ? WHERE id = 1', (new_count,))
691
  conn.commit()
692
  conn.close()
693
 
694
  # -------------------------------
695
- # Application Tracking Database Functions
696
  # -------------------------------
697
-
698
  def init_db():
699
  """
700
- Initializes the SQLite database and creates the applications table if it doesn't exist.
701
  """
702
  conn = sqlite3.connect('applications.db')
703
  c = conn.cursor()
@@ -777,16 +545,15 @@ def delete_application(app_id):
777
  conn.close()
778
 
779
  # -------------------------------
780
- # Learning Path Generation Function
781
  # -------------------------------
782
-
783
- @st.cache_data(ttl=86400) # Cache results for 1 day
784
  def generate_learning_path(career_goal, current_skills):
785
  """
786
- Generates a personalized learning path using Groq based on career goal and current skills.
787
  """
788
  prompt = f"""
789
- Based on the following career goal and current skills, create a personalized learning path that includes recommended courses, projects, and milestones to achieve the career goal.
790
 
791
  **Career Goal:**
792
  {career_goal}
@@ -796,11 +563,9 @@ def generate_learning_path(career_goal, current_skills):
796
 
797
  **Learning Path:**
798
  """
799
-
800
  try:
801
  response = llm.invoke(prompt)
802
- learning_path = response.content.strip()
803
- return learning_path
804
  except Exception as e:
805
  st.error(f"Error generating learning path: {e}")
806
  return ""
@@ -808,19 +573,10 @@ def generate_learning_path(career_goal, current_skills):
808
  # -------------------------------
809
  # YouTube Video Search and Embed Functions
810
  # -------------------------------
811
-
812
- @st.cache_data(ttl=86400) # Cache results for 1 day
813
  def search_youtube_videos(query, max_results=2, video_duration="long"):
814
  """
815
- Searches YouTube for videos matching the query and returns video URLs.
816
-
817
- Args:
818
- query (str): Search query.
819
- max_results (int, optional): Number of videos to return. Defaults to 2.
820
- video_duration (str, optional): Duration filter ('any', 'short', 'medium', 'long'). Defaults to "long".
821
-
822
- Returns:
823
- list: List of YouTube video URLs.
824
  """
825
  search_url = "https://www.googleapis.com/youtube/v3/search"
826
  params = {
@@ -843,63 +599,23 @@ def search_youtube_videos(query, max_results=2, video_duration="long"):
843
 
844
  def embed_youtube_videos(video_urls, module_name):
845
  """
846
- Embeds YouTube videos in the Streamlit app.
847
-
848
- Args:
849
- video_urls (list): List of YouTube video URLs.
850
- module_name (str): Name of the module for context.
851
  """
852
  for url in video_urls:
853
  st.video(url)
854
 
855
  # -------------------------------
856
- # Job Recommendations and BLS Integration
857
  # -------------------------------
858
-
859
- def labor_market_insights_module():
860
- st.header("📈 Labor Market Insights")
861
-
862
- st.write("""
863
- Gain valuable insights into the current labor market trends, employment rates, and industry growth to make informed career decisions.
864
- """)
865
-
866
- # Define BLS Series IDs based on desired data
867
- # Example: Unemployment rate (Series ID: LNS14000000)
868
- # Reference: https://www.bls.gov/web/laus/laumstrk.htm
869
- unemployment_series_id = "LNS14000000" # Unemployment Rate
870
- employment_series_id = "CEU0000000001" # Total Employment
871
-
872
- # Display Unemployment Rate
873
- display_bls_data(unemployment_series_id, "Unemployment Rate (%)")
874
-
875
- # Display Total Employment
876
- display_bls_data(employment_series_id, "Total Employment")
877
-
878
- # Additional Insights
879
- st.subheader("💡 Additional Insights")
880
- st.write("""
881
- - **Industry Growth:** Understanding which industries are growing can help you target your job search effectively.
882
- - **Salary Trends:** Keeping an eye on salary trends ensures that you negotiate effectively and align your expectations.
883
- - **Geographical Demand:** Some regions may have higher demand for certain roles, guiding your location preferences.
884
- """)
885
-
886
- # -------------------------------
887
- # Page Functions
888
- # -------------------------------
889
-
890
  def email_generator_page():
891
  st.header("📧 Automated Email Generator")
 
892
 
893
- st.write("""
894
- Generate personalized cold emails based on job postings and your resume.
895
- """)
896
-
897
- # Create two columns for input fields
898
  col1, col2 = st.columns(2)
899
  with col1:
900
  job_link = st.text_input("🔗 Enter the job link:")
901
  with col2:
902
- uploaded_file = st.file_uploader("📄 Upload your resume (PDF format):", type="pdf")
903
 
904
  if st.button("Generate Email"):
905
  if not job_link:
@@ -908,54 +624,36 @@ def email_generator_page():
908
  if not uploaded_file:
909
  st.error("Please upload your resume.")
910
  return
911
-
912
  with st.spinner("Processing..."):
913
- # Extract job description
914
  job_description = extract_job_description(job_link)
915
  if not job_description:
916
  st.error("Failed to extract job description.")
917
  return
918
-
919
- # Extract requirements
920
  requirements = extract_requirements(job_description)
921
  if not requirements:
922
  st.error("Failed to extract requirements.")
923
  return
924
-
925
- # Extract resume text
926
  resume_text = extract_text_from_pdf(uploaded_file)
927
  if not resume_text:
928
  st.error("Failed to extract text from resume.")
929
  return
930
-
931
- # Generate email
932
  email_text = generate_email(job_description, requirements, resume_text)
933
  if email_text:
934
  st.subheader("📨 Generated Email:")
935
  st.write(email_text)
936
- # Provide download option
937
- st.download_button(
938
- label="Download Email",
939
- data=email_text,
940
- file_name="generated_email.txt",
941
- mime="text/plain"
942
- )
943
  else:
944
  st.error("Failed to generate email.")
945
 
946
  def cover_letter_generator_page():
947
  st.header("📝 Automated Cover Letter Generator")
 
948
 
949
- st.write("""
950
- Generate personalized cover letters based on job postings and your resume.
951
- """)
952
-
953
- # Create two columns for input fields
954
  col1, col2 = st.columns(2)
955
  with col1:
956
  job_link = st.text_input("🔗 Enter the job link:")
957
  with col2:
958
- uploaded_file = st.file_uploader("📄 Upload your resume (PDF format):", type="pdf")
959
 
960
  if st.button("Generate Cover Letter"):
961
  if not job_link:
@@ -964,104 +662,64 @@ def cover_letter_generator_page():
964
  if not uploaded_file:
965
  st.error("Please upload your resume.")
966
  return
967
-
968
  with st.spinner("Processing..."):
969
- # Extract job description
970
  job_description = extract_job_description(job_link)
971
  if not job_description:
972
  st.error("Failed to extract job description.")
973
  return
974
-
975
- # Extract requirements
976
  requirements = extract_requirements(job_description)
977
  if not requirements:
978
  st.error("Failed to extract requirements.")
979
  return
980
-
981
- # Extract resume text
982
  resume_text = extract_text_from_pdf(uploaded_file)
983
  if not resume_text:
984
  st.error("Failed to extract text from resume.")
985
  return
986
-
987
- # Generate cover letter
988
  cover_letter = generate_cover_letter(job_description, requirements, resume_text)
989
  if cover_letter:
990
  st.subheader("📝 Generated Cover Letter:")
991
  st.write(cover_letter)
992
- # Provide download option
993
- st.download_button(
994
- label="Download Cover Letter",
995
- data=cover_letter,
996
- file_name="generated_cover_letter.txt",
997
- mime="text/plain"
998
- )
999
  else:
1000
  st.error("Failed to generate cover letter.")
1001
 
1002
  def resume_analysis_page():
1003
  st.header("📄 Resume Analysis and Optimization")
1004
-
1005
- st.write("""
1006
- Enhance your resume's effectiveness with our comprehensive analysis tools. Upload your resume to extract key information, receive optimization suggestions, and visualize your skills and experience.
1007
- """)
1008
-
1009
- uploaded_file = st.file_uploader("📂 Upload your resume (PDF format):", type="pdf")
1010
-
1011
  if uploaded_file:
1012
  resume_text = extract_text_from_pdf(uploaded_file)
1013
  if resume_text:
1014
  st.success("✅ Resume uploaded successfully!")
1015
-
1016
- # Extracted Information
1017
  st.subheader("🔍 Extracted Information")
1018
-
1019
- # Create tabs for organized sections
1020
  tabs = st.tabs(["💼 Skills", "🔑 Suggested Keywords"])
1021
-
1022
  with tabs[0]:
1023
  skills = extract_skills(resume_text)
1024
  if skills:
1025
  st.markdown("**Identified Skills:**")
1026
- # Display skills as bullet points in columns
1027
  cols = st.columns(4)
1028
  for idx, skill in enumerate(skills, 1):
1029
  cols[idx % 4].write(f"- {skill}")
1030
  else:
1031
  st.info("No skills extracted.")
1032
-
1033
  with tabs[1]:
1034
  keywords = suggest_keywords(resume_text)
1035
  if keywords:
1036
  st.markdown("**Suggested Keywords for ATS Optimization:**")
1037
- # Display keywords as bullet points in columns
1038
  cols = st.columns(4)
1039
  for idx, keyword in enumerate(keywords, 1):
1040
  cols[idx % 4].write(f"- {keyword}")
1041
  else:
1042
  st.info("No keywords suggested.")
1043
-
1044
- # Optimization Suggestions
1045
  st.subheader("🛠️ Optimization Suggestions")
1046
- if keywords:
1047
- st.markdown("""
1048
- - **Keyword Optimization:** Incorporate the suggested keywords to improve ATS compatibility.
1049
- - **Enhance Relevant Sections:** Highlight skills and experiences that align closely with job descriptions.
1050
- """)
1051
- else:
1052
- st.markdown("- **Keyword Optimization:** No keywords suggested.")
1053
  st.markdown("""
1054
- - **Formatting:** Ensure consistent formatting for headings, bullet points, and text alignment to enhance readability.
1055
- - **Quantify Achievements:** Where possible, quantify your accomplishments to demonstrate impact.
1056
- - **Tailor Your Resume:** Customize your resume for each job application to emphasize relevant experiences.
1057
  """)
1058
-
1059
- # Visual Resume Analytics
1060
  st.subheader("📊 Visual Resume Analytics")
1061
-
1062
- # Create two columns for charts
1063
  viz_col1, viz_col2 = st.columns(2)
1064
-
1065
  with viz_col1:
1066
  if skills:
1067
  st.markdown("**Skill Distribution:**")
@@ -1069,7 +727,6 @@ def resume_analysis_page():
1069
  st.plotly_chart(fig_skills, use_container_width=True)
1070
  else:
1071
  st.info("No skills to display.")
1072
-
1073
  with viz_col2:
1074
  fig_experience = create_experience_timeline(resume_text)
1075
  if fig_experience:
@@ -1077,8 +734,6 @@ def resume_analysis_page():
1077
  st.plotly_chart(fig_experience, use_container_width=True)
1078
  else:
1079
  st.info("Not enough data to generate an experience timeline.")
1080
-
1081
- # Save the resume and analysis to the database
1082
  st.subheader("💾 Save Resume Analysis")
1083
  if st.button("Save Resume Analysis"):
1084
  add_application(
@@ -1098,12 +753,9 @@ def resume_analysis_page():
1098
 
1099
  def application_tracking_dashboard():
1100
  st.header("📋 Application Tracking Dashboard")
1101
-
1102
- # Initialize database
1103
  init_db()
1104
  init_api_usage_db()
1105
-
1106
- # Form to add a new application
1107
  st.subheader("➕ Add New Application")
1108
  with st.form("add_application"):
1109
  job_title = st.text_input("🖇️ Job Title")
@@ -1116,10 +768,7 @@ def application_tracking_dashboard():
1116
  uploaded_resume = st.file_uploader("📄 Upload Resume (PDF)", type="pdf")
1117
  submitted = st.form_submit_button("➕ Add Application")
1118
  if submitted:
1119
- if uploaded_file:
1120
- job_description = extract_text_from_pdf(uploaded_file)
1121
- else:
1122
- job_description = ""
1123
  if uploaded_resume:
1124
  resume_text = extract_text_from_pdf(uploaded_resume)
1125
  skills = extract_skills(resume_text)
@@ -1138,78 +787,49 @@ def application_tracking_dashboard():
1138
  skills=skills
1139
  )
1140
  st.success("✅ Application added successfully!")
1141
-
1142
- # Display applications
1143
  st.subheader("📊 Your Applications")
1144
  applications = fetch_applications()
1145
  if applications:
1146
  df = pd.DataFrame(applications)
1147
  df = df.drop(columns=["Job Description", "Resume Text", "Skills"])
1148
  st.dataframe(df)
1149
-
1150
- # Export Button
1151
  csv = df.to_csv(index=False).encode('utf-8')
1152
- st.download_button(
1153
- label="💾 Download Applications as CSV",
1154
- data=csv,
1155
- file_name='applications.csv',
1156
- mime='text/csv',
1157
- )
1158
-
1159
- # Import Button
1160
  st.subheader("📥 Import Applications")
1161
  uploaded_csv = st.file_uploader("📁 Upload a CSV file", type="csv")
1162
  if uploaded_csv:
1163
  try:
1164
  imported_df = pd.read_csv(uploaded_csv)
1165
- # Validate required columns
1166
  required_columns = {"Job Title", "Company", "Application Date", "Status", "Deadline", "Notes"}
1167
  if not required_columns.issubset(imported_df.columns):
1168
  st.error("❌ Uploaded CSV is missing required columns.")
1169
  else:
1170
- for index, row in imported_df.iterrows():
1171
- job_title = row.get("Job Title", "N/A")
1172
- company = row.get("Company", "N/A")
1173
- application_date = row.get("Application Date", datetime.now().strftime("%Y-%m-%d"))
1174
- status = row.get("Status", "Applied")
1175
- deadline = row.get("Deadline", "")
1176
- notes = row.get("Notes", "")
1177
- job_description = row.get("Job Description", "")
1178
- resume_text = row.get("Resume Text", "")
1179
- skills = row.get("Skills", "").split(', ') if row.get("Skills") else []
1180
  add_application(
1181
- job_title=job_title,
1182
- company=company,
1183
- application_date=application_date,
1184
- status=status,
1185
- deadline=deadline,
1186
- notes=notes,
1187
- job_description=job_description,
1188
- resume_text=resume_text,
1189
- skills=skills
1190
  )
1191
  st.success("✅ Applications imported successfully!")
1192
  except Exception as e:
1193
  st.error(f"❌ Error importing applications: {e}")
1194
-
1195
- # Actions: Update Status or Delete
1196
  for app in applications:
1197
  with st.expander(f"{app['Job Title']} at {app['Company']}"):
1198
  st.write(f"**📅 Application Date:** {app['Application Date']}")
1199
  st.write(f"**⏰ Deadline:** {app['Deadline']}")
1200
  st.write(f"**📈 Status:** {app['Status']}")
1201
  st.write(f"**📝 Notes:** {app['Notes']}")
1202
- if app['Job Description']:
1203
- st.write("**📄 Job Description:**")
1204
- st.write(app['Job Description'][:500] + "...")
1205
- if app['Skills']:
1206
- st.write("**💼 Skills:**", ', '.join(app['Skills']))
1207
- # Update status
1208
  new_status = st.selectbox("🔄 Update Status:", ["Applied", "Interviewing", "Offered", "Rejected"], key=f"status_{app['ID']}")
1209
  if st.button("🔁 Update Status", key=f"update_{app['ID']}"):
1210
  update_application_status(app['ID'], new_status)
1211
  st.success("✅ Status updated successfully!")
1212
- # Delete application
1213
  if st.button("🗑️ Delete Application", key=f"delete_{app['ID']}"):
1214
  delete_application(app['ID'])
1215
  st.success("✅ Application deleted successfully!")
@@ -1218,46 +838,29 @@ def application_tracking_dashboard():
1218
 
1219
  def job_recommendations_module():
1220
  st.header("🔍 Job Matching & Recommendations")
1221
-
1222
- st.write("""
1223
- Discover job opportunities tailored to your skills and preferences. Get personalized recommendations from multiple job platforms.
1224
- """)
1225
-
1226
- # User Preferences Form
1227
  st.subheader("🎯 Set Your Preferences")
1228
  with st.form("preferences_form"):
1229
- job_title = st.text_input("🔍 Desired Job Title", placeholder="e.g., Data Scientist, Backend Developer")
1230
- location = st.text_input("📍 Preferred Location", placeholder="e.g., New York, NY, USA or Remote")
1231
  category = st.selectbox("📂 Job Category", ["", "Engineering", "Marketing", "Design", "Sales", "Finance", "Healthcare", "Education", "Other"])
1232
  user_skills_input = st.text_input("💡 Your Skills (comma-separated)", placeholder="e.g., Python, Machine Learning, SQL")
1233
  submitted = st.form_submit_button("🚀 Get Recommendations")
1234
-
1235
  if submitted:
1236
  if not job_title or not user_skills_input:
1237
  st.error("❌ Please enter both job title and your skills.")
1238
  return
1239
-
1240
  user_skills = [skill.strip() for skill in user_skills_input.split(",") if skill.strip()]
1241
- user_preferences = {
1242
- "job_title": job_title,
1243
- "location": location,
1244
- "category": category
1245
- }
1246
-
1247
  with st.spinner("🔄 Fetching job recommendations..."):
1248
- # Fetch recommendations from all APIs (Remotive, The Muse, Indeed)
1249
  recommended_jobs = recommend_jobs(user_skills, user_preferences)
1250
-
1251
  if recommended_jobs:
1252
  st.subheader("💼 Recommended Jobs:")
1253
  for idx, job in enumerate(recommended_jobs, 1):
1254
- # Depending on the API, job data structure might differ
1255
  job_title_display = job.get("title") or job.get("name") or job.get("jobTitle")
1256
  company_display = job.get("company", {}).get("name") or job.get("company_name") or job.get("employer", {}).get("name")
1257
  location_display = job.get("candidate_required_location") or job.get("location") or job.get("country")
1258
- salary_display = "N/A" # Salary is removed
1259
  job_url = job.get("url") or job.get("redirect_url") or job.get("url_standard")
1260
-
1261
  st.markdown(f"### {idx}. {job_title_display}")
1262
  st.markdown(f"**🏢 Company:** {company_display}")
1263
  st.markdown(f"**📍 Location:** {location_display}")
@@ -1268,32 +871,22 @@ def job_recommendations_module():
1268
 
1269
  def interview_preparation_module():
1270
  st.header("🎤 Interview Preparation")
1271
-
1272
- st.write("""
1273
- Prepare for your interviews with tailored mock questions and expert answers.
1274
- """)
1275
-
1276
- # Create two columns for input fields
1277
  col1, col2 = st.columns(2)
1278
  with col1:
1279
  job_title = st.text_input("🔍 Enter the job title you're applying for:")
1280
  with col2:
1281
  company = st.text_input("🏢 Enter the company name:")
1282
-
1283
  if st.button("🎯 Generate Mock Interview Questions"):
1284
  if not job_title or not company:
1285
  st.error("❌ Please enter both job title and company name.")
1286
  return
1287
  with st.spinner("⏳ Generating questions..."):
1288
- # Prompt to generate 50 interview questions with answers
1289
  prompt = f"""
1290
  Generate a list of 50 interview questions along with their answers for the position of {job_title} at {company}. Each question should be followed by a concise and professional answer.
1291
  """
1292
-
1293
  try:
1294
- # Invoke the LLM to get questions and answers
1295
  qa_text = llm.invoke(prompt).content.strip()
1296
- # Split into question-answer pairs
1297
  qa_pairs = qa_text.split('\n\n')
1298
  st.subheader("🗣️ Mock Interview Questions and Answers:")
1299
  for idx, qa in enumerate(qa_pairs, 1):
@@ -1310,18 +903,12 @@ def interview_preparation_module():
1310
 
1311
  def personalized_learning_paths_module():
1312
  st.header("📚 Personalized Learning Paths")
1313
-
1314
- st.write("""
1315
- Receive tailored learning plans to help you acquire the skills needed for your desired career, complemented with curated video resources.
1316
- """)
1317
-
1318
- # Create two columns for input fields
1319
  col1, col2 = st.columns(2)
1320
  with col1:
1321
- career_goal = st.text_input("🎯 Enter your career goal (e.g., Data Scientist, Machine Learning Engineer):")
1322
  with col2:
1323
  current_skills = st.text_input("💡 Enter your current skills (comma-separated):")
1324
-
1325
  if st.button("🚀 Generate Learning Path"):
1326
  if not career_goal or not current_skills:
1327
  st.error("❌ Please enter both career goal and current skills.")
@@ -1331,21 +918,10 @@ def personalized_learning_paths_module():
1331
  if learning_path:
1332
  st.subheader("📜 Your Personalized Learning Path:")
1333
  st.write(learning_path)
1334
-
1335
- # Assuming the learning path is divided into modules/subparts separated by newlines or numbering
1336
- # We'll extract subparts and embed YouTube videos for each
1337
- # Example format:
1338
- # 1. Module One
1339
- # 2. Module Two
1340
- # etc.
1341
-
1342
- # Split learning path into modules
1343
  modules = re.split(r'\d+\.\s+', learning_path)
1344
  modules = [module.strip() for module in modules if module.strip()]
1345
-
1346
  st.subheader("📹 Recommended YouTube Videos for Each Module:")
1347
  for module in modules:
1348
- # Search for long videos related to the module
1349
  video_urls = search_youtube_videos(query=module, max_results=2, video_duration="long")
1350
  if video_urls:
1351
  st.markdown(f"### {module}")
@@ -1357,24 +933,17 @@ def personalized_learning_paths_module():
1357
 
1358
  def networking_opportunities_module():
1359
  st.header("🤝 Networking Opportunities")
1360
-
1361
- st.write("""
1362
- Expand your professional network by connecting with relevant industry peers and joining professional groups.
1363
- """)
1364
-
1365
- # Create two columns for input fields
1366
  col1, col2 = st.columns(2)
1367
  with col1:
1368
  user_skills = st.text_input("💡 Enter your key skills (comma-separated):")
1369
  with col2:
1370
- industry = st.text_input("🏭 Enter your industry (e.g., Technology, Finance):")
1371
-
1372
  if st.button("🔍 Find Networking Opportunities"):
1373
  if not user_skills or not industry:
1374
  st.error("❌ Please enter both key skills and industry.")
1375
  return
1376
  with st.spinner("🔄 Fetching networking opportunities..."):
1377
- # Suggest LinkedIn groups or connections based on skills and industry
1378
  prompt = f"""
1379
  Based on the following skills: {user_skills}, and industry: {industry}, suggest relevant LinkedIn groups, professional organizations, and industry events for networking.
1380
  """
@@ -1387,153 +956,46 @@ def networking_opportunities_module():
1387
 
1388
  def feedback_and_improvement_module():
1389
  st.header("🗣️ Feedback and Continuous Improvement")
1390
-
1391
- st.write("""
1392
- We value your feedback! Let us know how we can improve your experience.
1393
- """)
1394
-
1395
  with st.form("feedback_form"):
1396
  name = st.text_input("👤 Your Name")
1397
  email = st.text_input("📧 Your Email")
1398
  feedback_type = st.selectbox("📂 Type of Feedback", ["Bug Report", "Feature Request", "General Feedback"])
1399
  feedback = st.text_area("📝 Your Feedback")
1400
  submitted = st.form_submit_button("✅ Submit")
1401
-
1402
  if submitted:
1403
  if not name or not email or not feedback:
1404
  st.error("❌ Please fill in all the fields.")
1405
  else:
1406
- # Here you can implement logic to store feedback, e.g., in a database or send via email
1407
- # For demonstration, we'll print to the console
1408
- print(f"Feedback from {name} ({email}): {feedback_type} - {feedback}")
1409
  st.success("✅ Thank you for your feedback!")
1410
 
1411
- def gamification_module():
1412
- st.header("🏆 Gamification and Achievements")
1413
-
1414
- st.write("""
1415
- Stay motivated by earning badges and tracking your progress!
1416
- """)
1417
-
1418
- # Initialize database
1419
- init_db()
1420
-
1421
- # Example achievements
1422
- applications = fetch_applications()
1423
- num_apps = len(applications)
1424
- achievements = {
1425
- "First Application": num_apps >= 1,
1426
- "5 Applications": num_apps >= 5,
1427
- "10 Applications": num_apps >= 10,
1428
- "Resume Optimized": any(app['Skills'] for app in applications),
1429
- "Interview Scheduled": any(app['Status'] == 'Interviewing' for app in applications)
1430
- }
1431
-
1432
- for achievement, earned in achievements.items():
1433
- if earned:
1434
- st.success(f"🎉 {achievement}")
1435
- else:
1436
- st.info(f"🔜 {achievement}")
1437
-
1438
- # Progress Bar
1439
- progress = min(num_apps / 10, 1.0) # Ensure progress is between 0.0 and 1.0
1440
- st.write("**Overall Progress:**")
1441
- st.progress(progress)
1442
- st.write(f"{progress * 100:.0f}% complete")
1443
-
1444
  def resource_library_page():
1445
  st.header("📚 Resource Library")
1446
-
1447
- st.write("""
1448
- Access a collection of templates and guides to enhance your job search.
1449
- """)
1450
-
1451
  resources = [
1452
- {
1453
- "title": "Resume Template",
1454
- "description": "A professional resume template in DOCX format.",
1455
- "file": "./resume_template.docx"
1456
- },
1457
- {
1458
- "title": "Cover Letter Template",
1459
- "description": "A customizable cover letter template.",
1460
- "file": "./cover_letter_template.docx"
1461
- },
1462
- {
1463
- "title": "Job Application Checklist",
1464
- "description": "Ensure you have all the necessary steps covered during your job search.",
1465
- "file": "./application_checklist.pdf"
1466
- }
1467
  ]
1468
-
1469
  for resource in resources:
1470
  st.markdown(f"### {resource['title']}")
1471
  st.write(resource['description'])
1472
  try:
1473
  with open(resource['file'], "rb") as file:
1474
- btn = st.download_button(
1475
- label="⬇️ Download",
1476
- data=file,
1477
- file_name=os.path.basename(resource['file']),
1478
- mime="application/octet-stream"
1479
- )
1480
  except FileNotFoundError:
1481
  st.error(f"❌ File {resource['file']} not found. Please ensure the file is in the correct directory.")
1482
  st.write("---")
1483
 
1484
- def success_stories_page():
1485
- st.header("🌟 Success Stories")
1486
-
1487
- st.write("""
1488
- Hear from our users who have successfully landed their dream jobs with our assistance!
1489
- """)
1490
-
1491
- # Example testimonials
1492
- testimonials = [
1493
- {
1494
- "name": "Rahul Sharma",
1495
- "position": "Data Scientist at TechCorp",
1496
- "testimonial": "This app transformed my job search process. The resume analysis and personalized emails were game-changers!",
1497
- "image": "images/user1.jpg" # Replace with actual image paths
1498
- },
1499
- {
1500
- "name": "Priya Mehta",
1501
- "position": "Machine Learning Engineer at InnovateX",
1502
- "testimonial": "The interview preparation module helped me ace my interviews with confidence. Highly recommended!",
1503
- "image": "images/user2.jpg"
1504
- }
1505
- ]
1506
-
1507
- for user in testimonials:
1508
- col1, col2 = st.columns([1, 3])
1509
- with col1:
1510
- try:
1511
- st.image(user["image"], width=100)
1512
- except:
1513
- st.write("![User Image](https://via.placeholder.com/100)")
1514
- with col2:
1515
- st.write(f"**{user['name']}**")
1516
- st.write(f"*{user['position']}*")
1517
- st.write(f"\"{user['testimonial']}\"")
1518
- st.write("---")
1519
-
1520
  def chatbot_support_page():
1521
  st.header("🤖 AI-Powered Chatbot Support")
1522
-
1523
- st.write("""
1524
- Have questions or need assistance? Chat with our AI-powered assistant!
1525
- """)
1526
-
1527
- # Initialize session state for chatbot
1528
  if 'chat_history' not in st.session_state:
1529
  st.session_state['chat_history'] = []
1530
-
1531
- # User input
1532
  user_input = st.text_input("🗨️ You:", key="user_input")
1533
-
1534
  if st.button("Send"):
1535
  if user_input:
1536
- # Append user message to chat history
1537
  st.session_state['chat_history'].append({"message": user_input, "is_user": True})
1538
  prompt = f"""
1539
  You are a helpful assistant for a Job Application Assistant app. Answer the user's query based on the following context:
@@ -1541,17 +1003,13 @@ def chatbot_support_page():
1541
  {user_input}
1542
  """
1543
  try:
1544
- # Invoke the LLM to get a response
1545
  response = llm.invoke(prompt)
1546
  assistant_message = response.content.strip()
1547
- # Append assistant response to chat history
1548
  st.session_state['chat_history'].append({"message": assistant_message, "is_user": False})
1549
  except Exception as e:
1550
  error_message = "❌ Sorry, I encountered an error while processing your request."
1551
  st.session_state['chat_history'].append({"message": error_message, "is_user": False})
1552
  st.error(f"❌ Error in chatbot: {e}")
1553
-
1554
- # Display chat history using streamlit-chat
1555
  for chat in st.session_state['chat_history']:
1556
  if chat['is_user']:
1557
  message(chat['message'], is_user=True, avatar_style="thumbs")
@@ -1560,65 +1018,44 @@ def chatbot_support_page():
1560
 
1561
  def help_page():
1562
  st.header("❓ Help & FAQ")
1563
-
1564
  with st.expander("🛠️ How do I generate a cover letter?"):
1565
- st.write("""
1566
- To generate a cover letter, navigate to the **Cover Letter Generator** section, enter the job link, upload your resume, and click on **Generate Cover Letter**.
1567
- """)
1568
-
1569
  with st.expander("📋 How do I track my applications?"):
1570
- st.write("""
1571
- Use the **Application Tracking Dashboard** to add new applications, update their status, and monitor deadlines.
1572
- """)
1573
-
1574
  with st.expander("📄 How can I optimize my resume?"):
1575
- st.write("""
1576
- Upload your resume in the **Resume Analysis** section to extract skills and receive optimization suggestions.
1577
- """)
1578
-
1579
  with st.expander("📥 How do I import my applications?"):
1580
- st.write("""
1581
- In the **Application Tracking Dashboard**, use the **Import Applications** section to upload a CSV file containing your applications. Ensure the CSV has the required columns.
1582
- """)
1583
-
1584
  with st.expander("🗣️ How do I provide feedback?"):
1585
- st.write("""
1586
- Navigate to the **Feedback and Continuous Improvement** section, fill out the form, and submit your feedback.
1587
- """)
1588
 
1589
  # -------------------------------
1590
- # Main App Function
1591
  # -------------------------------
1592
-
1593
  def main_app():
1594
- # Apply a consistent theme or style
1595
  st.markdown(
1596
  """
1597
  <style>
1598
- .reportview-container {
1599
- background-color: #f5f5f5;
1600
- }
1601
- .sidebar .sidebar-content {
1602
- background-image: linear-gradient(#2e7bcf, #2e7bcf);
1603
- color: white;
1604
- }
1605
  </style>
1606
  """,
1607
  unsafe_allow_html=True
1608
  )
1609
-
1610
- # Sidebar Navigation using streamlit_option_menu
1611
  with st.sidebar:
1612
  selected = option_menu(
1613
  menu_title="📂 Main Menu",
1614
- options=["Email Generator", "Cover Letter Generator", "Resume Analysis", "Application Tracking",
1615
- "Job Recommendations", "Labor Market Insights", "Interview Preparation", "Personalized Learning Paths",
1616
- "Networking Opportunities", "Feedback", "Gamification", "Resource Library",
1617
- "Success Stories", "Chatbot Support", "Help"],
1618
- icons=["envelope", "file-earmark-text", "file-person", "briefcase",
1619
- "search", "bar-chart-line", "microphone", "book",
1620
- "people", "chat-left-text", "trophy", "collection",
1621
- "star", "robot", "question-circle"],
 
 
 
1622
  menu_icon="cast",
1623
  default_index=0,
1624
  styles={
@@ -1628,8 +1065,6 @@ def main_app():
1628
  "nav-link-selected": {"background-color": "#1e5aab"},
1629
  }
1630
  )
1631
-
1632
- # Route to the selected page
1633
  if selected == "Email Generator":
1634
  email_generator_page()
1635
  elif selected == "Cover Letter Generator":
@@ -1640,8 +1075,6 @@ def main_app():
1640
  application_tracking_dashboard()
1641
  elif selected == "Job Recommendations":
1642
  job_recommendations_module()
1643
- elif selected == "Labor Market Insights":
1644
- labor_market_insights_module()
1645
  elif selected == "Interview Preparation":
1646
  interview_preparation_module()
1647
  elif selected == "Personalized Learning Paths":
@@ -1650,17 +1083,12 @@ def main_app():
1650
  networking_opportunities_module()
1651
  elif selected == "Feedback":
1652
  feedback_and_improvement_module()
1653
- elif selected == "Gamification":
1654
- gamification_module()
1655
  elif selected == "Resource Library":
1656
  resource_library_page()
1657
- elif selected == "Success Stories":
1658
- success_stories_page()
1659
  elif selected == "Chatbot Support":
1660
  chatbot_support_page()
1661
  elif selected == "Help":
1662
  help_page()
1663
 
1664
-
1665
  if __name__ == "__main__":
1666
  main_app()
 
8
  from datetime import datetime, timedelta
9
  import re
10
  import os
 
11
  import fitz # PyMuPDF
12
  from bs4 import BeautifulSoup
13
+ from streamlit_option_menu import option_menu
14
 
15
+ # Secrets and API Keys
16
  GROQ_API_KEY = st.secrets["GROQ_API_KEY"]
17
  RAPIDAPI_KEY = st.secrets["RAPIDAPI_KEY"]
18
  YOUTUBE_API_KEY = st.secrets["YOUTUBE_API_KEY"]
19
  THE_MUSE_API_KEY = st.secrets.get("THE_MUSE_API_KEY", "")
20
  BLS_API_KEY = st.secrets.get("BLS_API_KEY", "")
21
 
 
22
  llm = ChatGroq(
23
  temperature=0,
24
  groq_api_key=GROQ_API_KEY,
25
  model_name="llama-3.1-70b-versatile"
26
  )
27
 
28
+ # -------------------------------
29
+ # PDF and HTML Extraction Functions
30
+ # -------------------------------
31
  @st.cache_data(ttl=3600)
32
  def extract_text_from_pdf(pdf_file):
33
  """
 
49
  Fetches and extracts job description text from a given URL.
50
  """
51
  try:
52
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
 
 
53
  response = requests.get(job_link, headers=headers)
54
  response.raise_for_status()
55
  soup = BeautifulSoup(response.text, 'html.parser')
 
56
  job_description = soup.get_text(separator='\n')
57
  return job_description.strip()
58
  except Exception as e:
 
73
 
74
  Requirements:
75
  """
 
76
  try:
77
  response = llm.invoke(prompt)
78
+ return response.content.strip()
 
79
  except Exception as e:
80
  st.error(f"Error extracting requirements: {e}")
81
  return ""
82
 
83
+ # -------------------------------
84
+ # Email and Cover Letter Generation
85
+ # -------------------------------
86
  @st.cache_data(ttl=3600)
87
  def generate_email(job_description, requirements, resume_text):
88
  """
89
+ Generates a personalized cold email using Groq.
90
  """
91
  prompt = f"""
92
  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:
 
101
  {resume_text}
102
 
103
  **Email Requirements:**
104
+ - Introduction: Briefly introduce yourself and mention the specific job you are applying for.
105
+ - Body: Highlight your relevant skills, projects, internships, and leadership experiences.
106
+ - Value Proposition: Explain how your fresh perspective can add value to the company.
107
+ - Closing: Express enthusiasm and request an interview.
108
  """
 
109
  try:
110
  response = llm.invoke(prompt)
111
+ return response.content.strip()
 
112
  except Exception as e:
113
  st.error(f"Error generating email: {e}")
114
  return ""
 
116
  @st.cache_data(ttl=3600)
117
  def generate_cover_letter(job_description, requirements, resume_text):
118
  """
119
+ Generates a personalized cover letter using Groq.
120
  """
121
  prompt = f"""
122
+ You are Adithya S Nair, a recent Computer Science graduate specializing in Artificial Intelligence and Machine Learning. Compose a professional cover letter based on the following information:
123
 
124
  **Job Description:**
125
  {job_description}
 
131
  {resume_text}
132
 
133
  **Cover Letter Requirements:**
134
+ 1. Greeting: Address the hiring manager.
135
+ 2. Introduction: Mention the position and your enthusiasm.
136
+ 3. Body: Highlight skills, experiences, and relevant projects.
137
+ 4. Value Proposition: Explain how you can contribute to the company.
138
+ 5. Conclusion: Express interest in an interview and thank the reader.
 
 
 
139
  """
 
140
  try:
141
  response = llm.invoke(prompt)
142
+ return response.content.strip()
 
143
  except Exception as e:
144
  st.error(f"Error generating cover letter: {e}")
145
  return ""
146
 
147
+ # -------------------------------
148
+ # Resume Analysis Functions
149
+ # -------------------------------
150
  @st.cache_data(ttl=3600)
151
  def extract_skills(text):
152
  """
 
160
 
161
  Skills:
162
  """
 
163
  try:
164
  response = llm.invoke(prompt)
165
  skills = response.content.strip()
 
166
  skills_list = [skill.strip() for skill in re.split(',|\n', skills) if skill.strip()]
167
  return skills_list
168
  except Exception as e:
 
172
  @st.cache_data(ttl=3600)
173
  def suggest_keywords(resume_text, job_description=None):
174
  """
175
+ Suggests additional relevant keywords for ATS optimization.
176
  """
177
  prompt = f"""
178
+ 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 accordingly.
179
 
180
  Resume Text:
181
  {resume_text}
 
185
 
186
  Suggested Keywords:
187
  """
 
188
  try:
189
  response = llm.invoke(prompt)
190
  keywords = response.content.strip()
 
209
  """
210
  Creates an experience timeline from the resume text.
211
  """
 
212
  prompt = f"""
213
  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).
214
 
 
217
 
218
  Table:
219
  """
 
220
  try:
221
  response = llm.invoke(prompt)
222
  table_text = response.content.strip()
 
223
  data = []
224
  for line in table_text.split('\n'):
225
  if line.strip() and not line.lower().startswith("job title"):
 
228
  job_title = parts[0].strip()
229
  company = parts[1].strip()
230
  duration = parts[2].strip()
 
231
  duration_years = parse_duration(duration)
232
  data.append({"Job Title": job_title, "Company": company, "Duration (years)": duration_years})
233
  df = pd.DataFrame(data)
234
  if not df.empty:
 
235
  df['Start Year'] = df['Duration (years)'].cumsum() - df['Duration (years)']
236
  df['End Year'] = df['Duration (years)'].cumsum()
237
  fig = px.timeline(df, x_start="Start Year", x_end="End Year", y="Job Title", color="Company", title="Experience Timeline")
 
260
  return 0
261
 
262
  # -------------------------------
263
+ # Job API Integration Functions
264
  # -------------------------------
265
+ @st.cache_data(ttl=86400)
 
 
266
  def fetch_remotive_jobs_api(job_title, location=None, category=None, remote=True, max_results=50):
267
  """
268
+ Fetches job listings from Remotive API.
 
 
 
 
 
 
 
 
 
 
269
  """
270
  base_url = "https://remotive.com/api/remote-jobs"
271
+ params = {"search": job_title, "limit": max_results}
 
 
 
272
  if category:
273
  params["category"] = category
274
  try:
 
276
  response.raise_for_status()
277
  jobs = response.json().get("jobs", [])
278
  if remote:
 
279
  jobs = [job for job in jobs if job.get("candidate_required_location") == "Worldwide" or job.get("remote") == True]
280
  return jobs
281
  except requests.exceptions.RequestException as e:
282
  st.error(f"Error fetching jobs from Remotive: {e}")
283
  return []
284
 
285
+ @st.cache_data(ttl=86400)
 
286
  def fetch_muse_jobs_api(job_title, location=None, category=None, max_results=50):
287
  """
288
+ Fetches job listings from The Muse API.
 
 
 
 
 
 
 
 
 
289
  """
290
  base_url = "https://www.themuse.com/api/public/jobs"
291
+ headers = {"Content-Type": "application/json"}
292
+ params = {"page": 1, "per_page": max_results, "category": category, "location": location, "company": None}
 
 
 
 
 
 
 
 
293
  try:
294
  response = requests.get(base_url, params=params, headers=headers)
295
  response.raise_for_status()
296
  jobs = response.json().get("results", [])
 
297
  filtered_jobs = [job for job in jobs if job_title.lower() in job.get("name", "").lower()]
298
  return filtered_jobs
299
  except requests.exceptions.RequestException as e:
300
  st.error(f"Error fetching jobs from The Muse: {e}")
301
  return []
302
 
303
+ @st.cache_data(ttl=86400)
 
304
  def fetch_indeed_jobs_list_api(job_title, location="United States", distance="1.0", language="en_GB", remoteOnly="false", datePosted="month", employmentTypes="fulltime;parttime;intern;contractor", index=0, page_size=10):
305
  """
306
+ Fetches a list of job IDs from Indeed API.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  """
308
  url = "https://jobs-api14.p.rapidapi.com/list"
 
309
  querystring = {
310
  "query": job_title,
311
  "location": location,
 
317
  "index": str(index),
318
  "page_size": str(page_size)
319
  }
320
+ headers = {"x-rapidapi-key": RAPIDAPI_KEY, "x-rapidapi-host": "jobs-api14.p.rapidapi.com"}
 
 
 
 
 
321
  try:
322
  response = requests.get(url, headers=headers, params=querystring)
323
  response.raise_for_status()
324
  data = response.json()
325
  job_ids = [job["id"] for job in data.get("jobs", [])]
326
  return job_ids
327
+ except requests.exceptions.RequestException as e:
328
+ st.error(f"Error fetching job IDs from Indeed: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  return []
330
 
331
+ @st.cache_data(ttl=86400)
332
  def fetch_indeed_job_details_api(job_id, language="en_GB"):
333
  """
334
+ Fetches job details from Indeed API.
 
 
 
 
 
 
 
335
  """
336
  url = "https://jobs-api14.p.rapidapi.com/get"
337
+ querystring = {"id": job_id, "language": language}
338
+ headers = {"x-rapidapi-key": RAPIDAPI_KEY, "x-rapidapi-host": "jobs-api14.p.rapidapi.com"}
 
 
 
 
 
 
 
 
 
339
  try:
340
  response = requests.get(url, headers=headers, params=querystring)
341
  response.raise_for_status()
342
+ return response.json()
343
+ except requests.exceptions.RequestException as e:
344
+ st.error(f"Error fetching job details from Indeed: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  return {}
346
 
347
  def recommend_indeed_jobs(user_skills, user_preferences):
348
  """
349
  Recommends jobs from Indeed API based on user skills and preferences.
 
 
 
 
 
 
 
350
  """
351
  job_title = user_preferences.get("job_title", "")
352
  location = user_preferences.get("location", "United States")
353
  category = user_preferences.get("category", "")
354
  language = "en_GB"
355
 
356
+ job_ids = fetch_indeed_jobs_list_api(job_title, location=location, category=category, page_size=5)
 
 
357
  recommended_jobs = []
358
+ api_calls_needed = len(job_ids)
359
 
 
360
  if not can_make_api_calls(api_calls_needed):
361
+ st.error("❌ You have reached your monthly API request limit. Please try again later.")
362
  return []
363
 
364
  for job_id in job_ids:
 
370
  recommended_jobs.append((match_score, job_details))
371
  decrement_api_calls(1)
372
 
 
373
  recommended_jobs.sort(reverse=True, key=lambda x: x[0])
374
+ return [job for score, job in recommended_jobs[:10]]
 
 
375
 
376
  def recommend_jobs(user_skills, user_preferences):
377
  """
378
+ Combines job recommendations from Remotive, The Muse, and Indeed.
 
 
 
 
 
 
 
379
  """
 
380
  remotive_jobs = fetch_remotive_jobs_api(user_preferences.get("job_title", ""), user_preferences.get("location"), user_preferences.get("category"))
 
 
381
  muse_jobs = fetch_muse_jobs_api(user_preferences.get("job_title", ""), user_preferences.get("location"), user_preferences.get("category"))
 
 
382
  indeed_jobs = recommend_indeed_jobs(user_skills, user_preferences)
383
 
 
384
  combined_jobs = remotive_jobs + muse_jobs + indeed_jobs
 
 
385
  unique_jobs = {}
386
  for job in combined_jobs:
387
  url = job.get("url") or job.get("redirect_url") or job.get("url_standard")
388
  if url and url not in unique_jobs:
389
  unique_jobs[url] = job
 
390
  return list(unique_jobs.values())
391
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  # -------------------------------
393
  # API Usage Counter Functions
394
  # -------------------------------
 
395
  def init_api_usage_db():
396
  """
397
  Initializes the SQLite database and creates the api_usage table if it doesn't exist.
 
405
  last_reset DATE
406
  )
407
  ''')
 
408
  c.execute('SELECT COUNT(*) FROM api_usage')
409
  if c.fetchone()[0] == 0:
 
410
  c.execute('INSERT INTO api_usage (count, last_reset) VALUES (?, ?)', (25, datetime.now().date()))
411
  conn.commit()
412
  conn.close()
 
414
  def get_api_usage():
415
  """
416
  Retrieves the current API usage count and last reset date.
 
 
 
417
  """
418
  conn = sqlite3.connect('applications.db')
419
  c = conn.cursor()
 
427
 
428
  def reset_api_usage():
429
  """
430
+ Resets the API usage count to 25.
431
  """
432
  conn = sqlite3.connect('applications.db')
433
  c = conn.cursor()
 
438
  def can_make_api_calls(requests_needed):
439
  """
440
  Checks if there are enough API calls remaining.
 
 
 
 
 
 
441
  """
442
  count, last_reset = get_api_usage()
443
  today = datetime.now().date()
444
  if today >= last_reset + timedelta(days=30):
445
  reset_api_usage()
446
  count, last_reset = get_api_usage()
447
+ return count >= requests_needed
 
 
 
448
 
449
  def decrement_api_calls(requests_used):
450
  """
451
+ Decrements the API usage count.
 
 
 
452
  """
453
  conn = sqlite3.connect('applications.db')
454
  c = conn.cursor()
455
  c.execute('SELECT count FROM api_usage WHERE id = 1')
456
  row = c.fetchone()
457
  if row:
458
+ new_count = max(row[0] - requests_used, 0)
 
 
459
  c.execute('UPDATE api_usage SET count = ? WHERE id = 1', (new_count,))
460
  conn.commit()
461
  conn.close()
462
 
463
  # -------------------------------
464
+ # Application Tracking Functions
465
  # -------------------------------
 
466
  def init_db():
467
  """
468
+ Initializes the SQLite database and creates the applications table.
469
  """
470
  conn = sqlite3.connect('applications.db')
471
  c = conn.cursor()
 
545
  conn.close()
546
 
547
  # -------------------------------
548
+ # Learning Path Generation
549
  # -------------------------------
550
+ @st.cache_data(ttl=86400)
 
551
  def generate_learning_path(career_goal, current_skills):
552
  """
553
+ Generates a personalized learning path using Groq.
554
  """
555
  prompt = f"""
556
+ Based on the following career goal and current skills, create a personalized learning path that includes recommended courses, projects, and milestones.
557
 
558
  **Career Goal:**
559
  {career_goal}
 
563
 
564
  **Learning Path:**
565
  """
 
566
  try:
567
  response = llm.invoke(prompt)
568
+ return response.content.strip()
 
569
  except Exception as e:
570
  st.error(f"Error generating learning path: {e}")
571
  return ""
 
573
  # -------------------------------
574
  # YouTube Video Search and Embed Functions
575
  # -------------------------------
576
+ @st.cache_data(ttl=86400)
 
577
  def search_youtube_videos(query, max_results=2, video_duration="long"):
578
  """
579
+ Searches YouTube for videos matching the query.
 
 
 
 
 
 
 
 
580
  """
581
  search_url = "https://www.googleapis.com/youtube/v3/search"
582
  params = {
 
599
 
600
  def embed_youtube_videos(video_urls, module_name):
601
  """
602
+ Embeds YouTube videos.
 
 
 
 
603
  """
604
  for url in video_urls:
605
  st.video(url)
606
 
607
  # -------------------------------
608
+ # Application Modules (Pages)
609
  # -------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
  def email_generator_page():
611
  st.header("📧 Automated Email Generator")
612
+ st.write("Generate personalized cold emails based on job postings and your resume.")
613
 
 
 
 
 
 
614
  col1, col2 = st.columns(2)
615
  with col1:
616
  job_link = st.text_input("🔗 Enter the job link:")
617
  with col2:
618
+ uploaded_file = st.file_uploader("📄 Upload your resume (PDF):", type="pdf")
619
 
620
  if st.button("Generate Email"):
621
  if not job_link:
 
624
  if not uploaded_file:
625
  st.error("Please upload your resume.")
626
  return
 
627
  with st.spinner("Processing..."):
 
628
  job_description = extract_job_description(job_link)
629
  if not job_description:
630
  st.error("Failed to extract job description.")
631
  return
 
 
632
  requirements = extract_requirements(job_description)
633
  if not requirements:
634
  st.error("Failed to extract requirements.")
635
  return
 
 
636
  resume_text = extract_text_from_pdf(uploaded_file)
637
  if not resume_text:
638
  st.error("Failed to extract text from resume.")
639
  return
 
 
640
  email_text = generate_email(job_description, requirements, resume_text)
641
  if email_text:
642
  st.subheader("📨 Generated Email:")
643
  st.write(email_text)
644
+ st.download_button("Download Email", data=email_text, file_name="generated_email.txt", mime="text/plain")
 
 
 
 
 
 
645
  else:
646
  st.error("Failed to generate email.")
647
 
648
  def cover_letter_generator_page():
649
  st.header("📝 Automated Cover Letter Generator")
650
+ st.write("Generate personalized cover letters based on job postings and your resume.")
651
 
 
 
 
 
 
652
  col1, col2 = st.columns(2)
653
  with col1:
654
  job_link = st.text_input("🔗 Enter the job link:")
655
  with col2:
656
+ uploaded_file = st.file_uploader("📄 Upload your resume (PDF):", type="pdf")
657
 
658
  if st.button("Generate Cover Letter"):
659
  if not job_link:
 
662
  if not uploaded_file:
663
  st.error("Please upload your resume.")
664
  return
 
665
  with st.spinner("Processing..."):
 
666
  job_description = extract_job_description(job_link)
667
  if not job_description:
668
  st.error("Failed to extract job description.")
669
  return
 
 
670
  requirements = extract_requirements(job_description)
671
  if not requirements:
672
  st.error("Failed to extract requirements.")
673
  return
 
 
674
  resume_text = extract_text_from_pdf(uploaded_file)
675
  if not resume_text:
676
  st.error("Failed to extract text from resume.")
677
  return
 
 
678
  cover_letter = generate_cover_letter(job_description, requirements, resume_text)
679
  if cover_letter:
680
  st.subheader("📝 Generated Cover Letter:")
681
  st.write(cover_letter)
682
+ st.download_button("Download Cover Letter", data=cover_letter, file_name="generated_cover_letter.txt", mime="text/plain")
 
 
 
 
 
 
683
  else:
684
  st.error("Failed to generate cover letter.")
685
 
686
  def resume_analysis_page():
687
  st.header("📄 Resume Analysis and Optimization")
688
+ st.write("Enhance your resume by extracting key information, suggestions, and visual analytics.")
689
+
690
+ uploaded_file = st.file_uploader("📂 Upload your resume (PDF):", type="pdf")
 
 
 
 
691
  if uploaded_file:
692
  resume_text = extract_text_from_pdf(uploaded_file)
693
  if resume_text:
694
  st.success("✅ Resume uploaded successfully!")
 
 
695
  st.subheader("🔍 Extracted Information")
 
 
696
  tabs = st.tabs(["💼 Skills", "🔑 Suggested Keywords"])
 
697
  with tabs[0]:
698
  skills = extract_skills(resume_text)
699
  if skills:
700
  st.markdown("**Identified Skills:**")
 
701
  cols = st.columns(4)
702
  for idx, skill in enumerate(skills, 1):
703
  cols[idx % 4].write(f"- {skill}")
704
  else:
705
  st.info("No skills extracted.")
 
706
  with tabs[1]:
707
  keywords = suggest_keywords(resume_text)
708
  if keywords:
709
  st.markdown("**Suggested Keywords for ATS Optimization:**")
 
710
  cols = st.columns(4)
711
  for idx, keyword in enumerate(keywords, 1):
712
  cols[idx % 4].write(f"- {keyword}")
713
  else:
714
  st.info("No keywords suggested.")
 
 
715
  st.subheader("🛠️ Optimization Suggestions")
 
 
 
 
 
 
 
716
  st.markdown("""
717
+ - **Keyword Optimization:** Incorporate suggested keywords.
718
+ - **Highlight Relevant Sections:** Emphasize skills that match job requirements.
719
+ - **Consistent Formatting:** Ensure readability and structure.
720
  """)
 
 
721
  st.subheader("📊 Visual Resume Analytics")
 
 
722
  viz_col1, viz_col2 = st.columns(2)
 
723
  with viz_col1:
724
  if skills:
725
  st.markdown("**Skill Distribution:**")
 
727
  st.plotly_chart(fig_skills, use_container_width=True)
728
  else:
729
  st.info("No skills to display.")
 
730
  with viz_col2:
731
  fig_experience = create_experience_timeline(resume_text)
732
  if fig_experience:
 
734
  st.plotly_chart(fig_experience, use_container_width=True)
735
  else:
736
  st.info("Not enough data to generate an experience timeline.")
 
 
737
  st.subheader("💾 Save Resume Analysis")
738
  if st.button("Save Resume Analysis"):
739
  add_application(
 
753
 
754
  def application_tracking_dashboard():
755
  st.header("📋 Application Tracking Dashboard")
 
 
756
  init_db()
757
  init_api_usage_db()
758
+
 
759
  st.subheader("➕ Add New Application")
760
  with st.form("add_application"):
761
  job_title = st.text_input("🖇️ Job Title")
 
768
  uploaded_resume = st.file_uploader("📄 Upload Resume (PDF)", type="pdf")
769
  submitted = st.form_submit_button("➕ Add Application")
770
  if submitted:
771
+ job_description = extract_text_from_pdf(uploaded_file) if uploaded_file else ""
 
 
 
772
  if uploaded_resume:
773
  resume_text = extract_text_from_pdf(uploaded_resume)
774
  skills = extract_skills(resume_text)
 
787
  skills=skills
788
  )
789
  st.success("✅ Application added successfully!")
790
+
 
791
  st.subheader("📊 Your Applications")
792
  applications = fetch_applications()
793
  if applications:
794
  df = pd.DataFrame(applications)
795
  df = df.drop(columns=["Job Description", "Resume Text", "Skills"])
796
  st.dataframe(df)
 
 
797
  csv = df.to_csv(index=False).encode('utf-8')
798
+ st.download_button("💾 Download Applications as CSV", data=csv, file_name='applications.csv', mime='text/csv')
 
 
 
 
 
 
 
799
  st.subheader("📥 Import Applications")
800
  uploaded_csv = st.file_uploader("📁 Upload a CSV file", type="csv")
801
  if uploaded_csv:
802
  try:
803
  imported_df = pd.read_csv(uploaded_csv)
 
804
  required_columns = {"Job Title", "Company", "Application Date", "Status", "Deadline", "Notes"}
805
  if not required_columns.issubset(imported_df.columns):
806
  st.error("❌ Uploaded CSV is missing required columns.")
807
  else:
808
+ for _, row in imported_df.iterrows():
 
 
 
 
 
 
 
 
 
809
  add_application(
810
+ job_title=row.get("Job Title", "N/A"),
811
+ company=row.get("Company", "N/A"),
812
+ application_date=row.get("Application Date", datetime.now().strftime("%Y-%m-%d")),
813
+ status=row.get("Status", "Applied"),
814
+ deadline=row.get("Deadline", ""),
815
+ notes=row.get("Notes", ""),
816
+ job_description=row.get("Job Description", ""),
817
+ resume_text=row.get("Resume Text", ""),
818
+ skills=row.get("Skills", "").split(', ') if row.get("Skills") else []
819
  )
820
  st.success("✅ Applications imported successfully!")
821
  except Exception as e:
822
  st.error(f"❌ Error importing applications: {e}")
 
 
823
  for app in applications:
824
  with st.expander(f"{app['Job Title']} at {app['Company']}"):
825
  st.write(f"**📅 Application Date:** {app['Application Date']}")
826
  st.write(f"**⏰ Deadline:** {app['Deadline']}")
827
  st.write(f"**📈 Status:** {app['Status']}")
828
  st.write(f"**📝 Notes:** {app['Notes']}")
 
 
 
 
 
 
829
  new_status = st.selectbox("🔄 Update Status:", ["Applied", "Interviewing", "Offered", "Rejected"], key=f"status_{app['ID']}")
830
  if st.button("🔁 Update Status", key=f"update_{app['ID']}"):
831
  update_application_status(app['ID'], new_status)
832
  st.success("✅ Status updated successfully!")
 
833
  if st.button("🗑️ Delete Application", key=f"delete_{app['ID']}"):
834
  delete_application(app['ID'])
835
  st.success("✅ Application deleted successfully!")
 
838
 
839
  def job_recommendations_module():
840
  st.header("🔍 Job Matching & Recommendations")
841
+ st.write("Discover job opportunities tailored to your skills and preferences.")
 
 
 
 
 
842
  st.subheader("🎯 Set Your Preferences")
843
  with st.form("preferences_form"):
844
+ job_title = st.text_input("🔍 Desired Job Title", placeholder="e.g., Data Scientist")
845
+ location = st.text_input("📍 Preferred Location", placeholder="e.g., New York, USA or Remote")
846
  category = st.selectbox("📂 Job Category", ["", "Engineering", "Marketing", "Design", "Sales", "Finance", "Healthcare", "Education", "Other"])
847
  user_skills_input = st.text_input("💡 Your Skills (comma-separated)", placeholder="e.g., Python, Machine Learning, SQL")
848
  submitted = st.form_submit_button("🚀 Get Recommendations")
 
849
  if submitted:
850
  if not job_title or not user_skills_input:
851
  st.error("❌ Please enter both job title and your skills.")
852
  return
 
853
  user_skills = [skill.strip() for skill in user_skills_input.split(",") if skill.strip()]
854
+ user_preferences = {"job_title": job_title, "location": location, "category": category}
 
 
 
 
 
855
  with st.spinner("🔄 Fetching job recommendations..."):
 
856
  recommended_jobs = recommend_jobs(user_skills, user_preferences)
 
857
  if recommended_jobs:
858
  st.subheader("💼 Recommended Jobs:")
859
  for idx, job in enumerate(recommended_jobs, 1):
 
860
  job_title_display = job.get("title") or job.get("name") or job.get("jobTitle")
861
  company_display = job.get("company", {}).get("name") or job.get("company_name") or job.get("employer", {}).get("name")
862
  location_display = job.get("candidate_required_location") or job.get("location") or job.get("country")
 
863
  job_url = job.get("url") or job.get("redirect_url") or job.get("url_standard")
 
864
  st.markdown(f"### {idx}. {job_title_display}")
865
  st.markdown(f"**🏢 Company:** {company_display}")
866
  st.markdown(f"**📍 Location:** {location_display}")
 
871
 
872
  def interview_preparation_module():
873
  st.header("🎤 Interview Preparation")
874
+ st.write("Prepare for your interviews with tailored mock questions and answers.")
 
 
 
 
 
875
  col1, col2 = st.columns(2)
876
  with col1:
877
  job_title = st.text_input("🔍 Enter the job title you're applying for:")
878
  with col2:
879
  company = st.text_input("🏢 Enter the company name:")
 
880
  if st.button("🎯 Generate Mock Interview Questions"):
881
  if not job_title or not company:
882
  st.error("❌ Please enter both job title and company name.")
883
  return
884
  with st.spinner("⏳ Generating questions..."):
 
885
  prompt = f"""
886
  Generate a list of 50 interview questions along with their answers for the position of {job_title} at {company}. Each question should be followed by a concise and professional answer.
887
  """
 
888
  try:
 
889
  qa_text = llm.invoke(prompt).content.strip()
 
890
  qa_pairs = qa_text.split('\n\n')
891
  st.subheader("🗣️ Mock Interview Questions and Answers:")
892
  for idx, qa in enumerate(qa_pairs, 1):
 
903
 
904
  def personalized_learning_paths_module():
905
  st.header("📚 Personalized Learning Paths")
906
+ st.write("Receive tailored learning plans to help you achieve your career goals, complemented with curated video resources.")
 
 
 
 
 
907
  col1, col2 = st.columns(2)
908
  with col1:
909
+ career_goal = st.text_input("🎯 Enter your career goal (e.g., Data Scientist):")
910
  with col2:
911
  current_skills = st.text_input("💡 Enter your current skills (comma-separated):")
 
912
  if st.button("🚀 Generate Learning Path"):
913
  if not career_goal or not current_skills:
914
  st.error("❌ Please enter both career goal and current skills.")
 
918
  if learning_path:
919
  st.subheader("📜 Your Personalized Learning Path:")
920
  st.write(learning_path)
 
 
 
 
 
 
 
 
 
921
  modules = re.split(r'\d+\.\s+', learning_path)
922
  modules = [module.strip() for module in modules if module.strip()]
 
923
  st.subheader("📹 Recommended YouTube Videos for Each Module:")
924
  for module in modules:
 
925
  video_urls = search_youtube_videos(query=module, max_results=2, video_duration="long")
926
  if video_urls:
927
  st.markdown(f"### {module}")
 
933
 
934
  def networking_opportunities_module():
935
  st.header("🤝 Networking Opportunities")
936
+ st.write("Expand your professional network by connecting with relevant industry peers and groups.")
 
 
 
 
 
937
  col1, col2 = st.columns(2)
938
  with col1:
939
  user_skills = st.text_input("💡 Enter your key skills (comma-separated):")
940
  with col2:
941
+ industry = st.text_input("🏭 Enter your industry (e.g., Technology):")
 
942
  if st.button("🔍 Find Networking Opportunities"):
943
  if not user_skills or not industry:
944
  st.error("❌ Please enter both key skills and industry.")
945
  return
946
  with st.spinner("🔄 Fetching networking opportunities..."):
 
947
  prompt = f"""
948
  Based on the following skills: {user_skills}, and industry: {industry}, suggest relevant LinkedIn groups, professional organizations, and industry events for networking.
949
  """
 
956
 
957
  def feedback_and_improvement_module():
958
  st.header("🗣️ Feedback and Continuous Improvement")
959
+ st.write("We value your feedback! Let us know how we can improve your experience.")
 
 
 
 
960
  with st.form("feedback_form"):
961
  name = st.text_input("👤 Your Name")
962
  email = st.text_input("📧 Your Email")
963
  feedback_type = st.selectbox("📂 Type of Feedback", ["Bug Report", "Feature Request", "General Feedback"])
964
  feedback = st.text_area("📝 Your Feedback")
965
  submitted = st.form_submit_button("✅ Submit")
 
966
  if submitted:
967
  if not name or not email or not feedback:
968
  st.error("❌ Please fill in all the fields.")
969
  else:
970
+ # You can store the feedback in a database or send via email
 
 
971
  st.success("✅ Thank you for your feedback!")
972
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  def resource_library_page():
974
  st.header("📚 Resource Library")
975
+ st.write("Access a collection of templates and guides to enhance your job search.")
 
 
 
 
976
  resources = [
977
+ {"title": "Resume Template", "description": "A professional resume template in DOCX format.", "file": "./resume_template.docx"},
978
+ {"title": "Cover Letter Template", "description": "A customizable cover letter template.", "file": "./cover_letter_template.docx"},
979
+ {"title": "Job Application Checklist", "description": "A checklist to ensure you cover all steps.", "file": "./application_checklist.pdf"}
 
 
 
 
 
 
 
 
 
 
 
 
980
  ]
 
981
  for resource in resources:
982
  st.markdown(f"### {resource['title']}")
983
  st.write(resource['description'])
984
  try:
985
  with open(resource['file'], "rb") as file:
986
+ st.download_button("⬇️ Download", data=file, file_name=os.path.basename(resource['file']), mime="application/octet-stream")
 
 
 
 
 
987
  except FileNotFoundError:
988
  st.error(f"❌ File {resource['file']} not found. Please ensure the file is in the correct directory.")
989
  st.write("---")
990
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
991
  def chatbot_support_page():
992
  st.header("🤖 AI-Powered Chatbot Support")
993
+ st.write("Have questions or need assistance? Chat with our AI-powered assistant!")
 
 
 
 
 
994
  if 'chat_history' not in st.session_state:
995
  st.session_state['chat_history'] = []
 
 
996
  user_input = st.text_input("🗨️ You:", key="user_input")
 
997
  if st.button("Send"):
998
  if user_input:
 
999
  st.session_state['chat_history'].append({"message": user_input, "is_user": True})
1000
  prompt = f"""
1001
  You are a helpful assistant for a Job Application Assistant app. Answer the user's query based on the following context:
 
1003
  {user_input}
1004
  """
1005
  try:
 
1006
  response = llm.invoke(prompt)
1007
  assistant_message = response.content.strip()
 
1008
  st.session_state['chat_history'].append({"message": assistant_message, "is_user": False})
1009
  except Exception as e:
1010
  error_message = "❌ Sorry, I encountered an error while processing your request."
1011
  st.session_state['chat_history'].append({"message": error_message, "is_user": False})
1012
  st.error(f"❌ Error in chatbot: {e}")
 
 
1013
  for chat in st.session_state['chat_history']:
1014
  if chat['is_user']:
1015
  message(chat['message'], is_user=True, avatar_style="thumbs")
 
1018
 
1019
  def help_page():
1020
  st.header("❓ Help & FAQ")
 
1021
  with st.expander("🛠️ How do I generate a cover letter?"):
1022
+ st.write("Navigate to the **Cover Letter Generator** section, enter the job link, upload your resume, and click **Generate Cover Letter**.")
 
 
 
1023
  with st.expander("📋 How do I track my applications?"):
1024
+ st.write("Use the **Application Tracking Dashboard** to add and manage your job applications.")
 
 
 
1025
  with st.expander("📄 How can I optimize my resume?"):
1026
+ st.write("Upload your resume in the **Resume Analysis** section to extract skills and receive optimization suggestions.")
 
 
 
1027
  with st.expander("📥 How do I import my applications?"):
1028
+ st.write("In the **Application Tracking Dashboard**, use the **Import Applications** section to upload a CSV file with the required columns.")
 
 
 
1029
  with st.expander("🗣️ How do I provide feedback?"):
1030
+ st.write("Go to the **Feedback** section, fill out the form, and submit your feedback.")
 
 
1031
 
1032
  # -------------------------------
1033
+ # Main Application
1034
  # -------------------------------
 
1035
  def main_app():
 
1036
  st.markdown(
1037
  """
1038
  <style>
1039
+ .reportview-container { background-color: #f5f5f5; }
1040
+ .sidebar .sidebar-content { background-image: linear-gradient(#2e7bcf, #2e7bcf); color: white; }
 
 
 
 
 
1041
  </style>
1042
  """,
1043
  unsafe_allow_html=True
1044
  )
 
 
1045
  with st.sidebar:
1046
  selected = option_menu(
1047
  menu_title="📂 Main Menu",
1048
+ options=[
1049
+ "Email Generator", "Cover Letter Generator", "Resume Analysis",
1050
+ "Application Tracking", "Job Recommendations", "Interview Preparation",
1051
+ "Personalized Learning Paths", "Networking Opportunities",
1052
+ "Feedback", "Resource Library", "Chatbot Support", "Help"
1053
+ ],
1054
+ icons=[
1055
+ "envelope", "file-earmark-text", "file-person", "briefcase",
1056
+ "search", "microphone", "book", "people",
1057
+ "chat-left-text", "collection", "robot", "question-circle"
1058
+ ],
1059
  menu_icon="cast",
1060
  default_index=0,
1061
  styles={
 
1065
  "nav-link-selected": {"background-color": "#1e5aab"},
1066
  }
1067
  )
 
 
1068
  if selected == "Email Generator":
1069
  email_generator_page()
1070
  elif selected == "Cover Letter Generator":
 
1075
  application_tracking_dashboard()
1076
  elif selected == "Job Recommendations":
1077
  job_recommendations_module()
 
 
1078
  elif selected == "Interview Preparation":
1079
  interview_preparation_module()
1080
  elif selected == "Personalized Learning Paths":
 
1083
  networking_opportunities_module()
1084
  elif selected == "Feedback":
1085
  feedback_and_improvement_module()
 
 
1086
  elif selected == "Resource Library":
1087
  resource_library_page()
 
 
1088
  elif selected == "Chatbot Support":
1089
  chatbot_support_page()
1090
  elif selected == "Help":
1091
  help_page()
1092
 
 
1093
  if __name__ == "__main__":
1094
  main_app()