""" Smart Resume AI - Main Application """ import time from PIL import Image from jobs.job_search import render_job_search from datetime import datetime from ui_components import ( apply_modern_styles, hero_section, feature_card, about_section, page_header, render_analytics_section, render_activity_section, render_suggestions_section ) from feedback.feedback import FeedbackManager from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.shared import Inches, Pt from docx import Document import io import base64 import plotly.graph_objects as go from streamlit_lottie import st_lottie import requests from dashboard.dashboard import DashboardManager from config.courses import COURSES_BY_CATEGORY, RESUME_VIDEOS, INTERVIEW_VIDEOS, get_courses_for_role, get_category_for_role from config.job_roles import JOB_ROLES from config.database import ( get_database_connection, save_resume_data, save_analysis_data, init_database, verify_admin, log_admin_action, save_ai_analysis_data, get_ai_analysis_stats, reset_ai_analysis_stats, get_detailed_ai_analysis_stats ) from utils.ai_resume_analyzer import AIResumeAnalyzer from utils.resume_builder import ResumeBuilder from utils.resume_analyzer import ResumeAnalyzer import traceback import plotly.express as px import pandas as pd import json import streamlit as st import datetime # Set page config at the very beginning st.set_page_config( page_title="Smart Resume AI", page_icon="🚀", layout="wide" ) class ResumeApp: def __init__(self): """Initialize the application""" if 'form_data' not in st.session_state: st.session_state.form_data = { 'personal_info': { 'full_name': '', 'email': '', 'phone': '', 'location': '', 'linkedin': '', 'portfolio': '' }, 'summary': '', 'experiences': [], 'education': [], 'projects': [], 'skills_categories': { 'technical': [], 'soft': [], 'languages': [], 'tools': [] } } # Initialize navigation state if 'page' not in st.session_state: st.session_state.page = 'home' # Initialize admin state if 'is_admin' not in st.session_state: st.session_state.is_admin = False self.pages = { "🏠 HOME": self.render_home, "🔍 RESUME ANALYZER": self.render_analyzer, "📝 RESUME BUILDER": self.render_builder, "📊 DASHBOARD": self.render_dashboard, "đŸŽ¯ JOB SEARCH": self.render_job_search, "đŸ’Ŧ FEEDBACK": self.render_feedback_page, "â„šī¸ ABOUT": self.render_about } # Initialize dashboard manager self.dashboard_manager = DashboardManager() self.analyzer = ResumeAnalyzer() self.ai_analyzer = AIResumeAnalyzer() self.builder = ResumeBuilder() self.job_roles = JOB_ROLES # Initialize session state if 'user_id' not in st.session_state: st.session_state.user_id = 'default_user' if 'selected_role' not in st.session_state: st.session_state.selected_role = None # Initialize database init_database() # Load external CSS with open('style/style.css') as f: st.markdown(f'', unsafe_allow_html=True) # Load Google Fonts st.markdown(""" """, unsafe_allow_html=True) if 'resume_data' not in st.session_state: st.session_state.resume_data = [] if 'ai_analysis_stats' not in st.session_state: st.session_state.ai_analysis_stats = { 'score_distribution': {}, 'total_analyses': 0, 'average_score': 0 } def load_lottie_url(self, url: str): """Load Lottie animation from URL""" r = requests.get(url) if r.status_code != 200: return None return r.json() def apply_global_styles(self): st.markdown(""" """, unsafe_allow_html=True) def add_footer(self): """Add a footer to all pages""" st.markdown("
", unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 3, 1]) with col2: # GitHub star button with lottie animation st.markdown("""
Star this repo
""", unsafe_allow_html=True) # Footer text st.markdown("""

Powered by Streamlit and Groq Llama 3.3 70B | Developed by Parthib karak

"Every star counts! If you find this project helpful, please consider starring the repo to help it reach more people."

""", unsafe_allow_html=True) def load_image(self, image_name): """Load image from static directory""" try: image_path = f"c:/Users/shree/Downloads/smart-resume-ai/{image_name}" with open(image_path, "rb") as f: image_bytes = f.read() encoded = base64.b64encode(image_bytes).decode() return f"data:image/png;base64,{encoded}" except Exception as e: print(f"Error loading image {image_name}: {e}") return None def export_to_excel(self): """Export resume data to Excel""" conn = get_database_connection() # Get resume data with analysis query = """ SELECT rd.name, rd.email, rd.phone, rd.linkedin, rd.github, rd.portfolio, rd.summary, rd.target_role, rd.target_category, rd.education, rd.experience, rd.projects, rd.skills, ra.ats_score, ra.keyword_match_score, ra.format_score, ra.section_score, ra.missing_skills, ra.recommendations, rd.created_at FROM resume_data rd LEFT JOIN resume_analysis ra ON rd.id = ra.resume_id """ try: # Read data into DataFrame df = pd.read_sql_query(query, conn) # Create Excel writer object output = io.BytesIO() with pd.ExcelWriter(output, engine='openpyxl') as writer: df.to_excel(writer, index=False, sheet_name='Resume Data') return output.getvalue() except Exception as e: print(f"Error exporting to Excel: {str(e)}") return None finally: conn.close() def render_dashboard(self): """Render the dashboard page""" self.dashboard_manager.render_dashboard() st.toast("Check out these repositories: [Awesome Hacking](https://github.com/Hunterdii/Awesome-Hacking)", icon="â„šī¸") def render_empty_state(self, icon, message): """Render an empty state with icon and message""" return f"""

{message}

