# Ensure system dependencies are installed import os import tempfile from utils.parsing_utils import extract_text_from_file, parse_cv_content from utils.latex_utils import generate_latex, compile_latex_to_pdf from utils.gemini_utils import generate_ats_optimized_content from pygments import highlight from pygments.lexers import TexLexer from pygments.formatters import HtmlFormatter import streamlit as st import os # Hugging Face Spaces specific configuration if os.environ.get('IS_HF_SPACE'): from streamlit.web import cli as stcli import sys def main(): sys.argv = [ "streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0", "--server.headless=true", "--browser.gatherUsageStats=false", "--server.enableXsrfProtection=false" ] sys.exit(stcli.main()) if __name__ == "__main__": main() # Set page config st.set_page_config( page_title="ATS-Friendly CV Builder", page_icon="📄", layout="centered", initial_sidebar_state="expanded" ) # Custom CSS def local_css(file_name): with open(file_name) as f: st.markdown(f'', unsafe_allow_html=True) local_css("style.css") # Initialize session state if 'original_cv_data' not in st.session_state: st.session_state.original_cv_data = {} if 'edited_cv_data' not in st.session_state: st.session_state.edited_cv_data = {} if 'job_description' not in st.session_state: st.session_state.job_description = "" if 'generated_latex' not in st.session_state: st.session_state.generated_latex = "" # App header st.title("📄 ATS-Friendly CV Builder") st.markdown(""" Upload your existing CV, customize it, and generate an optimized ATS-friendly resume tailored to any job description. """) # File upload section st.header("1. Upload Your Existing CV") uploaded_file = st.file_uploader( "Upload your CV (PDF, DOCX, or TXT)", type=["pdf", "docx", "txt"], help="We'll extract the information to create your base resume" ) if uploaded_file: with st.spinner("Extracting information from your CV..."): try: # Extract text from uploaded file file_content = extract_text_from_file(uploaded_file) # Parse the CV content into structured data cv_data = parse_cv_content(file_content) # Store in session state st.session_state.original_cv_data = cv_data st.session_state.edited_cv_data = cv_data.copy() st.success("CV information extracted successfully!") # Show extracted data for verification with st.expander("View extracted information"): st.json(cv_data, expanded=False) except Exception as e: st.error(f"Error processing your file: {str(e)}") # Edit CV section if st.session_state.original_cv_data: st.header("2. Edit Your Information") # Personal Information with st.expander("Personal Information", expanded=True): cols = st.columns(2) with cols[0]: st.session_state.edited_cv_data['name'] = st.text_input( "Full Name", value=st.session_state.edited_cv_data.get('name', '') ) st.session_state.edited_cv_data['email'] = st.text_input( "Email", value=st.session_state.edited_cv_data.get('email', '') ) with cols[1]: st.session_state.edited_cv_data['phone'] = st.text_input( "Phone", value=st.session_state.edited_cv_data.get('phone', '') ) st.session_state.edited_cv_data['linkedin'] = st.text_input( "LinkedIn URL", value=st.session_state.edited_cv_data.get('linkedin', '') ) # Professional Summary st.session_state.edited_cv_data['summary'] = st.text_area( "Professional Summary", value=st.session_state.edited_cv_data.get('summary', ''), height=100, help="A brief overview of your professional background and skills" ) # Skills skills = st.text_area( "Skills (comma separated)", value=", ".join(st.session_state.edited_cv_data.get('skills', [])), help="List your technical and soft skills" ) st.session_state.edited_cv_data['skills'] = [s.strip() for s in skills.split(",") if s.strip()] # Experience st.subheader("Work Experience") if 'experience' not in st.session_state.edited_cv_data: st.session_state.edited_cv_data['experience'] = [] for i, exp in enumerate(st.session_state.edited_cv_data['experience']): with st.expander(f"Experience {i+1}", expanded=i==0): cols = st.columns(2) with cols[0]: exp['title'] = st.text_input( "Job Title", value=exp.get('title', ''), key=f"exp_title_{i}" ) exp['company'] = st.text_input( "Company", value=exp.get('company', ''), key=f"exp_company_{i}" ) with cols[1]: exp['start_date'] = st.text_input( "Start Date", value=exp.get('start_date', ''), key=f"exp_start_{i}" ) exp['end_date'] = st.text_input( "End Date", value=exp.get('end_date', ''), key=f"exp_end_{i}" ) exp['description'] = st.text_area( "Description", value=exp.get('description', ''), height=100, key=f"exp_desc_{i}" ) # Add new experience if st.button("➕ Add Another Position"): st.session_state.edited_cv_data['experience'].append({ 'title': '', 'company': '', 'start_date': '', 'end_date': '', 'description': '' }) st.experimental_rerun() # Education st.subheader("Education") if 'education' not in st.session_state.edited_cv_data: st.session_state.edited_cv_data['education'] = [] for i, edu in enumerate(st.session_state.edited_cv_data['education']): with st.expander(f"Education {i+1}", expanded=i==0): cols = st.columns(2) with cols[0]: edu['degree'] = st.text_input( "Degree", value=edu.get('degree', ''), key=f"edu_degree_{i}" ) edu['institution'] = st.text_input( "Institution", value=edu.get('institution', ''), key=f"edu_institution_{i}" ) with cols[1]: edu['start_date'] = st.text_input( "Start Date", value=edu.get('start_date', ''), key=f"edu_start_{i}" ) edu['end_date'] = st.text_input( "End Date", value=edu.get('end_date', ''), key=f"edu_end_{i}" ) # Add new education if st.button("➕ Add Another Education"): st.session_state.edited_cv_data['education'].append({ 'degree': '', 'institution': '', 'start_date': '', 'end_date': '' }) st.experimental_rerun() # Projects st.subheader("Projects") if 'projects' not in st.session_state.edited_cv_data: st.session_state.edited_cv_data['projects'] = [] for i, proj in enumerate(st.session_state.edited_cv_data['projects']): with st.expander(f"Project {i+1}", expanded=i==0): proj['title'] = st.text_input( "Project Title", value=proj.get('title', ''), key=f"proj_title_{i}" ) proj['description'] = st.text_area( "Description", value=proj.get('description', ''), height=100, key=f"proj_desc_{i}" ) # Add new project if st.button("➕ Add Another Project"): st.session_state.edited_cv_data['projects'].append({ 'title': '', 'description': '' }) st.experimental_rerun() # Job Description and Optimization if st.session_state.edited_cv_data: st.header("3. Optimize for a Job") st.session_state.job_description = st.text_area( "Paste the Job Description", value=st.session_state.job_description, height=200, help="Paste the job description you're applying for to get tailored recommendations" ) # In your app.py where you have the rerun call if st.button("✨ Optimize for This Job"): if not st.session_state.job_description: st.warning("Please enter a job description first") else: with st.spinner("Generating ATS-optimized content..."): try: optimized_data = generate_ats_optimized_content( st.session_state.edited_cv_data, st.session_state.job_description ) st.session_state.edited_cv_data = optimized_data st.success("Optimization complete! Review the changes below.") st.experimental_rerun() # Remove this line st.rerun() # Use this instead except Exception as e: st.error(f"Error during optimization: {str(e)}") # Generate LaTeX and PDF if st.session_state.edited_cv_data: st.header("4. Generate Your ATS-Friendly Resume") if st.button("🔄 Generate LaTeX Code"): with st.spinner("Generating LaTeX code..."): try: latex_code = generate_latex(st.session_state.edited_cv_data) st.session_state.generated_latex = latex_code except Exception as e: st.error(f"Error generating LaTeX: {str(e)}") if st.session_state.generated_latex: # Display LaTeX code with syntax highlighting st.subheader("Generated LaTeX Code") st.markdown(""" Review and edit the LaTeX code below if needed before generating your PDF. """) # Syntax highlighting formatter = HtmlFormatter(style="colorful") styled_code = highlight( st.session_state.generated_latex, TexLexer(), formatter ) st.markdown(styled_code, unsafe_allow_html=True) # Download LaTeX st.download_button( label="📥 Download LaTeX File", data=st.session_state.generated_latex, file_name="resume.tex", mime="text/plain" ) # Generate and download PDF if st.button("🖨️ Generate PDF"): with st.spinner("Compiling PDF..."): try: with tempfile.TemporaryDirectory() as tempdir: tex_file = os.path.join(tempdir, "resume.tex") with open(tex_file, "w") as f: f.write(st.session_state.generated_latex) pdf_path = compile_latex_to_pdf(tex_file, tempdir) if pdf_path and os.path.exists(pdf_path): with open(pdf_path, "rb") as f: pdf_bytes = f.read() st.success("PDF generated successfully!") st.download_button( label="📥 Download PDF", data=pdf_bytes, file_name="resume.pdf", mime="application/pdf" ) else: st.error("Failed to generate PDF") except Exception as e: st.error(f"Error generating PDF: {str(e)}") st.text(str(e))