import streamlit as st import pdfplumber import docx import re import requests import mimetypes import base64 from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition from openai import OpenAI from collections import Counter import mimetypes import time # ---------------- Page Config ---------------- st.set_page_config(page_title="ApplyAi", layout="wide") st.sidebar.image("logo.png", use_container_width=True) # ---------------- Helper Functions --------------- def parse_resume(file): if file.name.endswith(".pdf"): with pdfplumber.open(file) as pdf: return "\n".join(page.extract_text() or "" for page in pdf.pages) elif file.name.endswith(".docx"): doc = docx.Document(file) return "\n".join([para.text for para in doc.paragraphs]) return "Unsupported file format" def extract_skills(text): keywords = [ # Programming Languages "python", "java", "javascript", "typescript", "c++", "c#", "go", "ruby", "kotlin", "swift", # Data & AI "machine learning", "deep learning", "artificial intelligence", "nlp", "computer vision", "pandas", "numpy", "scikit-learn", "tensorflow", "pytorch", "keras", "matplotlib", "seaborn", # Data Analysis & BI "sql", "excel", "power bi", "tableau", "looker", "data analysis", "data visualization", "data wrangling", "data engineering", "etl", "snowflake", "bigquery", "redshift", # Cloud & DevOps "aws", "azure", "gcp", "docker", "kubernetes", "git", "github", "gitlab", "ci/cd", "jenkins", "terraform", "linux", "bash", "shell scripting", # Web & App Development "html", "css", "react", "angular", "vue", "next.js", "node.js", "express", "flask", "django", "rest api", "graphql", "firebase", # Tools & Soft Skills "jira", "confluence", "notion", "agile", "scrum", "teamwork", "communication", "problem solving", "critical thinking", "leadership", "project management", "unit testing", "integration testing" ] text = text.lower() found = [kw for kw in keywords if kw in text] return [skill for skill, _ in Counter(found).most_common()] def extract_entities(text): emails = re.findall(r'\S+@\S+', text) phones = re.findall(r'\+?\d[\d\s()-]{7,}\d', text) return {"emails": list(set(emails)), "phones": list(set(phones))} def analyze_resume(text): return { "skills": extract_skills(text), "entities": extract_entities(text), } def fetch_jobs(query, location="Remote", num_pages=1): url = "https://jsearch.p.rapidapi.com/search" headers = { "X-RapidAPI-Key": st.secrets["api"], "X-RapidAPI-Host": "jsearch.p.rapidapi.com" } params = { "query": f"{query} in {location}", "page": 1, "num_pages": num_pages } response = requests.get(url, headers=headers, params=params) if response.status_code == 200: return response.json().get("data", []) else: st.error(f"Error: {response.status_code} - {response.text}") return [] # ---------------- Main App ---------------- st.title("ApplyAi — Job search, simplified.") st.markdown("Upload your resume and find matching jobs based on your skills.") # Upload Resume st.sidebar.header("📄 Upload Resume") uploaded_file = st.sidebar.file_uploader("Upload PDF or DOCX", type=["pdf", "docx"]) # Initialize lists to avoid undefined errors jobs = [] recommended_jobs = [] if uploaded_file: resume_text = parse_resume(uploaded_file) analysis = analyze_resume(resume_text) skills = analysis["skills"] entities = analysis["entities"] st.success("✅ Resume processed successfully!") # ---------------- Resume Analysis ---------------- with st.expander("🔍 Resume Analysis", expanded=True): st.subheader("📌 Extracted Skills") if skills: skill_tags = "".join( [f"{s.title()}" for s in skills] ) st.markdown(f"
{auto_keyword}", unsafe_allow_html=True)
recommended_jobs = fetch_jobs(auto_keyword, "Remote", num_pages=1)
if recommended_jobs:
best_job = recommended_jobs[0]
st.markdown("#### ✅ Recommended for You")
st.markdown(f"**[{best_job.get('job_title')}]({best_job.get('job_apply_link', '#')})** at *{best_job.get('employer_name')}*")
st.markdown(f"📍 {best_job.get('job_city', 'Remote')}, {best_job.get('job_country', '')}")
st.markdown(f"📝 {best_job.get('job_description', '')[:300]}... [Apply here]({best_job.get('job_apply_link', '#')})", unsafe_allow_html=True)
st.markdown("---")
else:
st.info("No auto-suggestions available. Try manual search below.")
st.markdown("### ✏️ Or enter your own search")
custom_term = st.text_input("Job Title / Keywords", value=auto_keyword)
custom_location = st.text_input("Location", value="Calgary")
if st.button("🔎 Find Jobs"):
jobs = fetch_jobs(custom_term, custom_location, num_pages=2)
if jobs:
st.subheader("📋 Job Listings")
for job in jobs:
link = job.get("job_apply_link") or "#"
st.markdown(f"### [{job.get('job_title')}]({link})")
st.write(f"**Company:** {job.get('employer_name')}")
st.write(f"**Location:** {job.get('job_city', 'Remote')}, {job.get('job_country')}")
st.write(f"📝 {job.get('job_description', '')[:300]}...")
st.markdown(f"[Apply here]({link})", unsafe_allow_html=True)
st.markdown("---")
else:
st.warning("No jobs found. Try different search terms.")
# ---------------- Email Section ----------------
all_jobs = []
if recommended_jobs:
all_jobs.extend(recommended_jobs)
if jobs:
all_jobs.extend(jobs)
st.markdown("## 📧 Compose and Send Email")
if not all_jobs:
st.info("No jobs found yet. Try uploading a resume or running a job search first.")
else:
job_titles = [f"{job.get('job_title')} at {job.get('employer_name', '')}" for job in all_jobs]
selected_title = st.selectbox("Select a job to apply for", job_titles)
selected_job = all_jobs[job_titles.index(selected_title)]
job_desc = selected_job.get("job_description", "")
email_matches = re.findall(r'[\w\.-]+@[\w\.-]+\.\w+', job_desc)
auto_email = email_matches[0] if email_matches else ""
smart_subject = f"Job Application: {selected_job.get('job_title')} at {selected_job.get('employer_name')}"
smart_body = f"""Dear Hiring Team,
I hope this message finds you well. I recently came across your job listing for the position of {selected_job.get('job_title')} at {selected_job.get('employer_name')}, and I am writing to express my strong interest in this opportunity.
With a background in {', '.join(skills[:3])}, I believe I bring the technical expertise and enthusiasm required to make a meaningful impact in this role. My experience includes developing scalable applications, collaborating on cross-functional teams, and continuously learning new tools to stay at the forefront of the industry.
What excites me about this opportunity at {selected_job.get('employer_name')} is not only the alignment with my skillset but also the chance to contribute to an organization that values innovation and growth.
Please find my resume attached for your review. I would welcome the opportunity to discuss how my background and passion align with your team's goals. Thank you for considering my application.
Warm regards,
Sri Nandan
"""
with st.form("email_form"):
to_email = st.text_input("Recipient Email", value=auto_email)
subject = st.text_input("Subject", value=smart_subject)
body = st.text_area("Email Body", value=smart_body, height=180)
if uploaded_file:
st.markdown("**📎 Your resume will be attached to the email**")
else:
st.warning("Please upload a resume before sending.")
submitted = st.form_submit_button("📨 Send Email")
if submitted:
if to_email and uploaded_file:
# Guess the MIME type based on the file name
mime_type, _ = mimetypes.guess_type(uploaded_file.name)
maintype, subtype = mime_type.split("/") if mime_type else ("application", "octet-stream")
uploaded_file.seek(0)
file_bytes = uploaded_file.read()
encoded = base64.b64encode(file_bytes).decode()
message = Mail(
from_email=st.secrets["email_user"],
to_emails=to_email,
subject=subject,
plain_text_content=body
)
attachment = Attachment(
FileContent(encoded),
FileName(uploaded_file.name),
FileType(mime_type or "application/octet-stream"),
Disposition("attachment")
)
message.attachment = attachment
progress = st.progress(0, text="📨 Sending email...")
try:
for percent in range(0, 101, 20):
time.sleep(0.2)
progress.progress(percent, text="📨 Sending email...")
sg = SendGridAPIClient(st.secrets["sendgrid_api_key"])
sg.send(message)
progress.empty()
st.success("✅ Email sent successfully!")
except Exception as e:
progress.empty()
st.error(f"❌ SendGrid failed: {e}")
else:
st.error("Missing recipient email or resume.")
client = OpenAI(api_key=st.secrets["openai_api_key"])
# Initialize memory
if "chat_history" not in st.session_state:
st.session_state.chat_history = [
{"role": "system", "content": (
"You are ApplyAi, an AI assistant that only answers questions about job search, resumes, interviews, and career advice. "
"If a user asks about something unrelated, politely ask them to stay on topic."
)}
]
# Input box ABOVE the expander
st.markdown("## 💬 Chat with ApplyAi")
user_input = st.text_input("Ask a job-related question:", key="job_chat_input")
if user_input:
st.session_state.chat_history.append({"role": "user", "content": user_input})
with st.spinner("Thinking..."):
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=st.session_state.chat_history
)
reply = response.choices[0].message.content
st.session_state.chat_history.append({"role": "assistant", "content": reply})
# Collapsible chat history
with st.expander("🗂️ Chat History", expanded=False):
for msg in st.session_state.chat_history[1:]: # skip system prompt
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
else:
st.info("👈 Upload your resume to get started.")