""" def analyze_resume(self, resume_text): """Analyze resume and store results""" analytics = self.analyzer.analyze_resume(resume_text) st.session_state.analytics_data = analytics return analytics def handle_resume_upload(self): """Handle resume upload and analysis""" uploaded_file = st.file_uploader( "Upload your resume", type=['pdf', 'docx']) if uploaded_file is not None: try: # Extract text from resume if uploaded_file.type == "application/pdf": resume_text = extract_text_from_pdf(uploaded_file) else: resume_text = extract_text_from_docx(uploaded_file) # Store resume data st.session_state.resume_data = { 'filename': uploaded_file.name, 'content': resume_text, 'upload_time': datetime.now().isoformat() } # Analyze resume analytics = self.analyze_resume(resume_text) return True except Exception as e: st.error(f"Error processing resume: {str(e)}") return False return False def render_builder(self): st.title("Resume Builder 📝") st.write("Create your professional resume") # Template selection template_options = ["Modern", "Professional", "Minimal", "Creative"] selected_template = st.selectbox( "Select Resume Template", template_options) st.success(f"🎨 Currently using: {selected_template} Template") # Personal Information st.subheader("Personal Information") col1, col2 = st.columns(2) with col1: # Get existing values from session state existing_name = st.session_state.form_data['personal_info']['full_name'] existing_email = st.session_state.form_data['personal_info']['email'] existing_phone = st.session_state.form_data['personal_info']['phone'] # Input fields with existing values full_name = st.text_input("Full Name", value=existing_name) email = st.text_input( "Email", value=existing_email, key="email_input") phone = st.text_input("Phone", value=existing_phone) # Immediately update session state after email input if 'email_input' in st.session_state: st.session_state.form_data['personal_info']['email'] = st.session_state.email_input with col2: # Get existing values from session state existing_location = st.session_state.form_data['personal_info']['location'] existing_linkedin = st.session_state.form_data['personal_info']['linkedin'] existing_portfolio = st.session_state.form_data['personal_info']['portfolio'] # Input fields with existing values location = st.text_input("Location", value=existing_location) linkedin = st.text_input("LinkedIn URL", value=existing_linkedin) portfolio = st.text_input( "Portfolio Website", value=existing_portfolio) # Update personal info in session state st.session_state.form_data['personal_info'] = { 'full_name': full_name, 'email': email, 'phone': phone, 'location': location, 'linkedin': linkedin, 'portfolio': portfolio } # Professional Summary st.subheader("Professional Summary") summary = st.text_area("Professional Summary", value=st.session_state.form_data.get('summary', ''), height=150, help="Write a brief summary highlighting your key skills and experience") # Experience Section st.subheader("Work Experience") if 'experiences' not in st.session_state.form_data: st.session_state.form_data['experiences'] = [] if st.button("Add Experience"): st.session_state.form_data['experiences'].append({ 'company': '', 'position': '', 'start_date': '', 'end_date': '', 'description': '', 'responsibilities': [], 'achievements': [] }) for idx, exp in enumerate(st.session_state.form_data['experiences']): with st.expander(f"Experience {idx + 1}", expanded=True): col1, col2 = st.columns(2) with col1: exp['company'] = st.text_input( "Company Name", key=f"company_{idx}", value=exp.get( 'company', '')) exp['position'] = st.text_input( "Position", key=f"position_{idx}", value=exp.get( 'position', '')) with col2: exp['start_date'] = st.text_input( "Start Date", key=f"start_date_{idx}", value=exp.get( 'start_date', '')) exp['end_date'] = st.text_input( "End Date", key=f"end_date_{idx}", value=exp.get( 'end_date', '')) exp['description'] = st.text_area("Role Overview", key=f"desc_{idx}", value=exp.get( 'description', ''), help="Brief overview of your role and impact") # Responsibilities st.markdown("##### Key Responsibilities") resp_text = st.text_area("Enter responsibilities (one per line)", key=f"resp_{idx}", value='\n'.join( exp.get('responsibilities', [])), height=100, help="List your main responsibilities, one per line") exp['responsibilities'] = [r.strip() for r in resp_text.split('\n') if r.strip()] # Achievements st.markdown("##### Key Achievements") achv_text = st.text_area("Enter achievements (one per line)", key=f"achv_{idx}", value='\n'.join( exp.get('achievements', [])), height=100, help="List your notable achievements, one per line") exp['achievements'] = [a.strip() for a in achv_text.split('\n') if a.strip()] if st.button("Remove Experience", key=f"remove_exp_{idx}"): st.session_state.form_data['experiences'].pop(idx) st.rerun() # Projects Section st.subheader("Projects") if 'projects' not in st.session_state.form_data: st.session_state.form_data['projects'] = [] if st.button("Add Project"): st.session_state.form_data['projects'].append({ 'name': '', 'technologies': '', 'description': '', 'responsibilities': [], 'achievements': [], 'link': '' }) for idx, proj in enumerate(st.session_state.form_data['projects']): with st.expander(f"Project {idx + 1}", expanded=True): proj['name'] = st.text_input( "Project Name", key=f"proj_name_{idx}", value=proj.get( 'name', '')) proj['technologies'] = st.text_input("Technologies Used", key=f"proj_tech_{idx}", value=proj.get( 'technologies', ''), help="List the main technologies, frameworks, and tools used") proj['description'] = st.text_area("Project Overview", key=f"proj_desc_{idx}", value=proj.get( 'description', ''), help="Brief overview of the project and its goals") # Project Responsibilities st.markdown("##### Key Responsibilities") proj_resp_text = st.text_area("Enter responsibilities (one per line)", key=f"proj_resp_{idx}", value='\n'.join( proj.get('responsibilities', [])), height=100, help="List your main responsibilities in the project") proj['responsibilities'] = [r.strip() for r in proj_resp_text.split('\n') if r.strip()] # Project Achievements st.markdown("##### Key Achievements") proj_achv_text = st.text_area("Enter achievements (one per line)", key=f"proj_achv_{idx}", value='\n'.join( proj.get('achievements', [])), height=100, help="List the project's key achievements and your contributions") proj['achievements'] = [a.strip() for a in proj_achv_text.split('\n') if a.strip()] proj['link'] = st.text_input("Project Link (optional)", key=f"proj_link_{idx}", value=proj.get('link', ''), help="Link to the project repository, demo, or documentation") if st.button("Remove Project", key=f"remove_proj_{idx}"): st.session_state.form_data['projects'].pop(idx) st.rerun() # Education Section st.subheader("Education") if 'education' not in st.session_state.form_data: st.session_state.form_data['education'] = [] if st.button("Add Education"): st.session_state.form_data['education'].append({ 'school': '', 'degree': '', 'field': '', 'graduation_date': '', 'gpa': '', 'achievements': [] }) for idx, edu in enumerate(st.session_state.form_data['education']): with st.expander(f"Education {idx + 1}", expanded=True): col1, col2 = st.columns(2) with col1: edu['school'] = st.text_input( "School/University", key=f"school_{idx}", value=edu.get( 'school', '')) edu['degree'] = st.text_input( "Degree", key=f"degree_{idx}", value=edu.get( 'degree', '')) with col2: edu['field'] = st.text_input( "Field of Study", key=f"field_{idx}", value=edu.get( 'field', '')) edu['graduation_date'] = st.text_input("Graduation Date", key=f"grad_date_{idx}", value=edu.get('graduation_date', '')) edu['gpa'] = st.text_input( "GPA (optional)", key=f"gpa_{idx}", value=edu.get( 'gpa', '')) # Educational Achievements st.markdown("##### Achievements & Activities") edu_achv_text = st.text_area("Enter achievements (one per line)", key=f"edu_achv_{idx}", value='\n'.join( edu.get('achievements', [])), height=100, help="List academic achievements, relevant coursework, or activities") edu['achievements'] = [a.strip() for a in edu_achv_text.split('\n') if a.strip()] if st.button("Remove Education", key=f"remove_edu_{idx}"): st.session_state.form_data['education'].pop(idx) st.rerun() # Skills Section st.subheader("Skills") if 'skills_categories' not in st.session_state.form_data: st.session_state.form_data['skills_categories'] = { 'technical': [], 'soft': [], 'languages': [], 'tools': [] } col1, col2 = st.columns(2) with col1: tech_skills = st.text_area("Technical Skills (one per line)", value='\n'.join( st.session_state.form_data['skills_categories']['technical']), height=150, help="Programming languages, frameworks, databases, etc.") st.session_state.form_data['skills_categories']['technical'] = [ s.strip() for s in tech_skills.split('\n') if s.strip()] soft_skills = st.text_area("Soft Skills (one per line)", value='\n'.join( st.session_state.form_data['skills_categories']['soft']), height=150, help="Leadership, communication, problem-solving, etc.") st.session_state.form_data['skills_categories']['soft'] = [ s.strip() for s in soft_skills.split('\n') if s.strip()] with col2: languages = st.text_area("Languages (one per line)", value='\n'.join( st.session_state.form_data['skills_categories']['languages']), height=150, help="Programming or human languages with proficiency level") st.session_state.form_data['skills_categories']['languages'] = [ l.strip() for l in languages.split('\n') if l.strip()] tools = st.text_area("Tools & Technologies (one per line)", value='\n'.join( st.session_state.form_data['skills_categories']['tools']), height=150, help="Development tools, software, platforms, etc.") st.session_state.form_data['skills_categories']['tools'] = [ t.strip() for t in tools.split('\n') if t.strip()] # Update form data in session state st.session_state.form_data.update({ 'summary': summary }) # Generate Resume button if st.button("Generate Resume 📄", type="primary"): print("Validating form data...") print(f"Session state form data: {st.session_state.form_data}") print(f"Email input value: {st.session_state.get('email_input', '')}") # Get the current values from form current_name = st.session_state.form_data['personal_info']['full_name'].strip( ) current_email = st.session_state.email_input if 'email_input' in st.session_state else '' print(f"Current name: {current_name}") print(f"Current email: {current_email}") # Validate required fields if not current_name: st.error("âš ī¸ Please enter your full name.") return if not current_email: st.error("âš ī¸ Please enter your email address.") return # Update email in form data one final time st.session_state.form_data['personal_info']['email'] = current_email try: print("Preparing resume data...") # Prepare resume data with current form values resume_data = { "personal_info": st.session_state.form_data['personal_info'], "summary": st.session_state.form_data.get('summary', '').strip(), "experience": st.session_state.form_data.get('experiences', []), "education": st.session_state.form_data.get('education', []), "projects": st.session_state.form_data.get('projects', []), "skills": st.session_state.form_data.get('skills_categories', { 'technical': [], 'soft': [], 'languages': [], 'tools': [] }), "template": selected_template } print(f"Resume data prepared: {resume_data}") try: # Generate resume resume_buffer = self.builder.generate_resume(resume_data) if resume_buffer: try: # Save resume data to database save_resume_data(resume_data) # Offer the resume for download st.success("✅ Resume generated successfully!") # Show snowflake effect st.snow() st.download_button( label="Download Resume đŸ“Ĩ", data=resume_buffer, file_name=f"{current_name.replace(' ', '_')}_resume.docx", mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document", on_click=lambda: st.balloons() ) except Exception as db_error: print(f"Warning: Failed to save to database: {str(db_error)}") # Still allow download even if database save fails st.warning( "âš ī¸ Resume generated but couldn't be saved to database") # Show balloons effect st.balloons() st.download_button( label="Download Resume đŸ“Ĩ", data=resume_buffer, file_name=f"{current_name.replace(' ', '_')}_resume.docx", mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document", on_click=lambda: st.balloons() ) else: st.error( "❌ Failed to generate resume. Please try again.") print("Resume buffer was None") except Exception as gen_error: print(f"Error during resume generation: {str(gen_error)}") print(f"Full traceback: {traceback.format_exc()}") st.error(f"❌ Error generating resume: {str(gen_error)}") except Exception as e: print(f"Error preparing resume data: {str(e)}") print(f"Full traceback: {traceback.format_exc()}") st.error(f"❌ Error preparing resume data: {str(e)}") st.toast("Check out these repositories: [30-Days-Of-Rust](https://github.com/Hunterdii/30-Days-Of-Rust)", icon="â„šī¸") def render_about(self): """Render the about page""" # Apply modern styles from ui_components import apply_modern_styles import base64 import os # Function to load image as base64 def get_image_as_base64(file_path): try: with open(file_path, "rb") as image_file: encoded = base64.b64encode(image_file.read()).decode() return f"data:image/jpeg;base64,{encoded}" except: return None # Get image path and convert to base64 image_path = os.path.join( os.path.dirname(__file__), "assets", "124852522.jpeg") image_base64 = get_image_as_base64(image_path) apply_modern_styles() # Add Font Awesome icons and custom CSS st.markdown(""" """, unsafe_allow_html=True) # Hero Section st.markdown("""

