Spaces:
Sleeping
Sleeping
| 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:** <span style='color:{color}; font-size:22px; font-weight:bold'>{score}/100</span>", 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.") | |