import streamlit as st from streamlit_chat import message from langchain_google_genai import ChatGoogleGenerativeAI import os import PyPDF2 import docx import requests from bs4 import BeautifulSoup import re # Set up Gemini AI GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyDs-vtZBkyLmEUH5NgkfUNkMJ8kxg_pR3Y") chat_model = ChatGoogleGenerativeAI(model="gemini-1.5-pro", api_version="v1", google_api_key=GEMINI_API_KEY) # Set page config st.set_page_config(page_title="AI-Driven Job Assistant", layout="wide") # Initialize session state for messages if not set if "messages" not in st.session_state: st.session_state.messages = [] if "resume_text" not in st.session_state: st.session_state.resume_text = "" # Extract text from resume def extract_text_from_resume(resume_file): text = "" if resume_file.name.endswith(".pdf"): pdf_reader = PyPDF2.PdfReader(resume_file) for page in pdf_reader.pages: extracted_text = page.extract_text() if extracted_text: text += extracted_text + "\n" elif resume_file.name.endswith(".docx"): doc = docx.Document(resume_file) for para in doc.paragraphs: text += para.text + "\n" return text.strip() # Sidebar resume upload with st.sidebar: st.header("Upload Your Resume") resume = st.file_uploader("Upload PDF or DOCX", type=["pdf", "docx"]) if resume: st.success("Resume uploaded successfully!") if st.button("Proceed"): with st.spinner("Analyzing your resume..."): resume_text = extract_text_from_resume(resume) if resume_text: st.session_state.resume_text = resume_text ats_prompt = f"Analyze this resume and provide an ATS score (out of 100) along with improvement suggestions keep the response in short:\n\n{resume_text}" ats_response = chat_model.predict(ats_prompt) st.session_state.messages.append({"text": ats_response, "is_user": False}) st.session_state["resume_analyzed"] = True # Mark resume as analyzed st.rerun() else: st.error("Could not extract text from the uploaded resume.") # Main chat interface st.title("šŸ’¬ AI-Driven Job Assist") st.subheader("ATS insights & Chatbot Assist") # Display chat messages for msg in st.session_state.messages: message(msg["text"], is_user=msg["is_user"]) # Ask for aspired job role after ATS score if "resume_analyzed" in st.session_state and "aspired job role" not in st.session_state: ai_response = "What is your aspired job role?" st.session_state.messages.append({"text": ai_response, "is_user": False}) del st.session_state["resume_analyzed"] # Remove flag after asking st.rerun() # Input area if "user_message" not in st.session_state: st.session_state.user_message = "" user_input = st.text_input("Type your message here...", key="user_message") if st.button("Send") and user_input: st.session_state.messages.append({"text": user_input, "is_user": True}) # AI response logic if "aspired job role" not in st.session_state: st.session_state["aspired job role"] = user_input job_role = st.session_state["aspired job role"] skills_prompt = f"For the job role of {job_role}, suggest the essential skills and any missing skills based on the user's resume:\n\nResume:\n{st.session_state.resume_text}\n\nAlso, provide relevant learning resources for upskilling with valid links of courses.keep the response in short" ai_response = chat_model.predict(skills_prompt) st.session_state.messages.append({"text": ai_response, "is_user": False}) st.session_state.pop("user_message", None) st.rerun() # Fetch jobs from LinkedIn def fetch_jobs_from_linkedin(keyword, location, max_results=5): search_url = f"https://www.linkedin.com/jobs/search?keywords={keyword}&location={location}" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" } response = requests.get(search_url, headers=headers) if response.status_code != 200: st.error("Failed to fetch jobs from LinkedIn. Try again later.") return [] soup = BeautifulSoup(response.text, "html.parser") job_listings = [] for job_card in soup.find_all("div", class_="base-card")[:max_results]: title_tag = job_card.find("h3", class_="base-search-card__title") company_tag = job_card.find("h4", class_="base-search-card__subtitle") location_tag = job_card.find("span", class_="job-search-card__location") link_tag = job_card.find("a", class_="base-card__full-link") if title_tag and company_tag and location_tag and link_tag: job_listings.append({ "title": title_tag.text.strip(), "company": company_tag.text.strip(), "location": location_tag.text.strip(), "link": link_tag["href"].strip(), "recruiter_email": fetch_recruiter_email(job_card) # Placeholder function }) return job_listings # Placeholder function for fetching recruiter email def fetch_recruiter_email(job_card): # Replace this with actual logic to find recruiter emails email_tag = job_card.find("span", class_="recruiter-email") # Example selector return email_tag.text.strip() if email_tag else None # Function to generate cold email and cover letter def generate_email_and_cover_letter(job_title, company, recruiter_email): cold_email = f""" Subject: Application for {job_title} Position at {company} Dear Hiring Manager, I am excited to apply for the {job_title} position at {company}. With my skills and experience, I believe I am a great fit for this role. I have attached my resume for your reference and would love the opportunity to discuss further. Looking forward to your response. Best Regards, [Your Name] """ cover_letter = f""" Dear Hiring Manager, I am writing to express my interest in the {job_title} position at {company}. I have a strong background in [mention relevant skills] and believe my expertise aligns with the job requirements. I am eager to bring my skills to your esteemed company and contribute effectively. Please find my resume attached for review. Thank you for your time and consideration. I look forward to the opportunity to speak with you. Sincerely, [Your Name] """ return cold_email, cover_letter # Function to generate LinkedIn message & connection request note def generate_linkedin_outreach(job_title, company): linkedin_message = f""" Hi [Recruiter Name], I hope you're doing well. I came across the {job_title} opening at {company} and I am very interested. I would love to connect and learn more about this opportunity. Looking forward to your response! Best, [Your Name] """ connection_request_note = f""" Hi [Recruiter Name], I’m interested in the {job_title} role at {company} and would love to connect. """ return linkedin_message, connection_request_note # Streamlit UI st.title("šŸ“Œ Job Listings from LinkedIn") keyword = st.text_input("Job Title (e.g., Data Scientist)", value=st.session_state.get("aspired job role", "")) location = st.text_input("Location (e.g., New York)") if st.button("Fetch Jobs"): jobs = fetch_jobs_from_linkedin(keyword, location) if jobs: for job in jobs[:3]: st.markdown(f"**{job['title']}** at {job['company']} ({job['location']})") st.markdown(f"[Apply Now]({job['link']})") # Call Gemini to get match score match_prompt = f"""Based on the following job role and resume, provide a match score out of 100 indicating how well the resume fits the job. Also give a 1-line reason for the score. Job Role: {job['title']} at {job['company']} in {job['location']} Resume: {st.session_state.resume_text} Keep the response short and structured like this: Score: 85 Reason: Strong experience in React and REST APIs aligns well. """ match_response = chat_model.predict(match_prompt) # Display the match score. Extract score and reason from the response match = re.search(r"Score:\s*(\d+)\s*\nReason:\s*(.*)", match_response) if match: score = int(match.group(1)) reason = match.group(2) # Use color coding for different match levels if score >= 80: color = "green" elif score >= 50: color = "orange" else: color = "red" # Display match score with highlight st.markdown(f"šŸ” **Resume Match Score:** {score}/100", unsafe_allow_html=True) # Display reason separately st.markdown(f"šŸ“Œ **Reason:** {reason}") else: st.markdown("āš ļø Could not extract match score. Please check the response format.") # Show recruiter email if available if job['recruiter_email']: st.markdown(f"**Recruiter Email:** {job['recruiter_email']}") cold_email, cover_letter = generate_email_and_cover_letter(job['title'], job['company'], job['recruiter_email']) with st.expander("šŸ“§ Suggested Cold Email"): st.code(cold_email) with st.expander("šŸ“œ Suggested Cover Letter"): st.code(cover_letter) else: st.markdown(f"**Recruiters Email not available**") # Provide LinkedIn message & connection note linkedin_msg, connection_note = generate_linkedin_outreach(job['title'], job['company']) with st.expander("šŸ’¬ Suggested LinkedIn Message"): st.code(linkedin_msg) with st.expander("šŸ”— Connection Request Note"): st.code(connection_note) else: st.write("No jobs found. Try a different search.")