About Smart Resume AI

A powerful AI-driven platform for optimizing your resume

""", unsafe_allow_html=True) # Profile Section st.markdown(f"""
Parthib karak

Parthib karak

Full Stack Developer & AI/ML Enthusiast

Hello! I'm a passionate Full Stack Developer with expertise in AI and Machine Learning. I created Smart Resume AI to revolutionize how job seekers approach their career journey. With my background in both software development and AI, I've designed this platform to provide intelligent, data-driven insights for resume optimization.

""", unsafe_allow_html=True) # Vision Section st.markdown("""

Our Vision

"Smart Resume AI represents my vision of democratizing career advancement through technology. By combining cutting-edge AI with intuitive design, this platform empowers job seekers at every career stage to showcase their true potential and stand out in today's competitive job market."

""", unsafe_allow_html=True) # Features Section st.markdown("""

AI-Powered Analysis

Advanced AI algorithms provide detailed insights and suggestions to optimize your resume for maximum impact.

Data-Driven Insights

Make informed decisions with our analytics-based recommendations and industry insights.

Privacy First

Your data security is our priority. We ensure your information is always protected and private.

Start Your Journey
""", unsafe_allow_html=True) st.toast("Check out these repositories: [Iriswise](https://github.com/Hunterdii/Iriswise)", icon="â„šī¸") def render_analyzer(self): """Render the resume analyzer page""" apply_modern_styles() # Page Header page_header( "Resume Analyzer", "Get instant AI-powered feedback to optimize your resume" ) # Create tabs for Normal Analyzer and AI Analyzer analyzer_tabs = st.tabs(["Standard Analyzer", "AI Analyzer"]) with analyzer_tabs[0]: # Job Role Selection categories = list(self.job_roles.keys()) selected_category = st.selectbox( "Job Category", categories, key="standard_category") roles = list(self.job_roles[selected_category].keys()) selected_role = st.selectbox( "Specific Role", roles, key="standard_role") role_info = self.job_roles[selected_category][selected_role] # Display role information st.markdown(f"""

