# app.py import streamlit as st from streamlit_option_menu import option_menu from langchain_groq import ChatGroq from langchain_core.prompts import PromptTemplate import fitz # PyMuPDF import requests from bs4 import BeautifulSoup import uuid import plotly.express as px import re import pandas as pd import json import sqlite3 import streamlit_authenticator as stauth from datetime import datetime, timedelta # Initialize the LLM with your Groq API key from Streamlit secrets llm = ChatGroq( temperature=0, groq_api_key="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", # Securely accessing the Groq API key model_name="llama-3.1-70b-versatile" ) def extract_text_from_pdf(pdf_file): """ Extracts text from an uploaded PDF file. """ text = "" try: with fitz.open(stream=pdf_file.read(), filetype="pdf") as doc: for page in doc: text += page.get_text() return text except Exception as e: st.error(f"Error extracting text from PDF: {e}") return "" def extract_job_description(job_link): """ Fetches and extracts job description text from a given URL. """ try: headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" } response = requests.get(job_link, headers=headers) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') # You might need to adjust the selectors based on the website's structure job_description = soup.get_text(separator='\n') return job_description.strip() except Exception as e: st.error(f"Error fetching job description: {e}") return "" def extract_requirements(job_description): """ Uses Groq to extract job requirements from the job description. """ prompt_text = f""" The following is a job description: {job_description} Extract the list of job requirements, qualifications, and skills from the job description. Provide them as a numbered list. Requirements: """ prompt = PromptTemplate.from_template(prompt_text) chain = prompt | llm response = chain.invoke({}) requirements = response.content.strip() return requirements def generate_email(job_description, requirements, resume_text): """ Generates a personalized cold email using Groq based on the job description, requirements, and resume. """ prompt_text = f""" 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: **Job Description:** {job_description} **Extracted Requirements:** {requirements} **Your Resume:** {resume_text} **Email Requirements:** - **Introduction:** Briefly introduce yourself and mention the specific job you are applying for. - **Body:** Highlight your relevant skills, projects, internships, and leadership experiences that align with the job requirements. - **Value Proposition:** Explain how your fresh perspective and recent academic knowledge can add value to the company. - **Closing:** Express enthusiasm for the opportunity, mention your willingness for an interview, and thank the recipient for their time. **Email:** """ prompt = PromptTemplate.from_template(prompt_text) chain = prompt | llm response = chain.invoke({}) email_text = response.content.strip() return email_text def generate_cover_letter(job_description, requirements, resume_text): """ Generates a personalized cover letter using Groq based on the job description, requirements, and resume. """ prompt_text = f""" You are Adithya S Nair, a recent Computer Science graduate specializing in Artificial Intelligence and Machine Learning. Compose a personalized and professional cover letter based on the following information: **Job Description:** {job_description} **Extracted Requirements:** {requirements} **Your Resume:** {resume_text} **Cover Letter Requirements:** 1. **Greeting:** Address the hiring manager by name if available; otherwise, use a generic greeting such as "Dear Hiring Manager." 2. **Introduction:** Begin with an engaging opening that mentions the specific position you are applying for and conveys your enthusiasm. 3. **Body:** - **Skills and Experiences:** Highlight relevant technical skills, projects, internships, and leadership roles that align with the job requirements. - **Alignment:** Demonstrate how your academic background and hands-on experiences make you a suitable candidate for the role. 4. **Value Proposition:** Explain how your fresh perspective, recent academic knowledge, and eagerness to learn can contribute to the company's success. 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. 6. **Professional Tone:** Maintain a respectful and professional tone throughout the letter. **Cover Letter:** """ prompt = PromptTemplate.from_template(prompt_text) chain = prompt | llm response = chain.invoke({}) cover_letter = response.content.strip() return cover_letter def extract_skills(text): """ Extracts a list of skills from the resume text using Groq. """ prompt_text = f""" Extract a comprehensive list of technical and soft skills from the following resume text. Provide the skills as a comma-separated list. Resume Text: {text} Skills: """ prompt = PromptTemplate.from_template(prompt_text) chain = prompt | llm response = chain.invoke({}) skills = response.content.strip() # Clean and split the skills skills_list = [skill.strip() for skill in re.split(',|\n', skills) if skill.strip()] return skills_list def suggest_keywords(resume_text, job_description=None): """ Suggests additional relevant keywords to enhance resume compatibility with ATS. """ prompt_text = f""" Analyze the following resume text and suggest additional relevant keywords that can enhance its compatibility with Applicant Tracking Systems (ATS). If a job description is provided, tailor the keywords to align with the job requirements. Resume Text: {resume_text} Job Description: {job_description if job_description else "N/A"} Suggested Keywords: """ prompt = PromptTemplate.from_template(prompt_text) chain = prompt | llm response = chain.invoke({}) keywords = response.content.strip() keywords_list = [keyword.strip() for keyword in re.split(',|\n', keywords) if keyword.strip()] return keywords_list def get_job_recommendations(resume_text, location="India"): """ Fetches job recommendations using the Remotive.io API based on the user's skills. """ # Extract skills from resume skills = extract_skills(resume_text) query = " ".join(skills) if skills else "Software Engineer" url = "https://remotive.io/api/remote-jobs" params = { "search": query, "limit": 20 } try: response = requests.get(url, params=params) response.raise_for_status() data = response.json() jobs = data.get("jobs", []) job_list = [] for job in jobs: job_info = { "title": job.get("title"), "company": job.get("company_name"), "link": job.get("url"), "job_description": job.get("description") # Including job description for Skill Matching } job_list.append(job_info) return job_list except Exception as e: st.error(f"Error fetching job recommendations: {e}") return [] def create_skill_distribution_chart(skills): """ Creates a bar chart showing the distribution of skills. """ skill_counts = {} for skill in skills: skill_counts[skill] = skill_counts.get(skill, 0) + 1 df = pd.DataFrame(list(skill_counts.items()), columns=['Skill', 'Count']) fig = px.bar(df, x='Skill', y='Count', title='Skill Distribution') return fig def create_experience_timeline(resume_text): """ Creates an experience timeline from the resume text. """ # Extract work experience details using Groq prompt_text = f""" 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). Resume Text: {resume_text} Table: """ prompt = PromptTemplate.from_template(prompt_text) chain = prompt | llm response = chain.invoke({}) table_text = response.content.strip() # Parse the table_text to create a DataFrame data = [] for line in table_text.split('\n'): if line.strip() and not line.lower().startswith("job title"): parts = line.split('|') if len(parts) == 3: job_title = parts[0].strip() company = parts[1].strip() duration = parts[2].strip() # Convert duration to a float representing years duration_years = parse_duration(duration) data.append({"Job Title": job_title, "Company": company, "Duration (years)": duration_years}) df = pd.DataFrame(data) if not df.empty: # Create a cumulative duration for timeline df['Start Year'] = df['Duration (years)'].cumsum() - df['Duration (years)'] df['End Year'] = df['Duration (years)'].cumsum() fig = px.timeline(df, x_start="Start Year", x_end="End Year", y="Job Title", color="Company", title="Experience Timeline") fig.update_yaxes(categoryorder="total ascending") return fig else: return None def parse_duration(duration_str): """ Parses duration strings like '2 years' or '6 months' into float years. """ try: if 'year' in duration_str.lower(): years = float(re.findall(r'\d+\.?\d*', duration_str)[0]) return years elif 'month' in duration_str.lower(): months = float(re.findall(r'\d+\.?\d*', duration_str)[0]) return months / 12 else: return 0 except: return 0 def init_db(): """ Initializes the SQLite database for application tracking. """ conn = sqlite3.connect('applications.db') c = conn.cursor() c.execute(''' CREATE TABLE IF NOT EXISTS applications ( id INTEGER PRIMARY KEY AUTOINCREMENT, job_title TEXT, company TEXT, application_date TEXT, status TEXT, deadline TEXT, notes TEXT, job_description TEXT, resume_text TEXT, skills TEXT ) ''') conn.commit() conn.close() def add_application(job_title, company, application_date, status, deadline, notes, job_description, resume_text, skills): """ Adds a new application to the database. """ conn = sqlite3.connect('applications.db') c = conn.cursor() c.execute(''' INSERT INTO applications (job_title, company, application_date, status, deadline, notes, job_description, resume_text, skills) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (job_title, company, application_date, status, deadline, notes, job_description, resume_text, ', '.join(skills))) conn.commit() conn.close() def fetch_applications(): """ Fetches all applications from the database. """ conn = sqlite3.connect('applications.db') c = conn.cursor() c.execute('SELECT * FROM applications') data = c.fetchall() conn.close() applications = [] for app in data: applications.append({ "ID": app[0], "Job Title": app[1], "Company": app[2], "Application Date": app[3], "Status": app[4], "Deadline": app[5], "Notes": app[6], "Job Description": app[7], "Resume Text": app[8], "Skills": app[9].split(', ') }) return applications def update_application_status(app_id, new_status): """ Updates the status of an application. """ conn = sqlite3.connect('applications.db') c = conn.cursor() c.execute('UPDATE applications SET status = ? WHERE id = ?', (new_status, app_id)) conn.commit() conn.close() def delete_application(app_id): """ Deletes an application from the database. """ conn = sqlite3.connect('applications.db') c = conn.cursor() c.execute('DELETE FROM applications WHERE id = ?', (app_id,)) conn.commit() conn.close() def generate_learning_path(career_goal, current_skills): """ Generates a personalized learning path using Groq based on career goal and current skills. """ prompt = f""" Based on the following career goal and current skills, create a personalized learning path that includes recommended courses, projects, and milestones to achieve the career goal. **Career Goal:** {career_goal} **Current Skills:** {current_skills} **Learning Path:** """ response = llm.invoke({"input": prompt}) learning_path = response.content.strip() return learning_path def setup_authentication(): """ Sets up user authentication using streamlit-authenticator. """ # In a real application, retrieve user data from a secure database names = ["Adithya S Nair"] # Replace with dynamic user data usernames = ["adithya"] # Passwords should be hashed in a real application passwords = ["$2b$12$KIXvUq2YdZ19FWJGzTqFUeAewH1/gO7xmD2z77Qvxh3A1F1C9KdaW"] # "password" hashed authenticator = stauth.Authenticate(names, usernames, passwords, "job_app_assistant", "abcdef", cookie_expiry_days=30) return authenticator def application_tracking_dashboard(): st.header("Application Tracking Dashboard") # Initialize database init_db() # Form to add a new application st.subheader("Add New Application") with st.form("add_application"): job_title = st.text_input("Job Title") company = st.text_input("Company") application_date = st.date_input("Application Date", datetime.today()) status = st.selectbox("Status", ["Applied", "Interviewing", "Offered", "Rejected"]) deadline = st.date_input("Application Deadline", datetime.today() + timedelta(days=30)) notes = st.text_area("Notes") uploaded_file = st.file_uploader("Upload Job Description (PDF)", type="pdf") uploaded_resume = st.file_uploader("Upload Resume (PDF)", type="pdf") submitted = st.form_submit_button("Add Application") if submitted: if uploaded_file: job_description = extract_text_from_pdf(uploaded_file) else: job_description = "" if uploaded_resume: resume_text = extract_text_from_pdf(uploaded_resume) skills = extract_skills(resume_text) else: resume_text = "" skills = [] add_application( job_title=job_title, company=company, application_date=application_date.strftime("%Y-%m-%d"), status=status, deadline=deadline.strftime("%Y-%m-%d"), notes=notes, job_description=job_description, resume_text=resume_text, skills=skills ) st.success("Application added successfully!") # Display applications st.subheader("Your Applications") applications = fetch_applications() if applications: df = pd.DataFrame(applications) df = df.drop(columns=["Job Description", "Resume Text", "Skills"]) st.dataframe(df) # Actions: Update Status or Delete for app in applications: with st.expander(f"{app['Job Title']} at {app['Company']}"): st.write(f"**Application Date:** {app['Application Date']}") st.write(f"**Deadline:** {app['Deadline']}") st.write(f"**Status:** {app['Status']}") st.write(f"**Notes:** {app['Notes']}") if app['Job Description']: st.write("**Job Description:**") st.write(app['Job Description'][:500] + "...") if app['Skills']: st.write("**Skills:**", ', '.join(app['Skills'])) # Update status new_status = st.selectbox("Update Status:", ["Applied", "Interviewing", "Offered", "Rejected"], key=f"status_{app['ID']}") if st.button("Update Status", key=f"update_{app['ID']}"): update_application_status(app['ID'], new_status) st.success("Status updated successfully!") # Delete application if st.button("Delete Application", key=f"delete_{app['ID']}"): delete_application(app['ID']) st.success("Application deleted successfully!") else: st.write("No applications found.") def interview_preparation_module(): st.header("Interview Preparation") st.write(""" Prepare for your interviews with tailored mock questions and expert tips. """) # Input fields job_title = st.text_input("Enter the job title you're applying for:") company = st.text_input("Enter the company name:") if st.button("Generate Mock Interview Questions"): if not job_title or not company: st.error("Please enter both job title and company name.") return with st.spinner("Generating questions..."): prompt = f""" Generate a list of 10 interview questions for a {job_title} position at {company}. Include a mix of technical and behavioral questions. """ questions = llm.invoke({"input": prompt}).content.strip() st.subheader("Mock Interview Questions:") st.write(questions) # Optionally, provide sample answers or tips if st.checkbox("Show Sample Answers"): sample_prompt = f""" Provide sample answers for the following interview questions for a {job_title} position at {company}. Questions: {questions} Sample Answers: """ sample_answers = llm.invoke({"input": sample_prompt}).content.strip() st.subheader("Sample Answers:") st.write(sample_answers) def personalized_learning_paths_module(): st.header("Personalized Learning Paths") st.write(""" Receive tailored learning plans to help you acquire the skills needed for your desired career. """) # Input fields career_goal = st.text_input("Enter your career goal (e.g., Data Scientist, Machine Learning Engineer):") current_skills = st.text_input("Enter your current skills (comma-separated):") if st.button("Generate Learning Path"): if not career_goal or not current_skills: st.error("Please enter both career goal and current skills.") return with st.spinner("Generating your personalized learning path..."): learning_path = generate_learning_path(career_goal, current_skills) st.subheader("Your Personalized Learning Path:") st.write(learning_path) def networking_opportunities_module(): st.header("Networking Opportunities") st.write(""" Expand your professional network by connecting with relevant industry peers and joining professional groups. """) user_skills = st.text_input("Enter your key skills (comma-separated):") industry = st.text_input("Enter your industry (e.g., Technology, Finance):") if st.button("Find Networking Opportunities"): if not user_skills or not industry: st.error("Please enter both key skills and industry.") return with st.spinner("Fetching networking opportunities..."): # Suggest LinkedIn groups or connections based on skills and industry prompt = f""" Based on the following skills: {user_skills}, and industry: {industry}, suggest relevant LinkedIn groups, professional organizations, and industry events for networking. """ suggestions = llm.invoke({"input": prompt}).content.strip() st.subheader("Recommended Networking Groups and Events:") st.write(suggestions) def salary_estimation_module(): st.header("Salary Estimation and Negotiation Tips") st.write(""" Understand the salary expectations for your desired roles and learn effective negotiation strategies. """) job_title = st.text_input("Enter the job title:") location = st.text_input("Enter the location (e.g., Bangalore, India):") if st.button("Get Salary Estimate"): if not job_title or not location: st.error("Please enter both job title and location.") return with st.spinner("Fetching salary data..."): # Example using a placeholder API or precompiled data # Replace with actual API integration if available # For demonstration, we'll use a mock response prompt = f""" Provide an estimated salary range for a {job_title} in {location}. Include the minimum, average, and maximum salaries. """ salary_data = llm.invoke({"input": prompt}).content.strip() st.subheader("Salary Estimate:") st.write(salary_data) # Generate negotiation tips tips_prompt = f""" Provide a list of 5 effective tips for negotiating a salary for a {job_title} position in {location}. """ tips = llm.invoke({"input": tips_prompt}).content.strip() st.subheader("Negotiation Tips:") st.write(tips) def feedback_and_improvement_module(): st.header("Feedback and Continuous Improvement") st.write(""" We value your feedback! Let us know how we can improve your experience. """) with st.form("feedback_form"): name = st.text_input("Your Name") email = st.text_input("Your Email") feedback_type = st.selectbox("Type of Feedback", ["Bug Report", "Feature Request", "General Feedback"]) feedback = st.text_area("Your Feedback") submitted = st.form_submit_button("Submit") if submitted: if not name or not email or not feedback: st.error("Please fill in all the fields.") else: # Here you can implement logic to store feedback, e.g., in a database or send via email # For demonstration, we'll print to the console print(f"Feedback from {name} ({email}): {feedback_type} - {feedback}") st.success("Thank you for your feedback!") # ------------------------------- # Authentication Setup # ------------------------------- def setup_authentication(): """ Sets up user authentication using streamlit-authenticator. """ # In a real application, retrieve user data from a secure database names = ["Adithya S Nair"] # Replace with dynamic user data usernames = ["adithya"] # Passwords should be hashed in a real application passwords = ["$2b$12$KIXvUq2YdZ19FWJGzTqFUeAewH1/gO7xmD2z77Qvxh3A1F1C9KdaW"] # "password" hashed authenticator = stauth.Authenticate(names, usernames, passwords, "job_app_assistant", "abcdef", cookie_expiry_days=30) return authenticator # ------------------------------- # Main App with Sidebar Navigation # ------------------------------- def main(): st.set_page_config(page_title="Job Application Assistant", layout="wide") # Setup authentication authenticator = setup_authentication() name, authentication_status = authenticator.login("Login", "main") if authentication_status: with st.sidebar: selected = option_menu( "Main Menu", ["Email Generator", "Cover Letter Generator", "Resume Analysis", "Application Tracking", "Interview Preparation", "Personalized Learning Paths", "Networking Opportunities", "Salary Estimation", "Feedback"], icons=["envelope", "file-earmark-text", "file-person", "briefcase", "gear", "book", "people", "currency-dollar", "chat-left-text"], menu_icon="cast", default_index=0, ) if selected == "Email Generator": email_generator_page() elif selected == "Cover Letter Generator": cover_letter_generator_page() elif selected == "Resume Analysis": resume_analysis_page() elif selected == "Application Tracking": application_tracking_dashboard() elif selected == "Interview Preparation": interview_preparation_module() elif selected == "Personalized Learning Paths": personalized_learning_paths_module() elif selected == "Networking Opportunities": networking_opportunities_module() elif selected == "Salary Estimation": salary_estimation_module() elif selected == "Feedback": feedback_and_improvement_module() elif authentication_status == False: st.error("Username/password is incorrect") elif authentication_status == None: st.warning("Please enter your username and password") if __name__ == "__main__": main()