# # import os, types, streamlit as st | |
# # import os | |
# # os.environ['STREAMLIT_CONFIG_DIR'] = '/tmp/.streamlit' | |
# # # Fetch the hidden code from env var | |
# # 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() | |
# # except Exception as e: | |
# # st.error(f"Error in hidden code: {e}") | |
# # if app_code: | |
# # execute_code(app_code) | |
# # else: | |
# # st.error("APP_CODE is empty. Did you set it?") | |
# import os, types, streamlit as st | |
# import requests # Make sure to import requests if used | |
# 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: | |
# # Extract required variables and functions from the 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") | |
# # Sidebar for navigation | |
# with st.sidebar: | |
# # About section with blue background | |
# 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) | |
# # How it Works section with light blue background | |
# 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) | |
# # Sample Job Descriptions section | |
# st.markdown("<h3 style='font-size:1.2em;'>Sample Job Descriptions</h3>", unsafe_allow_html=True) | |
# # Display sample job descriptions in sidebar with smaller font | |
# st.write("Click any job to prefill the form:") | |
# # Create a container with custom CSS for smaller buttons | |
# 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) | |
# # Add horizontal line before job listings | |
# st.markdown("<hr>", unsafe_allow_html=True) | |
# # Job buttons | |
# for i, job in enumerate(job_posts): | |
# if st.button(job["title"], key=f"job_{i}"): | |
# # This will be used to set the job description text area | |
# st.session_state.job_description = job["jd"] | |
# # Main content area | |
# st.title("Resume Matching System") | |
# # Initialize session state for job description if it doesn't exist | |
# if 'job_description' not in st.session_state: | |
# st.session_state.job_description = "Enter job description here..." | |
# # Input fields - using session state for job description | |
# job_description = st.text_area("Job Description", value=st.session_state.job_description, height=250) | |
# additional_requirements = st.text_area("Additional Requirements", "", height=100) | |
# # Create two columns for the numeric inputs | |
# limit = st.slider("Number of Results", min_value=1, max_value=10, value=5, step=1) | |
# # Search button and animation container | |
# search_button = st.button("Search Resumes", type="primary") | |
# # Create a container for the code animation | |
# code_container = st.container() | |
# # Add custom CSS for the code animation box | |
# 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: | |
# # Prepare the API request | |
# 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: | |
# # Display the animation while waiting | |
# with code_container: | |
# # Create the black box with yellow code animation | |
# code_display = simulated_code_animation(code_container) | |
# import time | |
# strt_time=time.time() | |
# # Make the API request | |
# response = requests.post(url, headers=headers, json=payload) | |
# print(f"total time taken by fast api to generate answer: {time.time()-strt_time}") | |
# # Clear the code animation | |
# code_container.empty() | |
# if response.status_code == 200: | |
# # Process successful response | |
# data = response.json() | |
# st.success(f"Found {data['count']} matching resumes") | |
# # Display each resume with the new card-based layout | |
# for i, resume in enumerate(data['results']): | |
# resume_data = resume['resume_data'] | |
# explainability = resume['explainibility'] | |
# # Generate synthetic contact information | |
# full_name = resume_data.get('full_name', 'No Name') | |
# # Add synthetic data to resume_data if not present | |
# 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) | |
# # Main card with essential information | |
# with st.container(): | |
# col1, col2 = st.columns([7, 3]) | |
# with col1: | |
# # Name with larger font and accent color | |
# st.markdown(f""" | |
# <h2 style="color:#2C3E50; font-size:28px; margin-bottom:5px;"> | |
# {full_name} | |
# </h2> | |
# """, unsafe_allow_html=True) | |
# # Years of experience with an icon | |
# 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 in a nice box with a border | |
# 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: | |
# # Contact card with background | |
# 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 with icon | |
# 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 with icon | |
# 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 with icon | |
# 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 with icon | |
# 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) | |
# # Create expandable sections for details - MOVED INSIDE THE LOOP | |
# with st.expander(f"Resume Content for {full_name}", expanded=False): | |
# tabs = st.tabs(["All Skills", "Experience", "Projects", "Education"]) | |
# # All Skills tab | |
# with tabs[0]: | |
# st.subheader("Skills") | |
# if 'skills' in resume_data and resume_data['skills']: | |
# skills_html = "" | |
# for skill in resume_data['skills']: | |
# # Check if this skill is in matched_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.") | |
# # Experiences tab | |
# 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.") | |
# # Projects tab | |
# 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.") | |
# # Education tab | |
# 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) | |
# # Create a centered container for the download button | |
# col1, col2, col3 = st.columns([1, 2, 1]) | |
# with col2: | |
# # Render the download button with icon | |
# 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) | |
# # Add a separator between resumes | |
# st.markdown("---") | |
# except Exception as e: | |
# # Clear the code animation in case of error | |
# code_container.empty() | |
# st.error(f"An error occurred: {str(e)}") | |
# if __name__ == "__main__": | |
# main() | |
import streamlit as st | |
import requests | |
import random | |
from sample_jobs import job_posts | |
from dotenv import load_dotenv | |
import os | |
load_dotenv() | |
api_endpoint = os.getenv("api_endpoint") | |
hf_token = os.getenv("hf_token") | |
import time | |
def simulated_code_animation(container): | |
"""Displays a simulated code loading animation in a container - one line at a time""" | |
code_snippets = [ | |
"Initializing resume agent", | |
"Initializing resume agent.", | |
"Initializing resume agent..", | |
"Initializing resume agent...", | |
"Vectorizing job description", | |
"Vectorizing job description.", | |
"Vectorizing job description..", | |
"Vectorizing job description...", | |
"Connecting to database", | |
"Connecting to database.", | |
"Connecting to database..", | |
"Connecting to database...", | |
"Database connected!", | |
"Database connected!", | |
"Database connected!", | |
"Database connected!", | |
"Choosing correct database of resumes", | |
"Choosing correct database of resumes.", | |
"Choosing correct database of resumes..", | |
"Choosing correct database of resumes...", | |
"Matching job description to resumes", | |
"Matching job description to resumes.", | |
"Matching job description to resumes..", | |
"Matching job description to resumes...", | |
"Ranking fetched resumes", | |
"Ranking fetched resumes.", | |
"Ranking fetched resumes..", | |
"Ranking fetched resumes...", | |
"Generating explanations", | |
"Generating explanations.", | |
"Generating explanations..", | |
"Generating explanations...", | |
"Resumes coming up!" | |
] | |
# Add custom CSS for the text display | |
container.markdown(""" | |
<style> | |
.terminal-text { | |
background-color: black; | |
color: #ffff00; | |
font-family: monospace; | |
font-size: 18px; | |
padding: 15px; | |
border-radius: 5px; | |
margin: 10px 0; | |
line-height: 1.5; | |
white-space: pre; | |
overflow: auto; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
terminal_placeholder = container.empty() | |
progress_bar = container.progress(0) | |
total_steps = len(code_snippets) | |
for idx, line in enumerate(code_snippets): | |
# Update progress bar | |
progress_value = idx / (total_steps - 1) | |
progress_bar.progress(progress_value) | |
# Update terminal text | |
terminal_placeholder.markdown(f'<div class="terminal-text">{line}</div>', unsafe_allow_html=True) | |
time.sleep(0.85) | |
# Ensure progress bar reaches 100% at the end | |
progress_bar.progress(1.0) | |
return terminal_placeholder | |
def generate_phone_number(): | |
"""Generate a random US phone number with +1 prefix""" | |
area_code = random.randint(200, 999) | |
middle_three = random.randint(100, 999) | |
last_four = random.randint(1000, 9999) | |
return f"+1 ({area_code}) {middle_three}-{last_four}" | |
def generate_email(full_name): | |
"""Generate email from full name""" | |
if not full_name or full_name == 'No Name': | |
return "[email protected]" | |
# Clean and format the name | |
name_parts = full_name.lower().strip().split() | |
if len(name_parts) >= 2: | |
first_name = name_parts[0] | |
last_name = name_parts[-1] | |
return f"{first_name}.{last_name}@gmail.com" | |
else: | |
return f"{name_parts[0]}@gmail.com" | |
def generate_linkedin(full_name): | |
"""Generate LinkedIn URL from full name""" | |
if not full_name or full_name == 'No Name': | |
return "https://linkedin.com/in/professional-profile" | |
# Clean and format the name | |
name_parts = full_name.lower().strip().split() | |
if len(name_parts) >= 2: | |
first_name = name_parts[0] | |
last_name = name_parts[-1] | |
suffix = random.randint(10, 999) if random.random() < 0.3 else "" | |
return f"https://linkedin.com/in/{first_name}-{last_name}{suffix}" | |
else: | |
return f"https://linkedin.com/in/{name_parts[0]}-profile" | |
if __name__ == "__main__": | |
main() |