MATCHHIVE_AI / app.py
MBilal-72's picture
Update app.py
6e64d6d verified
import streamlit as st
import requests
import pdfplumber
import docx
from sentence_transformers import SentenceTransformer
import faiss
from groq import Groq
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, ListFlowable, ListItem
import io
# -----------------------------
# CONFIG
# -----------------------------
REMOTEOK_URL = "https://remoteok.com/api"
EMBED_MODEL = "BAAI/bge-small-en-v1.5"
AI_MODEL = "openai/gpt-oss-120b" # Groq model
# Load embedding model
@st.cache_resource
def load_model():
return SentenceTransformer(EMBED_MODEL)
model = load_model()
# Initialize Groq client
groq_client = Groq(api_key=st.secrets.get("GROQ_API_KEY", None))
# -----------------------------
# FUNCTIONS
# -----------------------------
def extract_text_from_resume(file):
"""Extract text from PDF or DOCX file"""
if file.name.endswith(".pdf"):
text = ""
with pdfplumber.open(file) as pdf:
for page in pdf.pages:
text += page.extract_text() or ""
return text
elif file.name.endswith(".docx"):
doc = docx.Document(file)
text = "\n".join([p.text for p in doc.paragraphs])
return text
else:
st.error("Unsupported file type. Please upload PDF or DOCX.")
return ""
def fetch_jobs():
resp = requests.get(REMOTEOK_URL)
if resp.status_code == 200:
jobs = resp.json()[1:] # skip metadata
return jobs
return []
def embed_texts(texts):
return model.encode(texts, convert_to_numpy=True, normalize_embeddings=True)
def match_jobs(resume_text, jobs, top_k=5):
job_texts = [f"{job.get('position','')} {job.get('company','')} {job.get('description','')}" for job in jobs]
resume_vec = embed_texts([resume_text])
job_vecs = embed_texts(job_texts)
dim = job_vecs.shape[1]
index = faiss.IndexFlatIP(dim)
index.add(job_vecs)
scores, idx = index.search(resume_vec, top_k)
results = []
for i, score in zip(idx[0], scores[0]):
results.append((jobs[i], float(score)))
return results
def generate_resume(resume_text, job):
prompt = f"""
You are an AI career assistant.
Given this resume:\n{resume_text}\n
and this job description:\n{job['description']}\n
Generate a structured resume in this format:
Summary
-----------------
[2-3 line summary tailored for the job]
Skills
-----------------
- Skill 1
- Skill 2
- Skill 3
Experience
-----------------
Job Title | Company | Dates
• Achievement 1
• Achievement 2
Education
-----------------
Degree | Institution | Year
"""
chat_completion = groq_client.chat.completions.create(
model=AI_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
)
return chat_completion.choices[0].message.content
def generate_cover_letter(resume_text, job, name, email, phone):
prompt = f"""
You are an AI career assistant.
Given this resume:\n{resume_text}\n
and this job description:\n{job['description']}\n
Generate a professional, one-page cover letter tailored to this role.
Format it like this:
Dear Hiring Manager,
[Intro paragraph: Show enthusiasm and alignment with company/role]
[Body paragraph: Highlight 2-3 most relevant skills/experiences from resume]
[Closing paragraph: Express eagerness and thank them]
Sincerely,
{name}
{email} | {phone}
"""
chat_completion = groq_client.chat.completions.create(
model=AI_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
)
return chat_completion.choices[0].message.content
def build_pdf(content, title="Resume", name="John Doe", email="john.doe@email.com", phone="+1 234 567 890"):
buffer = io.BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=40, rightMargin=40, topMargin=40, bottomMargin=40)
styles = getSampleStyleSheet()
# Custom styles
header_style = ParagraphStyle("Header", parent=styles["Heading1"], fontSize=18, spaceAfter=6, textColor=colors.HexColor("#2C3E50"), alignment=1)
contact_style = ParagraphStyle("Contact", parent=styles["Normal"], fontSize=11, textColor=colors.HexColor("#566573"), alignment=1)
section_style = ParagraphStyle("Section", parent=styles["Heading2"], fontSize=13, spaceBefore=15, spaceAfter=8, textColor=colors.HexColor("#1B2631"))
normal_style = ParagraphStyle("Normal", parent=styles["Normal"], fontSize=11, leading=15)
bullet_style = ParagraphStyle("Bullet", parent=styles["Normal"], fontSize=11, leading=15, leftIndent=20)
story = []
# ---- HEADER ----
story.append(Paragraph(name, header_style))
story.append(Paragraph(f"{email} | {phone}", contact_style))
story.append(Spacer(1, 12))
# ---- BODY ----
if title == "Resume":
sections = content.split("**")
for sec in sections:
sec = sec.strip()
if not sec:
continue
if sec.lower().startswith("summary"):
story.append(Paragraph("Summary", section_style))
elif sec.lower().startswith("skills"):
story.append(Paragraph("Skills", section_style))
elif sec.lower().startswith("experience"):
story.append(Paragraph("Experience", section_style))
elif sec.lower().startswith("education"):
story.append(Paragraph("Education", section_style))
else:
if sec.startswith("- "):
bullets = [s.strip("- ").strip() for s in sec.split("\n") if s.strip()]
bullet_list = ListFlowable([ListItem(Paragraph(b, bullet_style)) for b in bullets], bulletType="bullet")
story.append(bullet_list)
else:
story.append(Paragraph(sec, normal_style))
story.append(Spacer(1, 8))
else:
# Treat as cover letter: keep paragraphs
for line in content.split("\n"):
if line.strip():
story.append(Paragraph(line.strip(), normal_style))
story.append(Spacer(1, 10))
doc.build(story)
buffer.seek(0)
return buffer
# -----------------------------
# STREAMLIT UI
# -----------------------------
st.title("MATCHHIVE - AI Job Matcher")
# Upload resume
resume_file = st.file_uploader("Upload your resume (PDF or DOCX)", type=["pdf", "docx"])
if resume_file:
resume_text = extract_text_from_resume(resume_file)
if resume_text.strip():
st.subheader("Contact Information")
name = st.text_input("Full Name", "John Doe")
email = st.text_input("Email", "john.doe@email.com")
phone = st.text_input("Phone", "+1 234 567 890")
st.subheader("Fetching jobs...")
jobs = fetch_jobs()
st.subheader("Best Matches")
matches = match_jobs(resume_text, jobs, top_k=5)
for job, score in matches:
st.markdown(f"**{job['position']}** at *{job['company']}* \n"
f"[View Job Posting]({job['url']}) \n"
f"**Match Score:** {score:.2f}")
col1, col2 = st.columns(2)
with col1:
if st.button(f"Generate Resume for {job['position']}", key=f"resume_{job['id']}"):
tailored_resume = generate_resume(resume_text, job)
edited_resume = st.text_area("Tailored Resume", tailored_resume, height=300)
pdf_buffer = build_pdf(edited_resume, title="Resume", name=name, email=email, phone=phone)
st.download_button(
label="📥 Download Resume (PDF)",
data=pdf_buffer,
file_name="tailored_resume.pdf",
mime="application/pdf",
)
with col2:
if st.button(f"Generate Cover Letter for {job['position']}", key=f"cl_{job['id']}"):
tailored_cl = generate_cover_letter(resume_text, job, name, email, phone)
edited_cl = st.text_area("Cover Letter", tailored_cl, height=300)
pdf_buffer = build_pdf(edited_cl, title="Cover Letter", name=name, email=email, phone=phone)
st.download_button(
label="📥 Download Cover Letter (PDF)",
data=pdf_buffer,
file_name="cover_letter.pdf",
mime="application/pdf",
)