Spaces:
Sleeping
Sleeping
Commit ·
ae6d295
1
Parent(s): 583aa92
minimal integrated with llm and pdf
Browse files- .gitattributes +3 -0
- .gitignore +2 -1
- DEPLOYMENT_GUIDE.md +90 -0
- Procfile +1 -0
- README.md +20 -11
- README_HF.md +37 -0
- USAGE_GUIDE.md +98 -0
- app.py +211 -9
- app/__init__.py +2 -0
- app/app.py +0 -136
- milestones.md +11 -15
- packages.txt +6 -0
- requirements.txt +5 -6
- space_metadata.json +12 -0
- templates/minimal_resume.tex +3 -3
- test_openai_integration.py +124 -0
- test_pdf_generation.py +110 -0
.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
|
| 17 |
-
3.
|
| 18 |
-
4. The
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 36 |
-
- `app/
|
| 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 |
-
- `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 3 |
-
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import os
|
| 7 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
|
|
|
|
| 14 |
|
| 15 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
app = create_interface()
|
| 17 |
|
| 18 |
-
# For Hugging Face Spaces
|
| 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:
|
| 15 |
-
- Set up LLM integration
|
| 16 |
-
-
|
| 17 |
-
- Create function to
|
|
|
|
| 18 |
|
| 19 |
-
## Milestone 4:
|
| 20 |
-
-
|
| 21 |
-
-
|
| 22 |
-
-
|
|
|
|
| 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 |
-
|
| 3 |
-
transformers>=4.36.0
|
| 4 |
-
torch>=2.0.0
|
| 5 |
-
PyPDF2>=3.0.0
|
| 6 |
python-dotenv>=1.0.0
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
| 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()
|