File size: 9,934 Bytes
44078fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
import os
import time
import tempfile
import gradio as gr
import warnings
from pathlib import Path
import PyPDF2
import markdown
from datetime import datetime, timedelta
from collections import defaultdict
import threading

warnings.filterwarnings('ignore')

# Rate limiting
class RateLimiter:
    def __init__(self, max_requests=5, time_window=60):
        self.max_requests = max_requests
        self.time_window = time_window
        self.requests = defaultdict(list)
        self.lock = threading.Lock()
    
    def is_allowed(self, user_id):
        with self.lock:
            now = datetime.now()
            # Clean old requests
            self.requests[user_id] = [
                req_time for req_time in self.requests[user_id]
                if now - req_time < timedelta(seconds=self.time_window)
            ]
            
            if len(self.requests[user_id]) >= self.max_requests:
                return False
            
            self.requests[user_id].append(now)
            return True

# Global rate limiter
rate_limiter = RateLimiter(max_requests=3, time_window=300)  # 3 requests per 5 minutes

def extract_text_from_pdf(pdf_file):
    """Extract text from uploaded PDF file."""
    try:
        reader = PyPDF2.PdfReader(pdf_file)
        text = ""
        for page in reader.pages:
            text += page.extract_text() + "\n"
        return text.strip()
    except Exception as e:
        return f"Error reading PDF: {str(e)}"

def setup_crewai():
    """Initialize CrewAI components."""
    try:
        from crewai import Agent, Task, Crew
        from crewai_tools import ScrapeWebsiteTool, SerperDevTool
        from langchain_openai import ChatOpenAI
        
        # Initialize tools
        search_tool = SerperDevTool()
        scrape_tool = ScrapeWebsiteTool()
        
        # Initialize LLM
        llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
        
        # Create agents
        researcher = Agent(
            role="Job Requirements Analyst",
            goal="Extract and analyze key job requirements efficiently",
            tools=[scrape_tool, search_tool],
            verbose=False,
            backstory="Expert at quickly identifying essential job requirements and qualifications from job postings.",
            llm=llm,
        )
        
        resume_strategist = Agent(
            role="Resume Enhancement Specialist",
            goal="Optimize resumes to match job requirements effectively",
            tools=[],
            verbose=False,
            backstory="Skilled at tailoring resumes to highlight relevant experience and skills for specific job applications.",
            llm=llm,
        )
        
        return researcher, resume_strategist, llm
        
    except ImportError:
        raise Exception("CrewAI not installed. Please install required packages.")

def create_tasks(researcher, resume_strategist, job_url, resume_text):
    """Create optimized tasks for the crew."""
    from crewai import Task
    
    # Research task - focused and efficient
    research_task = Task(
        description=f"""
        Analyze the job posting at {job_url} and extract the top 10 most important:
        1. Required skills and technologies
        2. Key qualifications and experience levels
        3. Preferred background and certifications
        
        Focus on the most critical requirements only.
        """,
        expected_output="A concise list of the top 10 most important job requirements.",
        agent=researcher,
    )
    
    # Resume optimization task
    resume_task = Task(
        description=f"""
        Using the job requirements from the research task, optimize this resume:
        
        {resume_text}
        
        Instructions:
        1. Rewrite the professional summary to align with the job
        2. Highlight relevant experience and skills
        3. Adjust technical skills section to match requirements
        4. Ensure ATS-friendly formatting
        5. Keep the same factual information but present it strategically
        
        Return the complete optimized resume in markdown format.
        """,
        expected_output="A complete, optimized resume in markdown format tailored to the job requirements.",
        agent=resume_strategist,
        context=[research_task]
    )
    
    return research_task, resume_task

