| # # 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@example.com") | |
| # 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 "contact@example.com" | |
| # 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" | |
| def main(): | |
| 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.") | |
| # # Accomplishments tab | |
| # with tabs[4]: | |
| # st.subheader("Accomplishments") | |
| # if 'accomplishments' in resume_data and resume_data['accomplishments']: | |
| # for accomplishment in resume_data['accomplishments']: | |
| # if isinstance(accomplishment, str): | |
| # st.markdown(f"- {accomplishment}") | |
| # elif isinstance(accomplishment, dict): | |
| # # Handle if accomplishments are stored as dictionary objects | |
| # title = accomplishment.get('title', 'Accomplishment') | |
| # description = accomplishment.get('description', '') | |
| # st.markdown(f"**{title}**") | |
| # if description: | |
| # st.markdown(f"{description}") | |
| # st.markdown("---") | |
| # else: | |
| # st.write("No accomplishments listed.") | |
| # # Certifications tab | |
| # with tabs[5]: | |
| # st.subheader("Certifications") | |
| # if 'certifications' in resume_data and resume_data['certifications']: | |
| # for cert in resume_data['certifications']: | |
| # if isinstance(cert, str): | |
| # st.markdown(f"- {cert}") | |
| # elif isinstance(cert, dict): | |
| # # Handle if certifications are stored as dictionary objects | |
| # name = cert.get('name', cert.get('title', 'Certification')) | |
| # issuer = cert.get('issuer', cert.get('organization', '')) | |
| # date = cert.get('date', cert.get('issued_date', '')) | |
| # st.markdown(f"**{name}**") | |
| # if issuer: | |
| # st.markdown(f"Issuer: {issuer}") | |
| # if date: | |
| # st.markdown(f"Date: {date}") | |
| # st.markdown("---") | |
| # else: | |
| # st.write("No certifications listed.") | |
| # Add the download button after each resume | |
| 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() |