{selected_role}

{role_info['description']}

Required Skills:

{', '.join(role_info['required_skills'])}

""", unsafe_allow_html=True) # File Upload uploaded_file = st.file_uploader( "Upload your resume", type=[ 'pdf', 'docx'], key="standard_file") if not uploaded_file: # Display empty state with a prominent upload button st.markdown( self.render_empty_state( "fas fa-cloud-upload-alt", "Upload your resume to get started with standard analysis" ), unsafe_allow_html=True ) # Add a prominent upload button col1, col2, col3 = st.columns([1, 2, 1]) with col2: st.markdown(""" """, unsafe_allow_html=True) col1, col2, col3 = st.columns(3) with col1: st.markdown(f"""
Total AI Analyses
{ai_stats["total_analyses"]}
""", unsafe_allow_html=True) with col2: # Determine color based on score score_color = "#38ef7d" if ai_stats["average_score"] >= 80 else "#FFEB3B" if ai_stats[ "average_score"] >= 60 else "#FF5252" st.markdown(f"""
Average Resume Score
{ai_stats["average_score"]}/100
""", unsafe_allow_html=True) with col3: # Create a gauge chart for average score import plotly.graph_objects as go fig = go.Figure(go.Indicator( mode="gauge+number", value=ai_stats["average_score"], domain={'x': [0, 1], 'y': [0, 1]}, title={ 'text': "Score", 'font': { 'size': 14, 'color': 'white'}}, gauge={ 'axis': {'range': [0, 100], 'tickwidth': 1, 'tickcolor': "white"}, 'bar': {'color': "#38ef7d" if ai_stats["average_score"] >= 80 else "#FFEB3B" if ai_stats["average_score"] >= 60 else "#FF5252"}, 'bgcolor': "rgba(0,0,0,0)", 'borderwidth': 2, 'bordercolor': "white", 'steps': [ {'range': [ 0, 40], 'color': 'rgba(255, 82, 82, 0.3)'}, {'range': [ 40, 70], 'color': 'rgba(255, 235, 59, 0.3)'}, {'range': [ 70, 100], 'color': 'rgba(56, 239, 125, 0.3)'} ], } )) fig.update_layout( paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font={'color': "white"}, height=150, margin=dict(l=10, r=10, t=30, b=10) ) st.plotly_chart(fig, use_container_width=True) # Display model usage with enhanced visualization if ai_stats["model_usage"]: st.markdown("### 🤖 Model Usage") model_data = pd.DataFrame(ai_stats["model_usage"]) # Create a more colorful pie chart import plotly.express as px fig = px.pie( model_data, values="count", names="model", color_discrete_sequence=px.colors.qualitative.Bold, hole=0.4 ) fig.update_traces( textposition='inside', textinfo='percent+label', marker=dict( line=dict( color='#000000', width=1.5)) ) fig.update_layout( margin=dict(l=20, r=20, t=30, b=20), height=300, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(color="#ffffff", size=14), legend=dict( orientation="h", yanchor="bottom", y=-0.1, xanchor="center", x=0.5 ), title={ 'text': 'AI Model Distribution', 'y': 0.95, 'x': 0.5, 'xanchor': 'center', 'yanchor': 'top', 'font': {'size': 18, 'color': 'white'} } ) st.plotly_chart(fig, use_container_width=True) # Display top job roles with enhanced visualization if ai_stats["top_job_roles"]: st.markdown("### đŸŽ¯ Top Job Roles") roles_data = pd.DataFrame( ai_stats["top_job_roles"]) # Create a more colorful bar chart fig = px.bar( roles_data, x="role", y="count", color="count", color_continuous_scale=px.colors.sequential.Viridis, labels={ "role": "Job Role", "count": "Number of Analyses"} ) fig.update_traces( marker_line_width=1.5, marker_line_color="white", opacity=0.9 ) fig.update_layout( margin=dict(l=20, r=20, t=50, b=30), height=350, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(color="#ffffff", size=14), title={ 'text': 'Most Analyzed Job Roles', 'y': 0.95, 'x': 0.5, 'xanchor': 'center', 'yanchor': 'top', 'font': {'size': 18, 'color': 'white'} }, xaxis=dict( title="", tickangle=-45, tickfont=dict(size=12) ), yaxis=dict( title="Number of Analyses", gridcolor="rgba(255, 255, 255, 0.1)" ), coloraxis_showscale=False ) st.plotly_chart(fig, use_container_width=True) # Add a timeline chart for analysis over time (mock # data for now) st.markdown("### 📈 Analysis Trend") st.info( "This is a conceptual visualization. To implement actual time-based analysis, additional data collection would be needed.") # Create mock data for timeline import datetime import numpy as np today = datetime.datetime.now() dates = [ (today - datetime.timedelta( days=i)).strftime('%Y-%m-%d') for i in range(7)] dates.reverse() # Generate some random data that sums to # total_analyses total = ai_stats["total_analyses"] if total > 7: values = np.random.dirichlet( np.ones(7)) * total values = [round(v) for v in values] # Adjust to make sure sum equals total diff = total - sum(values) values[-1] += diff else: values = [0] * 7 for i in range(total): values[-(i % 7) - 1] += 1 trend_data = pd.DataFrame({ 'Date': dates, 'Analyses': values }) fig = px.line( trend_data, x='Date', y='Analyses', markers=True, line_shape='spline', color_discrete_sequence=["#38ef7d"] ) fig.update_traces( line=dict(width=3), marker=dict( size=8, line=dict( width=2, color='white')) ) fig.update_layout( margin=dict(l=20, r=20, t=50, b=30), height=300, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(color="#ffffff", size=14), title={ 'text': 'Analysis Activity (Last 7 Days)', 'y': 0.95, 'x': 0.5, 'xanchor': 'center', 'yanchor': 'top', 'font': {'size': 18, 'color': 'white'} }, xaxis=dict( title="", gridcolor="rgba(255, 255, 255, 0.1)" ), yaxis=dict( title="Number of Analyses", gridcolor="rgba(255, 255, 255, 0.1)" ) ) st.plotly_chart(fig, use_container_width=True) # Display score distribution if available if ai_stats["score_distribution"]: st.markdown("""