def process_application(pdf_file, job_url, user_session):
    """Main processing function with rate limiting."""
    
    # Rate limiting check
    if not rate_limiter.is_allowed(user_session):
        return "⚠️ Rate limit exceeded. Please wait 5 minutes before submitting another request.", ""
    
    if not pdf_file or not job_url:
        return "❌ Please provide both a PDF resume and job URL.", ""
    
    try:
        # Extract text from PDF
        with gr.Progress() as progress:
            progress(0.1, desc="Extracting text from PDF...")
            resume_text = extract_text_from_pdf(pdf_file)
            
            if "Error reading PDF" in resume_text:
                return f"❌ {resume_text}", ""
            
            progress(0.3, desc="Setting up AI agents...")
            researcher, resume_strategist, llm = setup_crewai()
            
            progress(0.5, desc="Creating optimization tasks...")
            research_task, resume_task = create_tasks(researcher, resume_strategist, job_url, resume_text)
            
            progress(0.7, desc="Analyzing job requirements...")
            # Execute tasks
            from crewai import Crew
            crew = Crew(
                agents=[researcher, resume_strategist],
                tasks=[research_task, resume_task],
                verbose=False
            )
            
            progress(0.9, desc="Generating tailored resume...")
            result = crew.kickoff()
            
            progress(1.0, desc="Complete!")
            
            # Convert markdown to HTML for better display
            html_result = markdown.markdown(str(result))
            
            return "βœ… Resume successfully tailored!", html_result
            
    except Exception as e:
        return f"❌ Error processing your request: {str(e)}", ""

def create_interface():
    """Create the Gradio interface."""
    
    with gr.Blocks(
        title="CV Tailor - AI Resume Optimizer",
        theme=gr.themes.Soft(),
        css="""
        .gradio-container {
            max-width: 1200px;
            margin: auto;
        }
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        .rate-limit-info {
            background-color: #f0f8ff;
            padding: 10px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
        """
    ) as app:
        
        gr.HTML("""
        <div class="header">
            <h1>🎯 CV Tailor - AI Resume Optimizer</h1>
            <p>Upload your PDF resume and job URL to get an AI-tailored resume that matches the job requirements!</p>
        </div>
        """)
        
        gr.HTML("""
        <div class="rate-limit-info">
            <strong>⚑ Rate Limit:</strong> 3 requests per 5 minutes to manage API costs. 
            Please be patient and make each request count!
        </div>
        """)
        
        with gr.Row():
            with gr.Column(scale=1):
                pdf_input = gr.File(
                    label="πŸ“„ Upload Your Resume (PDF)",
                    file_types=[".pdf"],
                    file_count="single"
                )
                
                job_url_input = gr.Textbox(
                    label="πŸ”— Job Posting URL",
                    placeholder="https://company.com/jobs/position",
                    lines=1
                )
                
                submit_btn = gr.Button(
                    "πŸš€ Generate Tailored Resume",
                    variant="primary",
                    size="lg"
                )
                
                # Examples
                gr.Examples(
                    examples=[
                        ["https://jobs.lever.co/example-company/software-engineer"],
                        ["https://www.linkedin.com/jobs/view/example-job-id"],
                        ["https://careers.google.com/jobs/results/example-position"]
                    ],
                    inputs=job_url_input,
                    label="πŸ“ Example Job URLs"
                )
            
            with gr.Column(scale=2):
                status_output = gr.Textbox(
                    label="πŸ“Š Status",
                    interactive=False,
                    lines=1
                )
                
                result_output = gr.HTML(
                    label="πŸ“‹ Tailored Resume",
                    value="Your optimized resume will appear here..."
                )
        
        # Event handlers
        submit_btn.click(
            fn=process_application,
            inputs=[pdf_input, job_url_input, gr.State(lambda: str(time.time()))],
            outputs=[status_output, result_output]
        )
        
        # Footer
        gr.HTML("""
        <div style="text-align: center; margin-top: 50px; color: #666;">
            <p>Powered by CrewAI & OpenAI GPT-4o Mini | 
            <a href="https://github.com/joaomdmoura/crewAI" target="_blank">CrewAI</a> | 
            Built with ❀️ using Gradio</p>
        </div>
        """)
    
    return app


# Create the app
if __name__ == "__main__":
    # Set up environment variables
    # In Hugging Face Spaces, set these in the Settings > Variables section
    if not os.getenv("OPENAI_API_KEY"):
        print("⚠️ Warning: OPENAI_API_KEY not found in environment variables")
    
    app = create_interface()
    app.launch(
        server_name="0.0.0.0",
        server_port=7860,
        show_api=False,
        share=False
    )