import streamlit as st import requests from urllib.parse import urlparse import os # API endpoint (adjust if needed) API_BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8000") # ========== Helper Functions ========== def get_repo_name(url): """Extract repository name from URL""" parsed = urlparse(url) if parsed.netloc != 'github.com': return None path_parts = parsed.path.strip('/').split('/') return path_parts[1] if len(path_parts) >= 2 else None def call_api(endpoint, method="get", data=None, params=None): """Make API call to backend service with better error handling""" url = f"{API_BASE_URL}{endpoint}" try: if method.lower() == "get": response = requests.get(url, params=params, timeout=60) elif method.lower() == "post": response = requests.post(url, json=data, timeout=60) else: st.error(f"Unsupported method: {method}") return None if response.status_code == 200: return response elif response.status_code == 500 and "wkhtmltopdf" in response.text: st.warning(""" **wkhtmltopdf not found on the server!** PDF generation requires wkhtmltopdf to be installed: **Installation Instructions:** - **Linux:** `sudo apt-get install wkhtmltopdf` - **macOS:** `brew install wkhtmltopdf` - **Windows:** Download from [https://wkhtmltopdf.org/downloads.html](https://wkhtmltopdf.org/downloads.html) After installing, please restart the application. Using markdown export as fallback. """) # Return a special response for this specific error return {"error": "wkhtmltopdf_not_found", "content": data.get("markdown_text", "")} else: st.error(f"API Error: {response.status_code} - {response.text}") return None except Exception as e: st.error(f"API Connection Error: {str(e)}") return None # ========== Page Configuration ========== st.set_page_config( page_title="Smart Resume Generator", page_icon="📄", layout="wide", initial_sidebar_state="expanded", ) # ========== Custom CSS ========== st.markdown(""" """, unsafe_allow_html=True) # ========== App Header ========== col1, col2 = st.columns([1, 5]) with col1: st.markdown("# 📄") with col2: st.markdown("# Smart Resume Generator") st.markdown("##### Create an ATS-friendly professional resume optimized for your target job") # ========== Initialize Session State ========== if 'step' not in st.session_state: st.session_state.step = 1 if 'personal_info' not in st.session_state: st.session_state.personal_info = {} if 'projects' not in st.session_state: st.session_state.projects = [] if 'resume_markdown' not in st.session_state: st.session_state.resume_markdown = None if 'pdf_data' not in st.session_state: st.session_state.pdf_data = None # ========== Progress Bar ========== # Calculate progress based on current step progress = (st.session_state.step - 1) / 3 # We have 4 steps (0-indexed) st.progress(progress) # Display current step indicator step_labels = { 1: "Personal Information", 2: "Project Selection", 3: "Resume Preview", 4: "Download Resume" } st.markdown(f"**Current Step: {step_labels[st.session_state.step]}**") # ========== Step 1: Personal Information ========== if st.session_state.step == 1: with st.container(): st.markdown("""
1
Personal Information
Enter your details and paste the job description you're targeting.
""", unsafe_allow_html=True) with st.form("personal_form"): col1, col2 = st.columns(2) with col1: name = st.text_input("Full Name*", help="Your complete name as it should appear on your resume") email = st.text_input("Email Address*", help="Your professional email address") phone = st.text_input("Phone Number", help="Your contact number (optional)") with col2: github = st.text_input("GitHub Profile", help="Your GitHub username or complete URL") linkedin = st.text_input("LinkedIn Profile", help="Your LinkedIn username or complete URL") skills = st.text_input("Key Skills (optional)", help="Comma-separated list of your top skills") education = st.text_area("Education*", placeholder="e.g., Bachelor of Science in Computer Science, University of XYZ, 2020-2024") work_exp = st.text_area("Work Experience", placeholder="e.g., Software Engineer at ABC Corp (2022-Present): Developed and maintained...") job_desc = st.text_area("Target Job Description*", placeholder="Paste the complete job description here. This helps tailor your resume to the specific role.") col1, col2, col3 = st.columns([1, 2, 1]) with col2: submit_button = st.form_submit_button("Continue to Next Step") if submit_button: if not all([name, email, education, job_desc]): st.error("Please fill all required fields marked with *") else: st.session_state.personal_info = { "name": name, "email": email, "phone": phone, "github": github, "linkedin": linkedin, "skills": skills, "education": education, "work_experience": work_exp, "job_description": job_desc } st.session_state.step = 2 st.rerun() # ========== Step 2: Project Selection ========== elif st.session_state.step == 2: with st.container(): st.markdown("""
2
Project Selection
Add GitHub repositories to generate tailored project descriptions.
""", unsafe_allow_html=True) # Back button if st.button("← Back to Personal Information"): st.session_state.step = 1 st.rerun() repos = st.text_area("GitHub Repository URLs (one per line)", placeholder="https://github.com/username/repository-name", help="Enter links to your GitHub repositories that you want to include in your resume") col1, col2 = st.columns([1, 1]) with col1: if st.button("Generate Project Descriptions", key="gen_proj_btn", use_container_width=True): if not repos: st.error("Please add at least one GitHub repository URL") else: with st.spinner("Analyzing repositories and generating tailored descriptions..."): projects = [] success_count = 0 for url in repos.split("\n"): url = url.strip() if not url: continue # Get repository name repo_name = get_repo_name(url) if not repo_name: st.error(f"Invalid GitHub URL: {url}") continue # Fetch README content readme_response = call_api("/get_readme", params={"url": url}) if not readme_response: continue readme_data = readme_response.json() if "error" in readme_data: st.error(f"Failed to fetch README for {url}: {readme_data['error']}") continue readme_content = readme_data.get("content", "") # Generate description desc_response = call_api("/generate_description", "post", { "readme_content": readme_content, "job_description": st.session_state.personal_info["job_description"] }) if not desc_response: continue desc_data = desc_response.json() description = desc_data.get("description", "") # Generate category cat_response = call_api("/generate_category", "post", { "readme_content": readme_content, "job_description": st.session_state.personal_info["job_description"] }) if not cat_response: continue cat_data = cat_response.json() category = cat_data.get("category", "Other") # Add project projects.append({ "name": repo_name, "description": description, "category": category }) success_count += 1 if success_count > 0: st.session_state.projects = projects st.success(f"Successfully processed {success_count} repositories!") else: st.error("Failed to process any repositories. Please check the URLs and try again.") with col2: if st.session_state.projects and st.button("Continue to Resume Preview", use_container_width=True): st.session_state.step = 3 st.rerun() # Display project previews if st.session_state.projects: st.subheader("Project Previews") for idx, proj in enumerate(st.session_state.projects): st.markdown(f"""

