EATosin's picture
Update app.py
a1cca06 verified
import streamlit as st
import google.generativeai as genai
import os
import json
import pandas as pd
from pypdf import PdfReader
from dotenv import load_dotenv
from supabase import create_client, Client
# --- SETUP ---
st.set_page_config(page_title="TalentAlign AI", layout="wide", page_icon="πŸ‘”")
# Load Keys
api_key = os.getenv("GEMINI_API_KEY")
supa_url = os.getenv("SUPABASE_URL")
supa_key = os.getenv("SUPABASE_KEY")
# Fallback for local dev
if not api_key:
load_dotenv()
api_key = os.getenv("GEMINI_API_KEY")
supa_url = os.getenv("SUPABASE_URL")
supa_key = os.getenv("SUPABASE_KEY")
# Initialize Supabase
supabase: Client = None
if supa_url and supa_key:
try:
supabase = create_client(supa_url, supa_key)
except Exception as e:
print(f"Db Error: {e}")
# --- FUNCTIONS ---
def save_to_db(job_title, candidate_name, score, summary):
"""Saves the scan result to Supabase."""
if not supabase:
return
try:
data = {
"job_title": job_title,
"candidate_name": candidate_name,
"match_score": score,
"summary": summary
}
supabase.table("resume_scans").insert(data).execute()
except Exception as e:
print(f"Failed to save to DB: {e}")
def extract_text_from_pdf(file):
try:
reader = PdfReader(file)
text = ""
for page in reader.pages:
content = page.extract_text()
if content:
text += content + "\n"
if len(text.strip()) < 50:
return None
return text.strip()
except Exception as e:
return None
def analyze_resumes(jd, resumes, blind_mode=False):
if not api_key:
return {"error": "API Key missing."}
genai.configure(api_key=api_key)
try:
model = genai.GenerativeModel('gemini-2.5-flash', generation_config={"response_mime_type": "application/json"})
except:
model = genai.GenerativeModel('gemini-1.5-flash', generation_config={"response_mime_type": "application/json"})
prompt = f"""
You are an Expert Technical Recruiter.
JOB DESCRIPTION: {jd}
TASK: Analyze candidates.
{'BLIND MODE: Ignore names/gender.' if blind_mode else ''}
OUTPUT FORMAT (JSON list):
[{{ "name": "Name", "match_score": 85, "key_skills": [], "missing_skills": [], "summary": "...", "status": "Interview" }}]
RESUMES:
"""
for i, res in enumerate(resumes):
prompt += f"\n--- CANDIDATE {i+1} ({res['name']}) ---\n{res['text']}\n"
try:
response = model.generate_content(prompt)
return json.loads(response.text)
except Exception as e:
return {"error": f"AI Error: {str(e)}"}
# --- UI LAYOUT ---
with st.sidebar:
st.title("βš™οΈ Controls")
blind_mode = st.toggle("Blind Hiring Mode", value=True)
st.info("Powered by **Gemini 2.5**")
# DB Status Indicator
if supabase:
st.success("🟒 Database Connected")
else:
st.warning("πŸ”΄ Database Offline")
st.title("πŸ‘” TalentAlign: AI Recruitment Agent")
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("1. Job Description")
jd_input = st.text_area("Paste JD here:", height=300)
with col2:
st.subheader("2. Resumes")
uploaded_files = st.file_uploader("Upload PDF Resumes", type="pdf", accept_multiple_files=True)
if st.button("πŸš€ Screen Candidates", type="primary"):
if not jd_input or not uploaded_files:
st.warning("Please upload Resumes and paste a JD.")
else:
with st.spinner("Analyzing..."):
valid_resumes = []
for file in uploaded_files:
text = extract_text_from_pdf(file)
if text:
valid_resumes.append({"name": file.name, "text": text})
if not valid_resumes:
st.error("No valid text found.")
else:
results = analyze_resumes(jd_input, valid_resumes, blind_mode)
if "error" in results:
st.error(results['error'])
else:
st.success("Analysis Complete!")
# Scoreboard
df = pd.DataFrame(results)
st.dataframe(df[["name", "match_score", "status", "summary"]], use_container_width=True)
# Save to DB & Display Cards
for cand in results:
# Save each candidate to Supabase
save_to_db(
jd_input[:50], # Save first 50 chars of JD title
cand.get('name', 'Unknown'),
cand.get('match_score', 0),
cand.get('summary', '')
)
with st.expander(f"{cand.get('match_score')}% - {cand.get('name')}"):
st.write(f"**Verdict:** {cand.get('status')}")
st.write(f"**Summary:** {cand.get('summary')}")