lukmanaj commited on
Commit
44078fc
Β·
verified Β·
1 Parent(s): 356ab57

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +302 -0
app.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import tempfile
4
+ import gradio as gr
5
+ import warnings
6
+ from pathlib import Path
7
+ import PyPDF2
8
+ import markdown
9
+ from datetime import datetime, timedelta
10
+ from collections import defaultdict
11
+ import threading
12
+
13
+ warnings.filterwarnings('ignore')
14
+
15
+ # Rate limiting
16
+ class RateLimiter:
17
+ def __init__(self, max_requests=5, time_window=60):
18
+ self.max_requests = max_requests
19
+ self.time_window = time_window
20
+ self.requests = defaultdict(list)
21
+ self.lock = threading.Lock()
22
+
23
+ def is_allowed(self, user_id):
24
+ with self.lock:
25
+ now = datetime.now()
26
+ # Clean old requests
27
+ self.requests[user_id] = [
28
+ req_time for req_time in self.requests[user_id]
29
+ if now - req_time < timedelta(seconds=self.time_window)
30
+ ]
31
+
32
+ if len(self.requests[user_id]) >= self.max_requests:
33
+ return False
34
+
35
+ self.requests[user_id].append(now)
36
+ return True
37
+
38
+ # Global rate limiter
39
+ rate_limiter = RateLimiter(max_requests=3, time_window=300) # 3 requests per 5 minutes
40
+
41
+ def extract_text_from_pdf(pdf_file):
42
+ """Extract text from uploaded PDF file."""
43
+ try:
44
+ reader = PyPDF2.PdfReader(pdf_file)
45
+ text = ""
46
+ for page in reader.pages:
47
+ text += page.extract_text() + "\n"
48
+ return text.strip()
49
+ except Exception as e:
50
+ return f"Error reading PDF: {str(e)}"
51
+
52
+ def setup_crewai():
53
+ """Initialize CrewAI components."""
54
+ try:
55
+ from crewai import Agent, Task, Crew
56
+ from crewai_tools import ScrapeWebsiteTool, SerperDevTool
57
+ from langchain_openai import ChatOpenAI
58
+
59
+ # Initialize tools
60
+ search_tool = SerperDevTool()
61
+ scrape_tool = ScrapeWebsiteTool()
62
+
63
+ # Initialize LLM
64
+ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
65
+
66
+ # Create agents
67
+ researcher = Agent(
68
+ role="Job Requirements Analyst",
69
+ goal="Extract and analyze key job requirements efficiently",
70
+ tools=[scrape_tool, search_tool],
71
+ verbose=False,
72
+ backstory="Expert at quickly identifying essential job requirements and qualifications from job postings.",
73
+ llm=llm,
74
+ )
75
+
76
+ resume_strategist = Agent(
77
+ role="Resume Enhancement Specialist",
78
+ goal="Optimize resumes to match job requirements effectively",
79
+ tools=[],
80
+ verbose=False,
81
+ backstory="Skilled at tailoring resumes to highlight relevant experience and skills for specific job applications.",
82
+ llm=llm,
83
+ )
84
+
85
+ return researcher, resume_strategist, llm
86
+
87
+ except ImportError:
88
+ raise Exception("CrewAI not installed. Please install required packages.")
89
+
90
+ def create_tasks(researcher, resume_strategist, job_url, resume_text):
91
+ """Create optimized tasks for the crew."""
92
+ from crewai import Task
93
+
94
+ # Research task - focused and efficient
95
+ research_task = Task(
96
+ description=f"""
97
+ Analyze the job posting at {job_url} and extract the top 10 most important:
98
+ 1. Required skills and technologies
99
+ 2. Key qualifications and experience levels
100
+ 3. Preferred background and certifications
101
+
102
+ Focus on the most critical requirements only.
103
+ """,
104
+ expected_output="A concise list of the top 10 most important job requirements.",
105
+ agent=researcher,
106
+ )
107
+
108
+ # Resume optimization task
109
+ resume_task = Task(
110
+ description=f"""
111
+ Using the job requirements from the research task, optimize this resume:
112
+
113
+ {resume_text}
114
+
115
+ Instructions:
116
+ 1. Rewrite the professional summary to align with the job
117
+ 2. Highlight relevant experience and skills
118
+ 3. Adjust technical skills section to match requirements
119
+ 4. Ensure ATS-friendly formatting
120
+ 5. Keep the same factual information but present it strategically
121
+
122
+ Return the complete optimized resume in markdown format.
123
+ """,
124
+ expected_output="A complete, optimized resume in markdown format tailored to the job requirements.",
125
+ agent=resume_strategist,
126
+ context=[research_task]
127
+ )
128
+
129
+ return research_task, resume_task
130
+
131
+ def process_application(pdf_file, job_url, user_session):
132
+ """Main processing function with rate limiting."""
133
+
134
+ # Rate limiting check
135
+ if not rate_limiter.is_allowed(user_session):
136
+ return "⚠️ Rate limit exceeded. Please wait 5 minutes before submitting another request.", ""
137
+
138
+ if not pdf_file or not job_url:
139
+ return "❌ Please provide both a PDF resume and job URL.", ""
140
+
141
+ try:
142
+ # Extract text from PDF
143
+ with gr.Progress() as progress:
144
+ progress(0.1, desc="Extracting text from PDF...")
145
+ resume_text = extract_text_from_pdf(pdf_file)
146
+
147
+ if "Error reading PDF" in resume_text:
148
+ return f"❌ {resume_text}", ""
149
+
150
+ progress(0.3, desc="Setting up AI agents...")
151
+ researcher, resume_strategist, llm = setup_crewai()
152
+
153
+ progress(0.5, desc="Creating optimization tasks...")
154
+ research_task, resume_task = create_tasks(researcher, resume_strategist, job_url, resume_text)
155
+
156
+ progress(0.7, desc="Analyzing job requirements...")
157
+ # Execute tasks
158
+ from crewai import Crew
159
+ crew = Crew(
160
+ agents=[researcher, resume_strategist],
161
+ tasks=[research_task, resume_task],
162
+ verbose=False
163
+ )
164
+
165
+ progress(0.9, desc="Generating tailored resume...")
166
+ result = crew.kickoff()
167
+
168
+ progress(1.0, desc="Complete!")
169
+
170
+ # Convert markdown to HTML for better display
171
+ html_result = markdown.markdown(str(result))
172
+
173
+ return "βœ… Resume successfully tailored!", html_result
174
+
175
+ except Exception as e:
176
+ return f"❌ Error processing your request: {str(e)}", ""
177
+
178
+ def create_interface():
179
+ """Create the Gradio interface."""
180
+
181
+ with gr.Blocks(
182
+ title="CV Tailor - AI Resume Optimizer",
183
+ theme=gr.themes.Soft(),
184
+ css="""
185
+ .gradio-container {
186
+ max-width: 1200px;
187
+ margin: auto;
188
+ }
189
+ .header {
190
+ text-align: center;
191
+ margin-bottom: 30px;
192
+ }
193
+ .rate-limit-info {
194
+ background-color: #f0f8ff;
195
+ padding: 10px;
196
+ border-radius: 5px;
197
+ margin-bottom: 20px;
198
+ }
199
+ """
200
+ ) as app:
201
+
202
+ gr.HTML("""
203
+ <div class="header">
204
+ <h1>🎯 CV Tailor - AI Resume Optimizer</h1>
205
+ <p>Upload your PDF resume and job URL to get an AI-tailored resume that matches the job requirements!</p>
206
+ </div>
207
+ """)
208
+
209
+ gr.HTML("""
210
+ <div class="rate-limit-info">
211
+ <strong>⚑ Rate Limit:</strong> 3 requests per 5 minutes to manage API costs.
212
+ Please be patient and make each request count!
213
+ </div>
214
+ """)
215
+
216
+ with gr.Row():
217
+ with gr.Column(scale=1):
218
+ pdf_input = gr.File(
219
+ label="πŸ“„ Upload Your Resume (PDF)",
220
+ file_types=[".pdf"],
221
+ file_count="single"
222
+ )
223
+
224
+ job_url_input = gr.Textbox(
225
+ label="πŸ”— Job Posting URL",
226
+ placeholder="https://company.com/jobs/position",
227
+ lines=1
228
+ )
229
+
230
+ submit_btn = gr.Button(
231
+ "πŸš€ Generate Tailored Resume",
232
+ variant="primary",
233
+ size="lg"
234
+ )
235
+
236
+ # Examples
237
+ gr.Examples(
238
+ examples=[
239
+ ["https://jobs.lever.co/example-company/software-engineer"],
240
+ ["https://www.linkedin.com/jobs/view/example-job-id"],
241
+ ["https://careers.google.com/jobs/results/example-position"]
242
+ ],
243
+ inputs=job_url_input,
244
+ label="πŸ“ Example Job URLs"
245
+ )
246
+
247
+ with gr.Column(scale=2):
248
+ status_output = gr.Textbox(
249
+ label="πŸ“Š Status",
250
+ interactive=False,
251
+ lines=1
252
+ )
253
+
254
+ result_output = gr.HTML(
255
+ label="πŸ“‹ Tailored Resume",
256
+ value="Your optimized resume will appear here..."
257
+ )
258
+
259
+ # Event handlers
260
+ submit_btn.click(
261
+ fn=process_application,
262
+ inputs=[pdf_input, job_url_input, gr.State(lambda: str(time.time()))],
263
+ outputs=[status_output, result_output]
264
+ )
265
+
266
+ # Footer
267
+ gr.HTML("""
268
+ <div style="text-align: center; margin-top: 50px; color: #666;">
269
+ <p>Powered by CrewAI & OpenAI GPT-4o Mini |
270
+ <a href="https://github.com/joaomdmoura/crewAI" target="_blank">CrewAI</a> |
271
+ Built with ❀️ using Gradio</p>
272
+ </div>
273
+ """)
274
+
275
+ return app
276
+
277
+ # Requirements for requirements.txt
278
+ requirements_txt = """
279
+ crewai==0.28.8
280
+ crewai-tools==0.1.6
281
+ langchain-openai==0.1.7
282
+ langchain-community==0.0.29
283
+ gradio==4.20.0
284
+ PyPDF2==3.0.1
285
+ markdown==3.5.2
286
+ python-dotenv==1.0.0
287
+ """
288
+
289
+ # Create the app
290
+ if __name__ == "__main__":
291
+ # Set up environment variables
292
+ # In Hugging Face Spaces, set these in the Settings > Variables section
293
+ if not os.getenv("OPENAI_API_KEY"):
294
+ print("��️ Warning: OPENAI_API_KEY not found in environment variables")
295
+
296
+ app = create_interface()
297
+ app.launch(
298
+ server_name="0.0.0.0",
299
+ server_port=7860,
300
+ show_api=False,
301
+ share=False
302
+ )