{proj['name']}

Category: {proj['category']}

{proj['description']}

""", unsafe_allow_html=True) col1, col2 = st.columns([8, 2]) with col2: if st.button("Remove", key=f"remove_{idx}"): st.session_state.projects.pop(idx) st.rerun() st.divider() # ========== Step 3: Resume Preview ========== elif st.session_state.step == 3: with st.container(): st.markdown("""
3
Resume Preview
Review your generated resume and make final adjustments.
""", unsafe_allow_html=True) # Back button if st.button("← Back to Project Selection"): st.session_state.step = 2 st.rerun() # Generate resume if not st.session_state.resume_markdown: with st.spinner("Generating your professional resume..."): resume_data = { **st.session_state.personal_info, "projects": st.session_state.projects } response = call_api("/generate_resume", "post", resume_data) if response: data = response.json() st.session_state.resume_markdown = data.get("resume_markdown", "") # Display and edit resume if st.session_state.resume_markdown: edited_markdown = st.text_area( "Edit your resume content if needed:", value=st.session_state.resume_markdown, height=400 ) # Update if edited if edited_markdown != st.session_state.resume_markdown: st.session_state.resume_markdown = edited_markdown st.session_state.pdf_data = None # Reset PDF if content changed col1, col2 = st.columns([1, 1]) with col1: if st.button("Regenerate Resume", use_container_width=True): st.session_state.resume_markdown = None st.rerun() with col2: if st.button("Continue to Download", use_container_width=True): # Generate PDF when moving to download step with st.spinner("Preparing PDF document..."): response = call_api("/generate_pdf", "post", { "markdown_text": st.session_state.resume_markdown }) if response: if isinstance(response, dict) and response.get("error") == "wkhtmltopdf_not_found": # Handle the special wkhtmltopdf error case st.session_state.pdf_data = None st.session_state.fallback_markdown = response.get("content") st.session_state.step = 4 st.rerun() else: st.session_state.pdf_data = response.content st.session_state.step = 4 st.rerun() # ========== Step 4: Download Resume ========== elif st.session_state.step == 4: with st.container(): st.markdown("""
4
Download Resume
Your resume is ready! Download it and start applying to your target job.
""", unsafe_allow_html=True) # Back button if st.button("← Back to Resume Preview"): st.session_state.step = 3 st.rerun() col1, col2 = st.columns([1, 1]) with col1: st.markdown("### Resume Preview") st.markdown(st.session_state.resume_markdown) with col2: st.markdown("### Download Options") if st.session_state.pdf_data: filename = f"{st.session_state.personal_info['name'].replace(' ', '_')}_Resume.pdf" st.download_button( label="Download Resume PDF", data=st.session_state.pdf_data, file_name=filename, mime="application/pdf", use_container_width=True ) else: # Fallback to markdown download filename = f"{st.session_state.personal_info['name'].replace(' ', '_')}_Resume.md" st.download_button( label="Download Resume Markdown", data=st.session_state.resume_markdown, file_name=filename, mime="text/markdown", use_container_width=True ) st.warning(""" **PDF generation is not available.** To enable PDF generation, please install wkhtmltopdf: - **Linux:** `sudo apt-get install wkhtmltopdf` - **macOS:** `brew install wkhtmltopdf` - **Windows:** Download from [wkhtmltopdf.org](https://wkhtmltopdf.org/downloads.html) After installing, restart the application. """) st.markdown("""

Tips for using your resume

""", unsafe_allow_html=True) # ========== Footer ========== st.divider() st.markdown("""
Smart Resume Generator | Created with ❤️ | © 2025
""", unsafe_allow_html=True)