Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
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
|
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 |
-
-
|
107 |
-
-
|
108 |
-
-
|
109 |
-
-
|
110 |
"""
|
111 |
-
|
112 |
try:
|
113 |
response = llm.invoke(prompt)
|
114 |
-
|
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
|
124 |
"""
|
125 |
prompt = f"""
|
126 |
-
You are Adithya S Nair, a recent Computer Science graduate specializing in Artificial Intelligence and Machine Learning. Compose a
|
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.
|
139 |
-
2.
|
140 |
-
3.
|
141 |
-
|
142 |
-
|
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 |
-
|
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
|
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
|
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
|
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 |
-
|
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
|
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 |
-
|
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 |
-
|
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
|
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.
|
398 |
-
|
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)
|
415 |
def fetch_indeed_job_details_api(job_id, language="en_GB"):
|
416 |
"""
|
417 |
-
Fetches job details from Indeed API
|
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 |
-
|
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 |
-
|
442 |
-
|
443 |
-
|
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 |
-
|
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)
|
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
|
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 |
-
|
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
|
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 |
-
|
671 |
-
return True
|
672 |
-
else:
|
673 |
-
return False
|
674 |
|
675 |
def decrement_api_calls(requests_used):
|
676 |
"""
|
677 |
-
Decrements the API usage count
|
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
|
696 |
# -------------------------------
|
697 |
-
|
698 |
def init_db():
|
699 |
"""
|
700 |
-
Initializes the SQLite database and creates the applications table
|
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
|
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
|
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
|
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 |
-
|
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
|
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
|
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 |
-
#
|
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
|
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 |
-
|
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
|
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 |
-
|
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 |
-
|
1006 |
-
|
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 |
-
|
1055 |
-
|
1056 |
-
|
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
|
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=
|
1182 |
-
company=
|
1183 |
-
application_date=
|
1184 |
-
status=
|
1185 |
-
deadline=
|
1186 |
-
notes=
|
1187 |
-
job_description=
|
1188 |
-
resume_text=
|
1189 |
-
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
|
1230 |
-
location = st.text_input("📍 Preferred Location", placeholder="e.g., New York,
|
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
|
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
|
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 |
-
#
|
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 |
-
|
1454 |
-
|
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 |
-
|
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("")
|
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
|
1591 |
# -------------------------------
|
1592 |
-
|
1593 |
def main_app():
|
1594 |
-
# Apply a consistent theme or style
|
1595 |
st.markdown(
|
1596 |
"""
|
1597 |
<style>
|
1598 |
-
.reportview-container {
|
1599 |
-
|
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=[
|
1615 |
-
|
1616 |
-
|
1617 |
-
|
1618 |
-
|
1619 |
-
|
1620 |
-
|
1621 |
-
|
|
|
|
|
|
|
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()
|