# 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 # Initialize the LLM with your Groq API key from Streamlit secrets llm = ChatGroq( temperature=0, groq_api_key=st.secrets["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 resume: {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') # Adjust selectors based on the website's structure for better extraction 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 JSearch 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://jsearch.p.rapidapi.com/search" headers = { "X-RapidAPI-Key": st.secrets["rapidapi_key"], # Accessing RapidAPI key securely "X-RapidAPI-Host": "jsearch.p.rapidapi.com" } params = { "query": query, "page": "1", "num_pages": "1", "size": "20", "remote_filter": "false", "location": location, "sort": "relevance", "salary_min": "0", "salary_max": "0", "salary_currency": "INR", "radius": "0", "company_type": "", "job_type": "", "degree_level": "", "career_level": "", "include_remote": "false" } try: response = requests.get(url, headers=headers, params=params) response.raise_for_status() data = response.json() jobs = data.get("data", []) job_list = [] for job in jobs: job_info = { "title": job.get("job_title"), "company": job.get("employer", {}).get("name"), "link": job.get("job_apply_link") or job.get("job_listing_url") } 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 # ------------------------------- # Page Functions # ------------------------------- def email_generator_page(): st.header("Automated Email Generator") st.write(""" This application generates a personalized cold email based on a job posting and your resume. """) # Input fields job_link = st.text_input("Enter the job link:") uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf") if st.button("Generate Email"): if not job_link: st.error("Please enter a job link.") return if not uploaded_file: st.error("Please upload your resume.") return with st.spinner("Processing..."): # Extract job description job_description = extract_job_description(job_link) if not job_description: st.error("Failed to extract job description.") return # Extract requirements requirements = extract_requirements(job_description) if not requirements: st.error("Failed to extract requirements.") return # Extract resume text resume_text = extract_text_from_pdf(uploaded_file) if not resume_text: st.error("Failed to extract text from resume.") return # Generate email email_text = generate_email(job_description, requirements, resume_text) if email_text: st.subheader("Generated Email:") st.write(email_text) # Provide download option st.download_button( label="Download Email", data=email_text, file_name="generated_email.txt", mime="text/plain" ) else: st.error("Failed to generate email.") def cover_letter_generator_page(): st.header("Automated Cover Letter Generator") st.write(""" This application generates a personalized cover letter based on a job posting and your resume. """) # Input fields job_link = st.text_input("Enter the job link:") uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf") if st.button("Generate Cover Letter"): if not job_link: st.error("Please enter a job link.") return if not uploaded_file: st.error("Please upload your resume.") return with st.spinner("Processing..."): # Extract job description job_description = extract_job_description(job_link) if not job_description: st.error("Failed to extract job description.") return # Extract requirements requirements = extract_requirements(job_description) if not requirements: st.error("Failed to extract requirements.") return # Extract resume text resume_text = extract_text_from_pdf(uploaded_file) if not resume_text: st.error("Failed to extract text from resume.") return # Generate cover letter cover_letter = generate_cover_letter(job_description, requirements, resume_text) if cover_letter: st.subheader("Generated Cover Letter:") st.write(cover_letter) # Provide download option st.download_button( label="Download Cover Letter", data=cover_letter, file_name="generated_cover_letter.txt", mime="text/plain" ) else: st.error("Failed to generate cover letter.") def resume_analysis_page(): import pandas as pd # Importing here to prevent unnecessary imports if not used st.header("Resume Analysis and Optimization") uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf") if uploaded_file: resume_text = extract_text_from_pdf(uploaded_file) if resume_text: st.success("Resume uploaded successfully!") # Perform analysis st.subheader("Extracted Information") # Extracted skills skills = extract_skills(resume_text) st.write("**Skills:**", ', '.join(skills)) # Extract keywords keywords = suggest_keywords(resume_text) st.write("**Suggested Keywords for ATS Optimization:**", ', '.join(keywords)) # Provide optimization suggestions st.subheader("Optimization Suggestions") st.write("- **Keyword Optimization:** Incorporate the suggested keywords to improve ATS compatibility.") st.write("- **Formatting:** Ensure consistent formatting for headings and bullet points to enhance readability.") st.write("- **Experience Details:** Provide specific achievements and quantify your accomplishments where possible.") # Visual Resume Analytics st.subheader("Visual Resume Analytics") # Skill Distribution Chart if skills: st.write("**Skill Distribution:**") fig_skills = create_skill_distribution_chart(skills) st.plotly_chart(fig_skills) # Experience Timeline (if applicable) fig_experience = create_experience_timeline(resume_text) if fig_experience: st.write("**Experience Timeline:**") st.plotly_chart(fig_experience) else: st.write("**Experience Timeline:** Not enough data to generate a timeline.") else: st.error("Failed to extract text from resume.") def job_recommendations_page(): st.header("Job Recommendations") uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf") if uploaded_file: resume_text = extract_text_from_pdf(uploaded_file) if resume_text: st.success("Resume uploaded successfully!") # Fetch job recommendations st.subheader("Recommended Jobs") jobs = get_job_recommendations(resume_text) if jobs: for job in jobs: st.write(f"**{job['title']}** at {job['company']}") st.markdown(f"[Apply Here]({job['link']})") else: st.write("No job recommendations found based on your skills.") else: st.error("Failed to extract text from resume.") def skill_matching_page(): st.header("Skill Matching and Gap Analysis") job_description_input = st.text_area("Paste the job description here:") uploaded_file = st.file_uploader("Upload your resume (PDF format):", type="pdf") if st.button("Analyze Skills"): if not job_description_input: st.error("Please paste the job description.") return if not uploaded_file: st.error("Please upload your resume.") return with st.spinner("Analyzing..."): # Extract resume text resume_text = extract_text_from_pdf(uploaded_file) if not resume_text: st.error("Failed to extract text from resume.") return # Extract skills resume_skills = extract_skills(resume_text) job_skills = extract_skills(job_description_input) # Find matches and gaps matching_skills = set(resume_skills).intersection(set(job_skills)) missing_skills = set(job_skills) - set(resume_skills) # Display results st.subheader("Matching Skills") st.write(', '.join(matching_skills) if matching_skills else "No matching skills found.") st.subheader("Missing Skills") st.write(', '.join(missing_skills) if missing_skills else "No missing skills.") # ------------------------------- # Main App with Sidebar Navigation # ------------------------------- def main(): st.set_page_config(page_title="Job Application Assistant", layout="wide") with st.sidebar: selected = option_menu( "Main Menu", ["Email Generator", "Cover Letter Generator", "Resume Analysis", "Job Recommendations", "Skill Matching"], icons=["envelope", "file-earmark-text", "file-person", "briefcase", "bar-chart"], 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 == "Job Recommendations": job_recommendations_page() elif selected == "Skill Matching": skill_matching_page() if __name__ == "__main__": main()