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("""
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("""
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("""
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("""
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
- Tailor your resume further for each specific job application
- Use the same keywords from the job description
- Quantify your achievements wherever possible
- Follow up after submitting your application
""", unsafe_allow_html=True)
# ========== Footer ==========
st.divider()
st.markdown("""
Smart Resume Generator | Created with ❤️ | © 2025
""", unsafe_allow_html=True)