📊 Score Distribution Analysis

""", unsafe_allow_html=True) score_data = pd.DataFrame( ai_stats["score_distribution"]) # Create a more visually appealing bar chart for # score distribution fig = px.bar( score_data, x="range", y="count", color="range", color_discrete_map={ "0-20": "#FF5252", "21-40": "#FF7043", "41-60": "#FFEB3B", "61-80": "#8BC34A", "81-100": "#38ef7d" }, labels={ "range": "Score Range", "count": "Number of Resumes"}, text="count" # Display count values on bars ) fig.update_traces( marker_line_width=2, marker_line_color="white", opacity=0.9, textposition='outside', textfont=dict( color="white", size=14, family="Arial, sans-serif"), hovertemplate="Score Range: %{x}
Number of Resumes: %{y}" ) # Add a gradient background to the chart fig.update_layout( margin=dict(l=20, r=20, t=50, b=30), height=400, # Increase height for better visibility paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict( color="#ffffff", size=14, family="Arial, sans-serif"), # title={ # # 'text': 'Resume Score Distribution', # 'y': 0.95, # 'x': 0.5, # 'xanchor': 'center', # 'yanchor': 'top', # 'font': {'size': 22, 'color': 'white', 'family': 'Arial, sans-serif', 'weight': 'bold'} # }, xaxis=dict( title=dict( text="Score Range", font=dict( size=16, color="white")), categoryorder="array", categoryarray=[ "0-20", "21-40", "41-60", "61-80", "81-100"], tickfont=dict(size=14, color="white"), gridcolor="rgba(255, 255, 255, 0.1)" ), yaxis=dict( title=dict( text="Number of Resumes", font=dict( size=16, color="white")), tickfont=dict(size=14, color="white"), gridcolor="rgba(255, 255, 255, 0.1)", zeroline=False ), showlegend=False, bargap=0.2, # Adjust gap between bars shapes=[ # Add gradient background dict( type="rect", xref="paper", yref="paper", x0=0, y0=0, x1=1, y1=1, fillcolor="rgba(26, 26, 44, 0.5)", layer="below", line_width=0, ) ] ) # Add annotations for insights if len(score_data) > 0: max_count_idx = score_data["count"].idxmax() max_range = score_data.iloc[max_count_idx]["range"] max_count = score_data.iloc[max_count_idx]["count"] fig.add_annotation( x=0.5, y=1.12, xref="paper", yref="paper", text=f"Most resumes fall in the {max_range} score range", showarrow=False, font=dict(size=14, color="#FFEB3B"), bgcolor="rgba(0,0,0,0.5)", bordercolor="#FFEB3B", borderwidth=1, borderpad=4, opacity=0.8 ) # Display the chart in a styled container st.markdown("""
""", unsafe_allow_html=True) st.plotly_chart(fig, use_container_width=True) # Add descriptive text below the chart st.markdown("""

This chart shows the distribution of resume scores across different ranges, helping identify common performance levels.

""", unsafe_allow_html=True) # Display recent analyses if available if ai_stats["recent_analyses"]: st.markdown("""

🕒 Recent Resume Analyses

