diff --git "a/app.py" "b/app.py" new file mode 100644--- /dev/null +++ "b/app.py" @@ -0,0 +1,2978 @@ +""" +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 ModelScoreJob RoleDate
{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() \ No newline at end of file