Spaces:
Sleeping
Sleeping
| 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 | |
| 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", | |
| ) | |