""", unsafe_allow_html=True) # Create a more modern styled table for recent # analyses st.markdown("""
""", unsafe_allow_html=True) for analysis in ai_stats["recent_analyses"]: score = analysis["score"] score_class = "score-high" if score >= 80 else "score-medium" if score >= 60 else "score-low" # Determine model class model_name = analysis["model"] model_class = "model-gemini" if "Gemini" in model_name else "model-claude" if "Claude" in model_name else "" # Format the date try: from datetime import datetime date_obj = datetime.strptime( analysis["date"], "%Y-%m-%d %H:%M:%S") formatted_date = date_obj.strftime( "%b %d, %Y") except: formatted_date = analysis["date"] st.markdown(f""" """, unsafe_allow_html=True) st.markdown("""
AI Model Score Job Role Date
{model_name}
{score}/100
{analysis["job_role"]}
{formatted_date}

These are the most recent resume analyses performed by our AI models.

""", unsafe_allow_html=True) else: st.info( "No AI analysis data available yet. Upload and analyze resumes to see statistics here.") except Exception as e: st.error(f"Error loading AI analysis statistics: {str(e)}") # Job Role Selection for AI Analysis categories = list(self.job_roles.keys()) selected_category = st.selectbox( "Job Category", categories, key="ai_category") roles = list(self.job_roles[selected_category].keys()) selected_role = st.selectbox("Specific Role", roles, key="ai_role") role_info = self.job_roles[selected_category][selected_role] # Display role information st.markdown(f"""

{selected_role}

{role_info['description']}

Required Skills:

{', '.join(role_info['required_skills'])}

""", unsafe_allow_html=True) # File Upload for AI Analysis uploaded_file = st.file_uploader( "Upload your resume", type=[ 'pdf', 'docx'], key="ai_file") if not uploaded_file: # Display empty state with a prominent upload button st.markdown( self.render_empty_state( "fas fa-robot", "Upload your resume to get AI-powered analysis and recommendations" ), unsafe_allow_html=True ) else: # Add a prominent analyze button analyze_ai = st.button("🤖 Analyze with AI", type="primary", use_container_width=True, key="analyze_ai_button") if analyze_ai: with st.spinner(f"Analyzing your resume with {ai_model}..."): # Get file content text = "" try: if uploaded_file.type == "application/pdf": text = self.analyzer.extract_text_from_pdf( uploaded_file) elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document": text = self.analyzer.extract_text_from_docx( uploaded_file) else: text = uploaded_file.getvalue().decode() except Exception as e: st.error(f"Error reading file: {str(e)}") st.stop() # Analyze with AI try: # Show a loading animation with st.spinner("🧠 AI is analyzing your resume..."): progress_bar = st.progress(0) # Get the selected model selected_model = "Groq Llama 3.3 70B" # Update progress progress_bar.progress(10) # Extract text from the resume analyzer = AIResumeAnalyzer() if uploaded_file.type == "application/pdf": resume_text = analyzer.extract_text_from_pdf( uploaded_file) elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document": resume_text = analyzer.extract_text_from_docx( uploaded_file) else: # For text files or other formats resume_text = uploaded_file.getvalue().decode('utf-8') # Initialize the AI analyzer (moved after text extraction) progress_bar.progress(30) # Get the job role job_role = selected_role if selected_role else "Not specified" # Update progress progress_bar.progress(50) # Analyze the resume with Groq Llama 3.3 70B if use_custom_job_desc and custom_job_description: # Use custom job description for analysis analysis_result = analyzer.analyze_resume_with_gemini( resume_text, job_role=job_role, job_description=custom_job_description) # Show that custom job description was used st.session_state['used_custom_job_desc'] = True else: # Use standard role-based analysis analysis_result = analyzer.analyze_resume_with_gemini( resume_text, job_role=job_role) st.session_state['used_custom_job_desc'] = False # Update progress progress_bar.progress(80) # Save the analysis to the database if analysis_result and "error" not in analysis_result: # Extract the resume score resume_score = analysis_result.get( "resume_score", 0) # Save to database save_ai_analysis_data( None, # No user_id needed { "model_used": selected_model, "resume_score": resume_score, "job_role": job_role } ) # show snowflake effect st.snow() # Complete the progress progress_bar.progress(100) # Display the analysis result if analysis_result and "error" not in analysis_result: st.success("✅ Analysis complete!") # Extract data from the analysis full_response = analysis_result.get( "analysis", "") resume_score = analysis_result.get( "resume_score", 0) ats_score = analysis_result.get( "ats_score", 0) model_used = analysis_result.get( "model_used", selected_model) # Store the full response in session state for download st.session_state['full_analysis'] = full_response # Display the analysis in a nice format st.markdown("## Full Analysis Report") # Get current date from datetime import datetime current_date = datetime.now().strftime("%B %d, %Y") # Create a modern styled header for the report st.markdown(f"""

AI Resume Analysis Report

Job Role: {job_role if job_role else "Not specified"}

Analysis Date: {current_date}

AI Model: {model_used}

Overall Score: {resume_score}/100 - {"Excellent" if resume_score >= 80 else "Good" if resume_score >= 60 else "Needs Improvement"}

