Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# app.py
|
| 2 |
-
#
|
| 3 |
|
| 4 |
import streamlit as st
|
| 5 |
from transformers import BertTokenizer, BertForSequenceClassification, T5Tokenizer, T5ForConditionalGeneration
|
|
@@ -13,65 +13,71 @@ from docx import Document
|
|
| 13 |
import time
|
| 14 |
import pandas as pd
|
| 15 |
|
| 16 |
-
# Set page config with
|
| 17 |
st.set_page_config(
|
| 18 |
page_title="AI Talent Screening Tool",
|
| 19 |
-
page_icon="
|
| 20 |
layout="wide",
|
| 21 |
initial_sidebar_state="expanded",
|
| 22 |
)
|
| 23 |
|
| 24 |
-
# --- CUSTOM
|
| 25 |
st.markdown("""
|
| 26 |
<style>
|
| 27 |
-
/* 0. GLOBAL CONFIG &
|
| 28 |
:root {
|
| 29 |
-
--primary-color: #
|
| 30 |
-
--success-color: #
|
| 31 |
-
--warning-color: #FFC107; /*
|
| 32 |
-
--danger-color: #
|
| 33 |
-
--background-color: #
|
| 34 |
-
--container-background: #
|
| 35 |
-
--text-color: #
|
|
|
|
| 36 |
}
|
| 37 |
|
| 38 |
.main {
|
| 39 |
background-color: var(--background-color);
|
| 40 |
color: var(--text-color);
|
| 41 |
-
font-family: '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
}
|
| 43 |
|
| 44 |
/* 1. HEADER & TITLES */
|
| 45 |
h1 {
|
| 46 |
text-align: center;
|
| 47 |
color: var(--primary-color);
|
| 48 |
-
font-size: 2.
|
| 49 |
-
font-weight:
|
| 50 |
-
border-bottom: 3px solid rgba(
|
| 51 |
-
padding-bottom:
|
| 52 |
margin-bottom: 30px;
|
| 53 |
}
|
| 54 |
h2, h3, h4 {
|
| 55 |
color: var(--text-color);
|
| 56 |
-
border-left: 5px solid var(--
|
| 57 |
-
padding-left:
|
| 58 |
-
margin-top:
|
| 59 |
font-weight: 600;
|
| 60 |
}
|
| 61 |
|
| 62 |
/* 2. BUTTONS & HOVER EFFECTS */
|
| 63 |
.stButton>button {
|
| 64 |
-
color: var(--
|
| 65 |
-
border: 1px solid var(--
|
| 66 |
background-color: var(--container-background) !important;
|
| 67 |
-
border-radius:
|
| 68 |
transition: all 0.3s ease;
|
| 69 |
-
box-shadow: 0
|
| 70 |
font-weight: 600;
|
| 71 |
}
|
| 72 |
.stButton>button:hover {
|
| 73 |
-
background-color:
|
| 74 |
-
box-shadow: 0
|
| 75 |
transform: translateY(-2px);
|
| 76 |
}
|
| 77 |
/* Primary Button (Run Screening) */
|
|
@@ -81,70 +87,95 @@ st.markdown("""
|
|
| 81 |
border-color: var(--success-color) !important;
|
| 82 |
}
|
| 83 |
.stButton>button[kind="primary"]:hover {
|
| 84 |
-
background-color: #
|
| 85 |
-
border-color: #
|
| 86 |
}
|
| 87 |
|
| 88 |
-
/* 3. INPUTS, CONTAINERS &
|
| 89 |
.stTextArea, .stTextInput, .stFileUploader {
|
| 90 |
border-radius: 8px;
|
| 91 |
-
border: 1px solid #
|
| 92 |
-
background-color:
|
| 93 |
-
|
|
|
|
| 94 |
}
|
| 95 |
.stTabs [aria-selected="true"] {
|
| 96 |
-
color: var(--
|
| 97 |
-
border-bottom: 3px solid var(--
|
| 98 |
font-weight: bold;
|
| 99 |
}
|
| 100 |
.stSidebar {
|
| 101 |
-
background-color: #
|
| 102 |
-
border-right: 1px solid #
|
|
|
|
| 103 |
}
|
| 104 |
|
| 105 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
.scorecard-block {
|
| 107 |
-
border: 1px solid #
|
| 108 |
-
border-radius:
|
| 109 |
-
padding:
|
| 110 |
margin: 5px 0;
|
| 111 |
-
background-color:
|
| 112 |
transition: all 0.3s;
|
| 113 |
-
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.
|
| 114 |
}
|
| 115 |
.scorecard-block:hover {
|
| 116 |
-
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.
|
| 117 |
}
|
| 118 |
.scorecard-value {
|
| 119 |
-
font-size:
|
| 120 |
-
font-weight:
|
| 121 |
color: var(--primary-color);
|
| 122 |
}
|
| 123 |
.scorecard-label {
|
| 124 |
font-size: 14px;
|
| 125 |
-
color:
|
| 126 |
}
|
| 127 |
/* Color override for specific blocks */
|
| 128 |
.block-relevant { border-left: 5px solid var(--success-color); }
|
| 129 |
.block-uncertain { border-left: 5px solid var(--warning-color); }
|
| 130 |
.block-irrelevant { border-left: 5px solid var(--danger-color); }
|
| 131 |
|
| 132 |
-
/* Streamlit's Info/Success/Warning blocks made
|
| 133 |
[data-testid="stAlert"] {
|
| 134 |
border-radius: 8px;
|
| 135 |
padding: 15px;
|
| 136 |
font-size: 16px;
|
|
|
|
|
|
|
| 137 |
}
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
</style>
|
| 140 |
""", unsafe_allow_html=True)
|
| 141 |
|
| 142 |
-
# --- (Model and Helper Functions - Core logic remains the same) ---
|
| 143 |
-
# ... (skills_list, skills_pattern, extract_text_from_pdf/docx/file, normalize_text, check_experience_mismatch, validate_input, load_models, tokenize_inputs, extract_skills, classify_and_summarize_batch, generate_skill_pie_chart functions remain unchanged) ...
|
| 144 |
-
|
| 145 |
-
# NOTE: Since the file content is large, I'm only including the modified function `render_sidebar`
|
| 146 |
-
# and the affected part of `main` for brevity. The full code block at the end contains the complete, fixed file.
|
| 147 |
|
|
|
|
|
|
|
|
|
|
| 148 |
skills_list = [
|
| 149 |
'python', 'sql', 'c++', 'java', 'tableau', 'machine learning', 'data analysis',
|
| 150 |
'business intelligence', 'r', 'tensorflow', 'pandas', 'spark', 'scikit-learn', 'aws',
|
|
@@ -181,7 +212,7 @@ def extract_text_from_docx(file):
|
|
| 181 |
text += paragraph.text + "\n"
|
| 182 |
return text.strip()
|
| 183 |
except: return ""
|
| 184 |
-
|
| 185 |
def extract_text_from_file(uploaded_file):
|
| 186 |
if uploaded_file.name.endswith('.pdf'): return extract_text_from_pdf(uploaded_file)
|
| 187 |
elif uploaded_file.name.endswith('.docx'): return extract_text_from_docx(uploaded_file)
|
|
@@ -296,13 +327,13 @@ def classify_and_summarize_batch(resume, job_description, _bert_tok, _t5_input,
|
|
| 296 |
elif detected_skills: final_summary = f"Key Skills: {', '.join(detected_skills)}"
|
| 297 |
else: final_summary = f"Experience: {exp_match.group(0) if exp_match else 'Unknown'}"
|
| 298 |
|
| 299 |
-
if suitability == "Relevant": color = "#
|
| 300 |
-
elif suitability == "Irrelevant": color = "#
|
| 301 |
else: color = "#FFC107"
|
| 302 |
|
| 303 |
return {"Suitability": suitability, "Key Skills & Experience Summary": final_summary, "Flagging Reason": warning, "Suitability_Color": color}
|
| 304 |
except Exception as e:
|
| 305 |
-
return {"Suitability": "Error", "Key Skills & Experience Summary": "Failed to process profile", "Flagging Reason": str(e), "Suitability_Color": "#
|
| 306 |
|
| 307 |
@st.cache_data
|
| 308 |
def generate_skill_pie_chart(resumes):
|
|
@@ -326,29 +357,30 @@ def generate_skill_pie_chart(resumes):
|
|
| 326 |
labels = list(top_skills.keys())
|
| 327 |
sizes = [(count / sum(top_skills.values())) * 100 for count in top_skills.values()]
|
| 328 |
|
| 329 |
-
|
|
|
|
| 330 |
fig, ax = plt.subplots(figsize=(6, 4))
|
| 331 |
-
colors = plt.cm.
|
| 332 |
-
plt.rcParams['text.color'] = '
|
| 333 |
-
wedges, texts, autotexts = ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors, textprops={'fontsize': 10, 'color': '
|
| 334 |
ax.axis('equal')
|
| 335 |
-
plt.title("Top Candidate Skill Frequency", fontsize=14, color='#
|
| 336 |
return fig
|
| 337 |
|
| 338 |
def render_sidebar():
|
| 339 |
-
"""Render sidebar content with professional HR language.
|
| 340 |
-
# Define hex colors
|
| 341 |
-
SUCCESS_COLOR = "#
|
| 342 |
-
WARNING_COLOR = "#FFC107"
|
| 343 |
-
DANGER_COLOR = "#
|
| 344 |
-
PRIMARY_COLOR = "#
|
| 345 |
|
| 346 |
with st.sidebar:
|
| 347 |
st.markdown(f"""
|
| 348 |
<h2 style='text-align: center; border-left: none; padding-left: 0; color: {PRIMARY_COLOR};'>
|
| 349 |
-
|
| 350 |
</h2>
|
| 351 |
-
<p style='text-align: center; font-size: 14px; margin-top: 0; color: #
|
| 352 |
Powered by Advanced NLP (BERT + T5)
|
| 353 |
</p>
|
| 354 |
""", unsafe_allow_html=True)
|
|
@@ -388,7 +420,7 @@ def main():
|
|
| 388 |
if 'valid_resumes' not in st.session_state: st.session_state.valid_resumes = []
|
| 389 |
if 'models' not in st.session_state: st.session_state.models = None
|
| 390 |
|
| 391 |
-
st.markdown("<h1
|
| 392 |
|
| 393 |
# HR-friendly Tab Names
|
| 394 |
tab_setup, tab_resumes, tab_results = st.tabs(["1. Job Requirement Setup", "2. Candidate Profile Upload", "3. Screening Report & Analytics"])
|
|
@@ -503,17 +535,8 @@ def main():
|
|
| 503 |
|
| 504 |
for i, resume in enumerate(valid_resumes):
|
| 505 |
status_text.text(f"Status: Analyzing Profile {i+1} of {total_steps}...")
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
bert_tok_single = {
|
| 509 |
-
'input_ids': bert_tokenized['input_ids'][i].unsqueeze(0),
|
| 510 |
-
'attention_mask': bert_tokenized['attention_mask'][i].unsqueeze(0)
|
| 511 |
-
}
|
| 512 |
-
t5_tok_single = {
|
| 513 |
-
'input_ids': t5_tokenized['input_ids'][i].unsqueeze(0),
|
| 514 |
-
'attention_mask': t5_tokenized['attention_mask'][i].unsqueeze(0)
|
| 515 |
-
}
|
| 516 |
-
|
| 517 |
result = classify_and_summarize_batch(resume, job_description, bert_tok_single, t5_inputs[i], t5_tok_single, job_skills_set)
|
| 518 |
result["Profile ID"] = f"Candidate {i+1}"
|
| 519 |
results.append(result)
|
|
@@ -541,10 +564,10 @@ def main():
|
|
| 541 |
st.markdown(f"#### Overview: {total} Candidate Profiles Processed")
|
| 542 |
|
| 543 |
# Define hex colors again for the scorecard blocks
|
| 544 |
-
PRIMARY_COLOR = "#
|
| 545 |
-
SUCCESS_COLOR = "#
|
| 546 |
-
WARNING_COLOR = "#FFC107"
|
| 547 |
-
DANGER_COLOR = "#
|
| 548 |
|
| 549 |
col1, col2, col3, col4 = st.columns(4)
|
| 550 |
|
|
|
|
| 1 |
# app.py
|
| 2 |
+
# Modern Dark Mode Streamlit Application for AI Talent Screening
|
| 3 |
|
| 4 |
import streamlit as st
|
| 5 |
from transformers import BertTokenizer, BertForSequenceClassification, T5Tokenizer, T5ForConditionalGeneration
|
|
|
|
| 13 |
import time
|
| 14 |
import pandas as pd
|
| 15 |
|
| 16 |
+
# Set page config with modern dark theme and wide layout
|
| 17 |
st.set_page_config(
|
| 18 |
page_title="AI Talent Screening Tool",
|
| 19 |
+
page_icon="🚀",
|
| 20 |
layout="wide",
|
| 21 |
initial_sidebar_state="expanded",
|
| 22 |
)
|
| 23 |
|
| 24 |
+
# --- CUSTOM MODERN DARK MODE CSS OVERHAUL ---
|
| 25 |
st.markdown("""
|
| 26 |
<style>
|
| 27 |
+
/* 0. GLOBAL CONFIG & DARK THEME */
|
| 28 |
:root {
|
| 29 |
+
--primary-color: #9C27B0; /* Vibrant Purple/Magenta (Accent) */
|
| 30 |
+
--success-color: #4CAF50; /* Green (Good Match) */
|
| 31 |
+
--warning-color: #FFC107; /* Amber/Yellow (Review) */
|
| 32 |
+
--danger-color: #F44336; /* Red (Irrelevant/Error) */
|
| 33 |
+
--background-color: #1E1E1E; /* Deep Dark Background */
|
| 34 |
+
--container-background: #2D2D2D; /* Slightly Lighter Container */
|
| 35 |
+
--text-color: #F8F8F8; /* Light Text */
|
| 36 |
+
--secondary-text-color: #B0B0B0; /* Muted Light Gray */
|
| 37 |
}
|
| 38 |
|
| 39 |
.main {
|
| 40 |
background-color: var(--background-color);
|
| 41 |
color: var(--text-color);
|
| 42 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* Overall Streamlit container background (Dark) */
|
| 46 |
+
.stApp {
|
| 47 |
+
background-color: var(--background-color);
|
| 48 |
}
|
| 49 |
|
| 50 |
/* 1. HEADER & TITLES */
|
| 51 |
h1 {
|
| 52 |
text-align: center;
|
| 53 |
color: var(--primary-color);
|
| 54 |
+
font-size: 2.8em;
|
| 55 |
+
font-weight: 800;
|
| 56 |
+
border-bottom: 3px solid rgba(156, 39, 176, 0.3);
|
| 57 |
+
padding-bottom: 15px;
|
| 58 |
margin-bottom: 30px;
|
| 59 |
}
|
| 60 |
h2, h3, h4 {
|
| 61 |
color: var(--text-color);
|
| 62 |
+
border-left: 5px solid var(--primary-color); /* Purple marker for clarity */
|
| 63 |
+
padding-left: 15px;
|
| 64 |
+
margin-top: 30px;
|
| 65 |
font-weight: 600;
|
| 66 |
}
|
| 67 |
|
| 68 |
/* 2. BUTTONS & HOVER EFFECTS */
|
| 69 |
.stButton>button {
|
| 70 |
+
color: var(--text-color) !important;
|
| 71 |
+
border: 1px solid var(--container-background) !important;
|
| 72 |
background-color: var(--container-background) !important;
|
| 73 |
+
border-radius: 12px; /* More rounded for modern look */
|
| 74 |
transition: all 0.3s ease;
|
| 75 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
| 76 |
font-weight: 600;
|
| 77 |
}
|
| 78 |
.stButton>button:hover {
|
| 79 |
+
background-color: #404040 !important;
|
| 80 |
+
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.5);
|
| 81 |
transform: translateY(-2px);
|
| 82 |
}
|
| 83 |
/* Primary Button (Run Screening) */
|
|
|
|
| 87 |
border-color: var(--success-color) !important;
|
| 88 |
}
|
| 89 |
.stButton>button[kind="primary"]:hover {
|
| 90 |
+
background-color: #388E3C !important; /* Darker green on hover */
|
| 91 |
+
border-color: #388E3C !important;
|
| 92 |
}
|
| 93 |
|
| 94 |
+
/* 3. INPUTS, CONTAINERS, TABS & SIDEBAR */
|
| 95 |
.stTextArea, .stTextInput, .stFileUploader {
|
| 96 |
border-radius: 8px;
|
| 97 |
+
border: 1px solid #444444;
|
| 98 |
+
background-color: #333333; /* Darker input background */
|
| 99 |
+
color: var(--text-color); /* Ensure input text is light */
|
| 100 |
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
|
| 101 |
}
|
| 102 |
.stTabs [aria-selected="true"] {
|
| 103 |
+
color: var(--primary-color) !important;
|
| 104 |
+
border-bottom: 3px solid var(--primary-color) !important;
|
| 105 |
font-weight: bold;
|
| 106 |
}
|
| 107 |
.stSidebar {
|
| 108 |
+
background-color: #252525; /* Slightly lighter dark sidebar for contrast */
|
| 109 |
+
border-right: 1px solid #3A3A3A;
|
| 110 |
+
color: var(--text-color);
|
| 111 |
}
|
| 112 |
|
| 113 |
+
/* Fix: Ensure text in sidebar expanders is visible */
|
| 114 |
+
[data-testid="stSidebar"] p,
|
| 115 |
+
[data-testid="stSidebar"] li,
|
| 116 |
+
[data-testid="stSidebar"] [data-testid="stExpander"] {
|
| 117 |
+
color: var(--secondary-text-color) !important;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
/* Custom Scorecard Style (Dark Tiles) */
|
| 121 |
.scorecard-block {
|
| 122 |
+
border: 1px solid #3A3A3A;
|
| 123 |
+
border-radius: 12px;
|
| 124 |
+
padding: 20px;
|
| 125 |
margin: 5px 0;
|
| 126 |
+
background-color: #333333;
|
| 127 |
transition: all 0.3s;
|
| 128 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
| 129 |
}
|
| 130 |
.scorecard-block:hover {
|
| 131 |
+
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
|
| 132 |
}
|
| 133 |
.scorecard-value {
|
| 134 |
+
font-size: 38px;
|
| 135 |
+
font-weight: 800;
|
| 136 |
color: var(--primary-color);
|
| 137 |
}
|
| 138 |
.scorecard-label {
|
| 139 |
font-size: 14px;
|
| 140 |
+
color: var(--secondary-text-color);
|
| 141 |
}
|
| 142 |
/* Color override for specific blocks */
|
| 143 |
.block-relevant { border-left: 5px solid var(--success-color); }
|
| 144 |
.block-uncertain { border-left: 5px solid var(--warning-color); }
|
| 145 |
.block-irrelevant { border-left: 5px solid var(--danger-color); }
|
| 146 |
|
| 147 |
+
/* Streamlit's Info/Success/Warning blocks made darker */
|
| 148 |
[data-testid="stAlert"] {
|
| 149 |
border-radius: 8px;
|
| 150 |
padding: 15px;
|
| 151 |
font-size: 16px;
|
| 152 |
+
background-color: #333333 !important; /* Darker alert backgrounds */
|
| 153 |
+
color: var(--text-color) !important;
|
| 154 |
}
|
| 155 |
+
/* Override specific alert colors for dark theme contrast */
|
| 156 |
+
[data-testid="stAlert"] div[role="alert"] {
|
| 157 |
+
background-color: #333333 !important;
|
| 158 |
+
}
|
| 159 |
+
[data-testid="stAlert"] div[role="alert"].st-emotion-cache-1f81d5m { /* Info */
|
| 160 |
+
border-left: 5px solid #2196F3;
|
| 161 |
+
}
|
| 162 |
+
[data-testid="stAlert"] div[role="alert"].st-emotion-cache-1218yph { /* Warning */
|
| 163 |
+
border-left: 5px solid var(--warning-color);
|
| 164 |
+
}
|
| 165 |
+
[data-testid="stAlert"] div[role="alert"].st-emotion-cache-22lkyf { /* Error */
|
| 166 |
+
border-left: 5px solid var(--danger-color);
|
| 167 |
+
}
|
| 168 |
+
[data-testid="stAlert"] div[role="alert"].st-emotion-cache-5lq06g { /* Success */
|
| 169 |
+
border-left: 5px solid var(--success-color);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
</style>
|
| 173 |
""", unsafe_allow_html=True)
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
+
# --- (Model and Helper Functions - Core logic remains the same) ---
|
| 177 |
+
# NOTE: The core ML logic and utility functions for PDF/DOCX parsing remain unchanged
|
| 178 |
+
# as they are robust and purely functional.
|
| 179 |
skills_list = [
|
| 180 |
'python', 'sql', 'c++', 'java', 'tableau', 'machine learning', 'data analysis',
|
| 181 |
'business intelligence', 'r', 'tensorflow', 'pandas', 'spark', 'scikit-learn', 'aws',
|
|
|
|
| 212 |
text += paragraph.text + "\n"
|
| 213 |
return text.strip()
|
| 214 |
except: return ""
|
| 215 |
+
|
| 216 |
def extract_text_from_file(uploaded_file):
|
| 217 |
if uploaded_file.name.endswith('.pdf'): return extract_text_from_pdf(uploaded_file)
|
| 218 |
elif uploaded_file.name.endswith('.docx'): return extract_text_from_docx(uploaded_file)
|
|
|
|
| 327 |
elif detected_skills: final_summary = f"Key Skills: {', '.join(detected_skills)}"
|
| 328 |
else: final_summary = f"Experience: {exp_match.group(0) if exp_match else 'Unknown'}"
|
| 329 |
|
| 330 |
+
if suitability == "Relevant": color = "#4CAF50"
|
| 331 |
+
elif suitability == "Irrelevant": color = "#F44336"
|
| 332 |
else: color = "#FFC107"
|
| 333 |
|
| 334 |
return {"Suitability": suitability, "Key Skills & Experience Summary": final_summary, "Flagging Reason": warning, "Suitability_Color": color}
|
| 335 |
except Exception as e:
|
| 336 |
+
return {"Suitability": "Error", "Key Skills & Experience Summary": "Failed to process profile", "Flagging Reason": str(e), "Suitability_Color": "#F44336"}
|
| 337 |
|
| 338 |
@st.cache_data
|
| 339 |
def generate_skill_pie_chart(resumes):
|
|
|
|
| 357 |
labels = list(top_skills.keys())
|
| 358 |
sizes = [(count / sum(top_skills.values())) * 100 for count in top_skills.values()]
|
| 359 |
|
| 360 |
+
# Use dark theme settings for the chart
|
| 361 |
+
plt.style.use('dark_background')
|
| 362 |
fig, ax = plt.subplots(figsize=(6, 4))
|
| 363 |
+
colors = plt.cm.magma(np.linspace(0.3, 0.9, len(labels))) # Vibrant color map for dark mode
|
| 364 |
+
plt.rcParams['text.color'] = '#F8F8F8'
|
| 365 |
+
wedges, texts, autotexts = ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors, textprops={'fontsize': 10, 'color': '#F8F8F8'})
|
| 366 |
ax.axis('equal')
|
| 367 |
+
plt.title("Top Candidate Skill Frequency", fontsize=14, color='#9C27B0', pad=10)
|
| 368 |
return fig
|
| 369 |
|
| 370 |
def render_sidebar():
|
| 371 |
+
"""Render sidebar content with professional HR language."""
|
| 372 |
+
# Define hex colors
|
| 373 |
+
SUCCESS_COLOR = "#4CAF50"
|
| 374 |
+
WARNING_COLOR = "#FFC107"
|
| 375 |
+
DANGER_COLOR = "#F44336"
|
| 376 |
+
PRIMARY_COLOR = "#9C27B0"
|
| 377 |
|
| 378 |
with st.sidebar:
|
| 379 |
st.markdown(f"""
|
| 380 |
<h2 style='text-align: center; border-left: none; padding-left: 0; color: {PRIMARY_COLOR};'>
|
| 381 |
+
TALENT SCREENING ASSISTANT
|
| 382 |
</h2>
|
| 383 |
+
<p style='text-align: center; font-size: 14px; margin-top: 0; color: #B0B0B0;'>
|
| 384 |
Powered by Advanced NLP (BERT + T5)
|
| 385 |
</p>
|
| 386 |
""", unsafe_allow_html=True)
|
|
|
|
| 420 |
if 'valid_resumes' not in st.session_state: st.session_state.valid_resumes = []
|
| 421 |
if 'models' not in st.session_state: st.session_state.models = None
|
| 422 |
|
| 423 |
+
st.markdown("<h1>🚀 AI TALENT SCREENING TOOL</h1>", unsafe_allow_html=True)
|
| 424 |
|
| 425 |
# HR-friendly Tab Names
|
| 426 |
tab_setup, tab_resumes, tab_results = st.tabs(["1. Job Requirement Setup", "2. Candidate Profile Upload", "3. Screening Report & Analytics"])
|
|
|
|
| 535 |
|
| 536 |
for i, resume in enumerate(valid_resumes):
|
| 537 |
status_text.text(f"Status: Analyzing Profile {i+1} of {total_steps}...")
|
| 538 |
+
bert_tok_single = {'input_ids': bert_tokenized['input_ids'][i].unsqueeze(0), 'attention_mask': bert_tokenized['attention_mask'][i].unsqueeze(0)}
|
| 539 |
+
t5_tok_single = {'input_ids': t5_tokenized['input_ids'][i].unsqueeze(0), 'attention_mask': t5_tokenized['attention_mask'][i].unsqueeze(0)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
result = classify_and_summarize_batch(resume, job_description, bert_tok_single, t5_inputs[i], t5_tok_single, job_skills_set)
|
| 541 |
result["Profile ID"] = f"Candidate {i+1}"
|
| 542 |
results.append(result)
|
|
|
|
| 564 |
st.markdown(f"#### Overview: {total} Candidate Profiles Processed")
|
| 565 |
|
| 566 |
# Define hex colors again for the scorecard blocks
|
| 567 |
+
PRIMARY_COLOR = "#9C27B0"
|
| 568 |
+
SUCCESS_COLOR = "#4CAF50"
|
| 569 |
+
WARNING_COLOR = "#FFC107"
|
| 570 |
+
DANGER_COLOR = "#F44336"
|
| 571 |
|
| 572 |
col1, col2, col3, col4 = st.columns(4)
|
| 573 |
|