resume / app.py
noumanjavaid's picture
Update app.py
e2b3134 verified
raw
history blame
36.9 kB
import streamlit as st
import base64
from io import BytesIO
import json
from datetime import datetime
import time
import re
import os
# Set page configuration and title
st.set_page_config(
page_title="ResumeBuilder Pro",
layout="wide",
initial_sidebar_state="collapsed"
)
# Define a flag to check if API functionality is available
if "gemini_available" not in st.session_state:
st.session_state.gemini_available = False
# Try to import Google Generative AI library, but make it optional
try:
import google.generativeai as genai
st.session_state.gemini_import_success = True
except ImportError:
st.session_state.gemini_import_success = False
# Initialize Gemini API (modified to handle missing API key gracefully)
def initialize_gemini_api():
if not st.session_state.gemini_import_success:
st.warning("Google Generative AI library not installed. Install with: `pip install google-generativeai`")
return False
try:
# Get API key from session state first (from text input)
api_key = st.session_state.get("api_key", "")
# Only try to configure if API key is provided
if api_key:
genai.configure(api_key=api_key)
# Test the API with a simple call to verify it works
model = genai.GenerativeModel(model_name="gemini-pro")
_ = model.generate_content("Hello")
return True
else:
# No API key provided yet, not an error
return False
except Exception as e:
st.error(f"Failed to initialize Gemini API: {str(e)}")
return False
# Function to get Gemini model response
def get_gemini_response(prompt, temperature=0.7):
if not st.session_state.gemini_available:
st.warning("Gemini API not available. Please set up your API key.")
return None
try:
# Create the model
generation_config = {
"temperature": temperature,
"top_p": 0.95,
"top_k": 64,
"max_output_tokens": 8192,
}
model = genai.GenerativeModel(
model_name="gemini-pro",
generation_config=generation_config,
)
response = model.generate_content(prompt)
return response.text
except Exception as e:
st.error(f"Error getting AI response: {str(e)}")
return None
# Initialize session state for resume data
if "resume_data" not in st.session_state:
st.session_state.resume_data = {
"fullName": "Alexander Johnson",
"title": "Senior Frontend Developer",
"email": "[email protected]",
"phone": "(555) 123-4567",
"location": "San Francisco, CA",
"summary": "Experienced frontend developer with 6+ years specializing in React and modern JavaScript frameworks. Passionate about creating intuitive user interfaces and optimizing web performance.",
"experience": [
{
"id": 1,
"company": "Tech Innovations Inc.",
"position": "Senior Frontend Developer",
"duration": "2019 - Present",
"description": "Lead frontend development for enterprise SaaS platform. Improved performance by 40% through code optimization. Mentored junior developers."
},
{
"id": 2,
"company": "WebSolutions Co.",
"position": "Frontend Developer",
"duration": "2017 - 2019",
"description": "Developed responsive web applications using React. Collaborated with design team to implement UI/UX improvements."
}
],
"education": [
{
"id": 1,
"institution": "University of California, Berkeley",
"degree": "B.S. Computer Science",
"duration": "2013 - 2017"
}
],
"skills": ["React", "JavaScript", "TypeScript", "HTML/CSS", "Redux", "Next.js", "Tailwind CSS", "UI/UX Design"]
}
if "dark_mode" not in st.session_state:
st.session_state.dark_mode = False
if "show_preview" not in st.session_state:
st.session_state.show_preview = True
if "new_skill" not in st.session_state:
st.session_state.new_skill = ""
if "job_description" not in st.session_state:
st.session_state.job_description = ""
if "ai_suggestions" not in st.session_state:
st.session_state.ai_suggestions = {}
if "cover_letter" not in st.session_state:
st.session_state.cover_letter = ""
if "suggested_skills" not in st.session_state:
st.session_state.suggested_skills = []
# Apply custom styling based on dark/light mode
def apply_custom_styling():
dark_mode = st.session_state.dark_mode
primary_color = "#4F46E5" # Indigo
if dark_mode:
background_color = "#111827" # Dark gray
text_color = "#F9FAFB" # Almost white
card_bg = "#1F2937" # Medium gray
input_bg = "#374151" # Light gray
secondary_bg = "#1E3A8A" # Dark blue
accent_light = "#818CF8" # Lighter indigo
else:
background_color = "#F9FAFB" # Almost white
text_color = "#111827" # Dark gray
card_bg = "#FFFFFF" # White
input_bg = "#F3F4F6" # Light gray
secondary_bg = "#EEF2FF" # Light indigo
accent_light = "#C7D2FE" # Very light indigo
css = f"""
<style>
/* Base colors and fonts */
:root {{
--primary-color: {primary_color};
--bg-color: {background_color};
--text-color: {text_color};
--card-bg: {card_bg};
--input-bg: {input_bg};
--secondary-bg: {secondary_bg};
--accent-light: {accent_light};
}}
/* Main page styling */
.main .block-container {{
padding-top: 2rem;
max-width: 1200px;
}}
.stApp {{
background-color: var(--bg-color);
color: var(--text-color);
}}
/* Header styling */
h1, h2, h3, h4, h5, h6 {{
color: var(--text-color);
}}
/* Make inputs match theme */
.stTextInput > div > div > input,
.stTextArea > div > div > textarea {{
background-color: var(--input-bg);
color: var(--text-color);
border-radius: 0.375rem;
}}
/* Custom card styling */
.custom-card {{
background-color: var(--card-bg);
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}}
/* AI feature styling */
.ai-container {{
background-color: var(--secondary-bg);
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
border-left: 4px solid var(--primary-color);
}}
.ai-suggestion {{
background-color: var(--accent-light);
border-radius: 0.375rem;
padding: 0.75rem;
margin-top: 0.5rem;
font-size: 0.875rem;
border-left: 3px solid var(--primary-color);
}}
/* Resume preview styling */
.resume-container {{
background-color: var(--card-bg);
border-radius: 0.5rem;
padding: 2rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}}
.resume-header {{
margin-bottom: 1.5rem;
}}
.resume-name {{
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.25rem;
color: var(--text-color);
}}
.resume-title {{
font-size: 1.25rem;
color: var(--primary-color);
margin-bottom: 0.5rem;
}}
.contact-info {{
font-size: 0.875rem;
margin-top: 0.5rem;
color: var(--text-color);
}}
.section-title {{
font-size: 1.25rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.1)"};
color: var(--text-color);
}}
.company-name {{
color: var(--primary-color);
font-size: 1rem;
}}
.job-title {{
font-weight: 600;
font-size: 1.125rem;
color: var(--text-color);
}}
.duration {{
font-size: 0.875rem;
color: {"rgba(255,255,255,0.7)" if dark_mode else "rgba(0,0,0,0.6)"};
}}
.job-description {{
margin-top: 0.5rem;
font-size: 0.9375rem;
color: {"rgba(255,255,255,0.9)" if dark_mode else "rgba(0,0,0,0.8)"};
}}
.institution {{
font-weight: 600;
font-size: 1.125rem;
color: var(--text-color);
}}
.degree {{
font-size: 1rem;
color: {"rgba(255,255,255,0.9)" if dark_mode else "rgba(0,0,0,0.8)"};
}}
.skill-tag {{
display: inline-block;
background-color: {f"rgba(79, 70, 229, 0.2)" if dark_mode else "rgba(79, 70, 229, 0.1)"};
color: {f"rgba(255,255,255,0.9)" if dark_mode else "#4F46E5"};
padding: 0.35rem 0.7rem;
border-radius: 9999px;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
font-size: 0.875rem;
}}
.recommended-skill {{
display: inline-block;
background-color: {f"rgba(5, 150, 105, 0.2)" if dark_mode else "rgba(5, 150, 105, 0.1)"};
color: {f"rgba(255,255,255,0.9)" if dark_mode else "#059669"};
padding: 0.35rem 0.7rem;
border-radius: 9999px;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
font-size: 0.875rem;
border: 1px dashed {f"rgba(5, 150, 105, 0.5)" if dark_mode else "rgba(5, 150, 105, 0.5)"};
}}
/* Tab styling */
button[data-baseweb="tab"] {{
background-color: var(--secondary-bg);
border-radius: 0.375rem 0.375rem 0 0;
}}
button[data-baseweb="tab"][aria-selected="true"] {{
background-color: var(--primary-color) !important;
color: white !important;
}}
div[data-testid="stVerticalBlock"] div[data-testid="stHorizontalBlock"] {{
gap: 0.5rem;
}}
/* Section separators */
hr {{
margin: 1.5rem 0;
border-color: {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.1)"};
}}
/* Button styling */
.stButton button {{
border-radius: 0.375rem;
}}
/* Make the buttons in header look nicer */
.header-button {{
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
border: 1px solid {f"rgba(255,255,255,0.2)" if dark_mode else "rgba(0,0,0,0.1)"} !important;
border-radius: 0.375rem !important;
padding: 0.5rem 1rem !important;
font-size: 0.875rem !important;
transition: all 0.2s ease !important;
}}
.header-button:hover {{
background-color: {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.05)"} !important;
border-color: {f"rgba(255,255,255,0.3)" if dark_mode else "rgba(0,0,0,0.2)"} !important;
}}
/* Container styling */
.content-container {{
display: flex;
gap: 1.5rem;
}}
/* Adjust form field spacing */
div[data-baseweb="input"] {{
margin-bottom: 0.75rem;
}}
/* Make the expander look cleaner */
.st-emotion-cache-1r6slb0[data-testid="stExpander"] {{
background-color: var(--card-bg);
border-radius: 0.5rem;
border: 1px solid {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.1)"};
margin-bottom: 1rem;
}}
/* Remove the border around the tab content area */
.st-emotion-cache-cio0dv[data-testid="block-container"] {{
padding-left: 0;
padding-right: 0;
}}
/* Smaller margin for experience items */
.experience-row {{
margin-bottom: 0.25rem;
}}
/* AI button styling */
.ai-button {{
display: inline-flex;
align-items: center;
background-color: var(--primary-color);
color: white;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}}
.ai-button:hover {{
background-color: #4338CA;
}}
/* API key container */
.api-key-container {{
margin-bottom: 1rem;
padding: 1rem;
background-color: {f"rgba(255,255,255,0.05)" if dark_mode else "rgba(0,0,0,0.03)"};
border-radius: 0.5rem;
}}
/* Cover letter styling */
.cover-letter {{
background-color: var(--card-bg);
border-radius: 0.5rem;
padding: 2rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-top: 1rem;
}}
/* Match percentage meter */
.match-meter {{
height: 1rem;
border-radius: 9999px;
overflow: hidden;
background-color: {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.1)"};
margin-top: 0.5rem;
margin-bottom: 1rem;
}}
.match-fill {{
height: 100%;
background-color: var(--primary-color);
transition: width 1s ease-in-out;
}}
/* Analysis result cards */
.analysis-card {{
background-color: {f"rgba(255,255,255,0.05)" if dark_mode else "rgba(255,255,255,0.8)"};
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
border-left: 3px solid var(--primary-color);
}}
/* AI features banner */
.ai-features-banner {{
background-color: var(--accent-light);
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
border: 1px dashed var(--primary-color);
text-align: center;
}}
</style>
"""
st.markdown(css, unsafe_allow_html=True)
# Function to download resume as JSON
def download_resume_json():
json_str = json.dumps(st.session_state.resume_data, indent=2)
b64 = base64.b64encode(json_str.encode()).decode()
filename = f"resume_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
href = f'<a href="data:file/json;base64,{b64}" download="{filename}" class="header-button" style="text-decoration:none;padding:0.5rem 1rem;margin-right:0.5rem;">πŸ“₯ Download JSON</a>'
return href
# Function to create a placeholder for PDF export
def download_resume_pdf():
# In a real app, you would generate an actual PDF here
# For this example, we'll just return a placeholder message
return '<button class="header-button" onclick="alert(\'PDF generation would be implemented here in a real app\');">πŸ“„ Export PDF</button>'
# Main application header
def render_header():
col1, col2 = st.columns([6, 4])
with col1:
ai_badge = '<span style="background-color: var(--primary-color); color: white; font-size: 0.6em; padding: 0.2em 0.5em; border-radius: 0.5em; margin-left: 0.5em;">AI-Ready</span>' if st.session_state.gemini_import_success else ""
st.markdown(f'<h1 style="display:flex;align-items:center;"><span style="margin-right:10px;">πŸ“</span> ResumeBuilder Pro {ai_badge}</h1>', unsafe_allow_html=True)
with col2:
download_buttons = f"""
<div style="display:flex;justify-content:flex-end;align-items:center;gap:0.5rem;">
{download_resume_json()}
{download_resume_pdf()}
<button class="header-button" onclick="document.querySelector('button[data-testid*=\\"baseButton-secondary\\"]').click();">
{'πŸŒ™' if not st.session_state.dark_mode else 'β˜€οΈ'} Theme
</button>
<button class="header-button" onclick="document.querySelector('button[data-testid*=\\"baseButton-primary\\"]').click();">
{'πŸ‘οΈ' if st.session_state.show_preview else 'πŸ‘οΈβ€πŸ—¨οΈ'} Preview
</button>
</div>
"""
st.markdown(download_buttons, unsafe_allow_html=True)
# Hidden buttons that are triggered by the custom buttons above
col2_1, col2_2 = st.columns(2)
with col2_1:
if st.button("Toggle Theme", key="baseButton-secondary", type="secondary"):
st.session_state.dark_mode = not st.session_state.dark_mode
st.rerun()
with col2_2:
if st.button("Toggle Preview", key="baseButton-primary"):
st.session_state.show_preview = not st.session_state.show_preview
st.rerun()
# API Key Management - Improved to be more user-friendly and handle errors better
def manage_api_key():
if "api_key" not in st.session_state:
st.session_state.api_key = ""
st.markdown('<div class="api-key-container">', unsafe_allow_html=True)
# Show different message if the generative AI library is not installed
if not st.session_state.gemini_import_success:
st.markdown("### πŸ”§ Enable AI Features")
st.markdown("""
To use AI features, you need to install the Google Generative AI library:
```
pip install google-generativeai
```
After installation, restart the app to access AI-powered resume enhancements.
""")
st.markdown('</div>', unsafe_allow_html=True)
return
# If library is installed but API key not set
st.markdown("### πŸ”‘ Gemini API Setup")
st.markdown("To use AI features, enter your Google Gemini API key below:")
api_key = st.text_input("API Key",
value=st.session_state.api_key,
type="password",
placeholder="Enter your Gemini API key here")
col1, col2 = st.columns([1, 3])
with col1:
if st.button("Save API Key"):
st.session_state.api_key = api_key
if initialize_gemini_api():
st.session_state.gemini_available = True
st.success("API key saved and verified successfully!")
else:
if api_key:
st.error("Invalid API key. Please check and try again.")
else:
st.warning("Please enter an API key to enable AI features.")
with col2:
st.markdown("Get your API key from [Google AI Studio](https://makersuite.google.com/app/apikey)")
# Show what AI features are available
if not st.session_state.gemini_available:
st.markdown('<div class="ai-features-banner">', unsafe_allow_html=True)
st.markdown("### πŸš€ Unlock AI Features")
st.markdown("""
By adding your API key, you'll unlock powerful AI features:
- **Professional Summary Generator**: Create compelling summaries automatically
- **Job Match Analysis**: Score your resume against job descriptions
- **Skills Recommendations**: Get tailored skill suggestions for your role
- **Experience Description Enhancement**: Make your work history more impactful
- **Cover Letter Generator**: Create customized cover letters in seconds
""")
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# AI Features - These will only be activated when API is available
# Generate professional summary
def generate_ai_summary():
data = st.session_state.resume_data
# Create prompt for summary generation
prompt = f"""
You are an expert resume writer. Generate a compelling professional summary for a resume with these details:
Name: {data['fullName']}
Current Position: {data['title']}
Skills: {', '.join(data['skills'])}
Experience:
{' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
Education:
{' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
Rules for writing the summary:
1. Keep it concise (3-4 sentences maximum)
2. Highlight key skills and accomplishments
3. Focus on value provided in past roles
4. Use active language and avoid clichΓ©s
5. Target it toward professional growth
Write ONLY the summary. Don't include explanations or other text.
"""
with st.spinner("Generating professional summary..."):
summary = get_gemini_response(prompt, temperature=0.7)
if summary:
# Clean up the response
summary = summary.strip().replace('"', '')
return summary
return None
# Analyze job description and match skills
def analyze_job_description(job_description):
data = st.session_state.resume_data
prompt = f"""
You are an expert resume consultant. Analyze this job description and the candidate's resume to provide insights.
JOB DESCRIPTION:
{job_description}
CANDIDATE RESUME:
Name: {data['fullName']}
Current Position: {data['title']}
Skills: {', '.join(data['skills'])}
Experience:
{' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
Education:
{' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
Provide the following in JSON format:
1. "match_percentage": A numerical estimate (0-100) of how well the candidate's skills match the job requirements
2. "missing_skills": A list of 3-5 key skills mentioned in the job that are missing from the candidate's resume
3. "highlight_skills": A list of skills the candidate has that are particularly relevant to this job
4. "emphasis_suggestions": 2-3 specific parts of the candidate's experience that should be emphasized for this job
5. "improvement_tips": 2-3 brief suggestions to improve the resume for this specific job
Return ONLY the JSON, formatted as follows:
{{
"match_percentage": number,
"missing_skills": [list of strings],
"highlight_skills": [list of strings],
"emphasis_suggestions": [list of strings],
"improvement_tips": [list of strings]
}}
"""
with st.spinner("Analyzing job description..."):
analysis = get_gemini_response(prompt, temperature=0.2)
if analysis:
try:
# Extract just the JSON part from the response
# First find JSON pattern using regex
json_match = re.search(r'\{[\s\S]*\}', analysis)
if json_match:
json_str = json_match.group(0)
return json.loads(json_str)
return json.loads(analysis)
except Exception as e:
st.error(f"Error parsing AI response: {str(e)}")
st.write("Raw response:", analysis)
return None
# Improve experience descriptions
def improve_experience_description(description, position, company):
prompt = f"""
You are an expert resume writer. Enhance the following job description to be more impactful and achievement-oriented:
Position: {position}
Company: {company}
Current Description: {description}
Rewrite the description to:
1. Focus on achievements with measurable results
2. Start with strong action verbs
3. Highlight relevant skills and impact
4. Be concise but comprehensive (max 3 bullet points)
5. Use quantifiable metrics where possible
Return ONLY the improved description without additional commentary.
"""
with st.spinner("Improving description..."):
improved = get_gemini_response(prompt, temperature=0.7)
if improved:
return improved.strip()
return None
# Generate cover letter
def generate_cover_letter(job_description, company_name):
data = st.session_state.resume_data
prompt = f"""
You are an expert cover letter writer. Create a personalized cover letter for:
Applicant: {data['fullName']}
Current Position: {data['title']}
Skills: {', '.join(data['skills'])}
Target Company: {company_name}
Based on this job description:
{job_description}
Experience highlights:
{' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience'][:2]])}
Cover letter guidelines:
1. Keep it under 250 words
2. Include a personalized greeting
3. Start with an engaging opening that shows enthusiasm
4. Connect 2-3 of the applicant's skills/experiences directly to the job requirements
5. Include a specific reason why the applicant wants to work at this company
6. End with a call to action
7. Use a professional closing
Format it as a proper cover letter with date, greeting, paragraphs, and signature.
Return ONLY the cover letter, no explanations or additional notes.
"""
with st.spinner("Generating cover letter..."):
cover_letter = get_gemini_response(prompt, temperature=0.7)
if cover_letter:
return cover_letter.strip()
return None
# Suggest skills based on job title
def suggest_skills_for_role(job_title):
prompt = f"""
You are a career advisor specializing in resume building. Generate a list of 8-10 highly relevant technical and soft skills for someone in this role:
Job Title: {job_title}
Return the skills as a JSON array of strings. ONLY return the JSON array, no other text.
Example: ["Skill 1", "Skill 2", "Skill 3"]
"""
with st.spinner("Suggesting skills..."):
skills_json = get_gemini_response(prompt, temperature=0.3)
if skills_json:
try:
# Clean up and parse the response
skills_json = skills_json.strip()
if skills_json.startswith('```') and skills_json.endswith('```'):
skills_json = skills_json[3:-3].strip()
if skills_json.startswith('json'):
skills_json = skills_json[4:].strip()
return json.loads(skills_json)
except Exception as e:
st.error(f"Error parsing skills: {str(e)}")
return []
# Basic Info tab with AI enhancement
def render_basic_info():
data = st.session_state.resume_data
st.markdown('<div class="custom-card">', unsafe_allow_html=True)
st.subheader("Personal Information")
col1, col2 = st.columns(2)
with col1:
new_name = st.text_input("Full Name", value=data["fullName"], key="fullName")
if new_name != data["fullName"]:
st.session_state.resume_data["fullName"] = new_name
new_email = st.text_input("Email", value=data["email"], key="email")
if new_email != data["email"]:
st.session_state.resume_data["email"] = new_email
new_location = st.text_input("Location", value=data["location"], key="location")
if new_location != data["location"]:
st.session_state.resume_data["location"] = new_location
with col2:
new_title = st.text_input("Professional Title", value=data["title"], key="title")
if new_title != data["title"]:
st.session_state.resume_data["title"] = new_title
new_phone = st.text_input("Phone", value=data["phone"], key="phone")
if new_phone != data["phone"]:
st.session_state.resume_data["phone"] = new_phone
st.markdown("### Professional Summary")
# Add AI summary generation if API key is set
if st.session_state.gemini_available:
col1, col2 = st.columns([3, 1])
with col2:
if st.button("✨ Generate AI Summary"):
summary = generate_ai_summary()
if summary:
st.session_state.resume_data["summary"] = summary
st.rerun()
new_summary = st.text_area("", value=data["summary"], height=150, key="summary")
if new_summary != data["summary"]:
st.session_state.resume_data["summary"] = new_summary
st.markdown('</div>', unsafe_allow_html=True)
# Add Job Description Analysis section if API key is set
if st.session_state.gemini_available:
st.markdown('<div class="ai-container">', unsafe_allow_html=True)
st.markdown("### πŸ” Job Match Analysis")
st.markdown("Paste a job description to get AI-powered insights on how your resume matches the requirements.")
job_description = st.text_area("Job Description", height=100, key="job_desc_input",
value=st.session_state.job_description)
if job_description:
company_name = st.text_input("Company Name (for cover letter)", key="company_name")
col1, col2, col3 = st.columns([1,1,1])
with col1:
if st.button("Analyze Job Match"):
st.session_state.job_description = job_description
analysis = analyze_job_description(job_description)
if analysis:
st.session_state.ai_suggestions = analysis
st.rerun()
with col2:
if st.button("Generate Cover Letter") and company_name:
st.session_state.job_description = job_description
cover_letter = generate_cover_letter(job_description, company_name)
if cover_letter:
st.session_state.cover_letter = cover_letter
st.rerun()
with col3:
if st.button("Suggest Skills"):
suggested_skills = suggest_skills_for_role(data["title"])
if suggested_skills:
st.session_state.suggested_skills = suggested_skills
st.rerun()
# Display analysis results if available
if st.session_state.ai_suggestions:
analysis = st.session_state.ai_suggestions
st.markdown("### πŸ“Š Job Match Analysis Results")
# Match percentage
match_percentage = analysis.get("match_percentage", 0)
st.markdown(f"### Match Score: {match_percentage}%")
st.markdown('<div class="match-meter"><div class="match-fill" style="width: {}%;"></div></div>'.format(match_percentage), unsafe_allow_html=True)
# Missing skills
if "missing_skills" in analysis and analysis["missing_skills"]:
st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
st.markdown("#### πŸ”΄ Missing Skills")
st.markdown("Consider adding these skills to your resume:")
missing_skills_html = ""
for skill in analysis["missing_skills"]:
missing_skills_html += f'<span class="recommended-skill">{skill} <button onclick="document.getElementById(\'add_skill_{skill.replace(" ", "_")}\').click()">+</button></span>'
st.markdown(missing_skills_html, unsafe_allow_html=True)
# Hidden buttons for adding missing skills
for skill in analysis["missing_skills"]:
if st.button("Add", key=f"add_skill_{skill.replace(' ', '_')}", type="primary"):
if skill not in data["skills"]:
data["skills"].append(skill)
st.rerun()
st.markdown('</div>', unsafe_allow_html=True)
# Highlight skills
if "highlight_skills" in analysis and analysis["highlight_skills"]:
st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
st.markdown("#### 🟒 Relevant Skills")
st.markdown("Highlight these skills prominently in your resume:")
highlight_skills_html = ""
for skill in analysis["highlight_skills"]:
highlight_skills_html += f'<span class="skill-tag">{skill}</span>'
st.markdown(highlight_skills_html, unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Emphasis suggestions
if "emphasis_suggestions" in analysis and analysis["emphasis_suggestions"]:
st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
st.markdown("#### πŸ’‘ Experience to Emphasize")
emphasis_html = "<ul>"
for suggestion in analysis["emphasis_suggestions"]:
emphasis_html += f"<li>{suggestion}</li>"
emphasis_html += "</ul>"
st.markdown(emphasis_html, unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Improvement tips
if "improvement_tips" in analysis and analysis["improvement_tips"]:
st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
st.markdown("#### ⚑ Improvement Tips")
tips_html = "<ul>"
for tip in analysis["improvement_tips"]:
tips_html += f"<li>{tip}</li>"
tips_html += "</ul>"
st.markdown(tips_html, unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Display suggested skills if available
if hasattr(st.session_state, 'suggested_skills') and st.session_state.suggested_skills:
st.markdown("### 🏷️ Suggested Skills for Your Role")
suggested_skills_html = ""
for skill in st.session_state.suggested_skills:
if skill not in data["skills"]:
suggested_skills_html += f'<span class="recommended-skill">{skill} <button onclick="document.getElementById(\'add_suggested_skill_{skill.replace(" ", "_")}\').click()">+</button></span>'
if suggested_skills_html:
st.markdown(suggested_skills_html, unsafe_allow_html=True)
# Hidden buttons for adding suggested skills
for skill in st.session_state.suggested_skills:
if skill not in data["skills"]:
if st.button("Add", key=f"add_suggested_skill_{skill.replace(' ', '_')}", type="primary"):
data["skills"].append(skill)
st.rerun()
else:
st.info("All suggested skills are already in your resume!")
# Display cover letter if available
if st.session_state.cover_letter:
st.markdown("### πŸ“ Generated Cover Letter")
st.markdown('<div class="cover-letter">', unsafe_allow_html=True)
# Replace newlines with <br> tags for proper HTML display
formatted_letter = st.session_state.cover_letter.replace('\n', '<br>')
st.markdown(formatted_letter, unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Add button to copy cover letter to clipboard
if st.button("πŸ“‹ Copy Cover Letter"):
st.toast("Cover letter copied to clipboard!", icon="πŸ“‹")
st.markdown('</div>', unsafe_allow_html=True)