{f'

✓ Custom Job Description Used

' if st.session_state.get('used_custom_job_desc', False) else ''}
""", unsafe_allow_html=True) # Add gauge charts for scores import plotly.graph_objects as go col1, col2 = st.columns(2) with col1: # Resume Score Gauge fig1 = go.Figure(go.Indicator( mode="gauge+number", value=resume_score, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': "Resume Score", 'font': {'size': 16}}, gauge={ 'axis': {'range': [0, 100], 'tickwidth': 1}, 'bar': {'color': "#4CAF50" if resume_score >= 80 else "#FFA500" if resume_score >= 60 else "#FF4444"}, 'bgcolor': "white", 'borderwidth': 2, 'bordercolor': "gray", 'steps': [ {'range': [0, 40], 'color': 'rgba(255, 68, 68, 0.2)'}, {'range': [40, 60], 'color': 'rgba(255, 165, 0, 0.2)'}, {'range': [60, 80], 'color': 'rgba(255, 214, 0, 0.2)'}, {'range': [80, 100], 'color': 'rgba(76, 175, 80, 0.2)'} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 60 } } )) fig1.update_layout( height=250, margin=dict(l=20, r=20, t=50, b=20), ) st.plotly_chart(fig1, use_container_width=True) status = "Excellent" if resume_score >= 80 else "Good" if resume_score >= 60 else "Needs Improvement" st.markdown(f"
{status}
", unsafe_allow_html=True) with col2: # ATS Score Gauge fig2 = go.Figure(go.Indicator( mode="gauge+number", value=ats_score, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': "ATS Optimization Score", 'font': {'size': 16}}, gauge={ 'axis': {'range': [0, 100], 'tickwidth': 1}, 'bar': {'color': "#4CAF50" if ats_score >= 80 else "#FFA500" if ats_score >= 60 else "#FF4444"}, 'bgcolor': "white", 'borderwidth': 2, 'bordercolor': "gray", 'steps': [ {'range': [0, 40], 'color': 'rgba(255, 68, 68, 0.2)'}, {'range': [40, 60], 'color': 'rgba(255, 165, 0, 0.2)'}, {'range': [60, 80], 'color': 'rgba(255, 214, 0, 0.2)'}, {'range': [80, 100], 'color': 'rgba(76, 175, 80, 0.2)'} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 60 } } )) fig2.update_layout( height=250, margin=dict(l=20, r=20, t=50, b=20), ) st.plotly_chart(fig2, use_container_width=True) status = "Excellent" if ats_score >= 80 else "Good" if ats_score >= 60 else "Needs Improvement" st.markdown(f"
{status}
", unsafe_allow_html=True) # Add Job Description Match Score if custom job description was used if st.session_state.get('used_custom_job_desc', False) and custom_job_description: # Extract job match score from analysis result or calculate it job_match_score = analysis_result.get("job_match_score", 0) if not job_match_score and "job_match" in analysis_result: job_match_score = analysis_result["job_match"].get("score", 0) # If we have a job match score, display it if job_match_score: st.markdown("""

Job Description Match Analysis

""", unsafe_allow_html=True) col1, col2 = st.columns(2) with col1: # Job Match Score Gauge fig3 = go.Figure(go.Indicator( mode="gauge+number", value=job_match_score, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': "Job Match Score", 'font': {'size': 16}}, gauge={ 'axis': {'range': [0, 100], 'tickwidth': 1}, 'bar': {'color': "#4CAF50" if job_match_score >= 80 else "#FFA500" if job_match_score >= 60 else "#FF4444"}, 'bgcolor': "white", 'borderwidth': 2, 'bordercolor': "gray", 'steps': [ {'range': [0, 40], 'color': 'rgba(255, 68, 68, 0.2)'}, {'range': [40, 60], 'color': 'rgba(255, 165, 0, 0.2)'}, {'range': [60, 80], 'color': 'rgba(255, 214, 0, 0.2)'}, {'range': [80, 100], 'color': 'rgba(76, 175, 80, 0.2)'} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 60 } } )) fig3.update_layout( height=250, margin=dict(l=20, r=20, t=50, b=20), ) st.plotly_chart(fig3, use_container_width=True) match_status = "Excellent Match" if job_match_score >= 80 else "Good Match" if job_match_score >= 60 else "Low Match" st.markdown(f"
{match_status}
", unsafe_allow_html=True) with col2: st.markdown("""

What This Means

This score represents how well your resume matches the specific job description you provided.

  • 80-100: Excellent match - your resume is highly aligned with this job
  • 60-79: Good match - your resume matches many requirements
  • Below 60: Consider tailoring your resume more specifically to this job
""", unsafe_allow_html=True) # Format the full response with better styling formatted_analysis = full_response # Replace section headers with styled headers section_styles = { "## Overall Assessment": """

Overall Assessment

""", "## Professional Profile Analysis": """

Professional Profile Analysis

""", "## Skills Analysis": """

Skills Analysis

""", "## Experience Analysis": """

Experience Analysis

""", "## Education Analysis": """

Education Analysis

""", "## Key Strengths": """

Key Strengths

""", "## Areas for Improvement": """

Areas for Improvement

""", "## ATS Optimization Assessment": """

ATS Optimization Assessment

""", "## Recommended Courses": """

Recommended Courses

""", "## Resume Score": """

Resume Score

""", "## Role Alignment Analysis": """

Role Alignment Analysis

""", "## Job Match Analysis": """

Job Match Analysis

""", } # Apply the styling to each section for section, style in section_styles.items(): if section in formatted_analysis: formatted_analysis = formatted_analysis.replace( section, style) # Add closing div tags next_section = False for next_sec in section_styles.keys(): if next_sec != section and next_sec in formatted_analysis.split(style)[1]: split_text = formatted_analysis.split(style)[1].split(next_sec) formatted_analysis = formatted_analysis.split(style)[0] + style + split_text[0] + "
" + next_sec + "".join(split_text[1:]) next_section = True break if not next_section: formatted_analysis = formatted_analysis + "
" # Remove any extra closing div tags that might have been added formatted_analysis = formatted_analysis.replace("
", "
") # Ensure we don't have any orphaned closing tags at the end if formatted_analysis.endswith("
"): # Count opening and closing div tags open_tags = formatted_analysis.count("") # If we have more closing than opening tags, remove the extras if close_tags > open_tags: excess = close_tags - open_tags formatted_analysis = formatted_analysis[:-6 * excess] # Clean up any visible HTML tags that might appear in the text formatted_analysis = formatted_analysis.replace("</div>", "") formatted_analysis = formatted_analysis.replace("<div>", "") formatted_analysis = formatted_analysis.replace("
", "
") # Ensure proper opening formatted_analysis = formatted_analysis.replace("
", "
") # Ensure proper closing # Add CSS for the report st.markdown(""" """, unsafe_allow_html=True) # Display the formatted analysis st.markdown(f"""
{formatted_analysis}
""", unsafe_allow_html=True) # Create a PDF report pdf_buffer = self.ai_analyzer.generate_pdf_report( analysis_result={ "score": resume_score, "ats_score": ats_score, "model_used": model_used, "full_response": full_response, "strengths": analysis_result.get("strengths", []), "weaknesses": analysis_result.get("weaknesses", []), "used_custom_job_desc": st.session_state.get('used_custom_job_desc', False), "custom_job_description": custom_job_description if st.session_state.get('used_custom_job_desc', False) else "" }, candidate_name=st.session_state.get( 'candidate_name', 'Candidate'), job_role=selected_role ) # PDF download button if pdf_buffer: st.download_button( label="📊 Download PDF Report", data=pdf_buffer, file_name=f"resume_analysis_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf", mime="application/pdf", use_container_width=True, on_click=lambda: st.balloons() ) else: st.error("PDF generation failed. Please try again later.") else: st.error(f"Analysis failed: {analysis_result.get('error', 'Unknown error')}") except Exception as ai_error: st.error(f"Error during AI analysis: {str(ai_error)}") import traceback as tb st.code(tb.format_exc()) st.toast("Check out these repositories: [Awesome Java](https://github.com/Hunterdii/Awesome-Java)", icon="â„šī¸") def render_home(self): apply_modern_styles() # Hero Section hero_section( "Smart Resume AI", "Transform your career with AI-powered resume analysis and building. Get personalized insights and create professional resumes that stand out." ) # Features Section st.markdown('
', unsafe_allow_html=True) feature_card( "fas fa-robot", "AI-Powered Analysis", "Get instant feedback on your resume with advanced AI analysis that identifies strengths and areas for improvement." ) feature_card( "fas fa-magic", "Smart Resume Builder", "Create professional resumes with our intelligent builder that suggests optimal content and formatting." ) feature_card( "fas fa-chart-line", "Career Insights", "Access detailed analytics and personalized recommendations to enhance your career prospects." ) st.markdown('
', unsafe_allow_html=True) st.toast("Check out these repositories: [AI-Nexus(AI/ML)](https://github.com/Hunterdii/AI-Nexus)", icon="â„šī¸") # Call-to-Action with Streamlit navigation col1, col2, col3 = st.columns([1, 1, 1]) with col2: if st.button("Get Started", key="get_started_btn", help="Click to start analyzing your resume", type="primary", use_container_width=True): cleaned_name = "🔍 RESUME ANALYZER".lower().replace(" ", "_").replace("🔍", "").strip() st.session_state.page = cleaned_name st.rerun() def render_job_search(self): """Render the job search page""" render_job_search() st.toast("Check out these repositories: [GeeksforGeeks-POTD](https://github.com/Hunterdii/GeeksforGeeks-POTD)", icon="â„šī¸") def render_feedback_page(self): """Render the feedback page""" apply_modern_styles() # Page Header page_header( "Feedback & Suggestions", "Help us improve by sharing your thoughts" ) # Initialize feedback manager feedback_manager = FeedbackManager() # Create tabs for form and stats form_tab, stats_tab = st.tabs(["Submit Feedback", "Feedback Stats"]) with form_tab: feedback_manager.render_feedback_form() with stats_tab: feedback_manager.render_feedback_stats() st.toast("Check out these repositories: [TryHackMe Free Rooms](https://github.com/Hunterdii/tryhackme-free-rooms)", icon="â„šī¸") def show_repo_notification(self): message = """
Check out these other repositories:
Hacking Resources:
Programming Languages:
Data Structures & Algorithms:
AI/ML Projects:
If you find this project helpful, please consider ⭐ starring the repo!
""" st.sidebar.markdown(message, unsafe_allow_html=True) def main(self): """Main application entry point""" self.apply_global_styles() # Admin login/logout in sidebar with st.sidebar: st_lottie(self.load_lottie_url("https://assets5.lottiefiles.com/packages/lf20_xyadoh9h.json"), height=200, key="sidebar_animation") st.title("Smart Resume AI") st.markdown("---") # Navigation buttons for page_name in self.pages.keys(): if st.button(page_name, use_container_width=True): cleaned_name = page_name.lower().replace(" ", "_").replace("🏠", "").replace("🔍", "").replace("📝", "").replace("📊", "").replace("đŸŽ¯", "").replace("đŸ’Ŧ", "").replace("â„šī¸", "").strip() st.session_state.page = cleaned_name st.rerun() # Add some space before admin login st.markdown("

", unsafe_allow_html=True) st.markdown("---") # Admin Login/Logout section at bottom if st.session_state.get('is_admin', False): st.success(f"Logged in as: {st.session_state.get('current_admin_email')}") if st.button("Logout", key="logout_button"): try: log_admin_action(st.session_state.get('current_admin_email'), "logout") st.session_state.is_admin = False st.session_state.current_admin_email = None st.success("Logged out successfully!") st.rerun() except Exception as e: st.error(f"Error during logout: {str(e)}") else: with st.expander("👤 Admin Login"): admin_email_input = st.text_input("Email", key="admin_email_input") admin_password = st.text_input("Password", type="password", key="admin_password_input") if st.button("Login", key="login_button"): try: if verify_admin(admin_email_input, admin_password): st.session_state.is_admin = True st.session_state.current_admin_email = admin_email_input log_admin_action(admin_email_input, "login") st.success("Logged in successfully!") st.rerun() else: st.error("Invalid credentials") except Exception as e: st.error(f"Error during login: {str(e)}") # Display the repository notification in the sidebar self.show_repo_notification() # Force home page on first load if 'initial_load' not in st.session_state: st.session_state.initial_load = True st.session_state.page = 'home' st.rerun() # Get current page and render it current_page = st.session_state.get('page', 'home') # Create a mapping of cleaned page names to original names page_mapping = {name.lower().replace(" ", "_").replace("🏠", "").replace("🔍", "").replace("📝", "").replace("📊", "").replace("đŸŽ¯", "").replace("đŸ’Ŧ", "").replace("â„šī¸", "").strip(): name for name in self.pages.keys()} # Render the appropriate page if current_page in page_mapping: self.pages[page_mapping[current_page]]() else: # Default to home page if invalid page self.render_home() # Add footer to every page self.add_footer() if __name__ == "__main__": app = ResumeApp() app.main()