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": "alex.johnson@example.com", "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""" """ 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'πŸ“₯ Download JSON' 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 '' # Main application header def render_header(): col1, col2 = st.columns([6, 4]) with col1: ai_badge = 'AI-Ready' if st.session_state.gemini_import_success else "" st.markdown(f'

πŸ“ ResumeBuilder Pro {ai_badge}

', unsafe_allow_html=True) with col2: download_buttons = f"""
{download_resume_json()} {download_resume_pdf()}
""" 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('
', 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('
', 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('
', 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('
', unsafe_allow_html=True) st.markdown('', 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('
', 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('
', unsafe_allow_html=True) # Add Job Description Analysis section if API key is set if st.session_state.gemini_available: st.markdown('
', 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('
'.format(match_percentage), unsafe_allow_html=True) # Missing skills if "missing_skills" in analysis and analysis["missing_skills"]: st.markdown('
', 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'{skill} ' 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('
', unsafe_allow_html=True) # Highlight skills if "highlight_skills" in analysis and analysis["highlight_skills"]: st.markdown('
', 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'{skill}' st.markdown(highlight_skills_html, unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) # Emphasis suggestions if "emphasis_suggestions" in analysis and analysis["emphasis_suggestions"]: st.markdown('
', unsafe_allow_html=True) st.markdown("#### πŸ’‘ Experience to Emphasize") emphasis_html = "" st.markdown(emphasis_html, unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) # Improvement tips if "improvement_tips" in analysis and analysis["improvement_tips"]: st.markdown('
', unsafe_allow_html=True) st.markdown("#### ⚑ Improvement Tips") tips_html = "" st.markdown(tips_html, unsafe_allow_html=True) st.markdown('
', 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'{skill} ' 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('
', unsafe_allow_html=True) # Replace newlines with
tags for proper HTML display formatted_letter = st.session_state.cover_letter.replace('\n', '
') st.markdown(formatted_letter, unsafe_allow_html=True) st.markdown('
', 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('
', unsafe_allow_html=True)