arbabarshad commited on
Commit
ae6d295
·
1 Parent(s): 583aa92

minimal integrated with llm and pdf

Browse files
.gitattributes CHANGED
@@ -1,2 +1,5 @@
1
  # Auto detect text files and perform LF normalization
2
  * text=auto
 
 
 
 
1
  # Auto detect text files and perform LF normalization
2
  * text=auto
3
+
4
+ *.tex linguist-detectable=false
5
+ *.pdf filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -36,4 +36,5 @@ outputs/
36
  *.pdf
37
  *.aux
38
  *.log
39
- *.out
 
 
36
  *.pdf
37
  *.aux
38
  *.log
39
+ *.out
40
+ .env
DEPLOYMENT_GUIDE.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deployment Guide for Hugging Face Spaces
2
+
3
+ This guide explains how to deploy the Resume Customizer application on Hugging Face Spaces.
4
+
5
+ ## Prerequisites
6
+
7
+ Before deploying, you need to have:
8
+
9
+ 1. A Hugging Face account (sign up at [huggingface.co](https://huggingface.co/join))
10
+ 2. An OpenAI API key (get one at [platform.openai.com/api-keys](https://platform.openai.com/api-keys))
11
+
12
+ ## Deployment Steps
13
+
14
+ ### 1. Fork the Repository
15
+
16
+ Fork this repository to your GitHub account.
17
+
18
+ ### 2. Create a New Hugging Face Space
19
+
20
+ 1. Go to [huggingface.co/spaces](https://huggingface.co/spaces)
21
+ 2. Click on "Create new Space"
22
+ 3. Choose a name for your space (e.g., "resume-customizer")
23
+ 4. Select "Gradio" as the SDK
24
+ 5. Choose a license (MIT is recommended)
25
+ 6. Set visibility to "Public" or "Private" based on your preference
26
+ 7. Click "Create Space"
27
+
28
+ ### 3. Link GitHub Repository to Hugging Face Space
29
+
30
+ 1. In your new Hugging Face Space, go to the "Settings" tab
31
+ 2. Under "Repository", click "Link an existing repository"
32
+ 3. Select your forked GitHub repository
33
+ 4. Click "Link repository"
34
+
35
+ ### 4. Set Up Environment Variables
36
+
37
+ 1. In your Hugging Face Space, go to the "Settings" tab
38
+ 2. Scroll down to the "Repository secrets" section
39
+ 3. Click "Add secret"
40
+ 4. Enter "OPENAI_API_KEY" as the name and your OpenAI API key as the value
41
+ 5. Click "Add new secret"
42
+
43
+ ### 5. Configure Build Settings
44
+
45
+ 1. In your Hugging Face Space, go to the "Settings" tab
46
+ 2. Under "Build settings", make sure the following settings are configured:
47
+ - Python version: 3.10
48
+ - Space SDK: Gradio
49
+ - Requirement file: requirements.txt
50
+ - System Packages: packages.txt
51
+ - App file path: app.py
52
+
53
+ ### 6. Deploy the Application
54
+
55
+ 1. Go to the "Factory" tab in your Hugging Face Space
56
+ 2. Click "Build" to start the deployment process
57
+ 3. Wait for the build to complete (this may take several minutes)
58
+ 4. Once the build is complete, your app will be available at the URL provided
59
+
60
+ ## Troubleshooting
61
+
62
+ ### Common Issues
63
+
64
+ 1. **Build Failure**: If the build fails, check the build logs for errors. Common issues include:
65
+ - Missing system dependencies (check packages.txt)
66
+ - Invalid Python package versions (check requirements.txt)
67
+ - Syntax errors in your code (check app.py)
68
+
69
+ 2. **Runtime Errors**: If the app builds but fails to run, check the runtime logs for errors. Common issues include:
70
+ - Missing or invalid OpenAI API key
71
+ - Problems with LaTeX installation or PDF generation
72
+
73
+ 3. **PDF Generation Issues**: If PDFs aren't being generated correctly, ensure that all required LaTeX packages are installed. You may need to modify packages.txt to include additional LaTeX packages.
74
+
75
+ ### Getting Help
76
+
77
+ If you encounter any issues not covered here, you can:
78
+
79
+ 1. Check the Hugging Face Spaces documentation: [huggingface.co/docs/hub/spaces](https://huggingface.co/docs/hub/spaces)
80
+ 2. Ask for help in the Hugging Face forums: [discuss.huggingface.co](https://discuss.huggingface.co/)
81
+ 3. Open an issue on the GitHub repository with details about the problem and steps to reproduce it
82
+
83
+ ## Updating Your Deployment
84
+
85
+ To update your deployed application:
86
+
87
+ 1. Make your changes to the code in your local repository
88
+ 2. Commit and push the changes to your GitHub repository
89
+ 3. Your Hugging Face Space will automatically rebuild with the new changes (if auto-deploy is enabled)
90
+ 4. If auto-deploy is disabled, manually trigger a rebuild from the "Factory" tab
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: python app.py
README.md CHANGED
@@ -1,6 +1,6 @@
1
  # Resume Customizer
2
 
3
- A Hugging Face Spaces application that customizes your resume for specific job applications by analyzing job descriptions and generating tailored "Why Hire Me" sections.
4
 
5
  ## Features
6
 
@@ -13,10 +13,9 @@ A Hugging Face Spaces application that customizes your resume for specific job a
13
  ## How It Works
14
 
15
  1. The application takes your LaTeX resume template and a job description as input
16
- 2. It uses a language model to analyze the job description and identify key requirements
17
- 3. Based on the analysis, it generates a tailored "Why Hire Me" section that emphasizes your qualifications
18
- 4. The new section is injected into your LaTeX resume
19
- 5. The modified LaTeX is converted to PDF for download
20
 
21
  ## Getting Started
22
 
@@ -24,7 +23,11 @@ A Hugging Face Spaces application that customizes your resume for specific job a
24
 
25
  1. Clone this repository
26
  2. Install dependencies: `pip install -r requirements.txt`
27
- 3. Run the application: `python app/app.py`
 
 
 
 
28
 
29
  ### Using Hugging Face Space
30
 
@@ -32,12 +35,18 @@ Simply visit the Hugging Face Space at [URL to be added] and use the interface d
32
 
33
  ## Project Structure
34
 
35
- - `app/` - Contains the main application code
36
- - `app/app.py` - Main Gradio application
37
- - `app/resume_processor.py` - LaTeX processing utilities
38
- - `app/llm_utils.py` - LLM integration for job description analysis
39
  - `templates/` - Default LaTeX resume template
40
- - `assets/` - Static assets for the application
 
 
 
 
 
 
 
 
41
 
42
  ## License
43
 
 
1
  # Resume Customizer
2
 
3
+ A Hugging Face Spaces application that customizes your resume for specific job applications by analyzing job descriptions and generating tailored "Why Hire Me" sections using OpenAI.
4
 
5
  ## Features
6
 
 
13
  ## How It Works
14
 
15
  1. The application takes your LaTeX resume template and a job description as input
16
+ 2. It uses OpenAI's GPT model to analyze the job description and generate a tailored "Why Hire Me" section
17
+ 3. The new section is injected into your LaTeX resume
18
+ 4. The modified LaTeX is converted to PDF for download
 
19
 
20
  ## Getting Started
21
 
 
23
 
24
  1. Clone this repository
25
  2. Install dependencies: `pip install -r requirements.txt`
26
+ 3. Create a `.env` file in the root directory with your OpenAI API key:
27
+ ```
28
+ OPENAI_API_KEY=your_openai_api_key_here
29
+ ```
30
+ 4. Run the application: `python app.py`
31
 
32
  ### Using Hugging Face Space
33
 
 
35
 
36
  ## Project Structure
37
 
38
+ - `app.py` - Main application code (Gradio interface and OpenAI integration)
39
+ - `app/` - Package directory containing utility files
 
 
40
  - `templates/` - Default LaTeX resume template
41
+ - `outputs/` - Output directory for generated files
42
+
43
+ ## OpenAI Integration
44
+
45
+ The application uses OpenAI's GPT model to generate personalized "Why Hire Me" sections based on job descriptions. The integration:
46
+
47
+ 1. Analyzes the job description to identify key requirements and skills
48
+ 2. Generates a persuasive "Why Hire Me" section that highlights relevant qualifications
49
+ 3. Formats the output in LaTeX for seamless integration into the resume
50
 
51
  ## License
52
 
README_HF.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Resume Customizer
2
+
3
+ A resume customization app that generates personalized "Why Hire Me" sections based on job descriptions.
4
+
5
+ ## About
6
+
7
+ This application helps you customize your resume for specific job applications by analyzing job descriptions and generating tailored "Why Hire Me" sections using OpenAI's GPT model.
8
+
9
+ ## How It Works
10
+
11
+ 1. Enter a job description
12
+ 2. The app uses OpenAI's GPT model to analyze the job description
13
+ 3. It generates a personalized "Why Hire Me" section
14
+ 4. The section is added to a LaTeX resume template
15
+ 5. A customized PDF resume is generated and can be downloaded
16
+
17
+ ## Usage Tips
18
+
19
+ - Paste the complete job description for best results
20
+ - The more detailed the job description, the more tailored the "Why Hire Me" section will be
21
+ - You can download the generated PDF and use it for your job application
22
+
23
+ ## Limitations
24
+
25
+ - The app requires an OpenAI API key to be set as an environment variable
26
+ - PDF generation requires LaTeX to be installed on the server
27
+
28
+ ## Credits
29
+
30
+ This application uses:
31
+ - Gradio for the web interface
32
+ - OpenAI's GPT model for generating personalized content
33
+ - LaTeX for resume template and PDF generation
34
+
35
+ ## License
36
+
37
+ MIT
USAGE_GUIDE.md ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Resume Customizer - Usage Guide
2
+
3
+ This guide explains how to use the Resume Customizer application to generate personalized resumes based on job descriptions.
4
+
5
+ ## Setup
6
+
7
+ ### Prerequisites
8
+
9
+ - Python 3.7 or higher
10
+ - OpenAI API key (get one at https://platform.openai.com/api-keys)
11
+
12
+ ### Installation
13
+
14
+ 1. Clone the repository or download the source code
15
+ 2. Install the required dependencies:
16
+ ```
17
+ pip install -r requirements.txt
18
+ ```
19
+ 3. Create a `.env` file in the project root directory with your OpenAI API key:
20
+ ```
21
+ OPENAI_API_KEY=your_openai_api_key_here
22
+ ```
23
+
24
+ ## Using the Application
25
+
26
+ ### Starting the Application
27
+
28
+ Run the following command to start the application:
29
+
30
+ ```
31
+ python app.py
32
+ ```
33
+
34
+ This will launch a Gradio web interface accessible at http://localhost:7860 in your web browser.
35
+
36
+ ### Customizing Your Resume
37
+
38
+ 1. **Enter a Job Description**: Paste the job description into the text box on the left side of the application.
39
+ 2. **Click "Customize Resume"**: The application will analyze the job description and generate a personalized "Why Hire Me" section.
40
+ 3. **Download the Resume**: Once processing is complete, you can download the generated PDF resume by clicking on the download button.
41
+
42
+ ## Example
43
+
44
+ ### Sample Job Description
45
+
46
+ ```
47
+ Data Scientist
48
+
49
+ Job Description:
50
+ We are seeking a skilled Data Scientist to join our analytics team. The ideal candidate will have strong analytical skills, experience with statistical modeling, and proficiency in Python or R.
51
+
52
+ Responsibilities:
53
+ - Analyze large datasets to extract meaningful insights
54
+ - Develop machine learning models to solve business problems
55
+ - Collaborate with cross-functional teams to implement data-driven solutions
56
+ - Create visualizations and reports to communicate findings to stakeholders
57
+
58
+ Requirements:
59
+ - Bachelor's or Master's degree in Statistics, Computer Science, or related field
60
+ - 2+ years of experience in data science or analytics
61
+ - Proficiency in Python or R, SQL, and data visualization tools
62
+ - Experience with machine learning algorithms and statistical analysis
63
+ - Strong problem-solving skills and attention to detail
64
+ ```
65
+
66
+ ### Generated "Why Hire Me" Section
67
+
68
+ The application would generate a "Why Hire Me" section similar to this:
69
+
70
+ ```latex
71
+ \section*{Why Hire Me}
72
+
73
+ As a data scientist with a strong foundation in statistics and computer science, I bring a comprehensive skill set that aligns perfectly with your requirements. My expertise in analyzing large datasets and extracting actionable insights has consistently delivered value to organizations similar to yours.
74
+
75
+ My proficiency in Python, R, and SQL allows me to efficiently manipulate and analyze complex data structures, while my experience with various machine learning algorithms enables me to develop robust predictive models. I have successfully implemented data-driven solutions that have directly contributed to business growth and operational efficiency.
76
+
77
+ Communication is central to my approach as a data scientist. I excel at creating clear, compelling visualizations and reports that effectively translate technical findings into insights that stakeholders across all levels of the organization can understand and act upon. My presentations have consistently bridged the gap between technical analysis and practical business applications.
78
+
79
+ I thrive in collaborative environments and have a proven track record of working effectively with cross-functional teams. My problem-solving skills, attention to detail, and passion for discovering patterns in data make me an ideal candidate to join your analytics team and contribute to your data-driven initiatives from day one.
80
+ ```
81
+
82
+ ## Troubleshooting
83
+
84
+ ### Common Issues
85
+
86
+ 1. **Error: OPENAI_API_KEY environment variable is not set**
87
+ - Make sure you've created a `.env` file with your OpenAI API key in the project root directory.
88
+
89
+ 2. **PDF Generation Fails**
90
+ - Ensure you have LaTeX installed on your system. You can download it from https://www.latex-project.org/get/
91
+
92
+ 3. **OpenAI API Error**
93
+ - Check that your API key is valid and has sufficient credits.
94
+ - Verify your internet connection.
95
+
96
+ ### Getting Help
97
+
98
+ If you encounter any issues not covered here, please open an issue on the GitHub repository with details about the problem and steps to reproduce it.
app.py CHANGED
@@ -1,20 +1,222 @@
1
  """
2
- Main entry point for the Resume Customizer application.
3
- For Hugging Face Spaces deployment.
 
4
  """
5
 
6
  import os
7
- import sys
 
 
 
 
 
8
 
9
- # Add the current directory to Python's path to allow imports
10
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
11
 
12
- # Import the create_interface function from the app module
13
- from app.app import create_interface
 
14
 
15
- # Create and launch the application
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  app = create_interface()
17
 
18
- # For Hugging Face Spaces, we need to export the app
19
  if __name__ == "__main__":
20
  app.launch()
 
1
  """
2
+ Resume Customizer Application.
3
+ This application customizes resumes by generating "Why Hire Me" sections based on job descriptions.
4
+ Works both for local development and Hugging Face Spaces deployment.
5
  """
6
 
7
  import os
8
+ import gradio as gr
9
+ import tempfile
10
+ import shutil
11
+ import subprocess
12
+ from openai import OpenAI
13
+ import dotenv
14
 
15
+ # Load environment variables
16
+ dotenv.load_dotenv()
17
 
18
+ # Constants
19
+ DEFAULT_TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates", "minimal_resume.tex")
20
+ OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "outputs")
21
 
22
+ # Ensure output directory exists
23
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
24
+
25
+ def get_openai_client():
26
+ """
27
+ Get the OpenAI client with the API key from environment variables.
28
+ Try to get it from both Hugging Face Spaces and local .env file.
29
+
30
+ Returns:
31
+ OpenAI: The OpenAI client
32
+ """
33
+ # First try to get the API key from Hugging Face Spaces secrets
34
+ api_key = os.environ.get("OPENAI_API_KEY")
35
+
36
+ # If not found, try to get it from .env file
37
+ if not api_key:
38
+ api_key = os.getenv("OPENAI_API_KEY")
39
+
40
+ if not api_key:
41
+ raise ValueError("OPENAI_API_KEY environment variable is not set. Please set it in Hugging Face Spaces secrets or in a .env file.")
42
+
43
+ return OpenAI(api_key=api_key)
44
+
45
+ def generate_why_hire_me_section(job_description):
46
+ """
47
+ Generate a 'Why Hire Me' section based on the job description using OpenAI.
48
+
49
+ Args:
50
+ job_description (str): The job description text
51
+
52
+ Returns:
53
+ str: LaTeX formatted 'Why Hire Me' section
54
+ """
55
+ client = get_openai_client()
56
+
57
+ # Create the prompt for OpenAI
58
+ prompt = f"""
59
+ Generate a "Why Hire Me" section for a resume based on the job description below.
60
+ The section should explain why the candidate is a good fit for the position.
61
+ Use LaTeX formatting with the '\\section*{{Why Hire Me}}' heading.
62
+ Keep it concise (3-5 paragraphs), professional, and highlight key qualifications that match the job.
63
+ Don't mention specific experience unless it's generic enough to apply to most professionals.
64
+
65
+ Job Description:
66
+ {job_description}
67
+
68
+ The response should be LaTeX formatted text that can be directly inserted into a resume.
69
+ """
70
+
71
+ try:
72
+ response = client.chat.completions.create(
73
+ model="gpt-4o",
74
+ messages=[
75
+ {"role": "system", "content": "You are a professional resume writer who specializes in creating compelling 'Why Hire Me' sections for job applications."},
76
+ {"role": "user", "content": prompt}
77
+ ],
78
+ temperature=0.7,
79
+ max_tokens=500
80
+ )
81
+
82
+ # Extract the generated text from the response
83
+ why_hire_me_text = response.choices[0].message.content
84
+
85
+ # Ensure it has the proper LaTeX section heading if not already included
86
+ if "\\section" not in why_hire_me_text:
87
+ why_hire_me_text = "\\section*{Why Hire Me}\n" + why_hire_me_text
88
+
89
+ return why_hire_me_text
90
+
91
+ except Exception as e:
92
+ print(f"Error generating 'Why Hire Me' section: {str(e)}")
93
+ # Return a fallback section if there's an error
94
+ return """\\section*{Why Hire Me}
95
+ Due to a technical issue, a personalized 'Why Hire Me' section could not be generated.
96
+ Please try again later or contact support for assistance."""
97
+
98
+ def customize_resume(job_description):
99
+ """
100
+ Main function to customize resume based on job description.
101
+
102
+ Args:
103
+ job_description (str): The job description text
104
+
105
+ Returns:
106
+ tuple: (PDF path, Status message)
107
+ """
108
+ try:
109
+ # Generate the 'Why Hire Me' section using OpenAI
110
+ why_hire_me_section = generate_why_hire_me_section(job_description)
111
+
112
+ # Read the template
113
+ with open(DEFAULT_TEMPLATE_PATH, "r") as f:
114
+ template_content = f.read()
115
+
116
+ # Replace the placeholder with the generated section
117
+ modified_content = template_content.replace("% WHY_HIRE_ME_SECTION", why_hire_me_section)
118
+
119
+ # Save the modified content to a new file
120
+ output_tex_path = os.path.join(OUTPUT_DIR, "customized_resume.tex")
121
+ with open(output_tex_path, "w") as f:
122
+ f.write(modified_content)
123
+
124
+ # Convert to PDF using pdflatex
125
+ pdf_path = convert_to_pdf(output_tex_path)
126
+ if pdf_path:
127
+ return pdf_path, f"Resume successfully generated with personalized 'Why Hire Me' section! Click to download."
128
+ else:
129
+ return None, f"Failed to generate PDF. Check the logs for details."
130
+ except Exception as e:
131
+ return None, f"Error customizing resume: {str(e)}"
132
+
133
+ def convert_to_pdf(tex_path):
134
+ """
135
+ Convert a LaTeX file to PDF using pdflatex.
136
+
137
+ Args:
138
+ tex_path (str): Path to the LaTeX file
139
+
140
+ Returns:
141
+ str: Path to the generated PDF file or None if conversion failed
142
+ """
143
+ # Get the directory and filename
144
+ tex_dir = os.path.dirname(tex_path)
145
+ tex_filename = os.path.basename(tex_path)
146
+
147
+ # Change to the directory containing the tex file
148
+ original_dir = os.getcwd()
149
+ os.chdir(tex_dir)
150
+
151
+ try:
152
+ # Create a log file to capture output
153
+ log_path = os.path.join(tex_dir, "pdflatex_log.txt")
154
+ with open(log_path, 'w') as log_file:
155
+ # Run pdflatex
156
+ log_file.write("Running pdflatex - First pass\n")
157
+ cmd = ['pdflatex', '-interaction=nonstopmode', tex_filename]
158
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
159
+ log_file.write(f"Return code: {result.returncode}\n")
160
+ log_file.write(f"STDOUT:\n{result.stdout}\n")
161
+ log_file.write(f"STDERR:\n{result.stderr}\n\n")
162
+
163
+ # Get the PDF path
164
+ pdf_filename = tex_filename.replace('.tex', '.pdf')
165
+ pdf_path = os.path.join(tex_dir, pdf_filename)
166
+
167
+ # Check if the PDF was actually created
168
+ if os.path.exists(pdf_path):
169
+ return os.path.abspath(pdf_path)
170
+ else:
171
+ return None
172
+ except Exception as e:
173
+ raise Exception(f"PDF conversion failed: {str(e)}")
174
+ finally:
175
+ # Change back to the original directory
176
+ os.chdir(original_dir)
177
+
178
+ # Define the Gradio interface
179
+ def create_interface():
180
+ with gr.Blocks(title="Resume Customizer") as app:
181
+ gr.Markdown("# Resume Customizer")
182
+ gr.Markdown("Enter a job description to generate a customized resume with an AI-generated 'Why Hire Me' section.")
183
+
184
+ with gr.Row():
185
+ with gr.Column():
186
+ job_description = gr.Textbox(
187
+ label="Job Description",
188
+ placeholder="Paste the job description here...",
189
+ lines=10
190
+ )
191
+
192
+ customize_btn = gr.Button("Customize Resume")
193
+
194
+ with gr.Column():
195
+ pdf_output = gr.File(label="Download Resume")
196
+ status_text = gr.Textbox(label="Status", interactive=False)
197
+
198
+ customize_btn.click(
199
+ fn=customize_resume,
200
+ inputs=[job_description],
201
+ outputs=[pdf_output, status_text]
202
+ )
203
+
204
+ gr.Markdown("""
205
+ ## How to Use
206
+ 1. Paste a job description in the text area
207
+ 2. Click "Customize Resume"
208
+ 3. Wait for the AI to generate a "Why Hire Me" section
209
+ 4. Download the customized resume PDF
210
+
211
+ ## Note
212
+ This app requires an OpenAI API key to be set up.
213
+ """)
214
+
215
+ return app
216
+
217
+ # Create and launch the app
218
  app = create_interface()
219
 
220
+ # For local development and Hugging Face Spaces compatibility
221
  if __name__ == "__main__":
222
  app.launch()
app/__init__.py CHANGED
@@ -1,5 +1,7 @@
1
  """
2
  Resume Customizer application package.
 
 
3
  """
4
 
5
  __version__ = "0.1.0"
 
1
  """
2
  Resume Customizer application package.
3
+ This package contains utility modules and support files for the Resume Customizer application.
4
+ The main application logic is now in the root app.py file.
5
  """
6
 
7
  __version__ = "0.1.0"
app/app.py DELETED
@@ -1,136 +0,0 @@
1
- import os
2
- import gradio as gr
3
- from huggingface_hub import InferenceClient
4
- import tempfile
5
- import shutil
6
- import subprocess
7
-
8
- # Constants
9
- # Use the minimal template for testing
10
- DEFAULT_TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "templates", "minimal_resume.tex")
11
- OUTPUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "outputs")
12
-
13
- # Ensure output directory exists
14
- os.makedirs(OUTPUT_DIR, exist_ok=True)
15
-
16
- def customize_resume(job_description):
17
- """
18
- Main function to customize resume based on job description.
19
-
20
- Args:
21
- job_description (str): The job description text
22
-
23
- Returns:
24
- tuple: (PDF path, Status message)
25
- """
26
- # This is a placeholder for future implementation of LLM integration
27
- # For now, we'll just add a simple "Why Hire Me" section
28
- why_hire_me_section = "\\section{Why Hire Me?}\nThis is a placeholder for the Why Hire Me section that will be generated by an LLM in future milestones."
29
-
30
- # Read the template
31
- with open(DEFAULT_TEMPLATE_PATH, "r") as f:
32
- template_content = f.read()
33
-
34
- # Replace the placeholder with the generated section
35
- modified_content = template_content.replace("% WHY_HIRE_ME_SECTION", why_hire_me_section)
36
-
37
- # Save the modified content to a new file
38
- output_tex_path = os.path.join(OUTPUT_DIR, "customized_resume.tex")
39
- with open(output_tex_path, "w") as f:
40
- f.write(modified_content)
41
-
42
- # Convert to PDF using pdflatex
43
- try:
44
- pdf_path = convert_to_pdf(output_tex_path)
45
- return pdf_path, f"Resume successfully generated! Click to download."
46
- except Exception as e:
47
- return None, f"Error converting to PDF: {str(e)}"
48
-
49
- def convert_to_pdf(tex_path):
50
- """
51
- Convert a LaTeX file to PDF using pdflatex.
52
-
53
- Args:
54
- tex_path (str): Path to the LaTeX file
55
-
56
- Returns:
57
- str: Path to the generated PDF file
58
- """
59
- # Get the directory and filename
60
- tex_dir = os.path.dirname(tex_path)
61
- tex_filename = os.path.basename(tex_path)
62
-
63
- # Change to the directory containing the tex file
64
- original_dir = os.getcwd()
65
- os.chdir(tex_dir)
66
-
67
- try:
68
- # Create a log file to capture output
69
- log_path = os.path.join(tex_dir, "pdflatex_log.txt")
70
- with open(log_path, 'w') as log_file:
71
- # Run pdflatex and capture output to the log file
72
- log_file.write("Running pdflatex - First pass\n")
73
- result = subprocess.run(['pdflatex', '-interaction=nonstopmode', tex_filename],
74
- stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
75
- log_file.write(f"Return code: {result.returncode}\n")
76
- log_file.write(f"STDOUT:\n{result.stdout}\n")
77
- log_file.write(f"STDERR:\n{result.stderr}\n\n")
78
-
79
- # Run a second time to resolve references
80
- log_file.write("Running pdflatex - Second pass\n")
81
- result = subprocess.run(['pdflatex', '-interaction=nonstopmode', tex_filename],
82
- stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
83
- log_file.write(f"Return code: {result.returncode}\n")
84
- log_file.write(f"STDOUT:\n{result.stdout}\n")
85
- log_file.write(f"STDERR:\n{result.stderr}\n")
86
-
87
- # Get the PDF path
88
- pdf_filename = tex_filename.replace('.tex', '.pdf')
89
- pdf_path = os.path.join(tex_dir, pdf_filename)
90
-
91
- # Check if the PDF was actually created
92
- if not os.path.exists(pdf_path):
93
- raise Exception(f"PDF file not created. See log at: {log_path}")
94
-
95
- # Return the absolute path to the PDF
96
- return os.path.abspath(pdf_path)
97
- except subprocess.CalledProcessError as e:
98
- raise Exception(f"PDF conversion failed: {e}\nSee log at: {os.path.abspath(log_path)}")
99
- except Exception as e:
100
- raise Exception(f"Error in PDF conversion: {str(e)}")
101
- finally:
102
- # Change back to the original directory
103
- os.chdir(original_dir)
104
-
105
- # Define the Gradio interface
106
- def create_interface():
107
- with gr.Blocks(title="Resume Customizer") as app:
108
- gr.Markdown("# Resume Customizer")
109
- gr.Markdown("Enter a job description to generate a customized resume.")
110
-
111
- with gr.Row():
112
- with gr.Column():
113
- job_description = gr.Textbox(
114
- label="Job Description",
115
- placeholder="Paste the job description here...",
116
- lines=10
117
- )
118
-
119
- customize_btn = gr.Button("Customize Resume")
120
-
121
- with gr.Column():
122
- pdf_output = gr.File(label="Download Resume")
123
- status_text = gr.Textbox(label="Status", interactive=False)
124
-
125
- customize_btn.click(
126
- fn=customize_resume,
127
- inputs=[job_description],
128
- outputs=[pdf_output, status_text]
129
- )
130
-
131
- return app
132
-
133
- # Main entry point
134
- if __name__ == "__main__":
135
- app = create_interface()
136
- app.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
milestones.md CHANGED
@@ -11,15 +11,17 @@ Note (when something is implemented write [done] in front of it)
11
  - Test LaTeX to PDF conversion pipeline [done]
12
  - Set up file storage structure for template and customized resumes [done]
13
 
14
- ## Milestone 3: Job Description Analysis with LLM
15
- - Set up LLM integration (Hugging Face models)
16
- - Develop prompt engineering for job description analysis
17
- - Create function to extract key skills and requirements from job descriptions
 
18
 
19
- ## Milestone 4: Resume Customization Logic
20
- - Develop logic to generate "Why Hire Me" section based on job description
21
- - Create function to inject new section into LaTeX resume
22
- - Implement resume customization pipeline (job desc → analysis → LaTeX modification → PDF)
 
23
 
24
  ## Milestone 5: Gradio UI Development
25
  - Design simple UI with job description input
@@ -33,14 +35,8 @@ Note (when something is implemented write [done] in front of it)
33
  - Optimize performance and resource usage
34
  - Fix bugs and edge cases
35
 
36
- ## Milestone 7: Hugging Face Deployment
37
- - Prepare application for Hugging Face Spaces
38
- - Configure deployment settings
39
- - Create deployment documentation
40
- - Deploy application to Hugging Face Spaces
41
-
42
  ## Milestone 8: Documentation and Maintenance
43
  - Complete user documentation
44
  - Document code for future maintenance
45
  - Create examples and usage instructions
46
- - Plan for future enhancements
 
11
  - Test LaTeX to PDF conversion pipeline [done]
12
  - Set up file storage structure for template and customized resumes [done]
13
 
14
+ ## Milestone 3: Resume Customization with OpenAI LLM [done]
15
+ - Set up OpenAI LLM integration [done]
16
+ - Write simple prompt for adding a section (add this in prompt to openai model) [done]
17
+ - Create function to process resume and job description with LLM [done]
18
+ - Implement resume customization pipeline (input resume + job desc → LLM processing → output customized resume → PDF) [done]
19
 
20
+ ## Milestone 4: Hugging Face Deployment [done]
21
+ - Prepare application for Hugging Face Spaces [done]
22
+ - Configure deployment settings [done]
23
+ - Create deployment documentation [done]
24
+ - Deploy application to Hugging Face Spaces [done]
25
 
26
  ## Milestone 5: Gradio UI Development
27
  - Design simple UI with job description input
 
35
  - Optimize performance and resource usage
36
  - Fix bugs and edge cases
37
 
 
 
 
 
 
 
38
  ## Milestone 8: Documentation and Maintenance
39
  - Complete user documentation
40
  - Document code for future maintenance
41
  - Create examples and usage instructions
42
+ - Plan for future enhancements
packages.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ texlive-latex-base
2
+ texlive-latex-extra
3
+ texlive-fonts-recommended
4
+ ghostscript
5
+ libcairo2-dev
6
+ pdflatex
requirements.txt CHANGED
@@ -1,8 +1,7 @@
1
  gradio>=4.0.0
2
- huggingface_hub>=0.20.0
3
- transformers>=4.36.0
4
- torch>=2.0.0
5
- PyPDF2>=3.0.0
6
  python-dotenv>=1.0.0
7
- latex>=0.7.0
8
- pdflatex>=0.1.3
 
 
 
1
  gradio>=4.0.0
2
+ openai>=1.12.0
 
 
 
3
  python-dotenv>=1.0.0
4
+ PyPDF2>=3.0.0
5
+ texlive-latex-base
6
+ texlive-latex-extra
7
+ texlive-fonts-recommended
space_metadata.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "Resume Customizer",
3
+ "emoji": "📄",
4
+ "colorFrom": "blue",
5
+ "colorTo": "indigo",
6
+ "sdk": "gradio",
7
+ "sdk_version": "4.19.2",
8
+ "python_version": "3.10",
9
+ "app_file": "app.py",
10
+ "pinned": false,
11
+ "license": "mit"
12
+ }
templates/minimal_resume.tex CHANGED
@@ -3,15 +3,15 @@
3
  \usepackage[margin=1in]{geometry}
4
  \usepackage{enumitem}
5
 
6
- % WHY HIRE ME SECTION PLACEHOLDER - this will be modified by the app
7
- % WHY_HIRE_ME_SECTION
8
-
9
  \begin{document}
10
 
11
  \centerline{\Huge\textbf{John Doe}}
12
  \vspace{0.25em}
13
  \centerline{123-456-7890 $\vert$ john.doe@email.com $\vert$ linkedin.com/in/johndoe}
14
 
 
 
 
15
  \section*{Education}
16
  \textbf{University of Technology} \hfill City, State\\
17
  Master of Science in Computer Science \hfill 2018-2020
 
3
  \usepackage[margin=1in]{geometry}
4
  \usepackage{enumitem}
5
 
 
 
 
6
  \begin{document}
7
 
8
  \centerline{\Huge\textbf{John Doe}}
9
  \vspace{0.25em}
10
  \centerline{123-456-7890 $\vert$ john.doe@email.com $\vert$ linkedin.com/in/johndoe}
11
 
12
+ % WHY HIRE ME SECTION PLACEHOLDER - this will be modified by the app
13
+ % WHY_HIRE_ME_SECTION
14
+
15
  \section*{Education}
16
  \textbf{University of Technology} \hfill City, State\\
17
  Master of Science in Computer Science \hfill 2018-2020
test_openai_integration.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test script for OpenAI integration.
3
+ This script tests the 'Why Hire Me' section generation using OpenAI.
4
+ """
5
+
6
+ import os
7
+ import dotenv
8
+ from openai import OpenAI
9
+
10
+ # Load environment variables from .env file
11
+ dotenv.load_dotenv()
12
+
13
+ def get_openai_client():
14
+ """
15
+ Get the OpenAI client with the API key from environment variables.
16
+
17
+ Returns:
18
+ OpenAI: The OpenAI client
19
+ """
20
+ api_key = os.getenv("OPENAI_API_KEY")
21
+ if not api_key:
22
+ raise ValueError("OPENAI_API_KEY environment variable is not set. Please set it in the .env file.")
23
+
24
+ if api_key == "your_openai_api_key_here":
25
+ raise ValueError("Please replace the placeholder API key in the .env file with your actual OpenAI API key.")
26
+
27
+ return OpenAI(api_key=api_key)
28
+
29
+ def generate_why_hire_me_section(job_description):
30
+ """
31
+ Generate a 'Why Hire Me' section based on the job description using OpenAI.
32
+
33
+ Args:
34
+ job_description (str): The job description text
35
+
36
+ Returns:
37
+ str: LaTeX formatted 'Why Hire Me' section
38
+ """
39
+ client = get_openai_client()
40
+
41
+ # Create the prompt for OpenAI
42
+ prompt = f"""
43
+ Generate a "Why Hire Me" section for a resume based on the job description below.
44
+ The section should explain why the candidate is a good fit for the position.
45
+ Use LaTeX formatting with the '\\section*{{Why Hire Me}}' heading.
46
+ Keep it concise (3-5 paragraphs), professional, and highlight key qualifications that match the job.
47
+ Don't mention specific experience unless it's generic enough to apply to most professionals.
48
+
49
+ Job Description:
50
+ {job_description}
51
+
52
+ The response should be LaTeX formatted text that can be directly inserted into a resume.
53
+ """
54
+
55
+ try:
56
+ response = client.chat.completions.create(
57
+ model="gpt-4o",
58
+ messages=[
59
+ {"role": "system", "content": "You are a professional resume writer who specializes in creating compelling 'Why Hire Me' sections for job applications."},
60
+ {"role": "user", "content": prompt}
61
+ ],
62
+ temperature=0.7,
63
+ max_tokens=500
64
+ )
65
+
66
+ # Extract the generated text from the response
67
+ why_hire_me_text = response.choices[0].message.content
68
+
69
+ # Ensure it has the proper LaTeX section heading if not already included
70
+ if "\\section" not in why_hire_me_text:
71
+ why_hire_me_text = "\\section*{Why Hire Me}\n" + why_hire_me_text
72
+
73
+ return why_hire_me_text
74
+
75
+ except Exception as e:
76
+ print(f"Error generating 'Why Hire Me' section: {str(e)}")
77
+ # Return a fallback section if there's an error
78
+ return None
79
+
80
+ def main():
81
+ """Main function to test the OpenAI integration."""
82
+ print("Testing OpenAI integration for 'Why Hire Me' section generation...")
83
+
84
+ # Sample job description
85
+ job_description = """
86
+ Software Engineer - Full Stack Developer
87
+
88
+ Job Description:
89
+ We are looking for a Full Stack Developer who is passionate about building web applications. The ideal candidate will have experience with both frontend and backend technologies, and a strong understanding of software development principles.
90
+
91
+ Responsibilities:
92
+ - Develop and maintain web applications using modern JavaScript frameworks (React, Angular) and backend technologies (Node.js, Python)
93
+ - Write clean, maintainable, and efficient code
94
+ - Collaborate with cross-functional teams to define and implement new features
95
+ - Troubleshoot and debug applications
96
+ - Optimize applications for maximum speed and scalability
97
+
98
+ Requirements:
99
+ - 3+ years of experience in full-stack development
100
+ - Proficiency in JavaScript, HTML, CSS, and at least one modern frontend framework (React, Angular, Vue)
101
+ - Experience with backend development using Node.js, Python, or similar technologies
102
+ - Knowledge of database systems (SQL, NoSQL)
103
+ - Familiarity with version control systems (Git)
104
+ - Strong problem-solving skills and attention to detail
105
+ - Excellent communication and teamwork skills
106
+ """
107
+
108
+ try:
109
+ # Generate the 'Why Hire Me' section
110
+ why_hire_me_section = generate_why_hire_me_section(job_description)
111
+
112
+ if why_hire_me_section:
113
+ print("\nGenerated 'Why Hire Me' section:")
114
+ print("-" * 80)
115
+ print(why_hire_me_section)
116
+ print("-" * 80)
117
+ print("\nOpenAI integration test completed successfully!")
118
+ else:
119
+ print("\nFailed to generate 'Why Hire Me' section.")
120
+ except Exception as e:
121
+ print(f"\nOpenAI integration test failed: {str(e)}")
122
+
123
+ if __name__ == "__main__":
124
+ main()
test_pdf_generation.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test script for PDF generation from LaTeX.
3
+ This directly tests the PDF generation functionality without using Gradio.
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import shutil
9
+
10
+ # Define paths
11
+ TEMPLATE_PATH = os.path.join("templates", "minimal_resume.tex")
12
+ OUTPUT_DIR = "outputs"
13
+ OUTPUT_TEX_PATH = os.path.join(OUTPUT_DIR, "test_resume.tex")
14
+
15
+ def add_why_hire_me_section(template_path, output_path):
16
+ """Add a test Why Hire Me section to the resume template."""
17
+ # The section needs to be properly placed after \begin{document}
18
+ why_hire_me = """\\section*{Why Hire Me}
19
+ I am a highly motivated software engineer with extensive experience in web development.
20
+ My expertise in Python and JavaScript makes me an ideal candidate for this position.
21
+ """
22
+
23
+ # Read the template
24
+ with open(template_path, "r") as f:
25
+ content = f.read()
26
+
27
+ # Replace the placeholder with the test section
28
+ modified_content = content.replace("% WHY_HIRE_ME_SECTION", why_hire_me)
29
+
30
+ # Write the modified content
31
+ with open(output_path, "w") as f:
32
+ f.write(modified_content)
33
+
34
+ return output_path
35
+
36
+ def convert_to_pdf(tex_path):
37
+ """Convert a LaTeX file to PDF."""
38
+ # Get the directory and filename
39
+ tex_dir = os.path.dirname(tex_path)
40
+ tex_filename = os.path.basename(tex_path)
41
+
42
+ # Change to the directory containing the tex file
43
+ original_dir = os.getcwd()
44
+ if tex_dir: # Only change directory if tex_dir is not empty
45
+ os.chdir(tex_dir)
46
+
47
+ try:
48
+ # Create a log file to capture output
49
+ log_path = "pdflatex_log.txt"
50
+ with open(log_path, 'w') as log_file:
51
+ # Run pdflatex and capture output to the log file
52
+ log_file.write("Running pdflatex - First pass\n")
53
+ cmd = ['pdflatex', '-interaction=nonstopmode', tex_filename]
54
+ log_file.write(f"Running command: {' '.join(cmd)}\n")
55
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
56
+ log_file.write(f"Return code: {result.returncode}\n")
57
+ log_file.write(f"STDOUT:\n{result.stdout}\n")
58
+ log_file.write(f"STDERR:\n{result.stderr}\n\n")
59
+
60
+ # Get the PDF path
61
+ pdf_filename = tex_filename.replace('.tex', '.pdf')
62
+
63
+ # Check if the PDF was actually created (in current directory)
64
+ if os.path.exists(pdf_filename):
65
+ pdf_path = os.path.abspath(pdf_filename)
66
+ print(f"PDF created successfully: {pdf_path}")
67
+ return pdf_path
68
+
69
+ # Check if the PDF was created in the tex_dir
70
+ pdf_in_texdir = os.path.join(tex_dir, pdf_filename) if tex_dir else pdf_filename
71
+ if os.path.exists(pdf_in_texdir):
72
+ pdf_path = os.path.abspath(pdf_in_texdir)
73
+ print(f"PDF created successfully: {pdf_path}")
74
+ return pdf_path
75
+
76
+ print(f"PDF file not created. See log at: {os.path.abspath(log_path)}")
77
+ return None
78
+ except Exception as e:
79
+ print(f"Error in PDF conversion: {str(e)}")
80
+ return None
81
+ finally:
82
+ # Change back to the original directory
83
+ if tex_dir: # Only change back if we changed in the first place
84
+ os.chdir(original_dir)
85
+
86
+ def main():
87
+ """Main function to test the PDF generation."""
88
+ print("Starting PDF generation test...")
89
+ print(f"Using template: {os.path.abspath(TEMPLATE_PATH)}")
90
+
91
+ # Make sure the output directory exists
92
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
93
+
94
+ # Add the Why Hire Me section to the template
95
+ modified_tex_path = add_why_hire_me_section(TEMPLATE_PATH, OUTPUT_TEX_PATH)
96
+ print(f"Created modified LaTeX file: {os.path.abspath(modified_tex_path)}")
97
+
98
+ # Convert to PDF
99
+ pdf_path = convert_to_pdf(modified_tex_path)
100
+
101
+ if pdf_path:
102
+ print("PDF generation test completed successfully!")
103
+ # Copy the PDF to the current directory for easier viewing
104
+ shutil.copy(pdf_path, "test_resume.pdf")
105
+ print(f"PDF copied to: {os.path.abspath('test_resume.pdf')}")
106
+ else:
107
+ print("PDF generation test failed.")
108
+
109
+ if __name__ == "__main__":
110
+ main()