+
+
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