File size: 10,623 Bytes
3b9e7e3
 
 
 
 
 
 
 
 
 
 
aa0577f
3b9e7e3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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.")