noumanjavaid commited on
Commit
a18a8e4
·
verified ·
1 Parent(s): d981bc9

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +740 -0
app.py ADDED
@@ -0,0 +1,740 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ from datetime import datetime
4
+ import re
5
+ import subprocess
6
+ import sys
7
+
8
+ # Default resume data
9
+ default_resume = {
10
+ "fullName": "Alexander Johnson",
11
+ "title": "Senior Frontend Developer",
12
+ "email": "[email protected]",
13
+ "phone": "(555) 123-4567",
14
+ "location": "San Francisco, CA",
15
+ "summary": "Experienced frontend developer with 6+ years specializing in React and modern JavaScript frameworks. Passionate about creating intuitive user interfaces and optimizing web performance.",
16
+ "experience": [
17
+ {
18
+ "id": 1,
19
+ "company": "Tech Innovations Inc.",
20
+ "position": "Senior Frontend Developer",
21
+ "duration": "2019 - Present",
22
+ "description": "Lead frontend development for enterprise SaaS platform. Improved performance by 40% through code optimization. Mentored junior developers."
23
+ },
24
+ {
25
+ "id": 2,
26
+ "company": "WebSolutions Co.",
27
+ "position": "Frontend Developer",
28
+ "duration": "2017 - 2019",
29
+ "description": "Developed responsive web applications using React. Collaborated with design team to implement UI/UX improvements."
30
+ }
31
+ ],
32
+ "education": [
33
+ {
34
+ "id": 1,
35
+ "institution": "University of California, Berkeley",
36
+ "degree": "B.S. Computer Science",
37
+ "duration": "2013 - 2017"
38
+ }
39
+ ],
40
+ "skills": ["React", "JavaScript", "TypeScript", "HTML/CSS", "Redux", "Next.js", "Tailwind CSS", "UI/UX Design"]
41
+ }
42
+
43
+ # Global state
44
+ state = {
45
+ "resume_data": default_resume.copy(),
46
+ "dark_mode": False,
47
+ "show_preview": True,
48
+ "gemini_available": False,
49
+ "api_key": ""
50
+ }
51
+
52
+ # Try to import Gemini
53
+ try:
54
+ import google.generativeai as genai
55
+ state["gemini_available"] = True
56
+ except ImportError:
57
+ state["gemini_available"] = False
58
+
59
+ def initialize_gemini_api(api_key):
60
+ if not state["gemini_available"]:
61
+ return False, "Google Generative AI library not installed. Install with: `pip install google-generativeai`"
62
+
63
+ try:
64
+ if api_key:
65
+ genai.configure(api_key=api_key)
66
+ model = genai.GenerativeModel(model_name="gemini-1.5-pro")
67
+ _ = model.generate_content("Hello")
68
+ return True, "API key saved and verified!"
69
+ return False, "Please enter an API key."
70
+ except Exception as e:
71
+ return False, f"Failed to initialize Gemini API: {str(e)}"
72
+
73
+ def analyze_job_description(job_desc):
74
+ if not job_desc:
75
+ return "Please enter a job description to analyze."
76
+
77
+ if not state.get("gemini_available"):
78
+ return "Please set up your Gemini API key first."
79
+
80
+ prompt = f"""Analyze this job description and provide a detailed response with the following sections:
81
+ 1. Key Skills Required
82
+ 2. Experience Level
83
+ 3. Match Analysis with current resume
84
+ 4. Suggestions for Improvement
85
+
86
+ Job Description:
87
+ {job_desc}
88
+
89
+ Current Resume Skills:
90
+ {', '.join(state['resume_data']['skills'])}
91
+ """
92
+
93
+ response, error = get_gemini_response(prompt)
94
+ if error:
95
+ return f"Error analyzing job description: {error}"
96
+
97
+ return response
98
+
99
+
100
+ def get_gemini_response(prompt, temperature=0.7):
101
+ if not state["gemini_available"]:
102
+ return None, "Gemini API not available. Please set up your API key."
103
+
104
+ try:
105
+ generation_config = {
106
+ "temperature": temperature,
107
+ "top_p": 0.95,
108
+ "top_k": 64,
109
+ "max_output_tokens": 8192,
110
+ }
111
+
112
+ model = genai.GenerativeModel(
113
+ model_name="gemini-1.5-pro",
114
+ generation_config=generation_config,
115
+ )
116
+
117
+ response = model.generate_content(prompt)
118
+ return response.parts[0].text, None
119
+ except Exception as e:
120
+ return None, f"Error getting AI response: {str(e)}"
121
+
122
+ # Custom CSS for styling
123
+ custom_css = """
124
+ :root {
125
+ --primary-color: #4F46E5;
126
+ --bg-color-light: #F9FAFB;
127
+ --text-color-light: #111827;
128
+ --card-bg-light: #FFFFFF;
129
+ --input-bg-light: #F3F4F6;
130
+ --secondary-bg-light: #EEF2FF;
131
+ --accent-light-light: #C7D2FE;
132
+
133
+ --bg-color-dark: #111827;
134
+ --text-color-dark: #F9FAFB;
135
+ --card-bg-dark: #1F2937;
136
+ --input-bg-dark: #374151;
137
+ --secondary-bg-dark: #1E3A8A;
138
+ --accent-light-dark: #818CF8;
139
+ }
140
+
141
+ /* Light mode (default) */
142
+ body {
143
+ background-color: var(--bg-color-light);
144
+ color: var(--text-color-light);
145
+ transition: background-color 0.3s, color 0.3s;
146
+ }
147
+
148
+ /* Dark mode */
149
+ body.dark-mode {
150
+ background-color: var(--bg-color-dark);
151
+ color: var(--text-color-dark);
152
+ }
153
+
154
+ /* Resume preview styling */
155
+ .resume-preview {
156
+ background: white;
157
+ padding: 2rem;
158
+ border-radius: 8px;
159
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
160
+ color: #333;
161
+ font-family: 'Georgia', serif;
162
+ line-height: 1.4;
163
+ }
164
+
165
+ .skill-tag {
166
+ background: #f0f0f0;
167
+ padding: 0.25rem 0.5rem;
168
+ border-radius: 4px;
169
+ margin: 0.25rem;
170
+ display: inline-block;
171
+ }
172
+
173
+ /* Form styling */
174
+ .form-container {
175
+ padding: 1rem;
176
+ border-radius: 8px;
177
+ background-color: var(--card-bg-light);
178
+ }
179
+
180
+ .dark-mode .form-container {
181
+ background-color: var(--card-bg-dark);
182
+ }
183
+
184
+ /* Button styling */
185
+ .primary-button {
186
+ background-color: var(--primary-color);
187
+ color: white;
188
+ border: none;
189
+ padding: 0.5rem 1rem;
190
+ border-radius: 0.25rem;
191
+ cursor: pointer;
192
+ transition: background-color 0.3s;
193
+ }
194
+
195
+ .primary-button:hover {
196
+ background-color: #4338CA;
197
+ }
198
+
199
+ /* Input styling */
200
+ input, textarea, select {
201
+ background-color: var(--input-bg-light);
202
+ border: 1px solid #E5E7EB;
203
+ border-radius: 0.375rem;
204
+ padding: 0.5rem;
205
+ width: 100%;
206
+ color: var(--text-color-light);
207
+ }
208
+
209
+ .dark-mode input, .dark-mode textarea, .dark-mode select {
210
+ background-color: var(--input-bg-dark);
211
+ border-color: #4B5563;
212
+ color: var(--text-color-dark);
213
+ }
214
+
215
+ /* Section styling */
216
+ .section-title {
217
+ font-size: 1.1rem;
218
+ text-transform: uppercase;
219
+ letter-spacing: 1px;
220
+ color: #333;
221
+ margin-top: 1.5rem;
222
+ margin-bottom: 0.75rem;
223
+ font-weight: normal;
224
+ border-bottom: 1px solid #eee;
225
+ padding-bottom: 0.25rem;
226
+ }
227
+
228
+ .dark-mode .section-title {
229
+ color: var(--text-color-dark);
230
+ border-bottom-color: #4B5563;
231
+ }
232
+
233
+ /* Tab styling */
234
+ .tab-selected {
235
+ border-bottom: 2px solid var(--primary-color);
236
+ font-weight: bold;
237
+ }
238
+
239
+ /* Accordion styling */
240
+ .accordion {
241
+ border: 1px solid #E5E7EB;
242
+ border-radius: 0.375rem;
243
+ margin-bottom: 0.5rem;
244
+ overflow: hidden;
245
+ }
246
+
247
+ .dark-mode .accordion {
248
+ border-color: #4B5563;
249
+ }
250
+
251
+ .accordion-header {
252
+ background-color: var(--secondary-bg-light);
253
+ padding: 0.75rem 1rem;
254
+ cursor: pointer;
255
+ font-weight: 500;
256
+ }
257
+
258
+ .dark-mode .accordion-header {
259
+ background-color: var(--secondary-bg-dark);
260
+ }
261
+
262
+ .accordion-content {
263
+ padding: 1rem;
264
+ border-top: 1px solid #E5E7EB;
265
+ }
266
+
267
+ .dark-mode .accordion-content {
268
+ border-color: #4B5563;
269
+ }
270
+
271
+ /* AI feature styling */
272
+ .ai-container {
273
+ background-color: var(--secondary-bg-light);
274
+ border-radius: 0.5rem;
275
+ padding: 1rem;
276
+ margin-bottom: 1rem;
277
+ border-left: 4px solid var(--primary-color);
278
+ }
279
+
280
+ .dark-mode .ai-container {
281
+ background-color: var(--secondary-bg-dark);
282
+ }
283
+
284
+ .ai-suggestion {
285
+ background-color: var(--accent-light-light);
286
+ border-radius: 0.375rem;
287
+ padding: 0.75rem;
288
+ margin-top: 0.5rem;
289
+ font-size: 0.875rem;
290
+ border-left: 3px solid var(--primary-color);
291
+ }
292
+
293
+ .dark-mode .ai-suggestion {
294
+ background-color: var(--accent-light-dark);
295
+ }
296
+ """
297
+
298
+ # Helper functions for UI components
299
+ def toggle_dark_mode():
300
+ state["dark_mode"] = not state["dark_mode"]
301
+ js_code = "<script>document.body.classList.toggle('dark-mode');</script>"
302
+ return gr.HTML(js_code)
303
+
304
+ def toggle_preview():
305
+ state["show_preview"] = not state["show_preview"]
306
+ preview = gr.HTML(render_resume_preview()) if state["show_preview"] else gr.HTML("")
307
+ return preview
308
+
309
+ def download_json():
310
+ json_str = json.dumps(state["resume_data"], indent=2)
311
+ file_path = "resume_data.json"
312
+ with open(file_path, "w") as f:
313
+ f.write(json_str)
314
+ return file_path
315
+
316
+ # Resume editor functions
317
+ def update_personal_info(fullName, title, email, phone, location):
318
+ state["resume_data"]["fullName"] = fullName
319
+ state["resume_data"]["title"] = title
320
+ state["resume_data"]["email"] = email
321
+ state["resume_data"]["phone"] = phone
322
+ state["resume_data"]["location"] = location
323
+ return render_resume_preview()
324
+
325
+ def update_summary(summary):
326
+ state["resume_data"]["summary"] = summary
327
+ return render_resume_preview()
328
+
329
+ def add_experience():
330
+ new_id = max([exp["id"] for exp in state["resume_data"]["experience"]], default=0) + 1
331
+ state["resume_data"]["experience"].append({
332
+ "id": new_id,
333
+ "company": "",
334
+ "position": "",
335
+ "duration": "",
336
+ "description": ""
337
+ })
338
+ return render_resume_preview()
339
+
340
+ def remove_experience(exp_id):
341
+ state["resume_data"]["experience"] = [exp for exp in state["resume_data"]["experience"] if exp["id"] != exp_id]
342
+ return render_resume_preview()
343
+
344
+ def update_experience(exp_id, company, position, duration, description):
345
+ for i, exp in enumerate(state["resume_data"]["experience"]):
346
+ if exp["id"] == exp_id:
347
+ state["resume_data"]["experience"][i]["company"] = company
348
+ state["resume_data"]["experience"][i]["position"] = position
349
+ state["resume_data"]["experience"][i]["duration"] = duration
350
+ state["resume_data"]["experience"][i]["description"] = description
351
+ break
352
+ return render_resume_preview()
353
+
354
+ def add_education():
355
+ new_id = max([edu["id"] for edu in state["resume_data"]["education"]], default=0) + 1
356
+ state["resume_data"]["education"].append({
357
+ "id": new_id,
358
+ "institution": "",
359
+ "degree": "",
360
+ "duration": ""
361
+ })
362
+ # Return updated education list and preview
363
+ return render_resume_preview()
364
+
365
+ def remove_education(edu_id):
366
+ state["resume_data"]["education"] = [edu for edu in state["resume_data"]["education"] if edu["id"] != edu_id]
367
+ return render_resume_preview()
368
+
369
+ def update_education(edu_id, institution, degree, duration):
370
+ for i, edu in enumerate(state["resume_data"]["education"]):
371
+ if edu["id"] == edu_id:
372
+ state["resume_data"]["education"][i]["institution"] = institution
373
+ state["resume_data"]["education"][i]["degree"] = degree
374
+ state["resume_data"]["education"][i]["duration"] = duration
375
+ break
376
+ return render_resume_preview()
377
+
378
+ def add_skill(new_skill):
379
+ if new_skill and new_skill not in state["resume_data"]["skills"]:
380
+ state["resume_data"]["skills"].append(new_skill)
381
+ return "", state["resume_data"]["skills"], render_resume_preview()
382
+
383
+ def update_skills(skills):
384
+ state["resume_data"]["skills"] = skills
385
+ return render_resume_preview()
386
+
387
+ def manage_api_key():
388
+ with gr.Blocks() as api_settings:
389
+ gr.Markdown("### 🔑 Gemini API Setup")
390
+ gr.Markdown("To use AI features, enter your Google Gemini API key:")
391
+
392
+ api_key = gr.Textbox(
393
+ label="API Key",
394
+ type="password",
395
+ value=state.get("api_key", "")
396
+ )
397
+
398
+ save_btn = gr.Button("Save API Key")
399
+ status_output = gr.Markdown()
400
+
401
+ def save_api_key(key):
402
+ if not key:
403
+ return "⚠️ Please enter an API key."
404
+
405
+ success, message = initialize_gemini_api(key)
406
+ if success:
407
+ state["api_key"] = key
408
+ state["gemini_available"] = True
409
+ return "✅ API key saved and verified!"
410
+ else:
411
+ return f"❌ {message}"
412
+
413
+ save_btn.click(
414
+ fn=save_api_key,
415
+ inputs=[api_key],
416
+ outputs=[status_output]
417
+ )
418
+
419
+ return api_key, status_output
420
+
421
+ def job_match_tab():
422
+ with gr.Blocks() as job_match_container:
423
+ gr.Markdown("### Resume-Job Matching")
424
+
425
+ if not state.get("gemini_available"):
426
+ with gr.Blocks():
427
+ gr.Markdown("⚠️ API Setup Required")
428
+ api_key = gr.Textbox(
429
+ label="Enter Gemini API Key",
430
+ type="password",
431
+ placeholder="Enter your API key here..."
432
+ )
433
+ status_output = gr.Markdown()
434
+ save_btn = gr.Button("Save API Key", variant="primary")
435
+
436
+ def save_api_key(key):
437
+ if not key:
438
+ return "⚠️ Please enter an API key."
439
+ success, message = initialize_gemini_api(key)
440
+ if success:
441
+ state["gemini_available"] = True
442
+ state["api_key"] = key
443
+ return "✅ API key saved and verified!"
444
+ return f"❌ {message}"
445
+
446
+ save_btn.click(fn=save_api_key, inputs=[api_key], outputs=[status_output])
447
+
448
+ with gr.Blocks():
449
+ job_desc = gr.Textbox(
450
+ label="Paste job description here",
451
+ lines=8,
452
+ placeholder="Paste the job description you want to analyze..."
453
+ )
454
+ analyze_btn = gr.Button("Analyze Job Description", variant="primary")
455
+ result = gr.Markdown()
456
+
457
+ def analyze_wrapper(desc):
458
+ if not state.get("gemini_available"):
459
+ return "⚠️ Please set up your Gemini API key first."
460
+ if not desc:
461
+ return "⚠️ Please enter a job description to analyze."
462
+ return analyze_job_description(desc)
463
+
464
+ analyze_btn.click(
465
+ fn=analyze_wrapper,
466
+ inputs=[job_desc],
467
+ outputs=[result]
468
+ )
469
+
470
+ return job_match_container
471
+
472
+
473
+ def analyze_job_description(job_desc):
474
+ if not job_desc:
475
+ return "Please enter a job description to analyze."
476
+
477
+ prompt = f"""
478
+ Analyze how well the following resume matches this job description. Consider:
479
+ 1. Skills match
480
+ 2. Experience relevance
481
+ 3. Education requirements
482
+ 4. Key qualifications
483
+
484
+ Job Description:
485
+ {job_desc}
486
+
487
+ Resume:
488
+ {json.dumps(state['resume_data'], indent=2)}
489
+
490
+ Provide a detailed analysis with:
491
+ - Match percentage
492
+ - Key matching qualifications
493
+ - Missing requirements
494
+ - Suggestions for improvement
495
+ """
496
+
497
+ response, error = get_gemini_response(prompt)
498
+ if error:
499
+ return f"Error analyzing match: {error}"
500
+ return response
501
+
502
+ with gr.Blocks():
503
+ gr.Markdown("### Cover Letter Generator")
504
+ job_desc_cover = gr.Textbox(
505
+ label="Paste job description here",
506
+ lines=8
507
+ )
508
+ generate_btn = gr.Button("Generate Cover Letter")
509
+ cover_letter_output = gr.Markdown()
510
+
511
+ def generate_cover_letter(desc):
512
+ if not desc:
513
+ return "Please enter a job description."
514
+
515
+ prompt = f"""
516
+ Generate a professional cover letter for this job based on my resume:
517
+
518
+ Job Description:
519
+ {desc}
520
+
521
+ Resume Details:
522
+ {json.dumps(state['resume_data'], indent=2)}
523
+
524
+ Make it personal, professional, and highlight relevant experience and skills.
525
+ """
526
+
527
+ response, error = get_gemini_response(prompt)
528
+ if error:
529
+ return error
530
+ return response
531
+
532
+ generate_btn.click(
533
+ fn=generate_cover_letter,
534
+ inputs=[job_desc_cover],
535
+ outputs=[cover_letter_output]
536
+ )
537
+ def generate_ai_summary():
538
+ data = state["resume_data"]
539
+ prompt = f"""
540
+ You are an expert resume writer. Generate a compelling professional summary for a resume with these details:
541
+ Name: {data['fullName']}
542
+ Current Position: {data['title']}
543
+ Skills: {', '.join(data['skills'])}
544
+ Experience:
545
+ {' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
546
+ Education:
547
+ {' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
548
+ Rules for writing the summary:
549
+ 1. Keep it concise (3-4 sentences maximum)
550
+ 2. Highlight key skills and accomplishments
551
+ 3. Focus on quantifiable achievements and impact
552
+ 4. Use active voice and strong action verbs
553
+ 5. Tailor it to the candidate's career level and industry
554
+ """
555
+ response, error = get_gemini_response(prompt)
556
+ if error:
557
+ return error, error
558
+
559
+ # Update the resume data with the new summary
560
+ state["resume_data"]["summary"] = response
561
+
562
+ # Generate updated preview HTML
563
+ preview_html = render_resume_preview()
564
+
565
+ return response, preview_html
566
+ def render_resume_preview():
567
+ data = state["resume_data"]
568
+ preview_html = f"""
569
+ <div class="resume-preview">
570
+ <h1>{data['fullName']}</h1>
571
+ <h2>{data['title']}</h2>
572
+ <p>{data['email']} | {data['phone']} | {data['location']}</p>
573
+
574
+ <div class="section">
575
+ <h3>Professional Summary</h3>
576
+ <p>{data['summary']}</p>
577
+ </div>
578
+
579
+ <div class="section">
580
+ <h3>Experience</h3>
581
+ {''.join([f'''
582
+ <div class="experience-item">
583
+ <h4>{exp['company']} - {exp['position']}</h4>
584
+ <p>{exp['duration']}</p>
585
+ <p>{exp['description']}</p>
586
+ </div>
587
+ ''' for exp in data['experience']])}
588
+ </div>
589
+
590
+ <div class="section">
591
+ <h3>Education</h3>
592
+ {''.join([f'''
593
+ <div class="education-item">
594
+ <h4>{edu['institution']}</h4>
595
+ <p>{edu['degree']} ({edu['duration']})</p>
596
+ </div>
597
+ ''' for edu in data['education']])}
598
+ </div>
599
+
600
+ <div class="section">
601
+ <h3>Skills</h3>
602
+ <div class="skills-container">
603
+ {''.join([f'<span class="skill-tag">{skill}</span>' for skill in data['skills']])}
604
+ </div>
605
+ </div>
606
+ </div>
607
+ """
608
+ return preview_html
609
+ def main():
610
+ with gr.Blocks(css=custom_css) as app:
611
+ gr.Markdown("# Resume Builder")
612
+
613
+ with gr.Row():
614
+ dark_mode_btn = gr.Button("Toggle Dark Mode")
615
+ preview_btn = gr.Button("Toggle Preview")
616
+ download_btn = gr.Button("Download Resume Data")
617
+
618
+ preview_component = gr.HTML(render_resume_preview() if state["show_preview"] else "")
619
+
620
+ dark_mode_btn.click(fn=toggle_dark_mode, outputs=gr.HTML())
621
+ preview_btn.click(fn=toggle_preview, outputs=[preview_component])
622
+ download_btn.click(fn=download_json, outputs=[gr.File()])
623
+
624
+ with gr.Tabs():
625
+ with gr.Tab("Edit Resume"):
626
+ with gr.Row():
627
+ with gr.Column():
628
+ # Personal Info Section
629
+ with gr.Blocks():
630
+ gr.Markdown("### Personal Information")
631
+ name = gr.Textbox(label="Full Name", value=state["resume_data"]["fullName"])
632
+ title = gr.Textbox(label="Title", value=state["resume_data"]["title"])
633
+ email = gr.Textbox(label="Email", value=state["resume_data"]["email"])
634
+ phone = gr.Textbox(label="Phone", value=state["resume_data"]["phone"])
635
+ location = gr.Textbox(label="Location", value=state["resume_data"]["location"])
636
+ save_personal = gr.Button("Save Personal Info")
637
+
638
+ # Summary Section
639
+ with gr.Blocks():
640
+ gr.Markdown("### Professional Summary")
641
+ summary = gr.Textbox(
642
+ label="Summary",
643
+ lines=4,
644
+ value=state["resume_data"]["summary"]
645
+ )
646
+ save_summary = gr.Button("Save Summary")
647
+ generate_summary_btn = gr.Button("Generate AI Summary")
648
+
649
+ # Experience Section
650
+ with gr.Blocks():
651
+ gr.Markdown("### Experience")
652
+ add_exp_btn = gr.Button("Add Experience")
653
+ for exp in state["resume_data"]["experience"]:
654
+ with gr.Group():
655
+ exp_company = gr.Textbox(label="Company", value=exp["company"])
656
+ exp_position = gr.Textbox(label="Position", value=exp["position"])
657
+ exp_duration = gr.Textbox(label="Duration", value=exp["duration"])
658
+ exp_description = gr.Textbox(label="Description", lines=3, value=exp["description"])
659
+ with gr.Row():
660
+ save_exp_btn = gr.Button("Save")
661
+ remove_exp_btn = gr.Button("Remove")
662
+
663
+ # Education Section
664
+ with gr.Blocks():
665
+ gr.Markdown("### Education")
666
+ add_edu_btn = gr.Button("Add Education")
667
+ for edu in state["resume_data"]["education"]:
668
+ with gr.Group():
669
+ edu_institution = gr.Textbox(label="Institution", value=edu["institution"])
670
+ edu_degree = gr.Textbox(label="Degree", value=edu["degree"])
671
+ edu_duration = gr.Textbox(label="Duration", value=edu["duration"])
672
+ with gr.Row():
673
+ save_edu_btn = gr.Button("Save")
674
+ remove_edu_btn = gr.Button("Remove")
675
+
676
+ # Skills Section
677
+ with gr.Blocks():
678
+ gr.Markdown("### Skills")
679
+ with gr.Row():
680
+ new_skill = gr.Textbox(label="Add Skill")
681
+ add_skill_btn = gr.Button("Add")
682
+ skills_list = gr.List(
683
+ value=state["resume_data"]["skills"],
684
+ type="array",
685
+ interactive=True
686
+ )
687
+
688
+ with gr.Column():
689
+ preview = gr.HTML(render_resume_preview())
690
+
691
+ # Connect event handlers
692
+ save_personal.click(
693
+ fn=update_personal_info,
694
+ inputs=[name, title, email, phone, location],
695
+ outputs=[preview]
696
+ )
697
+
698
+ save_summary.click(
699
+ fn=update_summary,
700
+ inputs=[summary],
701
+ outputs=[preview]
702
+ )
703
+
704
+ generate_summary_btn.click(
705
+ fn=generate_ai_summary,
706
+ outputs=[summary, preview]
707
+ )
708
+
709
+ add_exp_btn.click(
710
+ fn=add_experience,
711
+ outputs=[preview]
712
+ )
713
+
714
+ add_edu_btn.click(
715
+ fn=add_education,
716
+ outputs=[preview]
717
+ )
718
+
719
+ add_skill_btn.click(
720
+ fn=add_skill,
721
+ inputs=[new_skill],
722
+ outputs=[new_skill, skills_list, preview]
723
+ )
724
+
725
+ skills_list.change(
726
+ fn=update_skills,
727
+ inputs=[skills_list],
728
+ outputs=[preview]
729
+ )
730
+
731
+ with gr.Tab("Job Match"):
732
+ job_match_tab()
733
+
734
+ with gr.Tab("Settings"):
735
+ manage_api_key()
736
+ app.launch()
737
+
738
+ if __name__ == "__main__":
739
+ main()
740
+