MBilal-72 commited on
Commit
7b2309e
·
verified ·
1 Parent(s): 191b03c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -71
app.py CHANGED
@@ -1,72 +1,95 @@
1
  import streamlit as st
2
- from backend.agents.remoteok_agent import fetch_remoteok_jobs
3
- from backend.agents.matcher import compute_match, extract_keywords
4
- from backend.agents.resume_parser import extract_text
5
- from bs4 import BeautifulSoup
6
- import re
7
-
8
- st.set_page_config(page_title="MATCHHIVE", layout="wide")
9
- st.title("🚀 MATCHHIVE - AI Job Matcher")
10
-
11
- # Function to clean HTML
12
- def clean_html(raw_html):
13
- soup = BeautifulSoup(raw_html, "html.parser")
14
- # Replace <br> and <p> with newlines
15
- for br in soup.find_all(["br", "p"]):
16
- br.insert_after("\n")
17
- # Extract text and remove extra whitespace
18
- text = soup.get_text(separator="\n")
19
- lines = [line.strip() for line in text.splitlines() if line.strip()]
20
- return "\n".join(lines)
21
-
22
- # Upload resume
23
- uploaded = st.file_uploader("Upload your resume (PDF/DOCX)", type=["pdf", "docx"])
24
- resume_text = None
25
-
26
- if uploaded:
27
- with open("temp_resume." + uploaded.name.split(".")[-1], "wb") as f:
28
- f.write(uploaded.read())
29
- resume_text = extract_text(f.name)
30
- st.success("✅ Resume uploaded & parsed!")
31
-
32
- # Extract keywords/skills from resume
33
- resume_keywords = extract_keywords(resume_text)
34
-
35
- # Fetch jobs
36
- st.subheader("Job Feed - Matched Jobs")
37
- jobs = fetch_remoteok_jobs(limit=10)
38
- matched_jobs = []
39
-
40
- for job in jobs:
41
- # Clean job description from HTML
42
- clean_desc = clean_html(job["description"])
43
-
44
- # Compute match score
45
- score = compute_match(resume_text, clean_desc)
46
- if score > 0.1: # show only jobs with some match
47
- matched_jobs.append((job, score, clean_desc))
48
-
49
- if matched_jobs:
50
- for job, score, clean_desc in matched_jobs:
51
- st.markdown(f"### {job['title']} at {job['company']}")
52
-
53
- # Highlight matched keywords
54
- highlighted_desc = clean_desc
55
- for kw in resume_keywords:
56
- # Use regex to highlight words ignoring case
57
- highlighted_desc = re.sub(
58
- fr"(?i)\b({re.escape(kw)})\b",
59
- r"**\1**",
60
- highlighted_desc
61
- )
62
-
63
- st.markdown(highlighted_desc[:500] + "...") # truncate for preview
64
- st.progress(score)
65
- st.write(f"**Match Score:** {score:.2f}")
66
- st.markdown(f"[Apply Here]({job['url']})")
67
- st.divider()
68
- else:
69
- st.info("No matching jobs found for your resume yet.")
70
-
71
- else:
72
- st.info("Please upload your resume to see relevant job offers.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import requests
3
+ from sentence_transformers import SentenceTransformer
4
+ import faiss
5
+ import numpy as np
6
+ from groq import Groq
7
+
8
+ # -----------------------------
9
+ # CONFIG
10
+ # -----------------------------
11
+ REMOTEOK_URL = "https://remoteok.com/api"
12
+ EMBED_MODEL = "BAAI/bge-small-en-v1.5"
13
+
14
+ # Load embedding model
15
+ @st.cache_resource
16
+ def load_model():
17
+ return SentenceTransformer(EMBED_MODEL)
18
+
19
+ model = load_model()
20
+
21
+ # Initialize Groq client
22
+ groq_client = Groq(api_key=st.secrets.get("GROQ_API_KEY", None))
23
+
24
+ # -----------------------------
25
+ # FUNCTIONS
26
+ # -----------------------------
27
+
28
+ def fetch_jobs():
29
+ resp = requests.get(REMOTEOK_URL)
30
+ if resp.status_code == 200:
31
+ jobs = resp.json()[1:] # skip metadata
32
+ return jobs
33
+ return []
34
+
35
+ def embed_texts(texts):
36
+ return model.encode(texts, convert_to_numpy=True, normalize_embeddings=True)
37
+
38
+ def match_jobs(resume_text, jobs, top_k=5):
39
+ # prepare job descriptions
40
+ job_texts = [f"{job.get('position','')} {job.get('company','')} {job.get('description','')}" for job in jobs]
41
+
42
+ # embeddings
43
+ resume_vec = embed_texts([resume_text])
44
+ job_vecs = embed_texts(job_texts)
45
+
46
+ # FAISS index
47
+ dim = job_vecs.shape[1]
48
+ index = faiss.IndexFlatIP(dim) # cosine similarity (normalized)
49
+ index.add(job_vecs)
50
+
51
+ scores, idx = index.search(resume_vec, top_k)
52
+ results = []
53
+ for i, score in zip(idx[0], scores[0]):
54
+ results.append((jobs[i], float(score)))
55
+ return results
56
+
57
+ def generate_resume(resume_text, job):
58
+ prompt = f"""
59
+ You are an AI career assistant.
60
+ Given this resume:\n{resume_text}\n
61
+ and this job description:\n{job['description']}\n
62
+ Generate a tailored one-page resume that highlights relevant skills and experience.
63
+ Keep it concise and professional.
64
+ """
65
+
66
+ chat_completion = groq_client.chat.completions.create(
67
+ model="llama-3.1-70b-versatile",
68
+ messages=[{"role": "user", "content": prompt}],
69
+ temperature=0.7,
70
+ )
71
+ return chat_completion.choices[0].message["content"]
72
+
73
+ # -----------------------------
74
+ # STREAMLIT UI
75
+ # -----------------------------
76
+ st.title("MATCHHIVE - AI Job Matcher")
77
+
78
+ resume_file = st.file_uploader("Upload your resume (txt/pdf/docx)", type=["txt"])
79
+ if resume_file:
80
+ resume_text = resume_file.read().decode("utf-8", errors="ignore")
81
+
82
+ st.subheader("Fetching jobs...")
83
+ jobs = fetch_jobs()
84
+
85
+ st.subheader("Best Matches")
86
+ matches = match_jobs(resume_text, jobs, top_k=5)
87
+
88
+ for job, score in matches:
89
+ st.markdown(f"**{job['position']}** at *{job['company']}* \n"
90
+ f"[View Job Posting]({job['url']}) \n"
91
+ f"**Match Score:** {score:.2f}")
92
+
93
+ if st.button(f"Generate Resume for {job['position']}", key=job['id']):
94
+ tailored_resume = generate_resume(resume_text, job)
95
+ st.text_area("Tailored Resume", tailored_resume, height=300)