Suryacoder commited on
Commit
7252d8a
·
verified ·
1 Parent(s): 78196ee

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +611 -0
  2. requirements.txt +4 -0
app.py ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pdfplumber
3
+ import google.generativeai as genai
4
+ from dotenv import load_dotenv
5
+ import json
6
+ import gradio as gr
7
+
8
+ # --- NEW: "Dark Mode" Custom CSS ---
9
+ custom_css = """
10
+ /* A modern, clean "Dark Mode" theme */
11
+ body {
12
+ /* A dark gradient background */
13
+ background: linear-gradient(135deg, #1e1e1e 0%, #121212 100%);
14
+ font-family: 'Inter', 'Segoe UI', 'Roboto', sans-serif;
15
+ }
16
+
17
+ /* Style the main app container with a "dark glass" effect */
18
+ .gradio-container {
19
+ border: none !important;
20
+ border-radius: 12px !important;
21
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3) !important;
22
+ background-color: rgba(30, 30, 30, 0.85) !important;
23
+ backdrop-filter: blur(10px) !important;
24
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
25
+ }
26
+
27
+ /* Style the primary 'generate' buttons (Blue stands out well on dark) */
28
+ button[data-testid="button-primary"] {
29
+ background: linear-gradient(90deg, #3A7BD5 0%, #00D2FF 100%);
30
+ color: white;
31
+ border-radius: 8px;
32
+ font-weight: bold;
33
+ box-shadow: 0 4px 14px 0 rgba(0, 118, 255, 0.39);
34
+ border: none !important;
35
+ transition: all 0.3s ease;
36
+ }
37
+ button[data-testid="button-primary"]:hover {
38
+ box-shadow: 0 6px 20px 0 rgba(0, 118, 255, 0.5);
39
+ transform: translateY(-2px);
40
+ }
41
+
42
+ /* --- FIX FOR TAB TITLES --- */
43
+ [data-testid="tab-button"] {
44
+ color: #a0a0a0 !important; /* Light grey for unselected tabs */
45
+ font-weight: 600 !important;
46
+ }
47
+ [data-testid="tab-button"].selected {
48
+ color: #ffffff !important; /* White for selected tab */
49
+ border-bottom: 2px solid #00D2FF !important; /* Accent color for the underline */
50
+ }
51
+
52
+ /* Style ALL markdown boxes (inputs and outputs) */
53
+ [data-testid="markdown"] {
54
+ background-color: #2a2a2a !important; /* Dark grey for boxes */
55
+ border-radius: 8px !important;
56
+ border: 1px solid #444 !important;
57
+ padding: 20px !important;
58
+ box-shadow: 0 2px 4px 0 rgba(0,0,0,0.1);
59
+ }
60
+
61
+ /* Force all text within markdown components to be light */
62
+ [data-testid="markdown"] p,
63
+ [data-testid="markdown"] h1,
64
+ [data-testid="markdown"] h2,
65
+ [data-testid="markdown"] h3,
66
+ [data-testid="markdown"] li,
67
+ [data-testid="markdown"] ol,
68
+ [data-testid="markdown"] ul {
69
+ color: #e0e0e0 !important; /* Light grey text */
70
+ }
71
+
72
+ /* Style the input textboxes and file upload */
73
+ [data-testid="textbox"] textarea, .gradio-file {
74
+ background-color: #2a2a2a !important;
75
+ border-radius: 8px !important;
76
+ border: 1px solid #444 !important;
77
+ color: #e0e0e0 !important; /* Make typed text light */
78
+ }
79
+ /* Style the labels for inputs (e.g., "Paste the Job Description Here") */
80
+ .gradio-form > * > label {
81
+ color: #a0a0a0 !important;
82
+ font-weight: 500 !important;
83
+ }
84
+ """
85
+
86
+ # --- (All 6 Agents and Helper Functions) ---
87
+ # (No changes to the agent functions themselves)
88
+
89
+ def setup_api_key():
90
+ """
91
+ Loads the Google API key from the .env file and configures the SDK.
92
+ """
93
+ try:
94
+ load_dotenv() # Loads environment variables from .env
95
+ api_key = os.getenv("GOOGLE_API_KEY")
96
+
97
+ if not api_key:
98
+ print("Error: GOOGLE_API_KEY not found.")
99
+ print("Please create a .env file in the project root and add:")
100
+ print("GOOGLE_API_KEY=YOUR_API_KEY_HERE")
101
+ return False
102
+
103
+ genai.configure(api_key=api_key)
104
+ print("API Key configured successfully.")
105
+ return True
106
+
107
+ except Exception as e:
108
+ print(f"Error during API configuration: {e}")
109
+ return False
110
+
111
+ def extract_text_from_file(file_path):
112
+ """
113
+ Extracts text from an uploaded file (.pdf or .txt).
114
+ """
115
+ text = ""
116
+ try:
117
+ file_extension = os.path.splitext(file_path)[1].lower()
118
+
119
+ if file_extension == '.pdf':
120
+ print(f"Extracting text from PDF: {file_path}")
121
+ with pdfplumber.open(file_path) as pdf:
122
+ for i, page in enumerate(pdf.pages):
123
+ page_text = page.extract_text()
124
+ if page_text:
125
+ text += page_text + "\n"
126
+ print("PDF extraction complete.")
127
+
128
+ elif file_extension == '.txt':
129
+ print(f"Extracting text from TXT: {file_path}")
130
+ with open(file_path, 'r', encoding='utf-8') as f:
131
+ text = f.read()
132
+ print("TXT extraction complete.")
133
+
134
+ else:
135
+ return "Unsupported file format. Please upload a .txt or .pdf file."
136
+
137
+ if not text.strip():
138
+ return "Error: File is empty or text could not be extracted."
139
+
140
+ return text
141
+
142
+ except Exception as e:
143
+ print(f"Error reading file {file_path}: {e}")
144
+ return f"Error reading file. It may be corrupted or in an unsupported format."
145
+
146
+ def analyze_job_description(jd_text):
147
+ """
148
+ Agent 1: Analyzes the job description (JD).
149
+ """
150
+ model = genai.GenerativeModel('models/gemini-flash-latest')
151
+ prompt = f"""
152
+ You are a Senior Technical Recruiter. Analyze the following job description (JD)
153
+ and extract the most critical information.
154
+
155
+ Return your analysis as a JSON object, and NOTHING ELSE. Do not add "```json".
156
+
157
+ The JSON structure must be:
158
+ {{
159
+ "job_title": "string",
160
+ "company": "string",
161
+ "key_responsibilities": ["list", "of", "strings"],
162
+ "hard_skills_keywords": ["list", "of", "tech", "skills"],
163
+ "soft_skills_keywords": ["list", "of", "interpersonal", "skills"],
164
+ "company_tone": "e.g., 'Formal & Corporate', 'Fast-paced & Startup'"
165
+ }}
166
+
167
+ Job Description Text:
168
+ ---
169
+ {jd_text}
170
+ ---
171
+ """
172
+ try:
173
+ print("\nSending JD to AI Recruiter Agent...")
174
+ response = model.generate_content(prompt)
175
+ cleaned_response_text = response.text.strip().replace("```json\n", "").replace("\n```", "").strip()
176
+ print("AI analysis complete.")
177
+ jd_analysis = json.loads(cleaned_response_text)
178
+ return jd_analysis
179
+ except Exception as e:
180
+ print(f"Error during AI analysis (Recruiter Agent): {e}")
181
+ return None
182
+
183
+ def analyze_resume(resume_text, jd_analysis_json):
184
+ """
185
+ Agent 2: Analyzes the resume against the JD's analysis.
186
+ """
187
+ model = genai.GenerativeModel('models/gemini-flash-latest')
188
+ jd_analysis_string = json.dumps(jd_analysis_json, indent=2)
189
+ prompt = f"""
190
+ You are an expert Career Coach. You are given a user's resume and a JSON analysis
191
+ of their target job. Your task is to find all relevant experiences and identify gaps.
192
+
193
+ Return your analysis as a JSON object, and NOTHING ELSE. Do not add "```json".
194
+
195
+ The JSON structure must be:
196
+ {{
197
+ "matching_experiences": ["list of text snippets from the resume that are highly relevant"],
198
+ "quantifiable_achievements_found": ["list of bullet points from the resume that already have numbers"],
199
+ "critical_gaps": ["list of key hard skills or responsibilities from the JD that are NOT mentioned in the resume"]
200
+ }}
201
+
202
+ ---
203
+ Target Job Analysis (JSON):
204
+ {jd_analysis_string}
205
+ ---
206
+ User's Resume Text:
207
+ {resume_text}
208
+ ---
209
+ """
210
+ try:
211
+ print("\nSending Resume and JD to AI Career Coach Agent...")
212
+ response = model.generate_content(prompt)
213
+ cleaned_response_text = response.text.strip().replace("```json\n", "").replace("\n```", "").strip()
214
+ print("AI career coach analysis complete.")
215
+ resume_analysis = json.loads(cleaned_response_text)
216
+ return resume_analysis
217
+ except Exception as e:
218
+ print(f"Error during AI analysis (Career Coach Agent): {e}")
219
+ return None
220
+
221
+ def generate_tailored_resume(resume_text, jd_analysis_json, resume_analysis_json):
222
+ """
223
+ Agent 3: The "Master Rewrite Agent."
224
+ """
225
+ model = genai.GenerativeModel('models/gemini-flash-latest')
226
+ jd_analysis_string = json.dumps(jd_analysis_json, indent=2)
227
+ resume_analysis_string = json.dumps(resume_analysis_json, indent=2)
228
+ prompt = f"""
229
+ You are a world-class Professional Resume Writer. Your task is to rewrite the "Experience"
230
+ section of the resume to align *perfectly* with a target job.
231
+
232
+ **CRITICAL RULES:**
233
+ 1. **Do NOT invent new experiences.** You must only rewrite and re-phrase the
234
+ *existing* experience from the original resume.
235
+ 2. Integrate the `hard_skills_keywords` from the Job Analysis naturally into the bullet points.
236
+ 3. Rewrite weak bullet points to use the **STAR method** (Situation, Task, Action, Result)
237
+ and quantify achievements where possible.
238
+ 4. Reflect the `company_tone` from the Job Analysis in your writing style.
239
+ 5. **Output *only* the new, fully rewritten 'Experience' section** in clean
240
+ Markdown format. Do not add *any* other text, headings, or explanations.
241
+
242
+ ---
243
+ [Target Job Analysis (JSON)]
244
+ {jd_analysis_string}
245
+ ---
246
+ [Career Coach's Gap Analysis (JSON)]
247
+ {resume_analysis_string}
248
+ ---
249
+ [Original Resume Text]
250
+ {resume_text}
251
+ ---
252
+
253
+ Provide the rewritten "Experience" section in Markdown:
254
+ """
255
+ try:
256
+ print("\nSending all data to Master Rewrite Agent...")
257
+ response = model.generate_content(prompt)
258
+ print("Master rewrite complete.")
259
+ return response.text.strip()
260
+ except Exception as e:
261
+ print(f"Error during AI analysis (Rewrite Agent): {e}")
262
+ return None
263
+
264
+ def generate_ats_report(tailored_resume_text, jd_analysis_json):
265
+ """
266
+ Agent 4: The "ATS Scorecard Agent."
267
+ """
268
+ model = genai.GenerativeModel('models/gemini-flash-latest')
269
+ jd_analysis_string = json.dumps(jd_analysis_json, indent=2)
270
+
271
+ prompt = f"""
272
+ You are an ATS (Applicant Tracking System) scanner. Your task is to generate a
273
+ 'Tailor Report' in Markdown format that scores the "New Resume Section" against
274
+ the "Target Job Analysis."
275
+
276
+ **Output Format (Must be Markdown):**
277
+
278
+ ### Your 'Tailor' Report
279
+
280
+ **ATS Match Score:** 85%
281
+
282
+ **Keywords Hit:**
283
+ - Python
284
+ - Django
285
+
286
+ **Keywords Missing:**
287
+ - Flask
288
+ - Data Analytics
289
+
290
+ **Suggestion:** Great job! Consider adding a project or skill that mentions 'Flask'.
291
+
292
+ ---
293
+ [Target Job Analysis (JSON)]
294
+ {jd_analysis_string}
295
+ ---
296
+ [New Resume Section]
297
+ {tailored_resume_text}
298
+ ---
299
+
300
+ Now, generate the 'Tailor Report' in Markdown, and NOTHING ELSE.
301
+ """
302
+
303
+ try:
304
+ print("\nSending data to ATS Scorecard Agent...")
305
+ response = model.generate_content(prompt)
306
+ print("ATS report complete.")
307
+ return response.text.strip()
308
+ except Exception as e:
309
+ print(f"Error during AI analysis (ATS Agent): {e}")
310
+ return None
311
+
312
+ def generate_cover_letter(resume_text, jd_analysis_json, resume_analysis_json):
313
+ """
314
+ Agent 5: The "Cover Letter Agent."
315
+ """
316
+
317
+ model = genai.GenerativeModel('models/gemini-flash-latest')
318
+
319
+ jd_analysis_string = json.dumps(jd_analysis_json, indent=2)
320
+ resume_analysis_string = json.dumps(resume_analysis_json, indent=2)
321
+
322
+ prompt = f"""
323
+ You are a world-class Professional Resume Writer and Career Coach.
324
+ Your task is to write a compelling, professional cover letter for a job applicant
325
+ based on their resume and a target job description.
326
+
327
+ **CRITICAL RULES:**
328
+ 1. **Tone:** The tone must be professional, confident, and aligned with the `company_tone`
329
+ from the Job Analysis.
330
+ 2. **Structure:** Follow a standard cover letter format (Salutation, Introduction, Body, Conclusion, Sign-off).
331
+ 3. **Body Paragraphs:**
332
+ * In the first body paragraph, highlight the applicant's skills that
333
+ match the `hard_skills_keywords` and `key_responsibilities`.
334
+ * In the second body paragraph, use the `matching_experiences` to provide a
335
+ specific example or story that proves their qualification.
336
+ * Address any `critical_gaps` by framing them positively, e.g., "While my
337
+ direct experience with 'Flask' is developing, my proven ability to master
338
+ 'Django' and other Python frameworks demonstrates my capacity to learn quickly..."
339
+ 4. **Do NOT invent new experiences.** You must only use information from the
340
+ "Original Resume Text" and the analyses.
341
+ 5. **Output *only* the cover letter** in clean Markdown format. Do not add
342
+ any other text, headings, or explanations.
343
+
344
+ ---
345
+ [Target Job Analysis (JSON)]
346
+ {jd_analysis_string}
347
+ ---
348
+ [Career Coach's Gap Analysis (JSON)]
349
+ {resume_analysis_string}
350
+ ---
351
+ [Original Resume Text]
352
+ {resume_text}
353
+ ---
354
+
355
+ Now, provide the complete, professional cover letter in Markdown:
356
+ """
357
+
358
+ try:
359
+ print("\nSending all data to Cover Letter Agent...")
360
+ response = model.generate_content(prompt)
361
+ print("Cover letter generation complete.")
362
+ return response.text.strip()
363
+
364
+ except Exception as e:
365
+ print(f"Error during AI analysis (Cover Letter Agent): {e}")
366
+ return None
367
+
368
+ def generate_interview_prep(jd_analysis_json, resume_analysis_json):
369
+ """
370
+ Agent 6: The "Hiring Manager Agent."
371
+ Generates custom interview questions based on the job and the candidate's gaps.
372
+ """
373
+
374
+ model = genai.GenerativeModel('models/gemini-flash-latest')
375
+
376
+ jd_analysis_string = json.dumps(jd_analysis_json, indent=2)
377
+ resume_analysis_string = json.dumps(resume_analysis_json, indent=2)
378
+
379
+ prompt = f"""
380
+ You are a senior Hiring Manager preparing to interview a candidate. You are given
381
+ an analysis of the job and an analysis of the candidate's resume.
382
+
383
+ Your task is to generate a custom "Interview Prep Sheet" in Markdown.
384
+
385
+ **Instructions:**
386
+ 1. Create 2-3 **Behavioral Questions** based on the `soft_skills_keywords`.
387
+ 2. Create 2-3 **Technical Questions** based on the `hard_skills_keywords`.
388
+ 3. Create 1-2 **Gap-Based Questions** based on the `critical_gaps`. These are the
389
+ most important questions to ask. (e.g., "I see you have experience in X, but
390
+ this role requires Y. Can you tell me how you'd bridge that gap?")
391
+ 4. For *each* question, provide a brief **"Hint for a strong answer"** that tells
392
+ the user what the interviewer is *really* looking for.
393
+
394
+ **Output *only* the prep sheet** in clean Markdown.
395
+
396
+ ---
397
+ [Target Job Analysis (JSON)]
398
+ {jd_analysis_string}
399
+ ---
400
+ [Career Coach's Gap Analysis (JSON)]
401
+ {resume_analysis_string}
402
+ ---
403
+
404
+ Now, provide the complete "Interview Prep Sheet" in Markdown:
405
+ """
406
+
407
+ try:
408
+ print("\nSending all data to Hiring Manager Agent...")
409
+ response = model.generate_content(prompt)
410
+ print("Interview prep generation complete.")
411
+ return response.text.strip()
412
+
413
+ except Exception as e:
414
+ print(f"Error during AI analysis (Hiring Manager Agent): {e}")
415
+ return None
416
+
417
+
418
+ # --- (Pipeline 1: Resume Tailor) ---
419
+ def tailor_resume_pipeline(resume_file, job_description):
420
+ """
421
+ Main controller for the "Resume Tailor" tab.
422
+ Runs the full 4-agent pipeline (Agents 1, 2, 3, 4).
423
+ """
424
+ if not setup_api_key():
425
+ return "Error: API Key is not configured. Please check your .env file.", ""
426
+
427
+ print("--- RESUME TAILOR PIPELINE INITIATED ---")
428
+ print("Step 1: Extracting text...")
429
+ if resume_file is None:
430
+ return "Error: Please upload a resume file.", ""
431
+ resume_text = extract_text_from_file(resume_file.name)
432
+ if "Error" in resume_text or not resume_text:
433
+ return f"Could not process the resume file. Error: {resume_text}", ""
434
+
435
+ if not job_description:
436
+ return "Error: Please paste the job description.", ""
437
+
438
+ print("Step 2: Analyzing job description...")
439
+ jd_data = analyze_job_description(job_description)
440
+ if not jd_data:
441
+ return "The AI could not analyze the job description. Please try again.", ""
442
+
443
+ print("Step 3: Analyzing resume...")
444
+ resume_data = analyze_resume(resume_text, jd_data)
445
+ if not resume_data:
446
+ return "The AI could not analyze the resume. Please try again.", ""
447
+
448
+ print("Step 4: Generating tailored resume...")
449
+ tailored_resume = generate_tailored_resume(resume_text, jd_data, resume_data)
450
+ if not tailored_resume:
451
+ return "The AI failed to generate the final resume. Please try again.", ""
452
+
453
+ print("Step 5: Generating ATS report...")
454
+ ats_report = generate_ats_report(tailored_resume, jd_data)
455
+ if not ats_report:
456
+ ats_report = "Error: Could not generate ATS report."
457
+
458
+ print("Resume pipeline complete! Returning 2 outputs.")
459
+ return tailored_resume, ats_report
460
+
461
+
462
+ # --- (Pipeline 2: Cover Letter) ---
463
+ def generate_cover_letter_pipeline(resume_file, job_description):
464
+ """
465
+ Main controller for the "Cover Letter" tab.
466
+ Runs a 3-agent pipeline (Agents 1, 2, 5).
467
+ """
468
+ if not setup_api_key():
469
+ return "Error: API Key is not configured. Please check your .env file."
470
+
471
+ print("--- COVER LETTER PIPELINE INITIATED ---")
472
+ print("Step 1: Extracting text...")
473
+ if resume_file is None:
474
+ return "Error: Please upload a resume file."
475
+ resume_text = extract_text_from_file(resume_file.name)
476
+ if "Error" in resume_text or not resume_text:
477
+ return f"Could not process the resume file. Error: {resume_text}"
478
+
479
+ if not job_description:
480
+ return "Error: Please paste the job description."
481
+
482
+ print("Step 2: Analyzing job description...")
483
+ jd_data = analyze_job_description(job_description)
484
+ if not jd_data:
485
+ return "The AI could not analyze the job description. Please try again."
486
+
487
+ print("Step 3: Analyzing resume...")
488
+ resume_data = analyze_resume(resume_text, jd_data)
489
+ if not resume_data:
490
+ return "The AI could not analyze the resume. Please try again."
491
+
492
+ print("Step 4: Generating cover letter...")
493
+ cover_letter = generate_cover_letter(resume_text, jd_data, resume_data)
494
+ if not cover_letter:
495
+ return "The AI failed to generate the cover letter. Please try again."
496
+
497
+ print("Cover letter pipeline complete! Returning 1 output.")
498
+ return cover_letter
499
+
500
+
501
+ # --- (Pipeline 3: Interview Prep) ---
502
+ def generate_interview_prep_pipeline(resume_file, job_description):
503
+ """
504
+ Main controller for the "Interview Prep" tab.
505
+ Runs a 3-agent pipeline (Agents 1, 2, 6).
506
+ """
507
+ if not setup_api_key():
508
+ return "Error: API Key is not configured. Please check your .env file."
509
+
510
+ print("--- INTERVIEW PREP PIPELINE INITIATED ---")
511
+ print("Step 1: Extracting text...")
512
+ if resume_file is None:
513
+ return "Error: Please upload a resume file."
514
+ resume_text = extract_text_from_file(resume_file.name)
515
+ if "Error" in resume_text or not resume_text:
516
+ return f"Could not process the resume file. Error: {resume_text}"
517
+
518
+ if not job_description:
519
+ return "Error: Please paste the job description."
520
+
521
+ print("Step 2: Analyzing job description...")
522
+ jd_data = analyze_job_description(job_description)
523
+ if not jd_data:
524
+ return "The AI could not analyze the job description. Please try again."
525
+
526
+ print("Step 3:Analyzing resume...")
527
+ resume_data = analyze_resume(resume_text, jd_data)
528
+ if not resume_data:
529
+ return "The AI could not analyze the resume. Please. Please try again."
530
+
531
+ print("Step 4: Generating interview prep...")
532
+ interview_prep = generate_interview_prep(jd_data, resume_data) # Note: Doesn't need resume_text
533
+ if not interview_prep:
534
+ return "The AI failed to generate the interview prep. Please try again."
535
+
536
+ print("Interview prep pipeline complete! Returning 1 output.")
537
+ return interview_prep
538
+
539
+
540
+ # --- FINAL Gradio Web Interface (with 3 Tabs + Custom UI) ---
541
+ # We use gr.themes.Base() as our starting point and apply our CSS
542
+ with gr.Blocks(theme=gr.themes.Base(), css=custom_css) as demo:
543
+ gr.Markdown(
544
+ """
545
+ <div style="text-align: center; padding: 20px 0;">
546
+ <h1 style="font-size: 2.5em; font-weight: 700; color: #ffffff;">
547
+ 🤖 AI-Powered Job Application Suite
548
+ </h1>
549
+ <p style="font-size: 1.2em; color: #a0a0a0;">
550
+ From Resume to Interview, get the AI-powered edge you need.
551
+ </p>
552
+ </div>
553
+ """
554
+ )
555
+
556
+ with gr.Tabs():
557
+ # --- Tab 1: Resume Tailor ---
558
+ with gr.TabItem("1. Resume Tailor"):
559
+ gr.Markdown("Upload your resume and paste a job description to get a tailored 'Experience' section and an ATS report.")
560
+ with gr.Row():
561
+ with gr.Column(scale=1):
562
+ resume_file_input_1 = gr.File(label="Upload Your Master Resume (.pdf or .txt)")
563
+ job_description_input_1 = gr.Textbox(lines=15, label="Paste the Job Description Here")
564
+ submit_button_1 = gr.Button("Tailor My Resume!", variant="primary")
565
+ with gr.Column(scale=2):
566
+ tailored_resume_output = gr.Markdown(label="Your New, Tailored Resume Section")
567
+ ats_report_output = gr.Markdown(label="Your 'Tailor' Report")
568
+
569
+ submit_button_1.click(
570
+ fn=tailor_resume_pipeline,
571
+ inputs=[resume_file_input_1, job_description_input_1],
572
+ outputs=[tailored_resume_output, ats_report_output]
573
+ )
574
+
575
+ # --- Tab 2: Cover Letter Generator ---
576
+ with gr.TabItem("2. Cover Letter Generator"):
577
+ gr.Markdown("Upload your resume and paste a job description to generate a custom cover letter in seconds.")
578
+ with gr.Row():
579
+ with gr.Column(scale=1):
580
+ resume_file_input_2 = gr.File(label="Upload Your Master Resume (.pdf or .txt)")
581
+ job_description_input_2 = gr.Textbox(lines=15, label="Paste the Job Description Here")
582
+ submit_button_2 = gr.Button("Generate My Cover Letter!", variant="primary")
583
+ with gr.Column(scale=2):
584
+ cover_letter_output = gr.Markdown(label="Your New, Generated Cover Letter")
585
+
586
+ submit_button_2.click(
587
+ fn=generate_cover_letter_pipeline,
588
+ inputs=[resume_file_input_2, job_description_input_2],
589
+ outputs=[cover_letter_output]
590
+ )
591
+
592
+ # --- Tab 3: Interview Prep ---
593
+ with gr.TabItem("3. Interview Prep"):
594
+ gr.Markdown("Upload your resume and paste a job description to get a custom list of interview questions and answer hints.")
595
+ with gr.Row():
596
+ with gr.Column(scale=1):
597
+ resume_file_input_3 = gr.File(label="Upload Your Master Resume (.pdf or .txt)")
598
+ job_description_input_3 = gr.Textbox(lines=15, label="Paste the Job Description Here")
599
+ submit_button_3 = gr.Button("Generate My Prep Sheet!", variant="primary")
600
+ with gr.Column(scale=2):
601
+ interview_prep_output = gr.Markdown(label="Your Custom Interview Prep Sheet")
602
+
603
+ submit_button_3.click(
604
+ fn=generate_interview_prep_pipeline,
605
+ inputs=[resume_file_input_3, job_description_input_3],
606
+ outputs=[interview_prep_output]
607
+ )
608
+
609
+ print("Launching the AI Job Application Suite (v6.0 DARK MODE)...")
610
+ # Added share=True so you can easily open it on your phone or share it
611
+ demo.launch(share=True)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ google-generativeai
2
+ pdfplumber
3
+ gradio
4
+ python-dotenv