|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os, types, streamlit as st |
|
import requests |
|
|
|
os.environ['STREAMLIT_CONFIG_DIR'] = '/tmp/.streamlit' |
|
app_code = os.environ.get("APP_CODE", "") |
|
|
|
def execute_code(code_str): |
|
module = types.ModuleType("dynamic_app") |
|
try: |
|
exec(code_str, module.__dict__) |
|
if hasattr(module, "main"): |
|
module.main() |
|
return module |
|
except Exception as e: |
|
st.error(f"Error in hidden code: {e}") |
|
return None |
|
|
|
def main(): |
|
global job_posts, api_endpoint, hf_token, simulated_code_animation, generate_email, generate_phone_number, generate_linkedin |
|
|
|
if app_code: |
|
module = execute_code(app_code) |
|
if module: |
|
|
|
job_posts = getattr(module, 'job_posts', []) |
|
api_endpoint = getattr(module, 'api_endpoint', '') |
|
hf_token = getattr(module, 'hf_token', '') |
|
simulated_code_animation = getattr(module, 'simulated_code_animation', lambda x: None) |
|
generate_email = getattr(module, 'generate_email', lambda x: "[email protected]") |
|
generate_phone_number = getattr(module, 'generate_phone_number', lambda: "555-123-4567") |
|
generate_linkedin = getattr(module, 'generate_linkedin', lambda x: "linkedin.com/in/example") |
|
else: |
|
st.error("APP_CODE is empty. Did you set it?") |
|
return |
|
|
|
|
|
st.set_page_config(layout="wide") |
|
|
|
|
|
with st.sidebar: |
|
|
|
st.markdown(""" |
|
<div style="background-color:#3299a8; padding:10px; border-radius:5px;"> |
|
<h3 style="color:white;">About</h3> |
|
<p style="color:white;"> |
|
This Resume Matching System helps you find the best candidates for your job openings. |
|
Simply input your job description and requirements, and our AI-powered system will |
|
analyze and rank resumes based on skill match, experience, and overall fit. |
|
</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown(""" |
|
<div style="background-color:#e6f3f5; padding:10px; border-radius:5px; margin-top:10px;"> |
|
<h3 style="color:#3299a8;">How it Works</h3> |
|
<ul style="margin-left: 15px; padding-left: 10px;"> |
|
<li>Enter your job description or select a sample</li> |
|
<li>Our AI-agent analyzes key skills and requirements</li> |
|
<li>View ranked candidates with match percentages</li> |
|
<li>Examine detailed skill comparisons for each resume</li> |
|
</ul> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("<h3 style='font-size:1.2em;'>Sample Job Descriptions</h3>", unsafe_allow_html=True) |
|
|
|
|
|
st.write("Click any job to prefill the form:") |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
div.stButton > button { |
|
font-size: 0.8em; |
|
padding: 2px 8px; |
|
margin: 3px 0px; |
|
width: 100%; |
|
text-align: left; |
|
} |
|
.resume-card { |
|
background-color: #f8f9fa; |
|
border-radius: 10px; |
|
padding: 15px; |
|
margin-bottom: 20px; |
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
|
} |
|
.card-header { |
|
border-bottom: 1px solid #e0e0e0; |
|
padding-bottom: 10px; |
|
margin-bottom: 15px; |
|
} |
|
.contact-section { |
|
background-color: #e6f3f5; |
|
padding: 10px; |
|
border-radius: 5px; |
|
margin-top: 10px; |
|
} |
|
.skill-badge { |
|
background-color: #3299a8; |
|
color: white; |
|
padding: 4px 8px; |
|
border-radius: 15px; |
|
margin: 2px; |
|
display: inline-block; |
|
font-size: 0.85em; |
|
} |
|
.missing-skill-badge { |
|
background-color: #6c757d; |
|
color: white; |
|
padding: 4px 8px; |
|
border-radius: 15px; |
|
margin: 2px; |
|
display: inline-block; |
|
font-size: 0.85em; |
|
} |
|
.experience-card { |
|
background-color: #f8f9fa; |
|
border-left: 3px solid #3299a8; |
|
padding: 10px; |
|
margin-bottom: 10px; |
|
border-radius: 0 5px 5px 0; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("<hr>", unsafe_allow_html=True) |
|
|
|
|
|
for i, job in enumerate(job_posts): |
|
if st.button(job["title"], key=f"job_{i}"): |
|
|
|
st.session_state.job_description = job["jd"] |
|
|
|
|
|
st.title("Resume Matching System") |
|
|
|
|
|
if 'job_description' not in st.session_state: |
|
st.session_state.job_description = "Enter job description here..." |
|
|
|
|
|
job_description = st.text_area("Job Description", value=st.session_state.job_description, height=250) |
|
additional_requirements = st.text_area("Additional Requirements", "", height=100) |
|
|
|
|
|
limit = st.slider("Number of Results", min_value=1, max_value=10, value=5, step=1) |
|
|
|
|
|
search_button = st.button("Search Resumes", type="primary") |
|
|
|
|
|
code_container = st.container() |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.highlight { |
|
background-color: black !important; |
|
} |
|
.highlight pre { |
|
color: yellow !important; |
|
} |
|
.highlight .gp, .highlight .gu, .highlight .gt { |
|
color: yellow !important; |
|
} |
|
pre code { |
|
white-space: pre-wrap !important; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
st.markdown(""" |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> |
|
""", unsafe_allow_html=True) |
|
if search_button: |
|
|
|
|
|
|
|
url = f"{api_endpoint}/resumes/search" |
|
headers = { |
|
"accept": "application/json", |
|
"Content-Type": "application/json", |
|
"Authorization": hf_token |
|
} |
|
payload = { |
|
"job_description": job_description, |
|
"additional_requirements": additional_requirements, |
|
"limit": limit, |
|
} |
|
|
|
try: |
|
|
|
with code_container: |
|
|
|
code_display = simulated_code_animation(code_container) |
|
import time |
|
strt_time=time.time() |
|
|
|
response = requests.post(url, headers=headers, json=payload) |
|
print(f"total time taken by fast api to generate answer: {time.time()-strt_time}") |
|
|
|
code_container.empty() |
|
|
|
if response.status_code == 200: |
|
|
|
data = response.json() |
|
st.success(f"Found {data['count']} matching resumes") |
|
|
|
|
|
for i, resume in enumerate(data['results']): |
|
resume_data = resume['resume_data'] |
|
explainability = resume['explainibility'] |
|
|
|
full_name = resume_data.get('full_name', 'No Name') |
|
|
|
if 'email' not in resume_data and 'email_id' not in resume_data: |
|
resume_data['email'] = generate_email(full_name) |
|
if 'phone' not in resume_data: |
|
resume_data['phone'] = generate_phone_number() |
|
if 'linkedin_profile' not in resume_data and 'linkedin' not in resume_data: |
|
resume_data['linkedin'] = generate_linkedin(full_name) |
|
|
|
|
|
with st.container(): |
|
col1, col2 = st.columns([7, 3]) |
|
|
|
with col1: |
|
|
|
st.markdown(f""" |
|
<h2 style="color:#2C3E50; font-size:28px; margin-bottom:5px;"> |
|
{full_name} |
|
</h2> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
years_exp = resume_data.get('total_experience_years', 'Not specified') |
|
st.markdown(f""" |
|
<div style="margin-bottom:15px;"> |
|
<span style="color:#3299a8; font-size:16px;"> |
|
<i class="fas fa-briefcase"></i> <strong>Years of Experience:</strong> |
|
</span> |
|
<span style="font-size:16px;">{years_exp}</span> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
summary = "" |
|
if 'overall_summary' in resume: |
|
summary = resume.get('overall_summary') |
|
elif isinstance(resume_data.get('summary'), str): |
|
summary = resume_data.get('summary') |
|
|
|
st.markdown(explainability) |
|
|
|
if summary: |
|
st.markdown(f""" |
|
<div style="border-left:4px solid #3299a8; padding-left:15px; |
|
background-color:#f8f9fa; padding:10px; border-radius:0 5px 5px 0;"> |
|
<div style="color:#3299a8; font-weight:bold; margin-bottom:5px;">SUMMARY</div> |
|
<div style="font-size:15px; line-height:1.5;">{summary}</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
st.markdown("<br>", unsafe_allow_html=True) |
|
with col2: |
|
|
|
st.markdown(""" |
|
<div style="background-color:#f0f7fa; border-radius:8px; padding:15px; |
|
box-shadow:0 2px 5px rgba(0,0,0,0.05); margin-bottom:15px;"> |
|
<h3 style="color:#3299a8; font-size:18px; border-bottom:2px solid #3299a8; |
|
padding-bottom:8px; margin-top:0;"> |
|
<i class="fas fa-address-card"></i> CONTACT INFORMATION |
|
</h3> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
email = resume_data.get('email', resume_data.get('email_id', 'Not provided')) |
|
st.markdown(f""" |
|
<div style="margin-bottom:10px;"> |
|
<i class="fas fa-envelope" style="color:#3299a8; width:20px;"></i> |
|
<strong style="color:#555;">Email:</strong><br> |
|
<a href="mailto:{email}" style="text-decoration:none; color:#3299a8; |
|
margin-left:25px; display:block; margin-top:3px;">{email}</a> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
phone = resume_data.get('phone', 'Not provided') |
|
st.markdown(f""" |
|
<div style="margin-bottom:10px;"> |
|
<i class="fas fa-phone" style="color:#3299a8; width:20px;"></i> |
|
<strong style="color:#555;">Phone:</strong><br> |
|
<span style="margin-left:25px; display:block; margin-top:3px;">{phone}</span> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
location = resume_data.get('location', 'Not specified') |
|
st.markdown(f""" |
|
<div style="margin-bottom:10px;"> |
|
<i class="fas fa-map-marker-alt" style="color:#3299a8; width:20px;"></i> |
|
<strong style="color:#555;">Location:</strong><br> |
|
<span style="margin-left:25px; display:block; margin-top:3px;">{location}</span> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
linkedin_url = resume_data.get('linkedin_profile', resume_data.get('linkedin', 'Not provided')) |
|
st.markdown(f""" |
|
<div style="margin-bottom:5px;"> |
|
<i class="fab fa-linkedin" style="color:#3299a8; width:20px;"></i> |
|
<strong style="color:#555;">LinkedIn:</strong><br> |
|
<a href="{linkedin_url}" target="_blank" style="text-decoration:none; |
|
color:#3299a8; margin-left:25px; display:block; margin-top:3px; |
|
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%;"> |
|
{linkedin_url.replace('https://', '')} |
|
</a> |
|
</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with st.expander(f"Resume Content for {full_name}", expanded=False): |
|
tabs = st.tabs(["All Skills", "Experience", "Projects", "Education"]) |
|
|
|
|
|
with tabs[0]: |
|
st.subheader("Skills") |
|
if 'skills' in resume_data and resume_data['skills']: |
|
skills_html = "" |
|
for skill in resume_data['skills']: |
|
|
|
is_matched = skill in resume.get('matched_skills', []) |
|
badge_class = "skill-badge" if is_matched else "missing-skill-badge" |
|
skills_html += f'<span class="{badge_class}">{skill}</span> ' |
|
st.markdown(skills_html, unsafe_allow_html=True) |
|
else: |
|
st.write("No skills listed.") |
|
|
|
|
|
with tabs[1]: |
|
st.subheader("Professional Experience") |
|
if 'experience' in resume_data and resume_data['experience']: |
|
for exp in resume_data['experience']: |
|
with st.container(): |
|
st.markdown(f""" |
|
<div class="experience-card"> |
|
<strong>{exp.get('title', 'Position')}</strong> at <strong>{exp.get('company', 'Company')}</strong> |
|
<br><em>{exp.get('duration', '')}</em> |
|
<p>{exp.get('description', '')}</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
elif 'professional_experiences' in resume_data and resume_data['professional_experiences']: |
|
for exp in resume_data['professional_experiences']: |
|
with st.container(): |
|
st.markdown(f""" |
|
<div class="experience-card"> |
|
<strong>{exp.get('title', 'Position')}</strong> at <strong>{exp.get('company', 'Company')}</strong> |
|
<br><em>{exp.get('duration', '')}</em> |
|
<p>{exp.get('description', '')}</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.write("No experience information available.") |
|
|
|
|
|
with tabs[2]: |
|
st.subheader("Projects") |
|
if 'projects' in resume_data and resume_data['projects']: |
|
for project in resume_data['projects']: |
|
if isinstance(project, dict): |
|
project_name = project.get('name', project.get('title', 'Project')) |
|
project_desc = project.get('description', '') |
|
st.markdown(f"**{project_name}**") |
|
st.markdown(f"{project_desc}") |
|
st.markdown("---") |
|
else: |
|
st.markdown(f"- {project}") |
|
else: |
|
st.write("No projects listed.") |
|
|
|
|
|
with tabs[3]: |
|
st.subheader("Education") |
|
if 'education' in resume_data and resume_data['education']: |
|
for edu in resume_data['education']: |
|
with st.container(): |
|
st.markdown(f""" |
|
<div class="experience-card"> |
|
<strong>{edu.get('degree', 'Degree')}</strong> |
|
<br><strong>{edu.get('institution', 'Institution')}</strong> |
|
<br><em>{edu.get('date', '')}</em> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.write("No education information available.") |
|
|
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.download-resume-btn { |
|
display: inline-block; |
|
width: 100%; |
|
background-color: #3299a8; |
|
color: white; |
|
text-align: center; |
|
padding: 12px 20px; |
|
margin: 8px 0 25px 0; |
|
border-radius: 5px; |
|
border: none; |
|
cursor: pointer; |
|
font-size: 16px; |
|
font-weight: 500; |
|
transition: all 0.3s ease; |
|
text-decoration: none; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
} |
|
|
|
.download-resume-btn:hover { |
|
background-color: #277a86; |
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2); |
|
transform: translateY(-2px); |
|
} |
|
|
|
.download-resume-btn i { |
|
margin-right: 10px; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
with col2: |
|
|
|
st.markdown(f""" |
|
<button class="download-resume-btn" onclick="alert('This is a demo feature. In a production environment, this would download the resume for {full_name}.')"> |
|
<i class="fas fa-file-download"></i> Download Resume |
|
</button> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
except Exception as e: |
|
|
|
code_container.empty() |
|
st.error(f"An error occurred: {str(e)}") |
|
|
|
if __name__ == "__main__": |
|
main() |
|
|